@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.
- package/dist/agent/index.js +2 -2
- package/dist/memory/hooks/inter-agent-receiver.d.ts.map +1 -1
- package/dist/memory/hooks/inter-agent-receiver.js +13 -2
- package/dist/sdk/coding-agent/core/tools/find.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/tools/find.js +63 -2
- package/dist/server/agent-bridge.d.ts +9 -0
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +63 -10
- package/dist/server/session-stream.d.ts +3 -0
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +118 -0
- package/package.json +1 -1
package/dist/agent/index.js
CHANGED
|
@@ -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
|
|
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) ||
|
|
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,
|
|
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
|
-
|
|
141
|
-
ctx.
|
|
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;
|
|
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: [
|
|
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":"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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1311
|
-
|
|
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
|
}
|