codekin 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/assets/index-84JYN21S.js +178 -0
  2. package/dist/assets/index-C0Iuc3iT.css +1 -0
  3. package/dist/index.html +2 -2
  4. package/package.json +30 -28
  5. package/server/dist/claude-process.d.ts +26 -22
  6. package/server/dist/claude-process.js +74 -71
  7. package/server/dist/claude-process.js.map +1 -1
  8. package/server/dist/native-permissions.js +1 -1
  9. package/server/dist/native-permissions.js.map +1 -1
  10. package/server/dist/orchestrator-manager.js +4 -1
  11. package/server/dist/orchestrator-manager.js.map +1 -1
  12. package/server/dist/orchestrator-routes.d.ts +3 -1
  13. package/server/dist/orchestrator-routes.js +3 -3
  14. package/server/dist/orchestrator-routes.js.map +1 -1
  15. package/server/dist/session-archive.js +2 -2
  16. package/server/dist/session-archive.js.map +1 -1
  17. package/server/dist/session-manager.d.ts +37 -1
  18. package/server/dist/session-manager.js +222 -52
  19. package/server/dist/session-manager.js.map +1 -1
  20. package/server/dist/session-persistence.js +2 -0
  21. package/server/dist/session-persistence.js.map +1 -1
  22. package/server/dist/tsconfig.tsbuildinfo +1 -1
  23. package/server/dist/types.d.ts +9 -1
  24. package/server/dist/types.js +1 -1
  25. package/server/dist/types.js.map +1 -1
  26. package/server/dist/upload-routes.js +3 -2
  27. package/server/dist/upload-routes.js.map +1 -1
  28. package/server/dist/webhook-config.js +9 -0
  29. package/server/dist/webhook-config.js.map +1 -1
  30. package/server/dist/webhook-handler.js +13 -0
  31. package/server/dist/webhook-handler.js.map +1 -1
  32. package/server/dist/webhook-types.d.ts +1 -0
  33. package/server/dist/workflow-loader.js +21 -21
  34. package/server/dist/workflow-loader.js.map +1 -1
  35. package/server/dist/ws-server.js +4 -0
  36. package/server/dist/ws-server.js.map +1 -1
  37. package/dist/assets/index-B8opKRtJ.js +0 -186
  38. package/dist/assets/index-wajPH8o6.css +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"session-archive.js","sourceRoot":"","sources":["../session-archive.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-archive.js","sourceRoot":"","sources":["../session-archive.ts"],"names":[],"mappings":"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"}
