aegis-bridge 0.1.0

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 (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +404 -0
  3. package/dashboard/dist/assets/index-BoZwGLAx.css +32 -0
  4. package/dashboard/dist/assets/index-C61BkKH-.js +312 -0
  5. package/dashboard/dist/assets/index-C61BkKH-.js.map +1 -0
  6. package/dashboard/dist/index.html +14 -0
  7. package/dist/api-contracts.d.ts +229 -0
  8. package/dist/api-contracts.js +7 -0
  9. package/dist/api-contracts.typecheck.d.ts +14 -0
  10. package/dist/api-contracts.typecheck.js +1 -0
  11. package/dist/api-error-envelope.d.ts +15 -0
  12. package/dist/api-error-envelope.js +80 -0
  13. package/dist/auth.d.ts +87 -0
  14. package/dist/auth.js +276 -0
  15. package/dist/channels/index.d.ts +8 -0
  16. package/dist/channels/index.js +8 -0
  17. package/dist/channels/manager.d.ts +47 -0
  18. package/dist/channels/manager.js +115 -0
  19. package/dist/channels/telegram-style.d.ts +118 -0
  20. package/dist/channels/telegram-style.js +202 -0
  21. package/dist/channels/telegram.d.ts +91 -0
  22. package/dist/channels/telegram.js +1518 -0
  23. package/dist/channels/types.d.ts +77 -0
  24. package/dist/channels/types.js +8 -0
  25. package/dist/channels/webhook.d.ts +60 -0
  26. package/dist/channels/webhook.js +216 -0
  27. package/dist/cli.d.ts +8 -0
  28. package/dist/cli.js +252 -0
  29. package/dist/config.d.ts +90 -0
  30. package/dist/config.js +214 -0
  31. package/dist/consensus.d.ts +16 -0
  32. package/dist/consensus.js +19 -0
  33. package/dist/continuation-pointer.d.ts +11 -0
  34. package/dist/continuation-pointer.js +65 -0
  35. package/dist/diagnostics.d.ts +27 -0
  36. package/dist/diagnostics.js +95 -0
  37. package/dist/error-categories.d.ts +39 -0
  38. package/dist/error-categories.js +73 -0
  39. package/dist/events.d.ts +133 -0
  40. package/dist/events.js +389 -0
  41. package/dist/fault-injection.d.ts +29 -0
  42. package/dist/fault-injection.js +115 -0
  43. package/dist/file-utils.d.ts +2 -0
  44. package/dist/file-utils.js +37 -0
  45. package/dist/handshake.d.ts +60 -0
  46. package/dist/handshake.js +124 -0
  47. package/dist/hook-settings.d.ts +80 -0
  48. package/dist/hook-settings.js +272 -0
  49. package/dist/hook.d.ts +19 -0
  50. package/dist/hook.js +231 -0
  51. package/dist/hooks.d.ts +32 -0
  52. package/dist/hooks.js +364 -0
  53. package/dist/jsonl-watcher.d.ts +59 -0
  54. package/dist/jsonl-watcher.js +166 -0
  55. package/dist/logger.d.ts +35 -0
  56. package/dist/logger.js +65 -0
  57. package/dist/mcp-server.d.ts +123 -0
  58. package/dist/mcp-server.js +869 -0
  59. package/dist/memory-bridge.d.ts +27 -0
  60. package/dist/memory-bridge.js +137 -0
  61. package/dist/memory-routes.d.ts +3 -0
  62. package/dist/memory-routes.js +100 -0
  63. package/dist/metrics.d.ts +126 -0
  64. package/dist/metrics.js +286 -0
  65. package/dist/model-router.d.ts +53 -0
  66. package/dist/model-router.js +150 -0
  67. package/dist/monitor.d.ts +103 -0
  68. package/dist/monitor.js +820 -0
  69. package/dist/path-utils.d.ts +11 -0
  70. package/dist/path-utils.js +21 -0
  71. package/dist/permission-evaluator.d.ts +10 -0
  72. package/dist/permission-evaluator.js +48 -0
  73. package/dist/permission-guard.d.ts +51 -0
  74. package/dist/permission-guard.js +196 -0
  75. package/dist/permission-request-manager.d.ts +12 -0
  76. package/dist/permission-request-manager.js +36 -0
  77. package/dist/permission-routes.d.ts +7 -0
  78. package/dist/permission-routes.js +28 -0
  79. package/dist/pipeline.d.ts +97 -0
  80. package/dist/pipeline.js +291 -0
  81. package/dist/process-utils.d.ts +4 -0
  82. package/dist/process-utils.js +73 -0
  83. package/dist/question-manager.d.ts +54 -0
  84. package/dist/question-manager.js +80 -0
  85. package/dist/retry.d.ts +11 -0
  86. package/dist/retry.js +34 -0
  87. package/dist/safe-json.d.ts +12 -0
  88. package/dist/safe-json.js +22 -0
  89. package/dist/screenshot.d.ts +28 -0
  90. package/dist/screenshot.js +60 -0
  91. package/dist/server.d.ts +10 -0
  92. package/dist/server.js +1973 -0
  93. package/dist/session-cleanup.d.ts +18 -0
  94. package/dist/session-cleanup.js +11 -0
  95. package/dist/session.d.ts +379 -0
  96. package/dist/session.js +1568 -0
  97. package/dist/shutdown-utils.d.ts +5 -0
  98. package/dist/shutdown-utils.js +24 -0
  99. package/dist/signal-cleanup-helper.d.ts +48 -0
  100. package/dist/signal-cleanup-helper.js +117 -0
  101. package/dist/sse-limiter.d.ts +47 -0
  102. package/dist/sse-limiter.js +61 -0
  103. package/dist/sse-writer.d.ts +31 -0
  104. package/dist/sse-writer.js +94 -0
  105. package/dist/ssrf.d.ts +102 -0
  106. package/dist/ssrf.js +267 -0
  107. package/dist/startup.d.ts +6 -0
  108. package/dist/startup.js +162 -0
  109. package/dist/suppress.d.ts +33 -0
  110. package/dist/suppress.js +79 -0
  111. package/dist/swarm-monitor.d.ts +117 -0
  112. package/dist/swarm-monitor.js +300 -0
  113. package/dist/template-store.d.ts +45 -0
  114. package/dist/template-store.js +142 -0
  115. package/dist/terminal-parser.d.ts +16 -0
  116. package/dist/terminal-parser.js +346 -0
  117. package/dist/tmux-capture-cache.d.ts +18 -0
  118. package/dist/tmux-capture-cache.js +34 -0
  119. package/dist/tmux.d.ts +183 -0
  120. package/dist/tmux.js +906 -0
  121. package/dist/tool-registry.d.ts +40 -0
  122. package/dist/tool-registry.js +83 -0
  123. package/dist/transcript.d.ts +63 -0
  124. package/dist/transcript.js +284 -0
  125. package/dist/utils/circular-buffer.d.ts +11 -0
  126. package/dist/utils/circular-buffer.js +37 -0
  127. package/dist/utils/redact-headers.d.ts +13 -0
  128. package/dist/utils/redact-headers.js +54 -0
  129. package/dist/validation.d.ts +406 -0
  130. package/dist/validation.js +415 -0
  131. package/dist/verification.d.ts +2 -0
  132. package/dist/verification.js +72 -0
  133. package/dist/worktree-lookup.d.ts +24 -0
  134. package/dist/worktree-lookup.js +71 -0
  135. package/dist/ws-terminal.d.ts +32 -0
  136. package/dist/ws-terminal.js +348 -0
  137. package/package.json +83 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * session-cleanup.ts — Shared per-session cleanup for terminated sessions.
3
+ *
4
+ * Ensures all server-side session-keyed tracking structures are cleaned in
5
+ * every termination path (API kill, inbound kill, stale reaper, zombie reaper).
6
+ */
7
+ export interface SessionCleanupDeps {
8
+ monitor: {
9
+ removeSession(sessionId: string): void;
10
+ };
11
+ metrics: {
12
+ cleanupSession(sessionId: string): void;
13
+ };
14
+ toolRegistry: {
15
+ cleanupSession(sessionId: string): void;
16
+ };
17
+ }
18
+ export declare function cleanupTerminatedSessionState(sessionId: string, deps: SessionCleanupDeps): void;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * session-cleanup.ts — Shared per-session cleanup for terminated sessions.
3
+ *
4
+ * Ensures all server-side session-keyed tracking structures are cleaned in
5
+ * every termination path (API kill, inbound kill, stale reaper, zombie reaper).
6
+ */
7
+ export function cleanupTerminatedSessionState(sessionId, deps) {
8
+ deps.monitor.removeSession(sessionId);
9
+ deps.metrics.cleanupSession(sessionId);
10
+ deps.toolRegistry.cleanupSession(sessionId);
11
+ }
@@ -0,0 +1,379 @@
1
+ /**
2
+ * session.ts — Session state manager.
3
+ *
4
+ * Manages the lifecycle of CC sessions running in tmux windows.
5
+ * Tracks: session ID, window ID, byte offset for JSONL reading, status.
6
+ */
7
+ import { TmuxManager } from './tmux.js';
8
+ import { type ParsedEntry } from './transcript.js';
9
+ import { type UIState } from './terminal-parser.js';
10
+ import type { Config } from './config.js';
11
+ import { type PermissionPolicy, type PermissionProfile } from './validation.js';
12
+ import { type PermissionDecision } from './permission-request-manager.js';
13
+ /**
14
+ * Canonical runtime metadata for an Aegis-managed Claude Code session.
15
+ *
16
+ * This structure is persisted to disk and reused by the REST API, SSE layer,
17
+ * monitoring loop, and session recovery logic.
18
+ */
19
+ export interface SessionInfo {
20
+ id: string;
21
+ windowId: string;
22
+ windowName: string;
23
+ workDir: string;
24
+ claudeSessionId?: string;
25
+ jsonlPath?: string;
26
+ byteOffset: number;
27
+ monitorOffset: number;
28
+ status: UIState;
29
+ createdAt: number;
30
+ lastActivity: number;
31
+ stallThresholdMs: number;
32
+ permissionStallMs: number;
33
+ permissionMode: string;
34
+ settingsPatched?: boolean;
35
+ hookSettingsFile?: string;
36
+ hookSecret?: string;
37
+ lastHookAt?: number;
38
+ activeSubagents?: Set<string>;
39
+ permissionPromptAt?: number;
40
+ permissionRespondedAt?: number;
41
+ lastHookReceivedAt?: number;
42
+ lastHookEventAt?: number;
43
+ model?: string;
44
+ lastDeadAt?: number;
45
+ ccPid?: number;
46
+ parentId?: string;
47
+ children?: string[];
48
+ permissionPolicy?: PermissionPolicy;
49
+ permissionProfile?: PermissionProfile;
50
+ prd?: string;
51
+ }
52
+ /** Persisted session store keyed by Aegis session ID. */
53
+ export interface SessionState {
54
+ sessions: Record<string, SessionInfo>;
55
+ }
56
+ /**
57
+ * Detect whether CC is showing numbered permission options (e.g. "1. Yes, 2. No")
58
+ * vs a simple y/N prompt. Returns the approval method to use.
59
+ *
60
+ * CC's permission UI uses indented numbered lines with "Esc to cancel" nearby.
61
+ * We look for the pattern " <N>. <option>" where N is 1-3, which distinguishes
62
+ * permission options from regular numbered lists in output.
63
+ */
64
+ export declare function detectApprovalMethod(paneText: string): 'numbered' | 'yes';
65
+ /** Resolves a pending PermissionRequest hook with a decision. */
66
+ export type { PermissionDecision };
67
+ /**
68
+ * Coordinates session lifecycle, persistence, transcript discovery, and
69
+ * interactive approval/question flows for all managed Claude Code sessions.
70
+ */
71
+ export declare class SessionManager {
72
+ private tmux;
73
+ private config;
74
+ private state;
75
+ private stateFile;
76
+ private sessionMapFile;
77
+ private pollTimers;
78
+ /** Next filesystem-scan time (ms epoch) for each discovery poller. */
79
+ private discoveryNextFilesystemScanAt;
80
+ /** #835: Discovery timeout timers — cleared in cleanupSession to prevent orphan callbacks. */
81
+ private discoveryTimeouts;
82
+ private saveQueue;
83
+ private saveDebounceTimer;
84
+ private static readonly SAVE_DEBOUNCE_MS;
85
+ private permissionRequests;
86
+ private questions;
87
+ private static readonly MAX_CACHE_ENTRIES_PER_SESSION;
88
+ private parsedEntriesCache;
89
+ private sessionsListCache;
90
+ private readonly sessionAcquireMutex;
91
+ constructor(tmux: TmuxManager, config: Config);
92
+ /**
93
+ * Issue #884: Worktree-aware session file lookup.
94
+ * When `worktreeAwareContinuation` is enabled, fans out to sibling worktree
95
+ * project dirs; otherwise falls back to the existing single-directory search.
96
+ */
97
+ private findSessionFileMaybeWorktree;
98
+ /** Validate that parsed data looks like a valid SessionState. */
99
+ private isValidState;
100
+ /** Clean up stale .tmp files left by crashed writes. */
101
+ private cleanTmpFiles;
102
+ /** Load state from disk. */
103
+ load(): Promise<void>;
104
+ /** Reconcile state with actual tmux windows. Remove dead sessions, restart discovery for live ones.
105
+ * Issue #397: Also handles re-attach by window name when windowId is stale after tmux restart. */
106
+ private reconcile;
107
+ /** Issue #397: Reconcile after tmux server crash recovery.
108
+ * Called when the monitor detects tmux server came back after a crash.
109
+ * Returns counts for observability. */
110
+ reconcileTmuxCrash(): Promise<{
111
+ recovered: number;
112
+ orphaned: number;
113
+ }>;
114
+ /** Save state to disk atomically (write to temp, then rename).
115
+ * #218: Uses a write queue to serialize concurrent saves and prevent corruption. */
116
+ save(): Promise<void>;
117
+ /** #357: Debounced save — skips immediate save for offset-only changes.
118
+ * Coalesces rapid successive reads into a single disk write. */
119
+ debouncedSave(): void;
120
+ private doSave;
121
+ /** Default stall threshold: 2 min (Issue #392: 1.5x CC's 90s default, configurable via CLAUDE_STREAM_IDLE_TIMEOUT_MS). */
122
+ static readonly DEFAULT_STALL_THRESHOLD_MS: number;
123
+ static readonly DEFAULT_PERMISSION_STALL_MS: number;
124
+ /** Create a new CC session. */
125
+ /** Default timeout for waiting CC to become ready (60s for cold starts). */
126
+ static readonly DEFAULT_PROMPT_TIMEOUT_MS = 60000;
127
+ /** Max retries if CC doesn't become ready in time. */
128
+ static readonly DEFAULT_PROMPT_MAX_RETRIES = 2;
129
+ /**
130
+ * Wait for CC to show its idle prompt in the tmux pane, then send the initial prompt.
131
+ * Uses exponential backoff on retry: first attempt waits timeoutMs, subsequent attempts
132
+ * wait 1.5x the previous timeout.
133
+ *
134
+ * Returns delivery result. Logs warnings on each retry for observability.
135
+ */
136
+ sendInitialPrompt(sessionId: string, prompt: string, timeoutMs?: number, maxRetries?: number): Promise<{
137
+ delivered: boolean;
138
+ attempts: number;
139
+ }>;
140
+ /** Wait for CC idle prompt, then send. Single attempt. */
141
+ private waitForReadyAndSend;
142
+ /**
143
+ * Issue #561: After sending an initial prompt, verify CC actually accepted it
144
+ * by polling for a state transition away from idle/unknown.
145
+ * Returns true if CC transitions to a recognized active state within the timeout.
146
+ */
147
+ private verifyPromptAccepted;
148
+ createSession(opts: {
149
+ workDir: string;
150
+ name?: string;
151
+ prd?: string;
152
+ resumeSessionId?: string;
153
+ claudeCommand?: string;
154
+ env?: Record<string, string>;
155
+ stallThresholdMs?: number;
156
+ permissionStallMs?: number;
157
+ permissionMode?: string;
158
+ /** @deprecated Use permissionMode instead. Maps true→bypassPermissions, false→default. */
159
+ autoApprove?: boolean;
160
+ /** Issue #702: Parent session ID for sub-agent hierarchy */
161
+ parentId?: string;
162
+ }): Promise<SessionInfo>;
163
+ /** Get a session by ID. */
164
+ getSession(id: string): SessionInfo | null;
165
+ /** Issue #169 Phase 3: Update session status from a hook event.
166
+ * Returns the previous status for change detection.
167
+ * Issue #87: Also records hook latency timestamps. */
168
+ updateStatusFromHook(id: string, hookEvent: string, hookTimestamp?: number): UIState | null;
169
+ /** Issue #812: Detect if CC is waiting for user input by analyzing the JSONL transcript.
170
+ * Returns true if the last assistant message has text content only (no tool_use). */
171
+ detectWaitingForInput(id: string): Promise<boolean>;
172
+ /** Issue #88: Add an active subagent to a session. */
173
+ addSubagent(id: string, name: string): void;
174
+ /** Issue #88: Remove an active subagent from a session. */
175
+ removeSubagent(id: string, name: string): void;
176
+ /** Issue #89 L25: Update the model field on a session from hook payload. */
177
+ updateSessionModel(id: string, model: string): void;
178
+ /** Issue #87: Get latency metrics for a session. */
179
+ getLatencyMetrics(id: string): {
180
+ hook_latency_ms: number | null;
181
+ state_change_detection_ms: number | null;
182
+ permission_response_ms: number | null;
183
+ } | null;
184
+ /** Check if a session's tmux window still exists and has a live process.
185
+ * Issue #69: A window can exist with a crashed/zombie CC process (zombie window).
186
+ * After checking window exists, also verify the pane PID is alive.
187
+ * Issue #390: Check stored ccPid first for immediate crash detection.
188
+ * When CC crashes (SIGKILL, OOM), the shell prompt returns in the pane,
189
+ * so the current pane PID is the shell (alive). Checking ccPid catches
190
+ * the crash within seconds instead of waiting for the 5-min stall timer. */
191
+ isWindowAlive(id: string): Promise<boolean>;
192
+ /** Issue #657: Invalidate the sessions list cache. Call on any mutation. */
193
+ private invalidateSessionsListCache;
194
+ /** List all sessions. */
195
+ listSessions(): SessionInfo[];
196
+ /** Issue #607: Find an idle session for the given workDir.
197
+ * Returns the most recently active idle session, or null if none found.
198
+ * Used to resume existing sessions instead of creating duplicates.
199
+ * Issue #636: Verifies tmux window is still alive before returning.
200
+ * Issue #840/#880: Atomically acquires the session under a mutex to prevent TOCTOU race. */
201
+ findIdleSessionByWorkDir(workDir: string): Promise<SessionInfo | null>;
202
+ /** Release a session claim after the reuse path completes (success or failure). */
203
+ releaseSessionClaim(id: string): void;
204
+ /** Get health info for a session.
205
+ * Issue #2: Returns comprehensive health status for orchestrators.
206
+ */
207
+ getHealth(id: string): Promise<{
208
+ alive: boolean;
209
+ windowExists: boolean;
210
+ claudeRunning: boolean;
211
+ paneCommand: string | null;
212
+ status: UIState;
213
+ hasTranscript: boolean;
214
+ lastActivity: number;
215
+ lastActivityAgo: number;
216
+ sessionAge: number;
217
+ details: string;
218
+ actionHints?: Record<string, {
219
+ method: string;
220
+ url: string;
221
+ description: string;
222
+ }>;
223
+ }>;
224
+ /** Send a message to a session with delivery verification.
225
+ * Issue #1: Uses capture-pane to verify the prompt was delivered.
226
+ * Returns delivery status for API response.
227
+ */
228
+ sendMessage(id: string, text: string): Promise<{
229
+ delivered: boolean;
230
+ attempts: number;
231
+ }>;
232
+ /** Send message bypassing the tmux serialize queue.
233
+ * Used by sendInitialPrompt for critical-path prompt delivery.
234
+ *
235
+ * Issue #285: Changed from sendKeysDirect (unverified) to sendKeysVerified
236
+ * with 3 retry attempts. tmux send-keys can silently fail even at session
237
+ * creation time, causing ~20% prompt delivery failure rate.
238
+ *
239
+ * We still bypass the serialize queue (using capturePaneDirect in verifyDelivery)
240
+ * but now verify actual delivery to CC.
241
+ */
242
+ private sendMessageDirect;
243
+ /** Record that a permission prompt was detected for this session. */
244
+ recordPermissionPrompt(id: string): void;
245
+ /** Approve a permission prompt. Resolves pending hook permission first, falls back to tmux send-keys. */
246
+ approve(id: string): Promise<void>;
247
+ /** Reject a permission prompt. Resolves pending hook permission first, falls back to tmux send-keys. */
248
+ reject(id: string): Promise<void>;
249
+ /**
250
+ * Issue #284: Store a pending permission request and return a promise that
251
+ * resolves when the client approves/rejects via the API.
252
+ *
253
+ * @param sessionId - Aegis session ID
254
+ * @param timeoutMs - Timeout before auto-rejecting (default 10_000ms, matching CC's hook timeout)
255
+ * @param toolName - Optional tool name from the hook payload
256
+ * @param prompt - Optional permission prompt text
257
+ * @returns Promise that resolves with the client's decision
258
+ */
259
+ waitForPermissionDecision(sessionId: string, timeoutMs?: number, toolName?: string, prompt?: string): Promise<PermissionDecision>;
260
+ /** Check if a session has a pending permission request. */
261
+ hasPendingPermission(sessionId: string): boolean;
262
+ /** Get info about a pending permission (for API responses). */
263
+ getPendingPermissionInfo(sessionId: string): {
264
+ toolName?: string;
265
+ prompt?: string;
266
+ } | null;
267
+ /** Clean up any pending permission for a session (e.g. on session delete). */
268
+ cleanupPendingPermission(sessionId: string): void;
269
+ /**
270
+ * Issue #336: Store a pending AskUserQuestion and return a promise that
271
+ * resolves when the external client provides an answer via POST /answer.
272
+ */
273
+ waitForAnswer(sessionId: string, toolUseId: string, question: string, timeoutMs?: number): Promise<string | null>;
274
+ /** Issue #336: Submit an answer to a pending question. Returns true if resolved. */
275
+ submitAnswer(sessionId: string, questionId: string, answer: string): boolean;
276
+ /** Issue #336: Check if a session has a pending question. */
277
+ hasPendingQuestion(sessionId: string): boolean;
278
+ /** Issue #336: Get info about a pending question. */
279
+ getPendingQuestionInfo(sessionId: string): {
280
+ toolUseId: string;
281
+ question: string;
282
+ timestamp: number;
283
+ } | null;
284
+ /** Issue #336: Clean up any pending question for a session. */
285
+ cleanupPendingQuestion(sessionId: string): void;
286
+ /** Send Escape key. */
287
+ escape(id: string): Promise<void>;
288
+ /** Send Ctrl+C. */
289
+ interrupt(id: string): Promise<void>;
290
+ /** Read new messages from a session. */
291
+ readMessages(id: string): Promise<{
292
+ messages: ParsedEntry[];
293
+ status: UIState;
294
+ statusText: string | null;
295
+ interactiveContent: string | null;
296
+ }>;
297
+ /** Read new messages for the monitor (separate offset from API reads). */
298
+ readMessagesForMonitor(id: string): Promise<{
299
+ messages: ParsedEntry[];
300
+ status: UIState;
301
+ statusText: string | null;
302
+ interactiveContent: string | null;
303
+ }>;
304
+ /** #357: Get all parsed entries for a session, using a cache to avoid full reparse.
305
+ * Reads only the delta from the last cached offset. */
306
+ private getCachedEntries;
307
+ /** Issue #35: Get a condensed summary of a session's transcript. */
308
+ getSummary(id: string, maxMessages?: number): Promise<{
309
+ sessionId: string;
310
+ windowName: string;
311
+ status: UIState;
312
+ totalMessages: number;
313
+ messages: Array<{
314
+ role: string;
315
+ contentType: string;
316
+ text: string;
317
+ }>;
318
+ createdAt: number;
319
+ lastActivity: number;
320
+ permissionMode: string;
321
+ prd?: string;
322
+ }>;
323
+ /** Paginated transcript read — does NOT advance the session's byteOffset. */
324
+ readTranscript(id: string, page?: number, limit?: number, roleFilter?: 'user' | 'assistant' | 'system'): Promise<{
325
+ messages: ParsedEntry[];
326
+ total: number;
327
+ page: number;
328
+ limit: number;
329
+ hasMore: boolean;
330
+ }>;
331
+ /**
332
+ * Cursor-based transcript read — stable under concurrent appends.
333
+ *
334
+ * Uses 1-based sequential entry indices as cursors.
335
+ * - `beforeId`: exclusive upper bound (fetch entries with index < beforeId).
336
+ * If omitted, fetch the newest `limit` entries.
337
+ * - `limit`: max entries to return (capped at 200).
338
+ * - Returns entries in ascending order (oldest first) within the window.
339
+ */
340
+ readTranscriptCursor(id: string, beforeId?: number, limit?: number, roleFilter?: 'user' | 'assistant' | 'system'): Promise<{
341
+ messages: (ParsedEntry & {
342
+ _cursor_id: number;
343
+ })[];
344
+ has_more: boolean;
345
+ oldest_id: number | null;
346
+ newest_id: number | null;
347
+ }>;
348
+ /** #405: Clean up all tracking maps for a session to prevent memory leaks. */
349
+ private cleanupSession;
350
+ /** Kill a session. */
351
+ killSession(id: string): Promise<void>;
352
+ /** Remove stale entries from session_map.json for a given window.
353
+ * P0 fix: After aegis service restarts, old session_map entries with stale windowIds
354
+ * can survive and cause new sessions to inherit context from old sessions.
355
+ * We must clean by BOTH windowName AND windowId to prevent collisions.
356
+ *
357
+ * After archiving old .jsonl files, old hook entries would cause discovery
358
+ * to map the new session to a ghost claudeSessionId whose file no longer exists.
359
+ */
360
+ private cleanSessionMapForWindow;
361
+ /** P0 fix: Purge session_map entries that don't correspond to active aegis sessions.
362
+ * After aegis restarts, old session_map entries with stale windowIds can survive
363
+ * and cause new sessions to inherit context from old sessions.
364
+ */
365
+ private purgeStaleSessionMapEntries;
366
+ /** Stop and remove the coordinated discovery poller/timer for a session. */
367
+ private stopDiscoveryPolling;
368
+ /** Attempt filesystem-based discovery for a single session poll tick. */
369
+ private maybeDiscoverFromFilesystem;
370
+ /**
371
+ * Coordinated discovery poller for a session.
372
+ *
373
+ * Consolidates hook/session_map sync and filesystem fallback into a single
374
+ * interval loop per session, reducing duplicate independent pollers.
375
+ */
376
+ private startDiscoveryPolling;
377
+ /** Sync CC session IDs from the hook-written session_map.json. */
378
+ private syncSessionMap;
379
+ }