|
@@ -19,6 +19,7 @@ import {
|
|
|
AGENT_ALIASES,
|
|
AGENT_ALIASES,
|
|
|
DEFAULT_MAX_CONTEXT_LINES,
|
|
DEFAULT_MAX_CONTEXT_LINES,
|
|
|
DEFAULT_MAX_RETAINED_SNAPSHOTS,
|
|
DEFAULT_MAX_RETAINED_SNAPSHOTS,
|
|
|
|
|
+ DEFAULT_MAX_SESSION_METADATA_ENTRIES,
|
|
|
DEFAULT_MAX_SESSIONS_PER_AGENT,
|
|
DEFAULT_MAX_SESSIONS_PER_AGENT,
|
|
|
DEFAULT_READ_CONTEXT_MAX_FILES,
|
|
DEFAULT_READ_CONTEXT_MAX_FILES,
|
|
|
DEFAULT_READ_CONTEXT_MIN_LINES,
|
|
DEFAULT_READ_CONTEXT_MIN_LINES,
|
|
@@ -75,6 +76,7 @@ import {
|
|
|
} from './utils';
|
|
} from './utils';
|
|
|
import { isPluginDisabledByEnv } from './utils/env';
|
|
import { isPluginDisabledByEnv } from './utils/env';
|
|
|
import { initLogger, log } from './utils/logger';
|
|
import { initLogger, log } from './utils/logger';
|
|
|
|
|
+import { SessionMetadataStore } from './utils/session-metadata';
|
|
|
import { collapseSystemInPlace } from './utils/system-collapse';
|
|
import { collapseSystemInPlace } from './utils/system-collapse';
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -147,10 +149,15 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
let multiplexerEnabled: boolean;
|
|
let multiplexerEnabled: boolean;
|
|
|
let multiplexerSessionManager: MultiplexerSessionManager;
|
|
let multiplexerSessionManager: MultiplexerSessionManager;
|
|
|
let autoUpdateChecker: ReturnType<typeof createAutoUpdateCheckerHook>;
|
|
let autoUpdateChecker: ReturnType<typeof createAutoUpdateCheckerHook>;
|
|
|
- let sessionAgentMap: Map<string, string>;
|
|
|
|
|
- // ponytail: cache sessionID -> project directory so TUI model writes
|
|
|
|
|
- // land in the right per-project file after a project switch (ctx.directory is stale)
|
|
|
|
|
- const sessionDirectories = new Map<string, string>();
|
|
|
|
|
|
|
+ const sessionMetadata = new SessionMetadataStore({
|
|
|
|
|
+ maxEntries: DEFAULT_MAX_SESSION_METADATA_ENTRIES,
|
|
|
|
|
+ onEvict: (sessionID) => {
|
|
|
|
|
+ log('[session] evicted oldest session metadata', {
|
|
|
|
|
+ threshold: DEFAULT_MAX_SESSION_METADATA_ENTRIES,
|
|
|
|
|
+ droppedSessionId: sessionID,
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
let sessionLifecycle: SessionLifecycle;
|
|
let sessionLifecycle: SessionLifecycle;
|
|
|
|
|
|
|
|
let chatHeadersHook: ReturnType<typeof createChatHeadersHook>;
|
|
let chatHeadersHook: ReturnType<typeof createChatHeadersHook>;
|
|
@@ -296,9 +303,6 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
companion: config.companion,
|
|
companion: config.companion,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // Track session → agent mapping for serve-mode system prompt injection
|
|
|
|
|
- sessionAgentMap = new Map<string, string>();
|
|
|
|
|
-
|
|
|
|
|
chatHeadersHook = createChatHeadersHook(ctx);
|
|
chatHeadersHook = createChatHeadersHook(ctx);
|
|
|
|
|
|
|
|
// Initialize foreground fallback manager for runtime model switching.
|
|
// Initialize foreground fallback manager for runtime model switching.
|
|
@@ -332,9 +336,9 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
continueOnIdle: config.backgroundJobs?.continueOnIdle === true,
|
|
continueOnIdle: config.backgroundJobs?.continueOnIdle === true,
|
|
|
backgroundJobBoard: backgroundJobCoordinator,
|
|
backgroundJobBoard: backgroundJobCoordinator,
|
|
|
shouldManageSession: (sessionID) =>
|
|
shouldManageSession: (sessionID) =>
|
|
|
- sessionAgentMap.get(sessionID) === 'orchestrator',
|
|
|
|
|
|
|
+ sessionMetadata.getAgent(sessionID) === 'orchestrator',
|
|
|
registerSessionAsOrchestrator: (sessionID) => {
|
|
registerSessionAsOrchestrator: (sessionID) => {
|
|
|
- sessionAgentMap.set(sessionID, 'orchestrator');
|
|
|
|
|
|
|
+ sessionMetadata.setAgent(sessionID, 'orchestrator');
|
|
|
},
|
|
},
|
|
|
isFallbackInProgress: (sessionID) =>
|
|
isFallbackInProgress: (sessionID) =>
|
|
|
foregroundFallback.isFallbackInProgress(sessionID),
|
|
foregroundFallback.isFallbackInProgress(sessionID),
|
|
@@ -373,7 +377,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
// Both message transforms share this gate so a rejected nudge cannot be
|
|
// Both message transforms share this gate so a rejected nudge cannot be
|
|
|
// followed by a phase reminder in the same outgoing turn.
|
|
// followed by a phase reminder in the same outgoing turn.
|
|
|
const shouldInjectOrchestratorReminder = (sessionID: string) =>
|
|
const shouldInjectOrchestratorReminder = (sessionID: string) =>
|
|
|
- sessionAgentMap.get(sessionID) === 'orchestrator';
|
|
|
|
|
|
|
+ sessionMetadata.getAgent(sessionID) === 'orchestrator';
|
|
|
|
|
|
|
|
phaseReminder = createPhaseReminderHook({
|
|
phaseReminder = createPhaseReminderHook({
|
|
|
shouldInject: shouldInjectOrchestratorReminder,
|
|
shouldInject: shouldInjectOrchestratorReminder,
|
|
@@ -415,14 +419,14 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
client: ctx.client,
|
|
client: ctx.client,
|
|
|
backgroundJobBoard: backgroundJobCoordinator,
|
|
backgroundJobBoard: backgroundJobCoordinator,
|
|
|
shouldManageSession: (sessionID) =>
|
|
shouldManageSession: (sessionID) =>
|
|
|
- sessionAgentMap.get(sessionID) === 'orchestrator',
|
|
|
|
|
|
|
+ sessionMetadata.getAgent(sessionID) === 'orchestrator',
|
|
|
});
|
|
});
|
|
|
waitForUserTools = createWaitForUserTool({
|
|
waitForUserTools = createWaitForUserTool({
|
|
|
shouldManageSession: (sessionID) =>
|
|
shouldManageSession: (sessionID) =>
|
|
|
- sessionAgentMap.get(sessionID) === 'orchestrator',
|
|
|
|
|
|
|
+ sessionMetadata.getAgent(sessionID) === 'orchestrator',
|
|
|
resolveAgentName: (agent) => resolveRuntimeAgentName(config, agent),
|
|
resolveAgentName: (agent) => resolveRuntimeAgentName(config, agent),
|
|
|
registerSessionAsOrchestrator: (sessionID) => {
|
|
registerSessionAsOrchestrator: (sessionID) => {
|
|
|
- sessionAgentMap.set(sessionID, 'orchestrator');
|
|
|
|
|
|
|
+ sessionMetadata.setAgent(sessionID, 'orchestrator');
|
|
|
},
|
|
},
|
|
|
beginUserWait: (sessionID) =>
|
|
beginUserWait: (sessionID) =>
|
|
|
taskSessionManagerHook.beginUserWait(sessionID),
|
|
taskSessionManagerHook.beginUserWait(sessionID),
|
|
@@ -918,6 +922,24 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
};
|
|
};
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ const eventSessionID =
|
|
|
|
|
+ event.properties?.info?.id ?? event.properties?.sessionID;
|
|
|
|
|
+ const statusType = event.properties?.status?.type;
|
|
|
|
|
+ if (eventSessionID) {
|
|
|
|
|
+ if (
|
|
|
|
|
+ event.type === 'session.status' &&
|
|
|
|
|
+ (statusType === 'busy' || statusType === 'retry')
|
|
|
|
|
+ ) {
|
|
|
|
|
+ sessionMetadata.markOrchestratorActive(eventSessionID);
|
|
|
|
|
+ } else if (
|
|
|
|
|
+ event.type === 'session.idle' ||
|
|
|
|
|
+ (event.type === 'session.status' && statusType === 'idle') ||
|
|
|
|
|
+ event.type === 'session.deleted'
|
|
|
|
|
+ ) {
|
|
|
|
|
+ sessionMetadata.markOrchestratorIdle(eventSessionID);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (event.type === 'message.updated') {
|
|
if (event.type === 'message.updated') {
|
|
|
const info = event.properties?.info;
|
|
const info = event.properties?.info;
|
|
|
const providerID =
|
|
const providerID =
|
|
@@ -942,7 +964,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
model,
|
|
model,
|
|
|
variant: variant ?? null,
|
|
variant: variant ?? null,
|
|
|
},
|
|
},
|
|
|
- (info?.sessionID && sessionDirectories.get(info.sessionID)) ??
|
|
|
|
|
|
|
+ (info?.sessionID && sessionMetadata.getDirectory(info.sessionID)) ??
|
|
|
ctx.directory,
|
|
ctx.directory,
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -952,7 +974,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
const createdSessionId = event.properties?.info?.id;
|
|
const createdSessionId = event.properties?.info?.id;
|
|
|
const createdSessionDir = event.properties?.info?.directory;
|
|
const createdSessionDir = event.properties?.info?.directory;
|
|
|
if (createdSessionId && createdSessionDir) {
|
|
if (createdSessionId && createdSessionDir) {
|
|
|
- sessionDirectories.set(createdSessionId, createdSessionDir);
|
|
|
|
|
|
|
+ sessionMetadata.setDirectory(createdSessionId, createdSessionDir);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1014,7 +1036,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
const sessionID = props?.sessionID;
|
|
const sessionID = props?.sessionID;
|
|
|
companionManager.onSessionStatus({
|
|
companionManager.onSessionStatus({
|
|
|
sessionId: sessionID,
|
|
sessionId: sessionID,
|
|
|
- agent: sessionID ? sessionAgentMap.get(sessionID) : undefined,
|
|
|
|
|
|
|
+ agent: sessionID ? sessionMetadata.getAgent(sessionID) : undefined,
|
|
|
status: props?.status?.type,
|
|
status: props?.status?.type,
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -1030,8 +1052,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
}
|
|
}
|
|
|
companionManager.onSessionDeleted(sessionID);
|
|
companionManager.onSessionDeleted(sessionID);
|
|
|
if (sessionID) {
|
|
if (sessionID) {
|
|
|
- sessionAgentMap.delete(sessionID);
|
|
|
|
|
- sessionDirectories.delete(sessionID);
|
|
|
|
|
|
|
+ sessionMetadata.delete(sessionID);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
@@ -1119,7 +1140,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
|
|
|
|
|
if (agent) {
|
|
if (agent) {
|
|
|
foregroundFallback.registerSessionAgent(input.sessionID, agent);
|
|
foregroundFallback.registerSessionAgent(input.sessionID, agent);
|
|
|
- sessionAgentMap.set(input.sessionID, agent);
|
|
|
|
|
|
|
+ sessionMetadata.setAgent(input.sessionID, agent);
|
|
|
// A chat message means this session is actively working. This also
|
|
// A chat message means this session is actively working. This also
|
|
|
// covers the race where session.status busy fires before the
|
|
// covers the race where session.status busy fires before the
|
|
|
// session's agent is known.
|
|
// session's agent is known.
|
|
@@ -1143,7 +1164,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
output: { system: string[] },
|
|
output: { system: string[] },
|
|
|
): Promise<void> => {
|
|
): Promise<void> => {
|
|
|
const agentName = input.sessionID
|
|
const agentName = input.sessionID
|
|
|
- ? sessionAgentMap.get(input.sessionID)
|
|
|
|
|
|
|
+ ? sessionMetadata.getAgent(input.sessionID)
|
|
|
: undefined;
|
|
: undefined;
|
|
|
if (agentName === 'orchestrator') {
|
|
if (agentName === 'orchestrator') {
|
|
|
const alreadyInjected = output.system.some(
|
|
const alreadyInjected = output.system.some(
|