@@ -60,7 +60,17 @@ export declare class SessionManager {
60
60
  private sessionPersistence;
61
61
  /** Delegated diff operations (git diff, discard changes). */
62
62
  private diffManager;
63
+ /** Interval handle for the idle session reaper. */
64
+ private _idleReaperInterval;
63
65
  constructor();
66
+ /**
67
+ * Stop Claude processes for sessions that have been idle too long.
68
+ * A session is idle when it has no connected clients and no activity
69
+ * for IDLE_SESSION_TIMEOUT_MS. Only stops the process — does not delete
70
+ * the session, so it can be resumed later via --resume.
71
+ * Headless sessions (webhook, workflow, stepflow) are exempt.
72
+ */
73
+ private reapIdleSessions;
64
74
  /** Direct access to the approval manager for callers that need repo-level approval operations. */
65
75
  get approvalManager(): ApprovalManager;
66
76
  /** Schedule session naming via AI provider. */
@@ -145,6 +155,13 @@ export declare class SessionManager {
145
155
  * Wires up all event handlers for streaming text, tools, prompts, and auto-restart.
146
156
  */
147
157
  startClaude(sessionId: string): boolean;
158
+ /**
159
+ * Wait for a session's Claude process to emit its system_init event,
160
+ * indicating it is ready to accept input. Resolves immediately if the
161
+ * session already has a claudeSessionId (process previously initialized).
162
+ * Times out after `timeoutMs` (default 30s) to avoid hanging indefinitely.
163
+ */
164
+ waitForReady(sessionId: string, timeoutMs?: number): Promise<void>;
148
165
  /**
149
166
  * Attach all ClaudeProcess event listeners for a session.
150
167
  * Extracted from startClaude() to keep that method focused on process setup.
@@ -173,6 +190,22 @@ export declare class SessionManager {
173
190
  * session naming on first completed turn.
174
191
  */
175
192
  private handleClaudeResult;
193
+ /**
194
+ * Emit a notification when the session has had enough turns that Claude's
195
+ * context window may start compressing older messages. Uses simple turn-count
196
+ * heuristic — imprecise but zero-risk and protocol-independent.
197
+ */
198
+ private checkContextWarning;
199
+ /**
200
+ * Detect transient API errors and schedule an automatic retry.
201
+ * Returns true if a retry was scheduled (caller should skip result broadcast).
202
+ */
203
+ private handleApiRetry;
204
+ /**
205
+ * Broadcast the turn result, suppress orchestrator noise, notify listeners,
206
+ * and trigger session naming if needed.
207
+ */
208
+ private finalizeResult;
176
209
  /**
177
210
  * Handle a Claude process 'exit' event: clean up state, notify exit listeners,
178
211
  * and either auto-restart (within limits) or broadcast the final exit message.
@@ -258,9 +291,12 @@ export declare class SessionManager {
258
291
  * Caps output at ~4000 chars, keeping the most recent exchanges.
259
292
  */
260
293
  private buildSessionContext;
294
+ /** Max size of a single output chunk in the history buffer. */
295
+ private static readonly MAX_OUTPUT_CHUNK;
261
296
  /**
262
297
  * Append a message to a session's output history for replay.
263
- * Merges consecutive 'output' chunks into a single entry to save space.
298
+ * Merges consecutive 'output' chunks up to MAX_OUTPUT_CHUNK to save space,
299
+ * and splits oversized outputs into multiple entries to bound replay cost.
264
300
  */
265
301
  addToHistory(session: Session, msg: WsServerMessage): void;
266
302
  /** Send a message to all connected clients of a session, with back-pressure protection. */
@@ -41,6 +41,16 @@ const MAX_HISTORY = 2000;
41
41
  const MAX_API_RETRIES = 3;
42
42
  /** Base delay for API error retry (doubles each attempt: 3s, 6s, 12s). */
43
43
  const API_RETRY_BASE_DELAY_MS = 3000;
44
+ /** How long a session can be idle (no clients, no activity) before its process is stopped. */
45
+ const IDLE_SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
46
+ /** How often to check for idle sessions. */
47
+ const IDLE_CHECK_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
48
+ /** How old a dead session must be before automatic pruning (7 days). */
49
+ const STALE_SESSION_AGE_MS = 7 * 24 * 60 * 60 * 1000;
50
+ /** Number of Claude turns before showing a context compression warning. */
51
+ const CONTEXT_WARNING_TURN_THRESHOLD = 15;
52
+ /** Second warning at this threshold. */
53
+ const CONTEXT_CRITICAL_TURN_THRESHOLD = 25;
44
54
  /** Patterns in result text that indicate a transient API error worth retrying. */
45
55
  const API_RETRY_PATTERNS = [
46
56
  /api_error/i,
@@ -79,6 +89,8 @@ export class SessionManager {
79
89
  sessionPersistence;
80
90
  /** Delegated diff operations (git diff, discard changes). */
81
91
  diffManager;
92
+ /** Interval handle for the idle session reaper. */
93
+ _idleReaperInterval = null;
82
94
  constructor() {
83
95
  this.archive = new SessionArchive();
84
96
  this._approvalManager = new ApprovalManager();
@@ -94,6 +106,58 @@ export class SessionManager {
94
106
  for (const session of this.sessions.values()) {
95
107
  this.wirePlanManager(session);
96
108
  }
109
+ // Start idle session reaper
110
+ this._idleReaperInterval = setInterval(() => this.reapIdleSessions(), IDLE_CHECK_INTERVAL_MS);
111
+ }
112
+ /**
113
+ * Stop Claude processes for sessions that have been idle too long.
114
+ * A session is idle when it has no connected clients and no activity
115
+ * for IDLE_SESSION_TIMEOUT_MS. Only stops the process — does not delete
116
+ * the session, so it can be resumed later via --resume.
117
+ * Headless sessions (webhook, workflow, stepflow) are exempt.
118
+ */
119
+ reapIdleSessions() {
120
+ const now = Date.now();
121
+ for (const session of this.sessions.values()) {
122
+ // Skip headless sessions — they are managed by their own lifecycles
123
+ if (session.source === 'webhook' || session.source === 'workflow' || session.source === 'stepflow')
124
+ continue;
125
+ // Skip sessions with connected clients or no running process
126
+ if (session.clients.size > 0 || !session.claudeProcess?.isAlive())
127
+ continue;
128
+ // Skip sessions that are actively processing
129
+ if (session.isProcessing)
130
+ continue;
131
+ const idleMs = now - session._lastActivityAt;
132
+ if (idleMs > IDLE_SESSION_TIMEOUT_MS) {
133
+ console.log(`[idle-reaper] stopping idle session=${session.id} name="${session.name}" idle=${Math.round(idleMs / 60_000)}min`);
134
+ session._stoppedByUser = true; // prevent auto-restart
135
+ session.claudeProcess.removeAllListeners();
136
+ session.claudeProcess.stop();
137
+ session.claudeProcess = null;
138
+ session.isProcessing = false;
139
+ const msg = { type: 'system_message', subtype: 'exit', text: 'Claude process stopped due to inactivity. It will resume when you send a new message.' };
140
+ this.addToHistory(session, msg);
141
+ this.persistToDiskDebounced();
142
+ this._globalBroadcast?.({ type: 'sessions_updated' });
143
+ }
144
+ }
145
+ // Prune stale sessions: no process, no clients, older than STALE_SESSION_AGE_MS
146
+ const staleIds = [];
147
+ for (const session of this.sessions.values()) {
148
+ if (session.claudeProcess?.isAlive())
149
+ continue;
150
+ if (session.clients.size > 0)
151
+ continue;
152
+ const ageMs = now - new Date(session.created).getTime();
153
+ if (ageMs > STALE_SESSION_AGE_MS) {
154
+ staleIds.push(session.id);
155
+ }
156
+ }
157
+ for (const id of staleIds) {
158
+ console.log(`[idle-reaper] pruning stale session=${id} (age > ${STALE_SESSION_AGE_MS / 86_400_000}d)`);
159
+ this.delete(id);
160
+ }
97
161
  }
98
162
  // ---------------------------------------------------------------------------
99
163
  // Approval — direct accessor (callers use sessions.approvalManager.xxx)
@@ -149,11 +213,13 @@ export class SessionManager {
149
213
  _wasActiveBeforeRestart: false,
150
214
  _apiRetryCount: 0,
151
215
  _turnCount: 0,
216
+ _claudeTurnCount: 0,
152
217
  _namingAttempts: 0,
153
218
  isProcessing: false,
154
219
  pendingControlRequests: new Map(),
155
220
  pendingToolApprovals: new Map(),
156
221
  _leaveGraceTimer: null,
222
+ _lastActivityAt: Date.now(),
157
223
  planManager: new PlanManager(),
158
224
  };
159
225
  this.wirePlanManager(session);
@@ -400,7 +466,7 @@ export class SessionManager {
400
466
  groupDir: s.groupDir,
401
467
  worktreePath: s.worktreePath,
402
468
  connectedClients: s.clients.size,
403
- lastActivity: s.created,
469
+ lastActivity: new Date(s._lastActivityAt).toISOString(),
404
470
  source: s.source,
405
471
  }));
406
472
  }
@@ -417,7 +483,7 @@ export class SessionManager {
417
483
  groupDir: s.groupDir,
418
484
  worktreePath: s.worktreePath,
419
485
  connectedClients: s.clients.size,
420
- lastActivity: s.created,
486
+ lastActivity: new Date(s._lastActivityAt).toISOString(),
421
487
  source: s.source,
422
488
  }));
