@aexol/spectral 0.9.112 → 0.9.113

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.
@@ -52,9 +52,9 @@ const PRIMARY_AGENT_PROMPT_DELIMITER = "\n\n---\n\n";
52
52
  * gets its own independent deadline so that no single subagent can
53
53
  * exhaust the parent prompt's entire timeout budget.
54
54
  *
55
- * Defaults to 15 minutes. Override via SPECTRAL_SUBAGENT_TIMEOUT_MS env var.
55
+ * Defaults to 4 hours. Override via SPECTRAL_SUBAGENT_TIMEOUT_MS env var.
56
56
  */
57
- const SUBAGENT_TIMEOUT_MS = Number(process.env.SPECTRAL_SUBAGENT_TIMEOUT_MS) || 15 * 60 * 1000;
57
+ const SUBAGENT_TIMEOUT_MS = Number(process.env.SPECTRAL_SUBAGENT_TIMEOUT_MS) || 4 * 60 * 60 * 1000;
58
58
  // ---------------------------------------------------------------------------
59
59
  // Helpers — formatting
60
60
  // ---------------------------------------------------------------------------
@@ -1 +1 @@
1
- {"version":3,"file":"inter-agent-receiver.d.ts","sourceRoot":"","sources":["../../../src/memory/hooks/inter-agent-receiver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAoFpE,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAgIlE"}
1
+ {"version":3,"file":"inter-agent-receiver.d.ts","sourceRoot":"","sources":["../../../src/memory/hooks/inter-agent-receiver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAoFpE,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CA0IlE"}
@@ -137,8 +137,19 @@ export function registerInterAgentReceiver(ext) {
137
137
  return;
138
138
  state.pending.push(obs);
139
139
  state.deliveredIds.add(obs.memoryId);
140
- if (ctx.hasUI && ctx.ui) {
141
- ctx.ui.notify(`Shared observation from ${msg.senderName ?? msg.senderSessionId}: ${obs.content.slice(0, 120)}`, "info");
140
+ try {
141
+ if (ctx.hasUI && ctx.ui) {
142
+ ctx.ui.notify(`Shared observation from ${msg.senderName ?? msg.senderSessionId}: ${obs.content.slice(0, 120)}`, "info");
143
+ }
144
+ }
145
+ catch (err) {
146
+ const message = err instanceof Error ? err.message : String(err);
147
+ if (message.includes("stale after session replacement")) {
148
+ state.unsubscribe?.();
149
+ return;
150
+ }
151
+ // Re-throw unexpected errors so the broker surfaces them.
152
+ throw err;
142
153
  }
143
154
  },
144
155
  });
@@ -1 +1 @@
1
- {"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/tools/find.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAI7D,OAAO,EAAiC,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AAMnG,QAAA,MAAM,UAAU;;;;EAMd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAItD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,2BAA2B;IAC3B,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC7D,4EAA4E;IAC5E,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;CACnH;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAmDD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAiPhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG"}
1
+ {"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/tools/find.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAI7D,OAAO,EAAiC,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AAMnG,QAAA,MAAM,UAAU;;;;EAMd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAiDtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,2BAA2B;IAC3B,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC7D,4EAA4E;IAC5E,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;CACnH;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAmDD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CA0QhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG"}
@@ -19,6 +19,48 @@ const findSchema = Type.Object({
19
19
  limit: Type.Optional(Type.Number({ description: "Maximum number of results (default: 1000)" })),
20
20
  });
21
21
  const DEFAULT_LIMIT = 1000;
22
+ // Hard cap on how long a single fd invocation may run. Prevents the find tool
23
+ // from hanging indefinitely when the search root contains large/irrelevant
24
+ // trees (node_modules, .git, .next, build artifacts, project temp folders).
25
+ const FD_TIMEOUT_MS = 20_000;
26
+ // Directories and patterns excluded from fd searches regardless of .gitignore.
27
+ // These are a safety net for trees that have no/incomplete .gitignore, and they
28
+ // also catch project-specific temp folders the agent itself creates.
29
+ const FD_EXCLUDE_PATTERNS = [
30
+ "node_modules",
31
+ ".git",
32
+ ".next",
33
+ "dist",
34
+ "build",
35
+ ".turbo",
36
+ ".cache",
37
+ ".tmp",
38
+ "tmp",
39
+ ".spectral",
40
+ ".agent-cache",
41
+ ".visual-agent-output",
42
+ "coverage",
43
+ ".deno-cache",
44
+ "*.log",
45
+ ];
46
+ // Glob equivalents of FD_EXCLUDE_PATTERNS, used by the custom glob branch.
47
+ const GLOB_IGNORE_PATTERNS = [
48
+ "**/node_modules/**",
49
+ "**/.git/**",
50
+ "**/.next/**",
51
+ "**/dist/**",
52
+ "**/build/**",
53
+ "**/.turbo/**",
54
+ "**/.cache/**",
55
+ "**/.tmp/**",
56
+ "**/tmp/**",
57
+ "**/.spectral/**",
58
+ "**/.agent-cache/**",
59
+ "**/.visual-agent-output/**",
60
+ "**/coverage/**",
61
+ "**/.deno-cache/**",
62
+ "**/*.log",
63
+ ];
22
64
  const defaultFindOperations = {
23
65
  exists: existsSync,
24
66
  // This is a placeholder. Actual fd execution happens in execute() when no custom glob is provided.
@@ -106,7 +148,7 @@ export function createFindToolDefinition(cwd, options) {
106
148
  return;
107
149
  }
108
150
  const results = await ops.glob(pattern, searchPath, {
109
- ignore: ["**/node_modules/**", "**/.git/**"],
151
+ ignore: [...GLOB_IGNORE_PATTERNS],
110
152
  limit: effectiveLimit,
111
153
  });
112
154
  if (signal?.aborted) {
@@ -162,14 +204,20 @@ export function createFindToolDefinition(cwd, options) {
162
204
  // Build fd arguments. --no-require-git makes fd apply hierarchical .gitignore
163
205
  // semantics whether or not the search path is inside a git repository, without
164
206
  // leaking sibling-directory rules the way --ignore-file (a global source) would.
207
+ // --hidden is intentionally omitted: it caused fd to descend into .git/ and
208
+ // .next/, which is the #1 cause of the find tool hanging on large trees.
165
209
  const args = [
166
210
  "--glob",
167
211
  "--color=never",
168
- "--hidden",
169
212
  "--no-require-git",
170
213
  "--max-results",
171
214
  String(effectiveLimit),
172
215
  ];
216
+ // Safety net excludes independent of .gitignore (handles trees with no or
217
+ // incomplete .gitignore). fd accepts repeated --exclude args.
218
+ for (const pattern of FD_EXCLUDE_PATTERNS) {
219
+ args.push("--exclude", pattern);
220
+ }
173
221
  // fd --glob matches against the basename unless --full-path is set; in --full-path
174
222
  // mode it matches against the absolute candidate path, so a path-containing
175
223
  // pattern like 'src/**/*.spec.ts' needs a leading '**/' to match anything.
@@ -185,12 +233,21 @@ export function createFindToolDefinition(cwd, options) {
185
233
  const rl = createInterface({ input: child.stdout });
186
234
  let stderr = "";
187
235
  const lines = [];
236
+ // Kill fd if it runs longer than FD_TIMEOUT_MS. Without this, a search
237
+ // rooted at a large tree can hang the find tool indefinitely.
238
+ let timedOut = false;
239
+ const timeoutHandle = setTimeout(() => {
240
+ timedOut = true;
241
+ child.kill("SIGTERM");
242
+ }, FD_TIMEOUT_MS);
243
+ const clearTimer = () => clearTimeout(timeoutHandle);
188
244
  stopChild = () => {
189
245
  if (!child.killed) {
190
246
  child.kill();
191
247
  }
192
248
  };
193
249
  const cleanup = () => {
250
+ clearTimer();
194
251
  rl.close();
195
252
  };
196
253
  child.stderr?.on("data", (chunk) => {
@@ -205,6 +262,10 @@ export function createFindToolDefinition(cwd, options) {
205
262
  });
206
263
  child.on("close", (code) => {
207
264
  cleanup();
265
+ if (timedOut) {
266
+ settle(() => reject(new Error(`Search timed out after ${FD_TIMEOUT_MS / 1000}s — the search scope was too large. Try narrowing the path or pattern.`)));
267
+ return;
268
+ }
208
269
  if (signal?.aborted) {
209
270
  settle(() => reject(new Error("Operation aborted")));
210
271
  return;
@@ -166,6 +166,8 @@ export declare class AgentBridge {
166
166
  * envelopes carrying the same modelId don't churn spectral's internal state.
167
167
  */
168
168
  private lastAppliedModelId?;
169
+ private promptActivityTimer?;
170
+ private promptReject?;
169
171
  /** Current model's credit rates (from availableAgentModels), used for token_usage. */
170
172
  private activeCreditRates;
171
173
  private memoryPhase;
@@ -329,6 +331,13 @@ export declare class AgentBridge {
329
331
  * observations can stay relevant to the current task.
330
332
  */
331
333
  private withPromptTimeout;
334
+ /**
335
+ * Reset the inactivity watchdog. Called from `handleEvent` on EVERY session
336
+ * event so a long-running prompt that is actively streaming / calling tools
337
+ * is never killed by the inactivity timeout.
338
+ */
339
+ private bumpPromptActivity;
340
+ private clearPromptTimers;
332
341
  compact(options?: string | AgentBridgeCompactionOptions): Promise<CompactionResult>;
333
342
  dispose(): void;
334
343
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAaH,OAAO,EAQL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EAEtB,MAAM,8BAA8B,CAAC;AAYtC,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAatC,OAAO,EACL,kBAAkB,IAAI,yBAAyB,EAEhD,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG3E,UAAU,4BAA4B;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CACpC;AAkBD,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,OAAO,yBAAyB,CAAC;AAmCpE,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,0BAA0B,EAAE,CAAC,GAAG,EAAE;QAChC,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,6EAA6E;QAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,yBAAyB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC9C;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAkjBD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAC,CAAe;IAC/B,OAAO,CAAC,iCAAiC,CAAqB;IAC9D,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,OAAO,CAAC,CAA0B;IAC1C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C;;;;;OAKG;IACH,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC;;;;OAIG;IACH,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB,CAAC,CAAS;IAEpC,OAAO,CAAC,mBAAmB,CAAC,CAAgC;IAC5D,OAAO,CAAC,YAAY,CAAC,CAAuB;IAC5C,sFAAsF;IACtF,OAAO,CAAC,iBAAiB,CAMT;IAChB,OAAO,CAAC,WAAW,CACuD;IAC1E,OAAO,CAAC,mBAAmB,CAA+B;IAC1D;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAAuB;gBAEjC,IAAI,EAAE,kBAAkB;IAIpC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiV5B,OAAO,CAAC,4BAA4B;IAWpC;;;;;OAKG;YACW,qBAAqB;IAcnC;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;YAI7B,oBAAoB;IAoBlC;;;;;;;;;;;;OAYG;IACH;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,0BAA0B;IA+GlC;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;OAQG;IACH,wBAAwB,IAAI,MAAM,GAAG,SAAS;IAI9C;;;;OAIG;IACH,eAAe,IACX;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxE,SAAS;IAIb,gBAAgB,IAAI,KAAK,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IAgBF,iBAAiB,IAAI;QACnB,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;QACtE,QAAQ,EAAE;YACR,QAAQ,EAAE,OAAO,CAAC;YAClB,UAAU,EAAE,OAAO,CAAC;YACpB,UAAU,EAAE,OAAO,CAAC;YACpB,MAAM,EAAE,OAAO,CAAC;SACjB,CAAC;KACH;IAYD,OAAO,CAAC,iBAAiB;IAmCnB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IA2DpE;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,iCAAiC;IAkBzC;;;;;;OAMG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAoBpD;;;;;;;;;;;OAWG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8CrE;;;;;;;;OAQG;YACW,iBAAiB;IAsB/B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,iBAAiB;IAOnB,OAAO,CACX,OAAO,CAAC,EAAE,MAAM,GAAG,4BAA4B,GAC9C,OAAO,CAAC,gBAAgB,CAAC;IAY5B,OAAO,IAAI,IAAI;IA2Bf;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAOrB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,sBAAsB;IAuD9B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI;CAydzC"}
@@ -533,7 +533,18 @@ function toRestoredMemorySnapshot(snapshot) {
533
533
  details: normalizeSnapshotDetails(snapshot.details),
534
534
  };
535
535
  }
536
- const PROMPT_HANG_TIMEOUT_MS = Number(process.env.SPECTRAL_PROMPT_TIMEOUT_MS) || 5 * 60 * 1000;
536
+ /**
537
+ * Inactivity-based prompt hang watchdog. Unlike a fixed wall-clock deadline,
538
+ * this only fires when the agent session has emitted NO events (text deltas,
539
+ * tool calls, tool results, thinking, message boundaries) for the configured
540
+ * window. Long-running but active prompts (multi-turn tool use, builds,
541
+ * subagents, streaming) are NOT killed — only truly stuck sessions are.
542
+ *
543
+ * Reset on every `AgentSessionEvent` seen by `handleEvent`. Default 120s of
544
+ * total silence. Hard ceiling removed — working agents (including
545
+ * subagents) must never be killed by a wall-clock timeout.
546
+ */
547
+ const PROMPT_INACTIVITY_TIMEOUT_MS = Number(process.env.SPECTRAL_PROMPT_INACTIVITY_MS) || 2 * 60 * 1000;
537
548
  export class AgentBridge {
538
549
  session;
539
550
  suppressNextAgentEndForGeneration;
@@ -563,6 +574,8 @@ export class AgentBridge {
563
574
  * envelopes carrying the same modelId don't churn spectral's internal state.
564
575
  */
565
576
  lastAppliedModelId;
577
+ promptActivityTimer;
578
+ promptReject;
566
579
  /** Current model's credit rates (from availableAgentModels), used for token_usage. */
567
580
  activeCreditRates = null;
568
581
  memoryPhase = "idle";
@@ -1283,6 +1296,19 @@ export class AgentBridge {
1283
1296
  this.finalizePendingMessage();
1284
1297
  this.pending = undefined;
1285
1298
  this.suppressNextAgentEndForGeneration = this.turnGeneration;
1299
+ // Root-cause fix: when the prompt times out (or any prompt rejection
1300
+ // fires), the SDK session is still running because Promise.race does
1301
+ // not cancel the underlying `session.prompt()` call. The SDK keeps
1302
+ // emitting events (notably `message_start`), which would re-arm
1303
+ // `currentTurn` downstream and leave the session stuck in "streaming"
1304
+ // forever. Abort the SDK session best-effort so it stops emitting; a
1305
+ // failure here must not mask the original error.
1306
+ try {
1307
+ await this.session?.abort();
1308
+ }
1309
+ catch {
1310
+ // best-effort: abort failure must not propagate out of error handler
1311
+ }
1286
1312
  this.opts.emit({ type: "error", message: e.message });
1287
1313
  this.opts.emit({ type: "agent_end" });
1288
1314
  }
@@ -1297,19 +1323,42 @@ export class AgentBridge {
1297
1323
  * observations can stay relevant to the current task.
1298
1324
  */
1299
1325
  async withPromptTimeout(prompt) {
1300
- let timeout;
1326
+ // Activity-based watchdog: reset on every session event via
1327
+ // `bumpPromptActivity()`. Only triggers on total silence, NOT on long
1328
+ // active prompts. Hard ceiling removed — working agents (including
1329
+ // subagents) must never be killed by a wall-clock timeout.
1330
+ const inactivityPromise = new Promise((_, reject) => {
1331
+ this.promptReject = reject;
1332
+ this.promptActivityTimer = setTimeout(() => {
1333
+ reject(new Error(`Agent prompt inactive for ${PROMPT_INACTIVITY_TIMEOUT_MS / 1000}s`));
1334
+ }, PROMPT_INACTIVITY_TIMEOUT_MS);
1335
+ });
1301
1336
  try {
1302
- await Promise.race([
1303
- prompt,
1304
- new Promise((_, reject) => {
1305
- timeout = setTimeout(() => reject(new Error(`Agent prompt timed out after ${PROMPT_HANG_TIMEOUT_MS / 1000}s`)), PROMPT_HANG_TIMEOUT_MS);
1306
- }),
1307
- ]);
1337
+ await Promise.race([prompt, inactivityPromise]);
1308
1338
  }
1309
1339
  finally {
1310
- if (timeout)
1311
- clearTimeout(timeout);
1340
+ this.clearPromptTimers();
1341
+ }
1342
+ }
1343
+ /**
1344
+ * Reset the inactivity watchdog. Called from `handleEvent` on EVERY session
1345
+ * event so a long-running prompt that is actively streaming / calling tools
1346
+ * is never killed by the inactivity timeout.
1347
+ */
1348
+ bumpPromptActivity() {
1349
+ if (!this.promptActivityTimer)
1350
+ return;
1351
+ clearTimeout(this.promptActivityTimer);
1352
+ this.promptActivityTimer = setTimeout(() => {
1353
+ this.promptReject?.(new Error(`Agent prompt inactive for ${PROMPT_INACTIVITY_TIMEOUT_MS / 1000}s`));
1354
+ }, PROMPT_INACTIVITY_TIMEOUT_MS);
1355
+ }
1356
+ clearPromptTimers() {
1357
+ if (this.promptActivityTimer) {
1358
+ clearTimeout(this.promptActivityTimer);
1359
+ this.promptActivityTimer = undefined;
1312
1360
  }
1361
+ this.promptReject = undefined;
1313
1362
  }
1314
1363
  async compact(options) {
1315
1364
  if (!this.session)
@@ -1347,6 +1396,7 @@ export class AgentBridge {
1347
1396
  this.session = undefined;
1348
1397
  this.unsubscribe = undefined;
1349
1398
  this.pending = undefined;
1399
+ this.clearPromptTimers();
1350
1400
  }
1351
1401
  // --- internals -----------------------------------------------------------
1352
1402
  /**
@@ -1434,6 +1484,9 @@ export class AgentBridge {
1434
1484
  handleEvent(ev) {
1435
1485
  if (this.disposed)
1436
1486
  return;
1487
+ // Reset inactivity watchdog on every session event so long active
1488
+ // prompts (multi-tool, streaming, subagents) are not killed.
1489
+ this.bumpPromptActivity();
1437
1490
  switch (ev.type) {
1438
1491
  case "message_start": {
1439
1492
  // Only assistant messages get our message_start frame on the wire.
@@ -384,6 +384,9 @@ export declare class SessionStreamManager {
384
384
  private getIntervalEventSnapshot;
385
385
  private scheduleIntervalTick;
386
386
  private clearIntervalTimer;
387
+ private clearCurrentTurnSilenceTimer;
388
+ private armCurrentTurnSilenceTimer;
389
+ private handleCurrentTurnSilenceTimeout;
387
390
  private intervalTick;
388
391
  /**
389
392
  * Fork & Compact: trigger compaction after the first assistant turn of a
@@ -1 +1 @@
1
- {"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"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"}
@@ -51,6 +51,32 @@ import { reflectionContent, reflectionId } from "../memory/types.js";
51
51
  const DEFAULT_BRIDGE_FACTORY = (args) => new AgentBridge(args);
52
52
  /** Safety limit for autonomous loop iterations per session. */
53
53
  const MAX_LOOP_ITERATIONS = 100;
54
+ // Fix E: safety-net for stale `currentTurn`. When an agent hangs, crashes,
55
+ // or the WS drops during sleep, `agent_end` may never arrive and
56
+ // `stream.currentTurn` stays non-null forever. On reconnect, `session_ready`
57
+ // then reports `currentTurn != null`, so landing shows a phantom "streaming"
58
+ // status. This timer clears the stale turn and emits a synthetic `agent_end`
59
+ // after 5 minutes of silence on streaming-meaningful events.
60
+ const CURRENT_TURN_SILENCE_TIMEOUT_MS = 30 * 60 * 1000;
61
+ // Streaming-meaningful events that reset the silence timer while a turn is
62
+ // active. Mirrors Fix C on the landing side, but EXCLUDES `token_usage`
63
+ // (arrives post-turn) and excludes terminal/control events handled by their
64
+ // own branches (`agent_end`, `error`) and non-streaming side channels
65
+ // (`inter_agent_observation`, `agent_notification`, `queue_changed`,
66
+ // `session_renamed`, `interval_*`, `loop_*`, `compaction_*`).
67
+ const STREAMING_MEANINGFUL_EVENT_TYPES = new Set([
68
+ "message_start",
69
+ "text_delta",
70
+ "thinking_start",
71
+ "thinking_delta",
72
+ "thinking_end",
73
+ "tool_call",
74
+ "tool_result",
75
+ "message_end",
76
+ "subagent_start",
77
+ "subagent_progress",
78
+ "subagent_end",
79
+ ]);
54
80
  /**
55
81
  * Defensive cap for fresh-bridge history replay when we already have a
56
82
  * persisted observational-memory snapshot. spectral's own compaction keeps a
@@ -960,6 +986,10 @@ export class SessionStreamManager {
960
986
  persistedEventCount: 0,
961
987
  cleanupStarted: false,
962
988
  };
989
+ // New prompt dispatched — clear the turn-closed fence so legitimate
990
+ // next-turn events (including the upcoming `message_start`) are not
991
+ // rejected. The fence is re-armed if this turn later closes.
992
+ stream.closedTurnGeneration = null;
963
993
  // 4. Fire spectral. `prompt` resolves on agent_end; errors inside
964
994
  // AgentBridge are caught and emitted as wire errors. Synchronous throws
965
995
  // (e.g. disposed bridge, missing session) arrive as a rejected promise
@@ -985,6 +1015,8 @@ export class SessionStreamManager {
985
1015
  for (const stream of this.streams.values()) {
986
1016
  stream.loopActive = false;
987
1017
  this.clearIntervalTimer(stream);
1018
+ // Fix E: drop any in-flight safety-net timers on teardown.
1019
+ this.clearCurrentTurnSilenceTimer(stream);
988
1020
  try {
989
1021
  stream.bridge.dispose();
990
1022
  }
@@ -1051,6 +1083,10 @@ export class SessionStreamManager {
1051
1083
  // through handleBridgeEvent — which clears currentTurn. We must
1052
1084
  // broadcast agent_end regardless of what dispose() does to currentTurn.
1053
1085
  const hadTurn = stream.currentTurn != null;
1086
+ // Fix E: cancelTurn forces a turn end — clear the safety-net timer so
1087
+ // the synthetic agent_end below isn't later double-emitted by a stale
1088
+ // timer.
1089
+ this.clearCurrentTurnSilenceTimer(stream);
1054
1090
  // Dispose the agent bridge immediately — this tears down spectral's session and
1055
1091
  // unsubscribe. The bridge's own event handler is detached; no further
1056
1092
  // events will flow. We broadcast agent_end ourselves below.
@@ -1078,6 +1114,7 @@ export class SessionStreamManager {
1078
1114
  this.broadcast(stream, { type: "agent_end" });
1079
1115
  this.runTurnCleanup(cancelledTurn);
1080
1116
  stream.currentTurn = null;
1117
+ stream.closedTurnGeneration = stream.bridgeTurnGeneration;
1081
1118
  this.maybeAutoDequeue(stream);
1082
1119
  }
1083
1120
  // Don't delete the stream — the subscribers are still attached and the
@@ -1240,6 +1277,51 @@ export class SessionStreamManager {
1240
1277
  stream.intervalTimer = null;
1241
1278
  }
1242
1279
  }
1280
+ // ---- Fix E: currentTurn silence safety-net timer ----
1281
+ clearCurrentTurnSilenceTimer(stream) {
1282
+ if (stream.currentTurnSilenceTimer) {
1283
+ clearTimeout(stream.currentTurnSilenceTimer);
1284
+ stream.currentTurnSilenceTimer = null;
1285
+ }
1286
+ }
1287
+ armCurrentTurnSilenceTimer(stream) {
1288
+ this.clearCurrentTurnSilenceTimer(stream);
1289
+ stream.currentTurnSilenceTimer = setTimeout(() => {
1290
+ this.handleCurrentTurnSilenceTimeout(stream);
1291
+ }, CURRENT_TURN_SILENCE_TIMEOUT_MS);
1292
+ }
1293
+ handleCurrentTurnSilenceTimeout(stream) {
1294
+ stream.currentTurnSilenceTimer = null;
1295
+ // Guard against double-fire: if currentTurn was already cleared
1296
+ // naturally (agent_end / error / cancelTurn) the timer is normally
1297
+ // cancelled, but defend against a race where a natural clear raced the
1298
+ // timer's expiry callback.
1299
+ if (stream.currentTurn == null)
1300
+ return;
1301
+ const sessionId = stream.sessionId;
1302
+ console.warn(`[session-stream] currentTurn silence timeout fired ` +
1303
+ `(${CURRENT_TURN_SILENCE_TIMEOUT_MS / 1000}s without a ` +
1304
+ `streaming-meaningful event), emitting synthetic agent_end ` +
1305
+ `for session ${sessionId}`);
1306
+ const finishedTurn = stream.currentTurn;
1307
+ stream.currentMessageId = null;
1308
+ stream.lastFlushedEventCount = 0;
1309
+ stream.currentTurn = null;
1310
+ stream.closedTurnGeneration = stream.bridgeTurnGeneration;
1311
+ // Reuse the normal broadcast path so relay subscribers (landing
1312
+ // WebSocket clients) receive agent_end and stop showing "streaming".
1313
+ this.broadcast(stream, { type: "agent_end" });
1314
+ this.runTurnCleanup(finishedTurn);
1315
+ if (!this.disposed) {
1316
+ try {
1317
+ this.maybeAutoDequeue(stream);
1318
+ }
1319
+ catch (err) {
1320
+ console.error(`[session-stream] error: failed to auto-dequeue after ` +
1321
+ `synthetic agent_end for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
1322
+ }
1323
+ }
1324
+ }
1243
1325
  intervalTick(stream) {
1244
1326
  if (!stream.intervalConfig)
1245
1327
  return;
@@ -1385,6 +1467,9 @@ export class SessionStreamManager {
1385
1467
  startError: null,
1386
1468
  subscribers: new Set(),
1387
1469
  currentTurn: null,
1470
+ bridgeTurnGeneration: 0,
1471
+ closedTurnGeneration: null,
1472
+ currentTurnSilenceTimer: null,
1388
1473
  currentMessageId: null,
1389
1474
  lastFlushedEventCount: 0,
1390
1475
  loopActive: false,
@@ -1540,6 +1625,17 @@ export class SessionStreamManager {
1540
1625
  // late attachers see the events. The first event in such a case is
1541
1626
  // typically `message_start`.
1542
1627
  if (isReplayable(event)) {
1628
+ // Defense-in-depth: reject replayable events from a turn that was
1629
+ // already closed (by a timeout-abort, error, agent_end, cancelTurn, or
1630
+ // the silence timer). A stray `message_start` emitted by an aborted
1631
+ // SDK session would otherwise re-arm `currentTurn` and leave the
1632
+ // session stuck in "streaming" forever. `closedTurnGeneration` is
1633
+ // cleared when a new prompt is dispatched, so legitimate next-turn
1634
+ // events are unaffected.
1635
+ if (stream.closedTurnGeneration !== null &&
1636
+ stream.bridgeTurnGeneration <= stream.closedTurnGeneration) {
1637
+ return;
1638
+ }
1543
1639
  if (!stream.currentTurn) {
1544
1640
  stream.currentTurn = {
1545
1641
  turnId: randomUUID(),
@@ -1556,6 +1652,11 @@ export class SessionStreamManager {
1556
1652
  }
1557
1653
  // Track message lifecycle for batch persistence.
1558
1654
  if (event.type === "message_start") {
1655
+ // Bump to mirror `AgentBridge.turnGeneration`, which is incremented
1656
+ // on every `message_start` (agent-bridge.ts). This lets the
1657
+ // `closedTurnGeneration` fence distinguish events from a closed turn
1658
+ // from events of a fresh turn that follows it.
1659
+ stream.bridgeTurnGeneration += 1;
1559
1660
  stream.currentMessageId = event.messageId;
1560
1661
  stream.lastFlushedEventCount = 0;
1561
1662
  // Stub-insert so the server can recover this message on restart even
@@ -1581,6 +1682,15 @@ export class SessionStreamManager {
1581
1682
  stream.currentTurn.events.length - stream.lastFlushedEventCount >= BATCH_FLUSH_INTERVAL) {
1582
1683
  this.flushInFlightTurn(stream);
1583
1684
  }
1685
+ // Fix E: reset the 5-min silence timer whenever a
1686
+ // streaming-meaningful event arrives while a turn is active. As long
1687
+ // as the LLM keeps streaming (even slowly), the timer keeps resetting.
1688
+ // `token_usage` is intentionally excluded (arrives post-turn and would
1689
+ // prevent cleanup of a hung turn).
1690
+ if (stream.currentTurn &&
1691
+ STREAMING_MEANINGFUL_EVENT_TYPES.has(event.type)) {
1692
+ this.armCurrentTurnSilenceTimer(stream);
1693
+ }
1584
1694
  }
1585
1695
  // Broadcast first, then maybe close out the turn. agent_end clears the
1586
1696
  // buffer because by that point the assistant message is already in
@@ -1728,6 +1838,10 @@ export class SessionStreamManager {
1728
1838
  stream.lastFlushedEventCount = 0;
1729
1839
  const finishedTurn = stream.currentTurn;
1730
1840
  stream.currentTurn = null;
1841
+ stream.closedTurnGeneration = stream.bridgeTurnGeneration;
1842
+ // Fix E: natural agent_end arrived — cancel the safety-net timer so we
1843
+ // don't double-emit.
1844
+ this.clearCurrentTurnSilenceTimer(stream);
1731
1845
  // Autonomous iterative loop (Ralph Wiggum pattern).
1732
1846
  // When loopActive is set, check for completion marker, then re-send
1733
1847
  // the ORIGINAL prompt so the agent sees its prior changes and
@@ -1829,6 +1943,10 @@ export class SessionStreamManager {
1829
1943
  stream.currentMessageId = null;
1830
1944
  stream.lastFlushedEventCount = 0;
1831
1945
  stream.currentTurn = null;
1946
+ stream.closedTurnGeneration = stream.bridgeTurnGeneration;
1947
+ // Fix E: error terminates the turn — cancel the safety-net timer so we
1948
+ // don't double-emit.
1949
+ this.clearCurrentTurnSilenceTimer(stream);
1832
1950
  this.runTurnCleanup(failedTurn);
1833
1951
  }
1834
1952
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.9.112",
3
+ "version": "0.9.113",
4
4
  "description": "AI coding agent for Aexol with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,