aegis-bridge 2.6.2 → 2.6.4

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Aegis Dashboard</title>
7
7
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🛡️</text></svg>" />
8
- <script type="module" crossorigin src="/dashboard/assets/index-Bfabq3q-.js"></script>
8
+ <script type="module" crossorigin src="/dashboard/assets/index-G8fziBeQ.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/dashboard/assets/index-9Hkkvm_I.css">
10
10
  </head>
11
11
  <body class="bg-[#0a0a0f] text-gray-200 antialiased">
@@ -135,6 +135,10 @@ export async function writeHookSettingsFile(baseUrl, sessionId, hookSecret, work
135
135
  mergedHooks[event] = [...(existingHooks[event] ?? []), ...entries];
136
136
  }
137
137
  const combined = { ...merged, hooks: mergedHooks };
138
+ // Issue #931: Always inject MCP_CONNECTION_NONBLOCKING so CC does not block
139
+ // on MCP server connections when launched via Aegis orchestration.
140
+ (combined.env = (combined.env || {}));
141
+ (combined.env || {})["MCP_CONNECTION_NONBLOCKING"] = "true";
138
142
  // Issue #648: Use unpredictable directory name and restrictive permissions
139
143
  // to prevent symlink attacks and information disclosure in /tmp.
140
144
  const suffix = randomBytes(4).toString('hex');
@@ -12,6 +12,51 @@
12
12
  * Issue #48: https://github.com/OneStepAt4time/aegis/issues/48
13
13
  */
14
14
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
15
+ import { type SessionInfo } from './session.js';
16
+ import { type SessionMetrics, type SessionLatency, type SessionLatencySummary } from './metrics.js';
17
+ import { type PipelineState, type BatchResult } from './pipeline.js';
18
+ interface ServerHealthResponse {
19
+ status: string;
20
+ version: string;
21
+ uptime: number;
22
+ sessions: {
23
+ active: number;
24
+ total: number;
25
+ };
26
+ tmux: {
27
+ healthy: boolean;
28
+ [key: string]: unknown;
29
+ };
30
+ timestamp: string;
31
+ }
32
+ interface CreateSessionResponse {
33
+ id: string;
34
+ windowName: string;
35
+ workDir: string;
36
+ status: string;
37
+ promptDelivery?: {
38
+ delivered: boolean;
39
+ attempts: number;
40
+ };
41
+ reused?: boolean;
42
+ [key: string]: unknown;
43
+ }
44
+ interface SendMessageResponse {
45
+ ok: boolean;
46
+ delivered: boolean;
47
+ attempts: number;
48
+ }
49
+ interface OkResponse {
50
+ ok: boolean;
51
+ }
52
+ interface CapturePaneResponse {
53
+ pane: string;
54
+ }
55
+ interface SessionLatencyResponse {
56
+ sessionId: string;
57
+ realtime: SessionLatency | null;
58
+ aggregated: SessionLatencySummary | null;
59
+ }
15
60
  export declare class AegisClient {
16
61
  private baseUrl;
17
62
  private authToken?;
@@ -21,40 +66,44 @@ export declare class AegisClient {
21
66
  listSessions(filter?: {
22
67
  status?: string;
23
68
  workDir?: string;
24
- }): Promise<any[]>;
25
- getSession(id: string): Promise<any>;
26
- getHealth(id: string): Promise<any>;
27
- getTranscript(id: string): Promise<any>;
28
- sendMessage(id: string, text: string): Promise<any>;
69
+ }): Promise<SessionInfo[]>;
70
+ getSession(id: string): Promise<Record<string, unknown>>;
71
+ getHealth(id: string): Promise<Record<string, unknown>>;
72
+ getTranscript(id: string): Promise<Record<string, unknown>>;
73
+ sendMessage(id: string, text: string): Promise<SendMessageResponse>;
29
74
  createSession(opts: {
30
75
  workDir: string;
31
76
  name?: string;
32
77
  prompt?: string;
33
- }): Promise<any>;
34
- killSession(id: string): Promise<any>;
35
- approvePermission(id: string): Promise<any>;
36
- rejectPermission(id: string): Promise<any>;
37
- getServerHealth(): Promise<any>;
38
- escapeSession(id: string): Promise<any>;
39
- interruptSession(id: string): Promise<any>;
40
- capturePane(id: string): Promise<any>;
41
- getSessionMetrics(id: string): Promise<any>;
42
- getSessionSummary(id: string): Promise<any>;
43
- sendBash(id: string, command: string): Promise<any>;
44
- sendCommand(id: string, command: string): Promise<any>;
45
- getSessionLatency(id: string): Promise<any>;
78
+ }): Promise<CreateSessionResponse>;
79
+ killSession(id: string): Promise<OkResponse>;
80
+ approvePermission(id: string): Promise<OkResponse>;
81
+ rejectPermission(id: string): Promise<OkResponse>;
82
+ getServerHealth(): Promise<ServerHealthResponse>;
83
+ escapeSession(id: string): Promise<OkResponse>;
84
+ interruptSession(id: string): Promise<OkResponse>;
85
+ capturePane(id: string): Promise<CapturePaneResponse>;
86
+ getSessionMetrics(id: string): Promise<SessionMetrics>;
87
+ getSessionSummary(id: string): Promise<Record<string, unknown>>;
88
+ sendBash(id: string, command: string): Promise<OkResponse>;
89
+ sendCommand(id: string, command: string): Promise<OkResponse>;
90
+ getSessionLatency(id: string): Promise<SessionLatencyResponse>;
46
91
  batchCreateSessions(sessions: Array<{
47
92
  workDir: string;
48
93
  name?: string;
49
94
  prompt?: string;
50
- }>): Promise<any>;
51
- listPipelines(): Promise<any>;
95
+ }>): Promise<BatchResult>;
96
+ listPipelines(): Promise<PipelineState[]>;
52
97
  createPipeline(config: {
53
98
  name: string;
54
99
  workDir: string;
55
- steps: any[];
56
- }): Promise<any>;
57
- getSwarm(): Promise<any>;
100
+ steps: Array<{
101
+ name?: string;
102
+ prompt: string;
103
+ }>;
104
+ }): Promise<PipelineState>;
105
+ getSwarm(): Promise<Record<string, unknown>>;
58
106
  }
