aegis-bridge 2.12.0 → 2.12.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.
- package/dashboard/dist/assets/index-DhUXvnKe.js +302 -0
- package/dashboard/dist/assets/index-hJxI_Ze7.css +32 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/api-contracts.d.ts +197 -0
- package/dist/api-contracts.js +7 -0
- package/dist/api-contracts.typecheck.d.ts +14 -0
- package/dist/api-contracts.typecheck.js +1 -0
- package/dist/channels/telegram.d.ts +12 -1
- package/dist/channels/telegram.js +123 -3
- package/dist/config.d.ts +2 -0
- package/dist/config.js +3 -0
- package/dist/events.d.ts +11 -0
- package/dist/events.js +42 -0
- package/dist/hook-settings.d.ts +1 -1
- package/dist/hook-settings.js +5 -13
- package/dist/hooks.js +2 -13
- package/dist/metrics.d.ts +2 -1
- package/dist/monitor.js +33 -6
- package/dist/server.js +21 -2
- package/dist/session.d.ts +12 -5
- package/dist/session.js +109 -119
- package/dist/tmux.d.ts +2 -0
- package/dist/tmux.js +24 -15
- package/dist/validation.d.ts +11 -7
- package/dist/validation.js +53 -8
- package/package.json +4 -1
- package/dashboard/dist/assets/index--qcihe0P.css +0 -32
- package/dashboard/dist/assets/index-DFdRqV0R.js +0 -302
package/dist/events.js
CHANGED
|
@@ -53,8 +53,43 @@ export class SessionEventBus {
|
|
|
53
53
|
static BUFFER_SIZE = 50;
|
|
54
54
|
/** Per-session ring buffer for event replay. */
|
|
55
55
|
eventBuffers = new Map();
|
|
56
|
+
/** Last activity time per session buffer for LRU eviction. */
|
|
57
|
+
sessionBufferLastTouched = new Map();
|
|
56
58
|
/** Global ring buffer for event replay across all sessions (Issue #301). */
|
|
57
59
|
globalEventBuffer = new CircularBuffer(SessionEventBus.BUFFER_SIZE);
|
|
60
|
+
maxSessionBuffers;
|
|
61
|
+
constructor(options = {}) {
|
|
62
|
+
this.maxSessionBuffers = options.maxSessionBuffers ?? 10_000;
|
|
63
|
+
}
|
|
64
|
+
touchSessionBuffer(sessionId) {
|
|
65
|
+
this.sessionBufferLastTouched.set(sessionId, Date.now());
|
|
66
|
+
}
|
|
67
|
+
pruneSessionBufferMap(protectedSessionId) {
|
|
68
|
+
if (this.eventBuffers.size <= this.maxSessionBuffers)
|
|
69
|
+
return;
|
|
70
|
+
let oldestSessionId;
|
|
71
|
+
let oldestTouched = Infinity;
|
|
72
|
+
for (const [sessionId, touchedAt] of this.sessionBufferLastTouched) {
|
|
73
|
+
if (sessionId === protectedSessionId)
|
|
74
|
+
continue;
|
|
75
|
+
if (!this.eventBuffers.has(sessionId)) {
|
|
76
|
+
this.sessionBufferLastTouched.delete(sessionId);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const emitter = this.emitters.get(sessionId);
|
|
80
|
+
if (emitter && emitter.listenerCount('event') > 0) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (touchedAt < oldestTouched) {
|
|
84
|
+
oldestTouched = touchedAt;
|
|
85
|
+
oldestSessionId = sessionId;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (oldestSessionId !== undefined) {
|
|
89
|
+
this.eventBuffers.delete(oldestSessionId);
|
|
90
|
+
this.sessionBufferLastTouched.delete(oldestSessionId);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
58
93
|
/** Get or create the emitter for a session. */
|
|
59
94
|
getEmitter(sessionId) {
|
|
60
95
|
let emitter = this.emitters.get(sessionId);
|
|
@@ -95,6 +130,8 @@ export class SessionEventBus {
|
|
|
95
130
|
this.eventBuffers.set(sessionId, buffer);
|
|
96
131
|
}
|
|
97
132
|
buffer.push({ id: event.id, event });
|
|
133
|
+
this.touchSessionBuffer(sessionId);
|
|
134
|
+
this.pruneSessionBufferMap(sessionId);
|
|
98
135
|
const emitter = this.emitters.get(sessionId);
|
|
99
136
|
if (emitter) {
|
|
100
137
|
const imm = setImmediate(() => {
|
|
@@ -120,6 +157,7 @@ export class SessionEventBus {
|
|
|
120
157
|
const buffer = this.eventBuffers.get(sessionId);
|
|
121
158
|
if (!buffer)
|
|
122
159
|
return [];
|
|
160
|
+
this.touchSessionBuffer(sessionId);
|
|
123
161
|
return buffer.toArray().filter(e => e.id > lastEventId).map(e => e.event);
|
|
124
162
|
}
|
|
125
163
|
/**
|
|
@@ -140,6 +178,7 @@ export class SessionEventBus {
|
|
|
140
178
|
newest_id: null,
|
|
141
179
|
};
|
|
142
180
|
}
|
|
181
|
+
this.touchSessionBuffer(sessionId);
|
|
143
182
|
const entries = buffer.toArray();
|
|
144
183
|
const clampedLimit = Math.min(SessionEventBus.BUFFER_SIZE, Math.max(1, limit));
|
|
145
184
|
const upperExclusive = beforeId !== undefined
|
|
@@ -227,6 +266,7 @@ export class SessionEventBus {
|
|
|
227
266
|
this.emitters.delete(sessionId);
|
|
228
267
|
}
|
|
229
268
|
this.eventBuffers.delete(sessionId);
|
|
269
|
+
this.sessionBufferLastTouched.delete(sessionId);
|
|
230
270
|
}, 1000);
|
|
231
271
|
this.pendingTimeouts.add(timeout);
|
|
232
272
|
}
|
|
@@ -317,6 +357,7 @@ export class SessionEventBus {
|
|
|
317
357
|
this.pendingTimeouts.delete(timeout);
|
|
318
358
|
}
|
|
319
359
|
this.eventBuffers.delete(sessionId);
|
|
360
|
+
this.sessionBufferLastTouched.delete(sessionId);
|
|
320
361
|
const emitter = this.emitters.get(sessionId);
|
|
321
362
|
if (emitter) {
|
|
322
363
|
emitter.removeAllListeners();
|
|
@@ -340,6 +381,7 @@ export class SessionEventBus {
|
|
|
340
381
|
}
|
|
341
382
|
this.emitters.clear();
|
|
342
383
|
this.eventBuffers.clear();
|
|
384
|
+
this.sessionBufferLastTouched.clear();
|
|
343
385
|
this.globalEventBuffer.clear();
|
|
344
386
|
this.globalEmitter?.removeAllListeners();
|
|
345
387
|
this.globalEmitter = null;
|
package/dist/hook-settings.d.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* - WorktreeCreate, WorktreeRemove (worktree management)
|
|
24
24
|
* - Elicitation, ElicitationResult (MCP-specific)
|
|
25
25
|
*/
|
|
26
|
-
declare const HTTP_HOOK_EVENTS: readonly ["Stop", "StopFailure", "PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionRequest", "TaskCompleted", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart", "SubagentStop", "PreCompact", "PostCompact", "FileChanged", "CwdChanged", "Notification", "TeammateIdle", "WorktreeCreate", "
|
|
26
|
+
declare const HTTP_HOOK_EVENTS: readonly ["Stop", "StopFailure", "PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionRequest", "TaskCompleted", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart", "SubagentStop", "PreCompact", "PostCompact", "FileChanged", "CwdChanged", "Notification", "TeammateIdle", "WorktreeCreate", "WorktreeRemove", "Elicitation", "ElicitationResult"];
|
|
27
27
|
export { HTTP_HOOK_EVENTS };
|
|
28
28
|
export type HttpHookEvent = typeof HTTP_HOOK_EVENTS[number];
|
|
29
29
|
/** Shape of a single HTTP hook entry in CC settings.json. */
|
package/dist/hook-settings.js
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { readFile, writeFile, unlink, mkdir, rmdir } from 'node:fs/promises';
|
|
17
17
|
import { existsSync } from 'node:fs';
|
|
18
|
-
import { join, resolve
|
|
18
|
+
import { join, resolve } from 'node:path';
|
|
19
19
|
import { tmpdir } from 'node:os';
|
|
20
20
|
import { randomBytes } from 'node:crypto';
|
|
21
|
-
import { ccSettingsSchema } from './validation.js';
|
|
21
|
+
import { ccSettingsSchema, containsTraversalSegment } from './validation.js';
|
|
22
22
|
function isRecord(value) {
|
|
23
23
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
24
24
|
}
|
|
@@ -53,15 +53,9 @@ function normalizeHookBaseUrl(baseUrl) {
|
|
|
53
53
|
* @returns Sanitized absolute path, or undefined if validation fails.
|
|
54
54
|
*/
|
|
55
55
|
function validateWorkDirPath(workDir) {
|
|
56
|
-
|
|
57
|
-
// Reject paths with traversal segments
|
|
58
|
-
if (normalized.includes('..'))
|
|
56
|
+
if (containsTraversalSegment(workDir))
|
|
59
57
|
return undefined;
|
|
60
|
-
|
|
61
|
-
const resolved = resolve(normalized);
|
|
62
|
-
if (resolved.includes('..'))
|
|
63
|
-
return undefined;
|
|
64
|
-
return resolved;
|
|
58
|
+
return resolve(workDir);
|
|
65
59
|
}
|
|
66
60
|
/** CC hook events that support `type: "http"`.
|
|
67
61
|
*
|
|
@@ -98,11 +92,9 @@ const HTTP_HOOK_EVENTS = [
|
|
|
98
92
|
// Notifications
|
|
99
93
|
'Notification',
|
|
100
94
|
'TeammateIdle',
|
|
101
|
-
// Worktree management
|
|
95
|
+
// Worktree management (only Create/Remove — *Failed variants don't exist in CC, see #1002)
|
|
102
96
|
'WorktreeCreate',
|
|
103
|
-
'WorktreeCreateFailed',
|
|
104
97
|
'WorktreeRemove',
|
|
105
|
-
'WorktreeRemoveFailed',
|
|
106
98
|
// Elicitation
|
|
107
99
|
'Elicitation',
|
|
108
100
|
'ElicitationResult',
|
package/dist/hooks.js
CHANGED
|
@@ -44,9 +44,7 @@ const KNOWN_HOOK_EVENTS = new Set([
|
|
|
44
44
|
'PostCompact',
|
|
45
45
|
'UserPromptSubmit',
|
|
46
46
|
'WorktreeCreate',
|
|
47
|
-
'WorktreeCreateFailed',
|
|
48
47
|
'WorktreeRemove',
|
|
49
|
-
'WorktreeRemoveFailed',
|
|
50
48
|
'Elicitation',
|
|
51
49
|
'ElicitationResult',
|
|
52
50
|
'FileChanged',
|
|
@@ -74,9 +72,7 @@ function hookToUIState(eventName) {
|
|
|
74
72
|
case 'Elicitation':
|
|
75
73
|
case 'ElicitationResult':
|
|
76
74
|
case 'WorktreeCreate':
|
|
77
|
-
case '
|
|
78
|
-
case 'WorktreeRemove':
|
|
79
|
-
case 'WorktreeRemoveFailed': return 'working';
|
|
75
|
+
case 'WorktreeRemove': return 'working';
|
|
80
76
|
case 'PreCompact': return 'compacting';
|
|
81
77
|
case 'PermissionRequest': return 'permission_prompt';
|
|
82
78
|
case 'TeammateIdle': return 'idle';
|
|
@@ -151,8 +147,7 @@ export function registerHookRoutes(app, deps) {
|
|
|
151
147
|
});
|
|
152
148
|
}
|
|
153
149
|
// Issue #89 L26: WorktreeCreate/Remove hooks — informational tracking only
|
|
154
|
-
if (eventName === 'WorktreeCreate' || eventName === '
|
|
155
|
-
eventName === 'WorktreeRemove' || eventName === 'WorktreeRemoveFailed') {
|
|
150
|
+
if (eventName === 'WorktreeCreate' || eventName === 'WorktreeRemove') {
|
|
156
151
|
console.log(`Hooks: ${eventName} for session ${sessionId}`);
|
|
157
152
|
}
|
|
158
153
|
// Informational events — log and forward to SSE (already forwarded below via emitHook)
|
|
@@ -228,15 +223,9 @@ export function registerHookRoutes(app, deps) {
|
|
|
228
223
|
case 'WorktreeCreate':
|
|
229
224
|
deps.eventBus.emitStatus(sessionId, 'working', `Worktree created: ${hookBody.worktree_path || 'unknown'} (hook: WorktreeCreate)`);
|
|
230
225
|
break;
|
|
231
|
-
case 'WorktreeCreateFailed':
|
|
232
|
-
deps.eventBus.emitStatus(sessionId, 'working', `Worktree creation failed: ${hookBody.error || 'unknown'} (hook: WorktreeCreateFailed)`);
|
|
233
|
-
break;
|
|
234
226
|
case 'WorktreeRemove':
|
|
235
227
|
deps.eventBus.emitStatus(sessionId, 'idle', `Worktree removed: ${hookBody.worktree_path || 'unknown'} (hook: WorktreeRemove)`);
|
|
236
228
|
break;
|
|
237
|
-
case 'WorktreeRemoveFailed':
|
|
238
|
-
deps.eventBus.emitStatus(sessionId, 'working', `Worktree removal failed: ${hookBody.error || 'unknown'} (hook: WorktreeRemoveFailed)`);
|
|
239
|
-
break;
|
|
240
229
|
case 'PermissionRequest':
|
|
241
230
|
deps.eventBus.emitApproval(sessionId, hookBody.permission_prompt || 'Permission requested (hook)');
|
|
242
231
|
break;
|
package/dist/metrics.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Issue #40: Global and per-session metrics for monitoring.
|
|
5
5
|
* Counters are in-memory, persisted to disk on shutdown, loaded on startup.
|
|
6
6
|
*/
|
|
7
|
+
import type { GlobalMetrics as GlobalMetricsResponse } from './api-contracts.js';
|
|
7
8
|
export interface GlobalMetrics {
|
|
8
9
|
sessionsCreated: number;
|
|
9
10
|
sessionsCompleted: number;
|
|
@@ -100,7 +101,7 @@ export declare class MetricsCollector {
|
|
|
100
101
|
clearSessionLatency(sessionId: string): void;
|
|
101
102
|
/** #357: Clean up all per-session data (call on session destroy). */
|
|
102
103
|
cleanupSession(sessionId: string): void;
|
|
103
|
-
getGlobalMetrics(activeSessionCount: number):
|
|
104
|
+
getGlobalMetrics(activeSessionCount: number): GlobalMetricsResponse;
|
|
104
105
|
getSessionMetrics(sessionId: string): SessionMetrics | null;
|
|
105
106
|
getTotalSessionsCreated(): number;
|
|
106
107
|
}
|
package/dist/monitor.js
CHANGED
|
@@ -155,6 +155,13 @@ export class SessionMonitor {
|
|
|
155
155
|
}
|
|
156
156
|
async poll() {
|
|
157
157
|
const now = Date.now();
|
|
158
|
+
// Issue #397: Run tmux health checks before dead-session reaping.
|
|
159
|
+
// This prevents false "status.dead" events when tmux is temporarily
|
|
160
|
+
// unreachable and windows still exist once the server recovers.
|
|
161
|
+
if (now - this.lastTmuxHealthCheck >= SessionMonitor.TMUX_HEALTH_CHECK_INTERVAL_MS) {
|
|
162
|
+
this.lastTmuxHealthCheck = now;
|
|
163
|
+
await this.checkTmuxHealth();
|
|
164
|
+
}
|
|
158
165
|
for (const session of this.sessions.listSessions()) {
|
|
159
166
|
try {
|
|
160
167
|
// Issue #84: Start watching when jsonlPath is discovered
|
|
@@ -178,11 +185,6 @@ export class SessionMonitor {
|
|
|
178
185
|
this.lastDeadCheck = now;
|
|
179
186
|
await this.checkDeadSessions();
|
|
180
187
|
}
|
|
181
|
-
// Issue #397: Tmux server health check (every 10s)
|
|
182
|
-
if (now - this.lastTmuxHealthCheck >= SessionMonitor.TMUX_HEALTH_CHECK_INTERVAL_MS) {
|
|
183
|
-
this.lastTmuxHealthCheck = now;
|
|
184
|
-
await this.checkTmuxHealth();
|
|
185
|
-
}
|
|
186
188
|
}
|
|
187
189
|
/** Smart stall detection: multiple stall types with graduated thresholds.
|
|
188
190
|
*
|
|
@@ -614,6 +616,10 @@ export class SessionMonitor {
|
|
|
614
616
|
}
|
|
615
617
|
/** Check for dead tmux windows and notify via channels. */
|
|
616
618
|
async checkDeadSessions() {
|
|
619
|
+
// Issue #397: While tmux server is down, defer dead-session cleanup.
|
|
620
|
+
// tmux commands can fail transiently and make healthy sessions look dead.
|
|
621
|
+
if (this.tmuxWasDown)
|
|
622
|
+
return;
|
|
617
623
|
const sessions = this.sessions.listSessions();
|
|
618
624
|
for (const session of sessions) {
|
|
619
625
|
if (this.deadNotified.has(session.id))
|
|
@@ -643,13 +649,34 @@ export class SessionMonitor {
|
|
|
643
649
|
async checkTmuxHealth() {
|
|
644
650
|
if (!this.tmux)
|
|
645
651
|
return;
|
|
646
|
-
|
|
652
|
+
let healthy = true;
|
|
653
|
+
let error = null;
|
|
654
|
+
try {
|
|
655
|
+
({ healthy, error } = await this.tmux.isServerHealthy());
|
|
656
|
+
}
|
|
657
|
+
catch (e) {
|
|
658
|
+
healthy = false;
|
|
659
|
+
error = e instanceof Error ? e.message : String(e);
|
|
660
|
+
}
|
|
647
661
|
if (!healthy) {
|
|
662
|
+
// Only treat known server/socket failures as "tmux down".
|
|
663
|
+
// Other tmux errors can be transient command failures.
|
|
664
|
+
const serverDown = this.tmux.isTmuxServerError(new Error(error ?? 'tmux unavailable'));
|
|
665
|
+
if (!serverDown) {
|
|
666
|
+
logger.warn({
|
|
667
|
+
component: 'monitor',
|
|
668
|
+
operation: 'tmux_health_check',
|
|
669
|
+
errorCode: 'TMUX_HEALTH_CHECK_ERROR',
|
|
670
|
+
attributes: { error: error ?? 'unknown tmux health error' },
|
|
671
|
+
});
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
648
674
|
if (!this.tmuxWasDown) {
|
|
649
675
|
logger.warn({
|
|
650
676
|
component: 'monitor',
|
|
651
677
|
operation: 'tmux_health_check',
|
|
652
678
|
errorCode: 'TMUX_UNREACHABLE',
|
|
679
|
+
attributes: { error: error ?? 'tmux server unavailable' },
|
|
653
680
|
});
|
|
654
681
|
this.tmuxWasDown = true;
|
|
655
682
|
}
|
package/dist/server.js
CHANGED
|
@@ -179,6 +179,7 @@ function checkIpRateLimit(ip, isMaster) {
|
|
|
179
179
|
const authFailLimits = new Map();
|
|
180
180
|
const AUTH_FAIL_WINDOW_MS = 60_000;
|
|
181
181
|
const AUTH_FAIL_MAX = 5;
|
|
182
|
+
const MAX_AUTH_FAIL_IP_ENTRIES = 10_000;
|
|
182
183
|
function checkAuthFailRateLimit(ip) {
|
|
183
184
|
const now = Date.now();
|
|
184
185
|
const cutoff = now - AUTH_FAIL_WINDOW_MS;
|
|
@@ -187,6 +188,19 @@ function checkAuthFailRateLimit(ip) {
|
|
|
187
188
|
bucket.timestamps = bucket.timestamps.filter(t => t >= cutoff);
|
|
188
189
|
bucket.timestamps.push(now);
|
|
189
190
|
authFailLimits.set(ip, bucket);
|
|
191
|
+
if (authFailLimits.size > MAX_AUTH_FAIL_IP_ENTRIES) {
|
|
192
|
+
let oldestIp = '';
|
|
193
|
+
let oldestTime = Infinity;
|
|
194
|
+
for (const [trackedIp, trackedBucket] of authFailLimits) {
|
|
195
|
+
const lastTs = trackedBucket.timestamps[trackedBucket.timestamps.length - 1];
|
|
196
|
+
if (lastTs !== undefined && lastTs < oldestTime) {
|
|
197
|
+
oldestTime = lastTs;
|
|
198
|
+
oldestIp = trackedIp;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (oldestIp)
|
|
202
|
+
authFailLimits.delete(oldestIp);
|
|
203
|
+
}
|
|
190
204
|
return bucket.timestamps.length > AUTH_FAIL_MAX;
|
|
191
205
|
}
|
|
192
206
|
function recordAuthFailure(ip) {
|
|
@@ -771,9 +785,13 @@ async function spawnChildHandler(req, reply) {
|
|
|
771
785
|
return reply.status(404).send({ error: 'Parent session not found' });
|
|
772
786
|
const { name, prompt, workDir, permissionMode } = req.body ?? {};
|
|
773
787
|
const childName = name ?? `${parent.windowName ?? 'session'}-child`;
|
|
774
|
-
const
|
|
788
|
+
const requestedWorkDir = workDir ?? parent.workDir;
|
|
789
|
+
const safeChildWorkDir = await validateWorkDirWithConfig(requestedWorkDir);
|
|
790
|
+
if (typeof safeChildWorkDir === 'object') {
|
|
791
|
+
return reply.status(400).send({ error: `Invalid workDir: ${safeChildWorkDir.error}`, code: safeChildWorkDir.code });
|
|
792
|
+
}
|
|
775
793
|
const childPermMode = permissionMode ?? parent.permissionMode ?? 'bypassPermissions';
|
|
776
|
-
const childSession = await sessions.createSession({ workDir:
|
|
794
|
+
const childSession = await sessions.createSession({ workDir: safeChildWorkDir, name: childName, parentId, permissionMode: childPermMode });
|
|
777
795
|
let promptDelivery;
|
|
778
796
|
if (prompt) {
|
|
779
797
|
promptDelivery = await sessions.sendInitialPrompt(childSession.id, prompt);
|
|
@@ -1504,6 +1522,7 @@ function registerChannels(cfg) {
|
|
|
1504
1522
|
botToken: cfg.tgBotToken,
|
|
1505
1523
|
groupChatId: cfg.tgGroupId,
|
|
1506
1524
|
allowedUserIds: cfg.tgAllowedUsers,
|
|
1525
|
+
topicTtlMs: cfg.tgTopicTtlMs,
|
|
1507
1526
|
}));
|
|
1508
1527
|
}
|
|
1509
1528
|
// Webhooks (optional)
|
package/dist/session.d.ts
CHANGED
|
@@ -62,6 +62,8 @@ export declare class SessionManager {
|
|
|
62
62
|
private stateFile;
|
|
63
63
|
private sessionMapFile;
|
|
64
64
|
private pollTimers;
|
|
65
|
+
/** Next filesystem-scan time (ms epoch) for each discovery poller. */
|
|
66
|
+
private discoveryNextFilesystemScanAt;
|
|
65
67
|
/** #835: Discovery timeout timers — cleared in cleanupSession to prevent orphan callbacks. */
|
|
66
68
|
private discoveryTimeouts;
|
|
67
69
|
private saveQueue;
|
|
@@ -346,12 +348,17 @@ export declare class SessionManager {
|
|
|
346
348
|
* and cause new sessions to inherit context from old sessions.
|
|
347
349
|
*/
|
|
348
350
|
private purgeStaleSessionMapEntries;
|
|
349
|
-
/**
|
|
350
|
-
private
|
|
351
|
-
/**
|
|
352
|
-
|
|
351
|
+
/** Stop and remove the coordinated discovery poller/timer for a session. */
|
|
352
|
+
private stopDiscoveryPolling;
|
|
353
|
+
/** Attempt filesystem-based discovery for a single session poll tick. */
|
|
354
|
+
private maybeDiscoverFromFilesystem;
|
|
355
|
+
/**
|
|
356
|
+
* Coordinated discovery poller for a session.
|
|
357
|
+
*
|
|
358
|
+
* Consolidates hook/session_map sync and filesystem fallback into a single
|
|
359
|
+
* interval loop per session, reducing duplicate independent pollers.
|
|
353
360
|
*/
|
|
354
|
-
private
|
|
361
|
+
private startDiscoveryPolling;
|
|
355
362
|
/** Sync CC session IDs from the hook-written session_map.json. */
|
|
356
363
|
private syncSessionMap;
|
|
357
364
|
}
|