|
|
@@ -74,6 +74,7 @@ import {
|
|
|
} from './utils';
|
|
|
import { isPluginDisabledByEnv } from './utils/env';
|
|
|
import { initLogger, log } from './utils/logger';
|
|
|
+import { SessionMetadataStore } from './utils/session-metadata';
|
|
|
import { collapseSystemInPlace } from './utils/system-collapse';
|
|
|
|
|
|
/**
|
|
|
@@ -173,10 +174,15 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
let multiplexerEnabled: boolean;
|
|
|
let multiplexerSessionManager: MultiplexerSessionManager;
|
|
|
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_DIRECTORIES,
|
|
|
+ onEvict: (sessionID) => {
|
|
|
+ log('[session] evicted oldest session metadata', {
|
|
|
+ threshold: DEFAULT_MAX_SESSION_DIRECTORIES,
|
|
|
+ droppedSessionId: sessionID,
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
let sessionLifecycle: SessionLifecycle;
|
|
|
|
|
|
let chatHeadersHook: ReturnType<typeof createChatHeadersHook>;
|
|
|
@@ -320,9 +326,6 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
companion: config.companion,
|
|
|
});
|
|
|
|
|
|
- // Track session → agent mapping for serve-mode system prompt injection
|
|
|
- sessionAgentMap = new Map<string, string>();
|
|
|
-
|
|
|
chatHeadersHook = createChatHeadersHook(ctx);
|
|
|
|
|
|
// Initialize foreground fallback manager for runtime model switching.
|
|
|
@@ -356,9 +359,9 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
continueOnIdle: config.backgroundJobs?.continueOnIdle === true,
|
|
|
backgroundJobBoard: backgroundJobCoordinator,
|
|
|
shouldManageSession: (sessionID) =>
|
|
|
- sessionAgentMap.get(sessionID) === 'orchestrator',
|
|
|
+ sessionMetadata.getAgent(sessionID) === 'orchestrator',
|
|
|
registerSessionAsOrchestrator: (sessionID) => {
|
|
|
- sessionAgentMap.set(sessionID, 'orchestrator');
|
|
|
+ sessionMetadata.setAgent(sessionID, 'orchestrator');
|
|
|
},
|
|
|
isFallbackInProgress: (sessionID) =>
|
|
|
foregroundFallback.isFallbackInProgress(sessionID),
|
|
|
@@ -397,7 +400,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
// Both message transforms share this gate so a rejected nudge cannot be
|
|
|
// followed by a phase reminder in the same outgoing turn.
|
|
|
const shouldInjectOrchestratorReminder = (sessionID: string) =>
|
|
|
- sessionAgentMap.get(sessionID) === 'orchestrator';
|
|
|
+ sessionMetadata.getAgent(sessionID) === 'orchestrator';
|
|
|
|
|
|
phaseReminder = createPhaseReminderHook({
|
|
|
shouldInject: shouldInjectOrchestratorReminder,
|
|
|
@@ -439,14 +442,14 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
client: ctx.client,
|
|
|
backgroundJobBoard: backgroundJobCoordinator,
|
|
|
shouldManageSession: (sessionID) =>
|
|
|
- sessionAgentMap.get(sessionID) === 'orchestrator',
|
|
|
+ sessionMetadata.getAgent(sessionID) === 'orchestrator',
|
|
|
});
|
|
|
waitForUserTools = createWaitForUserTool({
|
|
|
shouldManageSession: (sessionID) =>
|
|
|
- sessionAgentMap.get(sessionID) === 'orchestrator',
|
|
|
+ sessionMetadata.getAgent(sessionID) === 'orchestrator',
|
|
|
resolveAgentName: (agent) => resolveRuntimeAgentName(config, agent),
|
|
|
registerSessionAsOrchestrator: (sessionID) => {
|
|
|
- sessionAgentMap.set(sessionID, 'orchestrator');
|
|
|
+ sessionMetadata.setAgent(sessionID, 'orchestrator');
|
|
|
},
|
|
|
beginUserWait: (sessionID) =>
|
|
|
taskSessionManagerHook.beginUserWait(sessionID),
|
|
|
@@ -963,7 +966,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
model,
|
|
|
variant: variant ?? null,
|
|
|
},
|
|
|
- (info?.sessionID && sessionDirectories.get(info.sessionID)) ??
|
|
|
+ (info?.sessionID && sessionMetadata.getDirectory(info.sessionID)) ??
|
|
|
ctx.directory,
|
|
|
);
|
|
|
}
|
|
|
@@ -973,21 +976,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
const createdSessionId = event.properties?.info?.id;
|
|
|
const createdSessionDir = event.properties?.info?.directory;
|
|
|
if (createdSessionId && createdSessionDir) {
|
|
|
- // Guard against unbounded growth when session.deleted events
|
|
|
- // are missed: evict the oldest entry when the threshold is
|
|
|
- // reached, keeping both maps aligned.
|
|
|
- if (sessionDirectories.size >= DEFAULT_MAX_SESSION_DIRECTORIES) {
|
|
|
- const oldestId = sessionDirectories.keys().next().value;
|
|
|
- if (oldestId !== undefined) {
|
|
|
- sessionDirectories.delete(oldestId);
|
|
|
- sessionAgentMap.delete(oldestId);
|
|
|
- log('[session] evicted oldest session-directory mapping', {
|
|
|
- threshold: DEFAULT_MAX_SESSION_DIRECTORIES,
|
|
|
- droppedSessionId: oldestId,
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- sessionDirectories.set(createdSessionId, createdSessionDir);
|
|
|
+ sessionMetadata.setDirectory(createdSessionId, createdSessionDir);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1047,9 +1036,12 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
| { sessionID?: string; status?: { type?: string } }
|
|
|
| undefined;
|
|
|
const sessionID = props?.sessionID;
|
|
|
+ if (sessionID && props?.status?.type === 'busy') {
|
|
|
+ sessionMetadata.markOrchestratorBusy(sessionID);
|
|
|
+ }
|
|
|
companionManager.onSessionStatus({
|
|
|
sessionId: sessionID,
|
|
|
- agent: sessionID ? sessionAgentMap.get(sessionID) : undefined,
|
|
|
+ agent: sessionID ? sessionMetadata.getAgent(sessionID) : undefined,
|
|
|
status: props?.status?.type,
|
|
|
});
|
|
|
}
|
|
|
@@ -1065,8 +1057,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
}
|
|
|
companionManager.onSessionDeleted(sessionID);
|
|
|
if (sessionID) {
|
|
|
- sessionAgentMap.delete(sessionID);
|
|
|
- sessionDirectories.delete(sessionID);
|
|
|
+ sessionMetadata.delete(sessionID);
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
@@ -1154,7 +1145,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
|
|
|
if (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
|
|
|
// covers the race where session.status busy fires before the
|
|
|
// session's agent is known.
|
|
|
@@ -1178,7 +1169,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
|
|
|
output: { system: string[] },
|
|
|
): Promise<void> => {
|
|
|
const agentName = input.sessionID
|
|
|
- ? sessionAgentMap.get(input.sessionID)
|
|
|
+ ? sessionMetadata.getAgent(input.sessionID)
|
|
|
: undefined;
|
|
|
if (agentName === 'orchestrator') {
|
|
|
const alreadyInjected = output.system.some(
|