codekin 0.5.1 → 0.5.3

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 +223 -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) {
@@ -593,6 +660,7 @@ export class SessionManager {
593
660
  CODEKIN_TOKEN: sessionToken,
594
661
  CODEKIN_AUTH_TOKEN: sessionToken,
595
662
  CODEKIN_SESSION_TYPE: session.source || 'manual',
663
+ ...(session.permissionMode === 'dangerouslySkipPermissions' ? { CODEKIN_SKIP_PERMISSIONS: '1' } : {}),
596
664
  };
597
665
  // Pass CLAUDE_PROJECT_DIR so hooks and CLAUDE.md resolve correctly
598
666
  // even when the session's working directory differs from the project root
@@ -612,7 +680,14 @@ export class SessionManager {
612
680
  const repoDir = session.groupDir ?? session.workingDir;
613
681
  const registryPatterns = this._approvalManager.getAllowedToolsForRepo(repoDir);
614
682
  const mergedAllowedTools = [...new Set([...(session.allowedTools || []), ...registryPatterns])];
615
- const cp = new ClaudeProcess(session.workingDir, session.claudeSessionId || undefined, extraEnv, session.model, session.permissionMode, resume, mergedAllowedTools);
683
+ const cp = new ClaudeProcess(session.workingDir, {
684
+ sessionId: session.claudeSessionId || undefined,
685
+ extraEnv,
686
+ model: session.model,
687
+ permissionMode: session.permissionMode,
688
+ resume,
689
+ allowedTools: mergedAllowedTools,
690
+ });
616
691
  this.wireClaudeEvents(cp, session, sessionId);
617
692
  cp.start();
618
693
  session.claudeProcess = cp;
@@ -622,6 +697,30 @@ export class SessionManager {
622
697
  this.broadcast(session, startMsg);
623
698
  return true;
624
699
  }
700
+ /**
701
+ * Wait for a session's Claude process to emit its system_init event,
702
+ * indicating it is ready to accept input. Resolves immediately if the
703
+ * session already has a claudeSessionId (process previously initialized).
704
+ * Times out after `timeoutMs` (default 30s) to avoid hanging indefinitely.
705
+ */
706
+ waitForReady(sessionId, timeoutMs = 30_000) {
707
+ const session = this.sessions.get(sessionId);
708
+ if (!session?.claudeProcess)
709
+ return Promise.resolve();
710
+ // If the process already completed init in a prior turn, resolve immediately
711
+ if (session.claudeSessionId)
712
+ return Promise.resolve();
713
+ return new Promise((resolve) => {
714
+ const timer = setTimeout(() => {
715
+ console.warn(`[waitForReady] Timed out waiting for system_init on ${sessionId} after ${timeoutMs}ms`);
716
+ resolve();
717
+ }, timeoutMs);
718
+ session.claudeProcess.once('system_init', () => {
719
+ clearTimeout(timer);
720
+ resolve();
721
+ });
722
+ });
723
+ }
625
724
  /**
626
725
  * Attach all ClaudeProcess event listeners for a session.
627
726
  * Extracted from startClaude() to keep that method focused on process setup.
@@ -792,55 +891,104 @@ export class SessionManager {
792
891
  */
793
892
  handleClaudeResult(session, sessionId, result, isError) {
794
893
  session.isProcessing = false;
894
+ session._claudeTurnCount++;
795
895
  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 = {
896
+ // Attempt API retry for transient errors returns true if a retry was scheduled
897
+ if (isError && this.handleApiRetry(session, sessionId, result)) {
898
+ return;
899
+ }
900
+ // Warn about context window pressure at turn thresholds
901
+ this.checkContextWarning(session);
902
+ this.finalizeResult(session, sessionId, result, isError);
903
+ }
904
+ /**
905
+ * Emit a notification when the session has had enough turns that Claude's
906
+ * context window may start compressing older messages. Uses simple turn-count
907
+ * heuristic — imprecise but zero-risk and protocol-independent.
908
+ */
909
+ checkContextWarning(session) {
910
+ const turns = session._claudeTurnCount;
911
+ if (turns === CONTEXT_WARNING_TURN_THRESHOLD) {
912
+ const msg = {
913
+ type: 'system_message',
914
+ subtype: 'notification',
915
+ 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.`,
916
+ };
917
+ this.broadcastAndHistory(session, msg);
918
+ session._contextWarningShown = true;
919
+ }
920
+ else if (turns === CONTEXT_CRITICAL_TURN_THRESHOLD) {
921
+ const msg = {
824
922
  type: 'system_message',
825
- subtype: 'error',
826
- text: `API error persisted after ${MAX_API_RETRIES} retries. ${result}`,
923
+ subtype: 'notification',
924
+ 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
925
  };
828
- this.addToHistory(session, exhaustedMsg);
829
- this.broadcast(session, exhaustedMsg);
926
+ this.broadcastAndHistory(session, msg);
927
+ }
928
+ }
929
+ /**
930
+ * Detect transient API errors and schedule an automatic retry.
931
+ * Returns true if a retry was scheduled (caller should skip result broadcast).
932
+ */
933
+ handleApiRetry(session, sessionId, result) {
934
+ if (!session._lastUserInput || !this.isRetryableApiError(result)) {
830
935
  session._apiRetryCount = 0;
936
+ return false;
831
937
  }
832
- else {
833
- // Non-retryable error or successful result reset retry counter
938
+ // Skip retry if the original input is older than 60 seconds — context has likely moved on
939
+ if (session._lastUserInputAt && Date.now() - session._lastUserInputAt > 60_000) {
940
+ console.log(`[api-retry] skipping stale retry for session=${sessionId} (input age=${Math.round((Date.now() - session._lastUserInputAt) / 1000)}s)`);
834
941
  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
- }
942
+ return false;
943
+ }
944
+ if (session._apiRetryCount < MAX_API_RETRIES) {
945
+ session._apiRetryCount++;
946
+ const attempt = session._apiRetryCount;
947
+ const delay = API_RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
948
+ const retryMsg = {
949
+ type: 'system_message',
950
+ subtype: 'restart',
951
+ text: `API error (transient). Retrying automatically in ${delay / 1000}s (attempt ${attempt}/${MAX_API_RETRIES})...`,
952
+ };
953
+ this.addToHistory(session, retryMsg);
954
+ this.broadcast(session, retryMsg);
955
+ console.log(`[api-retry] session=${sessionId} attempt=${attempt}/${MAX_API_RETRIES} delay=${delay}ms error=${result.slice(0, 200)}`);
956
+ if (session._apiRetryTimer)
957
+ clearTimeout(session._apiRetryTimer);
958
+ session._apiRetryTimer = setTimeout(() => {
959
+ session._apiRetryTimer = undefined;
960
+ if (!session.claudeProcess?.isAlive() || session._stoppedByUser)
961
+ return;
962
+ console.log(`[api-retry] resending message for session=${sessionId} attempt=${attempt}`);
963
+ session.claudeProcess.sendMessage(session._lastUserInput);
964
+ }, delay);
965
+ return true;
966
+ }
967
+ // All retries exhausted
968
+ const exhaustedMsg = {
969
+ type: 'system_message',
970
+ subtype: 'error',
971
+ text: `API error persisted after ${MAX_API_RETRIES} retries. ${result}`,
972
+ };
973
+ this.addToHistory(session, exhaustedMsg);
974
+ this.broadcast(session, exhaustedMsg);
975
+ session._apiRetryCount = 0;
976
+ return false;
977
+ }
978
+ /**
979
+ * Broadcast the turn result, suppress orchestrator noise, notify listeners,
980
+ * and trigger session naming if needed.
981
+ */
982
+ finalizeResult(session, sessionId, result, isError) {
983
+ session._apiRetryCount = 0;
984
+ session._lastUserInput = undefined;
985
+ session._lastUserInputAt = undefined;
986
+ if (isError) {
987
+ const msg = { type: 'system_message', subtype: 'error', text: result };
988
+ this.addToHistory(session, msg);
989
+ this.broadcast(session, msg);
840
990
  }
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.
991
+ // Suppress noise from orchestrator/agent sessions
844
992
  if ((session.source === 'orchestrator' || session.source === 'agent') && !isError) {
845
993
  const turnText = this.extractCurrentTurnText(session);
846
994
  if (turnText && turnText.length < 80 && /^(no response requested|please approve|nothing to do|no action needed|acknowledged)[.!]?$/i.test(turnText.trim())) {
@@ -851,14 +999,13 @@ export class SessionManager {
851
999
  const resultMsg = { type: 'result' };
852
1000
  this.addToHistory(session, resultMsg);
853
1001
  this.broadcast(session, resultMsg);
854
- // Notify result listeners (orchestrator, child monitor, etc.)
855
1002
  for (const listener of this._resultListeners) {
856
1003
  try {
857
1004
  listener(sessionId, isError);
858
1005
  }
859
1006
  catch { /* listener error */ }
860
1007
  }
861
- // If session is still unnamed after first response, name it now — we have full context
1008
+ // If session is still unnamed after first response, name it now
862
1009
  if (session.name.startsWith('hub:') && session._namingAttempts === 0) {
863
1010
  if (session._namingTimer) {
864
1011
  clearTimeout(session._namingTimer);
@@ -958,8 +1105,11 @@ export class SessionManager {
958
1105
  const session = this.sessions.get(sessionId);
959
1106
  if (!session)
960
1107
  return;
1108
+ session._lastActivityAt = Date.now();
1109
+ // Reset stopped-by-user flag so idle-reaped sessions can auto-start
1110
+ session._stoppedByUser = false;
961
1111
  if (!session.claudeProcess?.isAlive()) {
962
- // Claude not running (e.g. after server restart) — auto-start first.
1112
+ // Claude not running (e.g. after server restart or idle reap) — auto-start first.
963
1113
  // Claude CLI in -p mode waits for first input before emitting init,
964
1114
  // so we write directly to the stdin pipe buffer (no waiting for init).
965
1115
  this.startClaude(sessionId);
@@ -972,6 +1122,7 @@ export class SessionManager {
972
1122
  if (context) {
973
1123
  const combined = context + '\n\n' + data;
974
1124
  session._lastUserInput = combined;
1125
+ session._lastUserInputAt = Date.now();
975
1126
  session._apiRetryCount = 0;
976
1127
  if (!session.isProcessing) {
977
1128
  session.isProcessing = true;
@@ -991,6 +1142,7 @@ export class SessionManager {
991
1142
  this.retrySessionNamingOnInteraction(sessionId);
992
1143
  }
993
1144
  session._lastUserInput = data;
1145
+ session._lastUserInputAt = Date.now();
994
1146
  session._apiRetryCount = 0;
995
1147
  if (!session.isProcessing) {
996
1148
  session.isProcessing = true;
@@ -1007,6 +1159,7 @@ export class SessionManager {
1007
1159
  const session = this.sessions.get(sessionId);
1008
1160
  if (!session)
1009
1161
  return;
1162
+ session._lastActivityAt = Date.now();
1010
1163
  // ExitPlanMode approvals are handled through the normal pendingToolApprovals
1011
1164
  // path (routed via the PreToolUse hook). No special plan_review_ prefix needed.
1012
1165
  // Check for pending tool approval from PreToolUse hook
@@ -1379,7 +1532,7 @@ export class SessionManager {
1379
1532
  if (session.allowedTools && this.matchesAllowedTools(session.allowedTools, toolName, toolInput)) {
1380
1533
  return 'session';
1381
1534
  }
1382
- if (session.clients.size === 0 && (session.source === 'webhook' || session.source === 'workflow' || session.source === 'stepflow')) {
1535
+ if (session.clients.size === 0 && (session.source === 'webhook' || session.source === 'workflow' || session.source === 'stepflow' || session.source === 'orchestrator')) {
1383
1536
  return 'headless';
1384
1537
  }
1385
1538
  return 'prompt';
@@ -1605,22 +1758,36 @@ export class SessionManager {
1605
1758
  }
1606
1759
  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
1760
  }
1761
+ /** Max size of a single output chunk in the history buffer. */
1762
+ static MAX_OUTPUT_CHUNK = 50_000; // 50KB
1608
1763
  /**
1609
1764
  * Append a message to a session's output history for replay.
1610
- * Merges consecutive 'output' chunks into a single entry to save space.
1765
+ * Merges consecutive 'output' chunks up to MAX_OUTPUT_CHUNK to save space,
1766
+ * and splits oversized outputs into multiple entries to bound replay cost.
1611
1767
  */
1612
1768
  addToHistory(session, msg) {
1613
1769
  if (msg.type === 'output') {
1614
1770
  const last = session.outputHistory[session.outputHistory.length - 1];
1615
- if (last?.type === 'output' && last.data.length < 100_000) {
1771
+ if (last?.type === 'output' && last.data.length < SessionManager.MAX_OUTPUT_CHUNK) {
1616
1772
  last.data += msg.data;
1617
1773
  this.persistToDiskDebounced();
1618
1774
  return;
1619
1775
  }
1776
+ // Split oversized output into bounded chunks
1777
+ if (msg.data.length > SessionManager.MAX_OUTPUT_CHUNK) {
1778
+ for (let i = 0; i < msg.data.length; i += SessionManager.MAX_OUTPUT_CHUNK) {
1779
+ session.outputHistory.push({ type: 'output', data: msg.data.slice(i, i + SessionManager.MAX_OUTPUT_CHUNK) });
1780
+ }
1781
+ if (session.outputHistory.length > MAX_HISTORY) {
1782
+ session.outputHistory.splice(0, session.outputHistory.length - MAX_HISTORY);
1783
+ }
1784
+ this.persistToDiskDebounced();
1785
+ return;
1786
+ }
1620
1787
  }
1621
1788
  session.outputHistory.push(msg);
1622
1789
  if (session.outputHistory.length > MAX_HISTORY) {
1623
- session.outputHistory = session.outputHistory.slice(-MAX_HISTORY);
1790
+ session.outputHistory.splice(0, session.outputHistory.length - MAX_HISTORY);
1624
1791
  }
1625
1792
  this.persistToDiskDebounced();
1626
1793
  }
@@ -1674,6 +1841,10 @@ export class SessionManager {
1674
1841
  /** Graceful shutdown: complete in-progress tasks, persist state, kill all processes.
1675
1842
  * Returns a promise that resolves once all Claude processes have exited. */
1676
1843
  shutdown() {
1844
+ if (this._idleReaperInterval) {
1845
+ clearInterval(this._idleReaperInterval);
1846
+ this._idleReaperInterval = null;
1847
+ }
1677
1848
  // Complete in-progress tasks for active sessions before persisting.
1678
1849
  // This handles self-deploy: the commit/push task was the last step, and
1679
1850
  // the server restart means it succeeded. Without this, restored sessions