423
489
  }
@@ -444,6 +510,7 @@ export class SessionManager {
444
510
  }
445
511
  session.clients.add(ws);
446
512
  this.clientSessionMap.set(ws, sessionId);
513
+ session._lastActivityAt = Date.now();
447
514
  // Re-broadcast pending tool approval prompts (PreToolUse hook path)
448
515
  for (const pending of session.pendingToolApprovals.values()) {
449
516
  if (pending.promptMsg) {
@@ -612,7 +679,14 @@ export class SessionManager {
612
679
  const repoDir = session.groupDir ?? session.workingDir;
613
680
  const registryPatterns = this._approvalManager.getAllowedToolsForRepo(repoDir);
614
681
  const mergedAllowedTools = [...new Set([...(session.allowedTools || []), ...registryPatterns])];
615
- const cp = new ClaudeProcess(session.workingDir, session.claudeSessionId || undefined, extraEnv, session.model, session.permissionMode, resume, mergedAllowedTools);
682
+ const cp = new ClaudeProcess(session.workingDir, {
683
+ sessionId: session.claudeSessionId || undefined,
684
+ extraEnv,
685
+ model: session.model,
686
+ permissionMode: session.permissionMode,
687
+ resume,
688
+ allowedTools: mergedAllowedTools,
689
+ });
616
690
  this.wireClaudeEvents(cp, session, sessionId);
617
691
  cp.start();
618
692
  session.claudeProcess = cp;
@@ -622,6 +696,30 @@ export class SessionManager {
622
696
  this.broadcast(session, startMsg);
623
697
  return true;
624
698
  }
699
+ /**
700
+ * Wait for a session's Claude process to emit its system_init event,
701
+ * indicating it is ready to accept input. Resolves immediately if the
702
+ * session already has a claudeSessionId (process previously initialized).
703
+ * Times out after `timeoutMs` (default 30s) to avoid hanging indefinitely.
704
+ */
705
+ waitForReady(sessionId, timeoutMs = 30_000) {
706
+ const session = this.sessions.get(sessionId);
707
+ if (!session?.claudeProcess)
708
+ return Promise.resolve();
709
+ // If the process already completed init in a prior turn, resolve immediately
710
+ if (session.claudeSessionId)
711
+ return Promise.resolve();
712
+ return new Promise((resolve) => {
713
+ const timer = setTimeout(() => {
714
+ console.warn(`[waitForReady] Timed out waiting for system_init on ${sessionId} after ${timeoutMs}ms`);
715
+ resolve();
716
+ }, timeoutMs);
717
+ session.claudeProcess.once('system_init', () => {
718
+ clearTimeout(timer);
719
+ resolve();
720
+ });
721
+ });
722
+ }
625
723
  /**
626
724
  * Attach all ClaudeProcess event listeners for a session.
627
725
  * Extracted from startClaude() to keep that method focused on process setup.
@@ -792,55 +890,104 @@ export class SessionManager {
792
890
  */
793
891
  handleClaudeResult(session, sessionId, result, isError) {
794
892
  session.isProcessing = false;
893
+ session._claudeTurnCount++;
795
894
  this._globalBroadcast?.({ type: 'sessions_updated' });
796
- // Detect transient API errors and auto-retry the last user message
797
- if (isError && session._lastUserInput && this.isRetryableApiError(result)) {
798
- if (session._apiRetryCount < MAX_API_RETRIES) {
799
- session._apiRetryCount++;
800
- const attempt = session._apiRetryCount;
801
- const delay = API_RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
802
- const retryMsg = {
803
- type: 'system_message',
804
- subtype: 'restart',
805
- text: `API error (transient). Retrying automatically in ${delay / 1000}s (attempt ${attempt}/${MAX_API_RETRIES})...`,
806
- };
807
- this.addToHistory(session, retryMsg);
808
- this.broadcast(session, retryMsg);
809
- console.log(`[api-retry] session=${sessionId} attempt=${attempt}/${MAX_API_RETRIES} delay=${delay}ms error=${result.slice(0, 200)}`);
810
- // Clear any previous retry timer
811
- if (session._apiRetryTimer)
812
- clearTimeout(session._apiRetryTimer);
813
- session._apiRetryTimer = setTimeout(() => {
814
- session._apiRetryTimer = undefined;
815
- if (!session.claudeProcess?.isAlive() || session._stoppedByUser)
816
- return;
817
- console.log(`[api-retry] resending message for session=${sessionId} attempt=${attempt}`);
818
- session.claudeProcess.sendMessage(session._lastUserInput);
819
- }, delay);
820
- return; // Don't broadcast result — we're retrying
821
- }
822
- // All retries exhausted
823
- const exhaustedMsg = {
895
+ // Attempt API retry for transient errors returns true if a retry was scheduled
896
+ if (isError && this.handleApiRetry(session, sessionId, result)) {
897
+ return;
898
+ }
899
+ // Warn about context window pressure at turn thresholds
900
+ this.checkContextWarning(session);
901
+ this.finalizeResult(session, sessionId, result, isError);
902
+ }
903
+ /**
904
+ * Emit a notification when the session has had enough turns that Claude's
905
+ * context window may start compressing older messages. Uses simple turn-count
906
+ * heuristic — imprecise but zero-risk and protocol-independent.
907
+ */
908
+ checkContextWarning(session) {
909
+ const turns = session._claudeTurnCount;
910
+ if (turns === CONTEXT_WARNING_TURN_THRESHOLD) {
911
+ const msg = {
912
+ type: 'system_message',
913
+ subtype: 'notification',
914
+ text: `This session has ${turns} turns. Claude may begin compressing older messages from its context window. Earlier parts of the conversation may no longer be fully available to Claude.`,
915
+ };
916
+ this.broadcastAndHistory(session, msg);
917
+ session._contextWarningShown = true;
918
+ }
919
+ else if (turns === CONTEXT_CRITICAL_TURN_THRESHOLD) {
920
+ const msg = {
824
921
  type: 'system_message',
825
- subtype: 'error',
826
- text: `API error persisted after ${MAX_API_RETRIES} retries. ${result}`,
922
+ subtype: 'notification',
923
+ text: `This session has ${turns} turns. Claude's context window is likely under pressure — older messages may have been compressed or dropped. Consider starting a new session for best results.`,
827
924
  };
828
- this.addToHistory(session, exhaustedMsg);
829
- this.broadcast(session, exhaustedMsg);
925
+ this.broadcastAndHistory(session, msg);
926
+ }
927
+ }
928
+ /**
929
+ * Detect transient API errors and schedule an automatic retry.
930
+ * Returns true if a retry was scheduled (caller should skip result broadcast).
931
+ */
932
+ handleApiRetry(session, sessionId, result) {
933
+ if (!session._lastUserInput || !this.isRetryableApiError(result)) {
830
934
  session._apiRetryCount = 0;
935
+ return false;
831
936
  }
832
- else {
833
- // Non-retryable error or successful result reset retry counter
937
+ // Skip retry if the original input is older than 60 seconds — context has likely moved on
938
+ if (session._lastUserInputAt && Date.now() - session._lastUserInputAt > 60_000) {
939
+ console.log(`[api-retry] skipping stale retry for session=${sessionId} (input age=${Math.round((Date.now() - session._lastUserInputAt) / 1000)}s)`);
834
940
  session._apiRetryCount = 0;
835
- if (isError) {
836
- const msg = { type: 'system_message', subtype: 'error', text: result };
837
- this.addToHistory(session, msg);
838
- this.broadcast(session, msg);
839
- }
941
+ return false;
942
+ }
943
+ if (session._apiRetryCount < MAX_API_RETRIES) {
944
+ session._apiRetryCount++;
945
+ const attempt = session._apiRetryCount;
946
+ const delay = API_RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
947
+ const retryMsg = {
948
+ type: 'system_message',
949
+ subtype: 'restart',
950
+ text: `API error (transient). Retrying automatically in ${delay / 1000}s (attempt ${attempt}/${MAX_API_RETRIES})...`,
951
+ };
952
+ this.addToHistory(session, retryMsg);
953
+ this.broadcast(session, retryMsg);
954
+ console.log(`[api-retry] session=${sessionId} attempt=${attempt}/${MAX_API_RETRIES} delay=${delay}ms error=${result.slice(0, 200)}`);
955
+ if (session._apiRetryTimer)
956
+ clearTimeout(session._apiRetryTimer);
957
+ session._apiRetryTimer = setTimeout(() => {
958
+ session._apiRetryTimer = undefined;
959
+ if (!session.claudeProcess?.isAlive() || session._stoppedByUser)
960
+ return;
961
+ console.log(`[api-retry] resending message for session=${sessionId} attempt=${attempt}`);
962
+ session.claudeProcess.sendMessage(session._lastUserInput);
963
+ }, delay);
964
+ return true;
965
+ }
966
+ // All retries exhausted
967
+ const exhaustedMsg = {
968
+ type: 'system_message',
969
+ subtype: 'error',
970
+ text: `API error persisted after ${MAX_API_RETRIES} retries. ${result}`,
971
+ };
972
+ this.addToHistory(session, exhaustedMsg);
973
+ this.broadcast(session, exhaustedMsg);
974
+ session._apiRetryCount = 0;
975
+ return false;
976
+ }
977
+ /**
978
+ * Broadcast the turn result, suppress orchestrator noise, notify listeners,
979
+ * and trigger session naming if needed.
980
+ */
981
+ finalizeResult(session, sessionId, result, isError) {
982
+ session._apiRetryCount = 0;
983
+ session._lastUserInput = undefined;
984
+ session._lastUserInputAt = undefined;
985
+ if (isError) {
986
+ const msg = { type: 'system_message', subtype: 'error', text: result };
987
+ this.addToHistory(session, msg);
988
+ this.broadcast(session, msg);
840
989
  }
841
- // Suppress noise from orchestrator/agent sessions: if the entire turn's
842
- // text output is a short, low-value phrase, strip it from history so it
843
- // doesn't pollute the chat or replay on rejoin.
990
+ // Suppress noise from orchestrator/agent sessions
844
991
  if ((session.source === 'orchestrator' || session.source === 'agent') && !isError) {
845
992
  const turnText = this.extractCurrentTurnText(session);
846
993
  if (turnText && turnText.length < 80 && /^(no response requested|please approve|nothing to do|no action needed|acknowledged)[.!]?$/i.test(turnText.trim())) {
@@ -851,14 +998,13 @@ export class SessionManager {
851
998
  const resultMsg = { type: 'result' };
852
999
  this.addToHistory(session, resultMsg);
853
1000
  this.broadcast(session, resultMsg);
854
- // Notify result listeners (orchestrator, child monitor, etc.)
855
1001
  for (const listener of this._resultListeners) {
856
1002
  try {
857
1003
  listener(sessionId, isError);
858
1004
  }
859
1005
  catch { /* listener error */ }
860
1006
  }
861
- // If session is still unnamed after first response, name it now — we have full context
1007
+ // If session is still unnamed after first response, name it now
862
1008
  if (session.name.startsWith('hub:') && session._namingAttempts === 0) {
863
1009
  if (session._namingTimer) {
864
1010
  clearTimeout(session._namingTimer);
@@ -958,8 +1104,11 @@ export class SessionManager {
958
1104
  const session = this.sessions.get(sessionId);
959
1105
  if (!session)
960
1106
  return;
1107
+ session._lastActivityAt = Date.now();
1108
+ // Reset stopped-by-user flag so idle-reaped sessions can auto-start
1109
+ session._stoppedByUser = false;
961
1110
  if (!session.claudeProcess?.isAlive()) {
962
- // Claude not running (e.g. after server restart) — auto-start first.
1111
+ // Claude not running (e.g. after server restart or idle reap) — auto-start first.
963
1112
  // Claude CLI in -p mode waits for first input before emitting init,
964
1113
  // so we write directly to the stdin pipe buffer (no waiting for init).
965
1114
  this.startClaude(sessionId);
@@ -972,6 +1121,7 @@ export class SessionManager {
972
1121
  if (context) {
973
1122
  const combined = context + '\n\n' + data;
974
1123
  session._lastUserInput = combined;
1124
+ session._lastUserInputAt = Date.now();
975
1125
  session._apiRetryCount = 0;
976
1126
  if (!session.isProcessing) {
977
1127
  session.isProcessing = true;
@@ -991,6 +1141,7 @@ export class SessionManager {
991
1141
  this.retrySessionNamingOnInteraction(sessionId);
992
1142
  }
993
1143
  session._lastUserInput = data;
1144
+ session._lastUserInputAt = Date.now();
994
1145
  session._apiRetryCount = 0;
995
1146
  if (!session.isProcessing) {
996
1147
  session.isProcessing = true;
@@ -1007,6 +1158,7 @@ export class SessionManager {
1007
1158
  const session = this.sessions.get(sessionId);
1008
1159
  if (!session)
1009
1160
  return;
1161
+ session._lastActivityAt = Date.now();
1010
1162
  // ExitPlanMode approvals are handled through the normal pendingToolApprovals
1011
1163
  // path (routed via the PreToolUse hook). No special plan_review_ prefix needed.
1012
1164
  // Check for pending tool approval from PreToolUse hook
@@ -1379,7 +1531,7 @@ export class SessionManager {
1379
1531
  if (session.allowedTools && this.matchesAllowedTools(session.allowedTools, toolName, toolInput)) {
1380
1532
  return 'session';
1381
1533
  }
1382
- if (session.clients.size === 0 && (session.source === 'webhook' || session.source === 'workflow' || session.source === 'stepflow')) {
1534
+ if (session.clients.size === 0 && (session.source === 'webhook' || session.source === 'workflow' || session.source === 'stepflow' || session.source === 'orchestrator')) {
1383
1535
  return 'headless';
1384
1536
  }
1385
1537
  return 'prompt';
@@ -1605,22 +1757,36 @@ export class SessionManager {
1605
1757
  }
1606
1758
  return `[This session was interrupted by a server restart. Here is the previous conversation for context:]\n${context}\n[End of previous context. The user's new message follows.]`;
1607
1759
  }
1760
+ /** Max size of a single output chunk in the history buffer. */
1761
+ static MAX_OUTPUT_CHUNK = 50_000; // 50KB
1608
1762
  /**
1609
1763
  * Append a message to a session's output history for replay.
1610
- * Merges consecutive 'output' chunks into a single entry to save space.
1764
+ * Merges consecutive 'output' chunks up to MAX_OUTPUT_CHUNK to save space,
1765
+ * and splits oversized outputs into multiple entries to bound replay cost.
1611
1766
  */
1612
1767
  addToHistory(session, msg) {
1613
1768
  if (msg.type === 'output') {
1614
1769
  const last = session.outputHistory[session.outputHistory.length - 1];
1615
- if (last?.type === 'output' && last.data.length < 100_000) {
1770
+ if (last?.type === 'output' && last.data.length < SessionManager.MAX_OUTPUT_CHUNK) {
1616
1771
  last.data += msg.data;
1617
1772
  this.persistToDiskDebounced();
1618
1773
  return;
1619
1774
  }
1775
+ // Split oversized output into bounded chunks
1776
+ if (msg.data.length > SessionManager.MAX_OUTPUT_CHUNK) {
1777
+ for (let i = 0; i < msg.data.length; i += SessionManager.MAX_OUTPUT_CHUNK) {
1778
+ session.outputHistory.push({ type: 'output', data: msg.data.slice(i, i + SessionManager.MAX_OUTPUT_CHUNK) });
1779
+ }
1780
+ if (session.outputHistory.length > MAX_HISTORY) {
1781
+ session.outputHistory.splice(0, session.outputHistory.length - MAX_HISTORY);
1782
+ }
1783
+ this.persistToDiskDebounced();
1784
+ return;
1785
+ }
1620
1786
  }
1621
1787
  session.outputHistory.push(msg);
1622
1788
  if (session.outputHistory.length > MAX_HISTORY) {
1623
- session.outputHistory = session.outputHistory.slice(-MAX_HISTORY);
1789
+ session.outputHistory.splice(0, session.outputHistory.length - MAX_HISTORY);
1624
1790
  }
1625
1791
  this.persistToDiskDebounced();
1626
1792
  }
@@ -1674,6 +1840,10 @@ export class SessionManager {
1674
1840
  /** Graceful shutdown: complete in-progress tasks, persist state, kill all processes.
1675
1841
  * Returns a promise that resolves once all Claude processes have exited. */
1676
1842
  shutdown() {
1843
+ if (this._idleReaperInterval) {
1844
+ clearInterval(this._idleReaperInterval);
1845
+ this._idleReaperInterval = null;
1846
+ }
1677
1847
  // Complete in-progress tasks for active sessions before persisting.
1678
1848
  // This handles self-deploy: the commit/push task was the last step, and
1679
1849
  // the server restart means it succeeded. Without this, restored sessions