59
107
  export declare function createMcpServer(aegisPort: number, authToken?: string): McpServer;
60
108
  export declare function startMcpServer(port: number, authToken?: string): Promise<void>;
109
+ export {};
package/dist/monitor.d.ts CHANGED
@@ -31,6 +31,16 @@ export declare class SessionMonitor {
31
31
  private lastStatus;
32
32
  private lastBytesSeen;
33
33
  private stallNotified;
34
+ /** Issue #663: O(1) stall notification check. */
35
+ private stallHas;
36
+ /** Issue #663: O(1) stall notification add. */
37
+ private stallAdd;
38
+ /** Issue #663: O(1) stall notification delete. */
39
+ private stallDelete;
40
+ /** Issue #663: Delete all stall notifications for a session. */
41
+ private stallDeleteAll;
42
+ /** Issue #663: Delete specific stall types for a session. */
43
+ private stallDeleteTypes;
34
44
  private lastStallCheck;
35
45
  private lastDeadCheck;
36
46
  private idleNotified;
package/dist/monitor.js CHANGED
@@ -34,7 +34,38 @@ export class SessionMonitor {
34
34
  running = false;
35
35
  lastStatus = new Map();
36
36
  lastBytesSeen = new Map();
37
- stallNotified = new Set(); // don't spam stall events
37
+ // Issue #663: Nested Map for O(1) per-session stall lookup (was Set with O(n) prefix scan)
38
+ stallNotified = new Map(); // sessionId → Set<stallType>
39
+ /** Issue #663: O(1) stall notification check. */
40
+ stallHas(sessionId, stallType) {
41
+ return this.stallNotified.get(sessionId)?.has(stallType) ?? false;
42
+ }
43
+ /** Issue #663: O(1) stall notification add. */
44
+ stallAdd(sessionId, stallType) {
45
+ const set = this.stallNotified.get(sessionId);
46
+ if (set) {
47
+ set.add(stallType);
48
+ }
49
+ else {
50
+ this.stallNotified.set(sessionId, new Set([stallType]));
51
+ }
52
+ }
53
+ /** Issue #663: O(1) stall notification delete. */
54
+ stallDelete(sessionId, stallType) {
55
+ this.stallNotified.get(sessionId)?.delete(stallType);
56
+ }
57
+ /** Issue #663: Delete all stall notifications for a session. */
58
+ stallDeleteAll(sessionId) {
59
+ this.stallNotified.delete(sessionId);
60
+ }
61
+ /** Issue #663: Delete specific stall types for a session. */
62
+ stallDeleteTypes(sessionId, types) {
63
+ const set = this.stallNotified.get(sessionId);
64
+ if (!set)
65
+ return;
66
+ for (const t of types)
67
+ set.delete(t);
68
+ }
38
69
  lastStallCheck = 0;
39
70
  lastDeadCheck = 0;
40
71
  idleNotified = new Set(); // prevent idle spam
@@ -196,13 +227,13 @@ export class SessionMonitor {
196
227
  }
197
228
  if (currentBytes > prev.bytes) {
198
229
  this.lastBytesSeen.set(session.id, { bytes: currentBytes, at: now });
199
- this.stallNotified.delete(`${session.id}:stall:jsonl`);
230
+ this.stallDelete(session.id, 'jsonl');
200
231
  }
201
232
  else {
202
233
  const stallDuration = now - prev.at;
203
234
  const threshold = session.stallThresholdMs || this.config.stallThresholdMs;
204
- if (stallDuration >= threshold && !this.stallNotified.has(`${session.id}:stall:jsonl`)) {
205
- this.stallNotified.add(`${session.id}:stall:jsonl`);
235
+ if (stallDuration >= threshold && !this.stallHas(session.id, 'jsonl')) {
236
+ this.stallAdd(session.id, 'jsonl');
206
237
  const minutes = Math.round(stallDuration / 60000);
207
238
  const detail = `Session stalled: "working" for ${minutes}min with no new output. ` +
208
239
  `Last activity: ${new Date(session.lastActivity).toISOString()}`;
@@ -213,15 +244,15 @@ export class SessionMonitor {
213
244
  }
214
245
  else {
215
246
  // Reset JSONL stall tracking when not working
216
- this.stallNotified.delete(`${session.id}:stall:jsonl`);
247
+ this.stallDelete(session.id, 'jsonl');
217
248
  }
218
249
  // --- Type 2: Permission stall (waiting for approval too long) ---
219
250
  if (currentStatus === 'permission_prompt' || currentStatus === 'bash_approval') {
220
251
  const entry = this.stateSince.get(session.id);
221
252
  const permDuration = entry ? now - entry.since : 0;
222
253
  if (permDuration >= this.config.permissionStallMs) {
223
- if (!this.stallNotified.has(`${session.id}:stall:permission`)) {
224
- this.stallNotified.add(`${session.id}:stall:permission`);
254
+ if (!this.stallHas(session.id, 'permission')) {
255
+ this.stallAdd(session.id, 'permission');
225
256
  const minutes = Math.round(permDuration / 60000);
226
257
  const detail = `Session stalled: waiting for permission approval for ${minutes}min. ` +
227
258
  `Auto-approve this session or POST /v1/sessions/${session.id}/approve`;
@@ -231,8 +262,8 @@ export class SessionMonitor {
231
262
  }
232
263
  // L9: Auto-reject permission after timeout
233
264
  if (permDuration >= this.config.permissionTimeoutMs) {
234
- if (!this.stallNotified.has(`${session.id}:stall:permission_timeout`)) {
235
- this.stallNotified.add(`${session.id}:stall:permission_timeout`);
265
+ if (!this.stallHas(session.id, 'permission_timeout')) {
266
+ this.stallAdd(session.id, 'permission_timeout');
236
267
  const minutes = Math.round(permDuration / 60000);
237
268
  logger.warn({
238
269
  component: 'monitor',
@@ -264,8 +295,8 @@ export class SessionMonitor {
264
295
  const entry = this.stateSince.get(session.id);
265
296
  const unkDuration = entry ? now - entry.since : 0;
266
297
  if (unkDuration >= this.config.unknownStallMs) {
267
- if (!this.stallNotified.has(`${session.id}:stall:unknown`)) {
268
- this.stallNotified.add(`${session.id}:stall:unknown`);
298
+ if (!this.stallHas(session.id, 'unknown')) {
299
+ this.stallAdd(session.id, 'unknown');
269
300
  const minutes = Math.round(unkDuration / 60000);
270
301
  const detail = `Session stalled: in "unknown" state for ${minutes}min. ` +
271
302
  `CC may be stuck. Try: POST /v1/sessions/${session.id}/interrupt or /kill`;
@@ -280,8 +311,8 @@ export class SessionMonitor {
280
311
  const stateDuration = entry ? now - entry.since : 0;
281
312
  const extendedThreshold = this.config.stallThresholdMs * 2;
282
313
  if (stateDuration >= extendedThreshold) {
283
- if (!this.stallNotified.has(`${session.id}:stall:extended`)) {
284
- this.stallNotified.add(`${session.id}:stall:extended`);
314
+ if (!this.stallHas(session.id, 'extended')) {
315
+ this.stallAdd(session.id, 'extended');
285
316
  const minutes = Math.round(stateDuration / 60000);
286
317
  const detail = `Session stalled: "${currentStatus}" state for ${minutes}min. ` +
287
318
  `May need intervention: /interrupt, /approve, or /kill`;
@@ -297,8 +328,8 @@ export class SessionMonitor {
297
328
  if (entry && entry.state === 'working') {
298
329
  const workingDuration = now - entry.since;
299
330
  const maxWorkingMs = this.config.stallThresholdMs * 3; // 15 min default
300
- if (workingDuration >= maxWorkingMs && !this.stallNotified.has(`${session.id}:stall:extended_working`)) {
301
- this.stallNotified.add(`${session.id}:stall:extended_working`);
331
+ if (workingDuration >= maxWorkingMs && !this.stallHas(session.id, 'extended_working')) {
332
+ this.stallAdd(session.id, 'extended_working');
302
333
  const minutes = Math.round(workingDuration / 60000);
303
334
  const detail = `Session stalled: in "working" state for ${minutes}min. ` +
304
335
  `CC may be stuck in an internal loop (e.g., Misting). Consider: POST /v1/sessions/${session.id}/interrupt or /kill`;
@@ -312,23 +343,18 @@ export class SessionMonitor {
312
343
  const exitedPermission = prevStallStatus === 'permission_prompt' || prevStallStatus === 'bash_approval';
313
344
  const exitedUnknown = prevStallStatus === 'unknown';
314
345
  if (exitedPermission) {
315
- this.stallNotified.delete(`${session.id}:stall:permission`);
316
- this.stallNotified.delete(`${session.id}:stall:permission_timeout`);
346
+ this.stallDeleteTypes(session.id, ['permission', 'permission_timeout']);
317
347
  }
318
348
  if (exitedUnknown) {
319
- this.stallNotified.delete(`${session.id}:stall:unknown`);
349
+ this.stallDelete(session.id, 'unknown');
320
350
  }
321
351
  }
322
352
  // Clean up all state tracking when idle (catch-all)
323
353
  if (currentStatus === 'idle') {
324
354
  this.rateLimitedSessions.delete(session.id);
325
355
  this.stateSince.delete(session.id);
326
- // Clean stall notifications (session recovered)
327
- for (const key of this.stallNotified) {
328
- if (key.startsWith(session.id)) {
329
- this.stallNotified.delete(key);
330
- }
331
- }
356
+ // Clean stall notifications (session recovered) — O(1) with Map
357
+ this.stallDeleteAll(session.id);
332
358
  }
333
359
  // Update prevStatusForStall for next cycle
334
360
  if (currentStatus) {
@@ -432,7 +458,7 @@ export class SessionMonitor {
432
458
  if (event.messages.length > 0) {
433
459
  // Real output — reset stall timer
434
460
  this.lastBytesSeen.set(event.sessionId, { bytes: event.newOffset, at: now });
435
- this.stallNotified.delete(`${event.sessionId}:stall:jsonl`);
461
+ this.stallDelete(event.sessionId, 'jsonl');
436
462
  }
437
463
  else {
438
464
  // File grew but no messages — only update bytes, keep timestamp
@@ -666,12 +692,8 @@ export class SessionMonitor {
666
692
  clearTimeout(pending);
667
693
  this.statusChangeDebounce.delete(sessionId);
668
694
  }
669
- // Clean all stall notifications for this session
670
- for (const key of this.stallNotified) {
671
- if (key.startsWith(sessionId)) {
672
- this.stallNotified.delete(key);
673
- }
674
- }
695
+ // Clean all stall notifications for this session — O(1) with Map
696
+ this.stallDeleteAll(sessionId);
675
697
  this.idleNotified.delete(sessionId);
676
698
  this.idleSince.delete(sessionId);
677
699
  this.stateSince.delete(sessionId);
package/dist/session.d.ts CHANGED
@@ -66,6 +66,7 @@ export declare class SessionManager {
66
66
  private pendingQuestions;
67
67
  private static readonly MAX_CACHE_ENTRIES_PER_SESSION;
68
68
  private parsedEntriesCache;
69
+ private sessionsListCache;
69
70
  private readonly sessionAcquireMutex;
70
71
  constructor(tmux: TmuxManager, config: Config);
71
72
  /**
@@ -165,6 +166,8 @@ export declare class SessionManager {
165
166
  * so the current pane PID is the shell (alive). Checking ccPid catches
166
167
  * the crash within seconds instead of waiting for the 5-min stall timer. */
167
168
  isWindowAlive(id: string): Promise<boolean>;
169
+ /** Issue #657: Invalidate the sessions list cache. Call on any mutation. */
170
+ private invalidateSessionsListCache;
168
171
  /** List all sessions. */
169
172
  listSessions(): SessionInfo[];
170
173
  /** Issue #607: Find an idle session for the given workDir.
package/dist/session.js CHANGED
@@ -67,6 +67,8 @@ export class SessionManager {
67
67
  // #424: Evict oldest entries when cache exceeds max to prevent unbounded growth
68
68
  static MAX_CACHE_ENTRIES_PER_SESSION = 10_000;
69
69
  parsedEntriesCache = new Map();
70
+ // Issue #657: Cached session list to avoid allocating a new array per call
71
+ sessionsListCache = null;
70
72
  // Issue #840/#880: Explicit mutex to prevent TOCTOU races in session acquisition.
71
73
  sessionAcquireMutex = new Mutex();
72
74
  constructor(tmux, config) {
@@ -168,6 +170,8 @@ export class SessionManager {
168
170
  await writeFile(`${this.stateFile}.bak`, JSON.stringify(this.state, null, 2));
169
171
  }
170
172
  catch { /* non-critical */ }
173
+ // Issue #657: Invalidate sessions list cache after loading state
174
+ this.invalidateSessionsListCache();
171
175
  // Reconcile: verify tmux windows still exist, clean up dead sessions
172
176
  await this.reconcile();
173
177
  }
@@ -190,6 +194,7 @@ export class SessionManager {
190
194
  await cleanOrphanedBackup(session.workDir);
191
195
  }
192
196
  delete this.state.sessions[id];
197
+ this.invalidateSessionsListCache();
193
198
  changed = true;
194
199
  }
195
200
  else if (!windowIdAlive && windowNameAlive) {
@@ -245,6 +250,7 @@ export class SessionManager {
245
250
  permissionMode: 'default',
246
251
  };
247
252
  this.state.sessions[id] = session;
253
+ this.invalidateSessionsListCache();
248
254
  console.log(`Reconcile: adopted orphaned window ${win.windowName} (${win.windowId}) as ${id.slice(0, 8)}`);
249
255
  this.startSessionIdDiscovery(id);
250
256
  this.startFilesystemDiscovery(id, session.workDir);
@@ -545,6 +551,7 @@ export class SessionManager {
545
551
  hookSecret,
546
552
  };
547
553
  this.state.sessions[id] = session;
554
+ this.invalidateSessionsListCache();
548
555
  await this.save();
549
556
  // Issue #353: Fetch CC process PID for swarm parent matching.
550
557
  // Fire-and-forget — PID is not needed synchronously.
@@ -744,9 +751,16 @@ export class SessionManager {
744
751
  return false;
745
752
  }
746
753
  }
754
+ /** Issue #657: Invalidate the sessions list cache. Call on any mutation. */
755
+ invalidateSessionsListCache() {
756
+ this.sessionsListCache = null;
757
+ }
747
758
  /** List all sessions. */
748
759
  listSessions() {
749
- return Object.values(this.state.sessions);
760
+ if (!this.sessionsListCache) {
761
+ this.sessionsListCache = Object.values(this.state.sessions);
762
+ }
763
+ return this.sessionsListCache;
750
764
  }
751
765
  /** Issue #607: Find an idle session for the given workDir.
752
766
  * Returns the most recently active idle session, or null if none found.
@@ -1320,6 +1334,7 @@ export class SessionManager {
1320
1334
  // #405: Clean up all tracking maps (pollTimers, pendingPermissions, pendingQuestions, parsedEntriesCache)
1321
1335
  this.cleanupSession(id);
1322
1336
  delete this.state.sessions[id];
1337
+ this.invalidateSessionsListCache();
1323
1338
  // #357: Cancel any pending debounced save before doing an immediate save
1324
1339
  if (this.saveDebounceTimer !== null) {
1325
1340
  clearTimeout(this.saveDebounceTimer);
@@ -4,8 +4,8 @@
4
4
  * Port of CCBot's transcript_parser.py.
5
5
  * Reads CC session JSONL files and extracts structured messages.
6
6
  */
7
- import { readFile, open } from 'node:fs/promises';
8
- import { createReadStream, existsSync } from 'node:fs';
7
+ import { readFile, open, access } from 'node:fs/promises';
8
+ import { createReadStream } from 'node:fs';
9
9
  import { join } from 'node:path';
10
10
  import { homedir } from 'node:os';
11
11
  import { readdir } from 'node:fs/promises';
@@ -218,10 +218,20 @@ export async function readNewEntries(filePath, fromOffset) {
218
218
  await fd.close();
219
219
  }
220
220
  }
221
+ /** Check if a path exists (async). Issue #658: replaces sync existsSync. */
222
+ async function pathExists(filePath) {
223
+ try {
224
+ await access(filePath);
225
+ return true;
226
+ }
227
+ catch {
228
+ return false;
229
+ }
230
+ }
221
231
  /** Find the JSONL file for a session ID. */
222
232
  export async function findSessionFile(sessionId, claudeProjectsDir = DEFAULT_CLAUDE_PROJECTS_DIR) {
223
233
  const projectsDir = claudeProjectsDir;
224
- if (!existsSync(projectsDir))
234
+ if (!(await pathExists(projectsDir)))
225
235
  return null;
226
236
  // Strategy 1: Direct glob across all project dirs
227
237
  const dirs = await readdir(projectsDir, { withFileTypes: true });
@@ -229,7 +239,7 @@ export async function findSessionFile(sessionId, claudeProjectsDir = DEFAULT_CLA
229
239
  if (!dir.isDirectory())
230
240
  continue;
231
241
  const jsonlPath = join(projectsDir, dir.name, `${sessionId}.jsonl`);
232
- if (existsSync(jsonlPath))
242
+ if (await pathExists(jsonlPath))
233
243
  return jsonlPath;
234
244
  }
235
245
  // Strategy 2: Check sessions-index.json files
@@ -237,7 +247,7 @@ export async function findSessionFile(sessionId, claudeProjectsDir = DEFAULT_CLA
237
247
  if (!dir.isDirectory())
238
248
  continue;
239
249
  const indexPath = join(projectsDir, dir.name, 'sessions-index.json');
240
- if (existsSync(indexPath)) {
250
+ if (await pathExists(indexPath)) {
241
251
  try {
242
252
  const indexRaw = await readFile(indexPath, 'utf-8');
243
253
  const indexParsed = sessionsIndexSchema.safeParse(JSON.parse(indexRaw));
@@ -245,7 +255,7 @@ export async function findSessionFile(sessionId, claudeProjectsDir = DEFAULT_CLA
245
255
  continue;
246
256
  const entries = indexParsed.data.entries || [];
247
257
  for (const entry of entries) {
248
- if (entry.sessionId === sessionId && entry.fullPath && existsSync(entry.fullPath)) {
258
+ if (entry.sessionId === sessionId && entry.fullPath && (await pathExists(entry.fullPath))) {
249
259
  return entry.fullPath;
250
260
  }
251
261
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aegis-bridge",
3
- "version": "2.6.2",
3
+ "version": "2.6.4",
4
4
  "type": "module",
5
5
  "description": "Orchestrate Claude Code sessions via API. Create, brief, monitor, refine, ship.",
6
6
  "main": "dist/server.js",