@aexol/spectral 0.9.198 → 0.9.200
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/commands/serve.d.ts +2 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +11 -1
- package/dist/extensions/session-fanout/index.d.ts +19 -0
- package/dist/extensions/session-fanout/index.d.ts.map +1 -0
- package/dist/extensions/session-fanout/index.js +139 -0
- package/dist/relay/client.d.ts +1 -0
- package/dist/relay/client.d.ts.map +1 -1
- package/dist/relay/client.js +34 -5
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +11 -9
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/native-extensions.js +10 -0
- package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/system-prompt.js +2 -1
- package/dist/server/agent-bridge.d.ts +25 -0
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +95 -1
- package/dist/server/fanout/fanout-manager.d.ts +126 -0
- package/dist/server/fanout/fanout-manager.d.ts.map +1 -0
- package/dist/server/fanout/fanout-manager.js +344 -0
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +5 -1
- package/package.json +1 -1
package/dist/commands/serve.d.ts
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Flag surface:
|
|
21
21
|
* - `--machine-name <name>` — display name used at registration time.
|
|
22
|
+
* - `SPECTRAL_MACHINE_NAME` — fallback display name used when the flag is
|
|
23
|
+
* not supplied. The flag takes precedence over the environment variable.
|
|
22
24
|
* - `--port <n>` / `-p <n>` / `SPECTRAL_PORT` — accepted but warned-and-ignored
|
|
23
25
|
* so users updating from the pre-relay version don't get a hard error.
|
|
24
26
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAwBH,OAAO,EAAE,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AAYlE,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAYrE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAazD;AAED,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CA6B9D;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,KAAK,EAAE,YAAY,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,oBAAoB,CAAC;IAC9B,4CAA4C;IAC5C,KAAK,EAAE,WAAW,CAAC;IACnB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAwiBlF;AA+DD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAc5E"}
|
package/dist/commands/serve.js
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Flag surface:
|
|
21
21
|
* - `--machine-name <name>` — display name used at registration time.
|
|
22
|
+
* - `SPECTRAL_MACHINE_NAME` — fallback display name used when the flag is
|
|
23
|
+
* not supplied. The flag takes precedence over the environment variable.
|
|
22
24
|
* - `--port <n>` / `-p <n>` / `SPECTRAL_PORT` — accepted but warned-and-ignored
|
|
23
25
|
* so users updating from the pre-relay version don't get a hard error.
|
|
24
26
|
*
|
|
@@ -37,6 +39,7 @@ import { getAgentDir } from "../sdk/coding-agent/config.js";
|
|
|
37
39
|
import { refreshCloudAgents as refreshCloudAgentsImpl } from "../agent/agents.js";
|
|
38
40
|
import { requireLogin } from "../preflight.js";
|
|
39
41
|
import { RelayClient } from "../relay/client.js";
|
|
42
|
+
import { getFanoutManager } from "../server/fanout/fanout-manager.js";
|
|
40
43
|
import { detachAllSubscribers, handleAutoOptimizeFrame, handleAutoResearchFrame, handleCancelTurn, handleClientMessage, handleRestRequest, handleSubscribe, } from "../relay/dispatcher.js";
|
|
41
44
|
import { ensureMachineRegistered } from "../relay/registration.js";
|
|
42
45
|
import { saveMachine } from "../relay/machine-store.js";
|
|
@@ -240,6 +243,12 @@ export async function runServe(opts = {}) {
|
|
|
240
243
|
onCloudAgentsAuthRejected: (reason) => forceReRegister(reason),
|
|
241
244
|
});
|
|
242
245
|
managerRef.current = manager;
|
|
246
|
+
// Fanout worker sessions are created directly in the store, outside the
|
|
247
|
+
// REST dispatcher, so nothing would notify connected tabs — they only
|
|
248
|
+
// appeared after a manual refresh. Publish `session_created` through the
|
|
249
|
+
// same deferred wrapper the dispatcher uses (no-ops until the relay is
|
|
250
|
+
// up, then fans out to every subscribed client).
|
|
251
|
+
getFanoutManager()?.setMetaEventPublisher(deferredPublishMetaEvent);
|
|
243
252
|
if (!silent) {
|
|
244
253
|
const action = registration.reused ? "Reusing" : "Registered";
|
|
245
254
|
process.stdout.write(`${action} machine "${registration.record.machineName}" (${registration.record.machineId})\n`);
|
|
@@ -670,11 +679,12 @@ async function handleConfigPatch(frame, deps) {
|
|
|
670
679
|
*/
|
|
671
680
|
export async function runServeCli(rawArgs) {
|
|
672
681
|
const parsed = parseServeArgs(rawArgs);
|
|
682
|
+
const machineName = parsed.machineName ?? process.env.SPECTRAL_MACHINE_NAME;
|
|
673
683
|
if (parsed.deprecatedPort || process.env.SPECTRAL_PORT) {
|
|
674
684
|
process.stderr.write(`warning: --port / SPECTRAL_PORT are no longer used. ` +
|
|
675
685
|
`spectral serve now connects outbound to the Aexol relay; there is no local port to bind.\n`);
|
|
676
686
|
}
|
|
677
687
|
return runServe({
|
|
678
|
-
machineName
|
|
688
|
+
machineName,
|
|
679
689
|
});
|
|
680
690
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Fanout — native spectral extension.
|
|
3
|
+
*
|
|
4
|
+
* Registers a single tool, `session_fanout`, that spawns multiple parallel
|
|
5
|
+
* worker sessions in the current project — one real session per task, each
|
|
6
|
+
* running the given prompt autonomously. Sessions appear immediately in the
|
|
7
|
+
* Studio session list; each worker's final message is its report.
|
|
8
|
+
*
|
|
9
|
+
* Fire-and-forget by design: no progress API, no result collection. The
|
|
10
|
+
* orchestrating agent only reports which sessions were created.
|
|
11
|
+
*
|
|
12
|
+
* Guardrails live in FanoutManager (src/server/fanout/fanout-manager.ts):
|
|
13
|
+
* recursion block, 20-task hard cap, concurrency semaphore, one active run
|
|
14
|
+
* per project, prompt dedupe/length limits.
|
|
15
|
+
*/
|
|
16
|
+
import type { ExtensionAPI } from "../../sdk/coding-agent/index.js";
|
|
17
|
+
export declare const SESSION_FANOUT_TOOL_NAME = "session_fanout";
|
|
18
|
+
export default function sessionFanoutExtension(ext: ExtensionAPI): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/session-fanout/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAEX,YAAY,EAEZ,MAAM,iCAAiC,CAAC;AASzC,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AAuCzD,wBAA8B,sBAAsB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+GrF"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Fanout — native spectral extension.
|
|
3
|
+
*
|
|
4
|
+
* Registers a single tool, `session_fanout`, that spawns multiple parallel
|
|
5
|
+
* worker sessions in the current project — one real session per task, each
|
|
6
|
+
* running the given prompt autonomously. Sessions appear immediately in the
|
|
7
|
+
* Studio session list; each worker's final message is its report.
|
|
8
|
+
*
|
|
9
|
+
* Fire-and-forget by design: no progress API, no result collection. The
|
|
10
|
+
* orchestrating agent only reports which sessions were created.
|
|
11
|
+
*
|
|
12
|
+
* Guardrails live in FanoutManager (src/server/fanout/fanout-manager.ts):
|
|
13
|
+
* recursion block, 20-task hard cap, concurrency semaphore, one active run
|
|
14
|
+
* per project, prompt dedupe/length limits.
|
|
15
|
+
*/
|
|
16
|
+
import { getFanoutManager, MAX_TASKS_PER_FANOUT, FanoutError, } from "../../server/fanout/fanout-manager.js";
|
|
17
|
+
export const SESSION_FANOUT_TOOL_NAME = "session_fanout";
|
|
18
|
+
function fail(message) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: `❌ session_fanout: ${message}` }],
|
|
21
|
+
details: { status: "error" },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function renderResult(result) {
|
|
25
|
+
const lines = [
|
|
26
|
+
`✅ Fanout ${result.runLabel} started: ${result.sessions.length} worker session(s) created ` +
|
|
27
|
+
`(max ${result.maxConcurrent} running at once).`,
|
|
28
|
+
"",
|
|
29
|
+
];
|
|
30
|
+
for (const session of result.sessions) {
|
|
31
|
+
lines.push(`- ${session.key} → ${session.sessionId} "${session.title}"`);
|
|
32
|
+
}
|
|
33
|
+
if (result.skipped.length > 0) {
|
|
34
|
+
lines.push("", "Skipped tasks:");
|
|
35
|
+
for (const s of result.skipped) {
|
|
36
|
+
lines.push(`- ${s.key}: ${s.reason}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
lines.push("", "The worker sessions are now visible in the session list. Each worker writes its report " +
|
|
40
|
+
"as the final message of its own session. Do not wait for them and do not poll — " +
|
|
41
|
+
"just tell the user the fanout is running and where to look.");
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
44
|
+
export default async function sessionFanoutExtension(ext) {
|
|
45
|
+
const tool = {
|
|
46
|
+
name: SESSION_FANOUT_TOOL_NAME,
|
|
47
|
+
label: "Session Fanout",
|
|
48
|
+
description: "Spawn multiple parallel worker sessions in the current project — one real session per task, " +
|
|
49
|
+
"each executing its own prompt autonomously. Sessions appear in the session list immediately. " +
|
|
50
|
+
`Use for batches of INDEPENDENT research/analysis tasks (e.g. gap analysis of up to ` +
|
|
51
|
+
`${MAX_TASKS_PER_FANOUT} well-described tasks against the codebase). Fire-and-forget: there is no ` +
|
|
52
|
+
"result collection — the user reads each worker's report in its own session, so each task prompt " +
|
|
53
|
+
"must be fully self-contained. Limits: max 20 tasks per call, 4 concurrent by default (hard cap 8), " +
|
|
54
|
+
"one active fanout per project, worker sessions cannot spawn further fanouts. " +
|
|
55
|
+
"Do NOT use for fewer than 2 tasks, and do NOT use it to run code edits in parallel.",
|
|
56
|
+
promptSnippet: "`session_fanout` — spawn parallel worker sessions for a batch of independent tasks (fire-and-forget; use sparingly)",
|
|
57
|
+
promptGuidelines: [
|
|
58
|
+
"Not a default tool: prefer doing the work in the current session, or the subagent tool when you need results returned to you; reach for session_fanout only for large batches (roughly 5 or more) of genuinely independent tasks, or when the user explicitly asks for parallel sessions.",
|
|
59
|
+
"Never for a single task, and never for quick lookups you can do yourself with grep/read — each spawned session is a real agent run that costs tokens and clutters the user's session list.",
|
|
60
|
+
"Never for code edits in parallel — worker sessions run in the same project without coordination.",
|
|
61
|
+
"Each task prompt must be self-contained — worker sessions share no context with you.",
|
|
62
|
+
"After spawning, report the created session ids/titles to the user and stop; results are read in the worker sessions themselves.",
|
|
63
|
+
],
|
|
64
|
+
parameters: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
tasks: {
|
|
68
|
+
type: "array",
|
|
69
|
+
description: `1-${MAX_TASKS_PER_FANOUT} tasks; one worker session is created per task.`,
|
|
70
|
+
items: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
key: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Short stable identifier of the task, e.g. 'task-3'.",
|
|
76
|
+
},
|
|
77
|
+
title: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Human-readable session title (shown in the session list).",
|
|
80
|
+
},
|
|
81
|
+
prompt: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Full, self-contained prompt for the worker session (goal, scope, what the final report must contain).",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ["key", "title", "prompt"],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
maxConcurrent: {
|
|
90
|
+
type: "number",
|
|
91
|
+
description: "Max worker prompts running at the same time (1-8, default 4).",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
required: ["tasks"],
|
|
95
|
+
},
|
|
96
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
97
|
+
const fanout = getFanoutManager();
|
|
98
|
+
if (!fanout) {
|
|
99
|
+
return fail("session_fanout needs the server-side session manager, which only exists in sessions " +
|
|
100
|
+
"hosted by a `spectral serve` process. Standalone CLI sessions have no session store, " +
|
|
101
|
+
"so there is nothing to spawn worker sessions into.");
|
|
102
|
+
}
|
|
103
|
+
const sessionId = ctx?.sessionManager?.getSessionId?.() ?? "";
|
|
104
|
+
if (!sessionId) {
|
|
105
|
+
return fail("no session context.");
|
|
106
|
+
}
|
|
107
|
+
if (fanout.isFanoutChild(sessionId)) {
|
|
108
|
+
return fail("worker sessions cannot spawn further fanouts (recursion blocked).");
|
|
109
|
+
}
|
|
110
|
+
const cwd = ctx?.cwd ?? process.cwd();
|
|
111
|
+
const projectId = fanout.resolveProjectId(cwd);
|
|
112
|
+
if (!projectId) {
|
|
113
|
+
return fail(`no project is registered for working directory ${cwd}.`);
|
|
114
|
+
}
|
|
115
|
+
const rawTasks = params.tasks;
|
|
116
|
+
if (!Array.isArray(rawTasks)) {
|
|
117
|
+
return fail("`tasks` must be an array of {key, title, prompt}.");
|
|
118
|
+
}
|
|
119
|
+
const tasks = rawTasks.map((t) => ({
|
|
120
|
+
key: typeof t?.key === "string" ? t.key : "",
|
|
121
|
+
title: typeof t?.title === "string" ? t.title : "",
|
|
122
|
+
prompt: typeof t?.prompt === "string" ? t.prompt : "",
|
|
123
|
+
}));
|
|
124
|
+
const maxConcurrent = typeof params.maxConcurrent === "number" ? params.maxConcurrent : undefined;
|
|
125
|
+
try {
|
|
126
|
+
const result = fanout.start({ parentSessionId: sessionId, projectId, tasks, maxConcurrent });
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: renderResult(result) }],
|
|
129
|
+
details: { status: "started", ...result },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
const msg = err instanceof FanoutError ? err.message : err instanceof Error ? err.message : String(err);
|
|
134
|
+
return fail(msg);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
ext.registerTool(tool);
|
|
139
|
+
}
|
package/dist/relay/client.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/relay/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,SAAS,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/relay/client.ts"],"names":[],"mappings":"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"}
|
package/dist/relay/client.js
CHANGED
|
@@ -49,6 +49,8 @@ import WebSocket from "ws";
|
|
|
49
49
|
import { getMachineFile } from "./machine-store.js";
|
|
50
50
|
/** Maximum outbound queue size while disconnected. */
|
|
51
51
|
const SEND_QUEUE_CAP = 100;
|
|
52
|
+
/** History chunks are large but low-priority — drop before any rest_response. */
|
|
53
|
+
const HISTORY_CHUNK_DROP_PRIORITY = true;
|
|
52
54
|
/** Backoff schedule (ms). The last value is the cap and is reused indefinitely. */
|
|
53
55
|
const RECONNECT_SCHEDULE = [1000, 2000, 5000, 15000, 30000];
|
|
54
56
|
/** ±20% jitter on each scheduled delay. */
|
|
@@ -327,13 +329,40 @@ export class RelayClient extends EventEmitter {
|
|
|
327
329
|
this.heartbeatPending = false;
|
|
328
330
|
}
|
|
329
331
|
// --- outbound send safety ---------------------------------------------------
|
|
332
|
+
isRestResponseWire(wire) {
|
|
333
|
+
return wire.includes('"kind":"rest_response"');
|
|
334
|
+
}
|
|
330
335
|
enqueueWire(wire) {
|
|
331
336
|
if (this.sendQueue.length >= SEND_QUEUE_CAP) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
this.sendQueue.
|
|
337
|
+
const isRest = this.isRestResponseWire(wire);
|
|
338
|
+
if (HISTORY_CHUNK_DROP_PRIORITY) {
|
|
339
|
+
const start = this.sendInFlight !== null ? 1 : 0;
|
|
340
|
+
let victim = -1;
|
|
341
|
+
for (let i = this.sendQueue.length - 1; i >= start; i--) {
|
|
342
|
+
if (!this.isRestResponseWire(this.sendQueue[i])) {
|
|
343
|
+
victim = i;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (victim >= 0) {
|
|
348
|
+
this.sendQueue.splice(victim, 1);
|
|
349
|
+
}
|
|
350
|
+
else if (!isRest) {
|
|
351
|
+
// Queue is full of rest_responses — fall back to oldest-dropped.
|
|
352
|
+
this.sendQueue.splice(start, 1);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
// Enqueueing a rest_response when every queued frame is a
|
|
356
|
+
// rest_response: still drop the oldest — REST must not be lost
|
|
357
|
+
// to non-REST, but one REST may yield to a newer one.
|
|
358
|
+
this.sendQueue.splice(start, 1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
const dropIndex = this.sendInFlight !== null ? 1 : 0;
|
|
363
|
+
if (this.sendQueue.length > dropIndex) {
|
|
364
|
+
this.sendQueue.splice(dropIndex, 1);
|
|
365
|
+
}
|
|
337
366
|
}
|
|
338
367
|
}
|
|
339
368
|
this.sendQueue.push(wire);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"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"}
|
package/dist/relay/dispatcher.js
CHANGED
|
@@ -1104,18 +1104,20 @@ async function dispatchRoute(match, body, deps) {
|
|
|
1104
1104
|
case "list_agents": {
|
|
1105
1105
|
const agentCwd = match.query?.get("cwd") || deps.cwd || homedir();
|
|
1106
1106
|
const teamId = match.query?.get("teamId") || undefined;
|
|
1107
|
-
//
|
|
1108
|
-
//
|
|
1109
|
-
//
|
|
1107
|
+
// Kick off cloud refresh in the background without blocking the response.
|
|
1108
|
+
// The previous version awaited refreshCloudAgents (network → backend GraphQL)
|
|
1109
|
+
// before returning, so every page refresh stalled the entire REST queue
|
|
1110
|
+
// behind this one slow call while 5-7 other rest_requests timed out.
|
|
1111
|
+
// Background refresh self-dedupes via CloudAgentCacheEntry.inFlight, so
|
|
1112
|
+
// bursts coalesce; stale cache renders immediately and the picker catches
|
|
1113
|
+
// up on the next meta_event/live refresh.
|
|
1110
1114
|
if (teamId && deps.refreshCloudAgents) {
|
|
1111
|
-
|
|
1112
|
-
await deps.refreshCloudAgents(teamId);
|
|
1113
|
-
}
|
|
1114
|
-
catch (err) {
|
|
1115
|
+
void deps.refreshCloudAgents(teamId).catch((err) => {
|
|
1115
1116
|
logger?.error?.(`[dispatcher] cloud agent refresh failed:`, err);
|
|
1116
|
-
}
|
|
1117
|
+
});
|
|
1117
1118
|
}
|
|
1118
|
-
|
|
1119
|
+
const agents = handleListAgents(agentCwd, deps.agentDir, teamId);
|
|
1120
|
+
return agents;
|
|
1119
1121
|
}
|
|
1120
1122
|
case "list_dev_processes":
|
|
1121
1123
|
return handleListDevProcesses(deps.devProcessRegistry);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-extensions.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/extensions/native-extensions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,aAAa,CAAC;IAChD;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,cAAc,EAAE,OAAO,CAAC;IACxB,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"native-extensions.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/extensions/native-extensions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,aAAa,CAAC;IAChD;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,cAAc,EAAE,OAAO,CAAC;IACxB,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,uBAAuB,EAkJtD,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CAElF;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAI1G;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;IAClG,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC,CAUD"}
|
|
@@ -141,6 +141,16 @@ export const NATIVE_EXTENSIONS = [
|
|
|
141
141
|
defaultEnabled: true,
|
|
142
142
|
tags: ["ports", "devops", "services", "collision", "docker", "network"],
|
|
143
143
|
},
|
|
144
|
+
{
|
|
145
|
+
id: "session-fanout",
|
|
146
|
+
label: "Session Fanout",
|
|
147
|
+
description: "Spawns parallel worker sessions for batches of independent tasks (e.g. research or gap analysis). " +
|
|
148
|
+
"One real session per task, fire-and-forget; reports are read in each worker session.",
|
|
149
|
+
category: "automation",
|
|
150
|
+
entryPath: "src/extensions/session-fanout/index.ts",
|
|
151
|
+
defaultEnabled: true,
|
|
152
|
+
tags: ["sessions", "parallel", "fanout", "orchestration"],
|
|
153
|
+
},
|
|
144
154
|
];
|
|
145
155
|
/**
|
|
146
156
|
* Look up a native extension by id. Returns undefined for unknown ids.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../../../src/sdk/coding-agent/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACxC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uCAAuC;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6FAA6F;IAC7F,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,yBAAyB;IACzB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../../../src/sdk/coding-agent/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACxC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uCAAuC;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6FAA6F;IAC7F,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,yBAAyB;IACzB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CA4L3E"}
|
|
@@ -95,7 +95,8 @@ export function buildSystemPrompt(options) {
|
|
|
95
95
|
const head = customPrompt
|
|
96
96
|
? customPrompt
|
|
97
97
|
: `You are an expert coding agent operating inside spectral, a coding agent harness. ${delegationIntro} When discussing effort, frame each enhancement around a ~20-hour coding scope tied to a concrete feature boundary, avoiding calendar-day or week-based estimates.
|
|
98
|
-
|
|
98
|
+
Be direct, helpful, and knowledgeable, and prioritize being genuinely useful over being verbose.
|
|
99
|
+
Be targeted and efficient in your exploration and investigation: search first, read only the files you need to act, and don't loop on planning or re-reading the same files. Prefer taking action over over-analyzing — if you have a reasonable next step, take it, and rely on error feedback from commands/tests to correct course rather than trying to predict every failure in advance.
|
|
99
100
|
|
|
100
101
|
## Recommended workflow
|
|
101
102
|
Work in short, verifiable steps and iterate:
|
|
@@ -211,6 +211,13 @@ export declare class AgentBridge {
|
|
|
211
211
|
private boundProjectId;
|
|
212
212
|
private bindingWatcher?;
|
|
213
213
|
private bindingWatcherTimer?;
|
|
214
|
+
/**
|
|
215
|
+
* Per-toolCallId trailing-edge throttle for `subagent_progress`. Keyed by
|
|
216
|
+
* the parent subagent tool call id so parallel/chain delegations coalesce
|
|
217
|
+
* independently. The pending event is the latest accumulated snapshot; the
|
|
218
|
+
* timer flushes it once the minimum interval has elapsed.
|
|
219
|
+
*/
|
|
220
|
+
private subagentProgressThrottle;
|
|
214
221
|
constructor(opts: AgentBridgeOptions);
|
|
215
222
|
/**
|
|
216
223
|
* Create the spectral session, wire up subscription, and return.
|
|
@@ -441,6 +448,24 @@ export declare class AgentBridge {
|
|
|
441
448
|
* `events_jsonl` for the current assistant message.
|
|
442
449
|
*/
|
|
443
450
|
private emitAndBuffer;
|
|
451
|
+
/**
|
|
452
|
+
* Live-only `subagent_progress` relay with a per-toolCallId trailing-edge
|
|
453
|
+
* throttle. The bridge receives `tool_execution_update` on every subagent
|
|
454
|
+
* token/thinking delta, each carrying the full accumulated snapshot. We
|
|
455
|
+
* coalesce those into at most one frame per
|
|
456
|
+
* `SUBAGENT_PROGRESS_MIN_INTERVAL_MS`, always keeping (and eventually
|
|
457
|
+
* flushing) the latest snapshot so the frontend's delta extraction still
|
|
458
|
+
* sees the complete transcript.
|
|
459
|
+
*/
|
|
460
|
+
private emitSubagentProgressThrottled;
|
|
461
|
+
/**
|
|
462
|
+
* Flush any pending throttled `subagent_progress` frame immediately (used at
|
|
463
|
+
* subagent completion and prompt teardown so the latest state is not lost),
|
|
464
|
+
* then drop the throttle state for that tool call.
|
|
465
|
+
*/
|
|
466
|
+
private flushSubagentProgress;
|
|
467
|
+
/** Flush and clear every pending throttled subagent progress frame. */
|
|
468
|
+
private flushAllSubagentProgress;
|
|
444
469
|
/**
|
|
445
470
|
* Finalize the current `pending` assistant message: persist its assembled
|
|
446
471
|
* content + wire events (including any tool_call / tool_result events that
|
|
@@ -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;AAatC,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,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,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;AAiBD,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,OAAO,yBAAyB,CAAC;AAmCpE,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,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,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,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;IAC9B,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;;;;;;;;;OAWG;IACH,yBAAyB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;
|
|
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;AAatC,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,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,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;AAiBD,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,OAAO,yBAAyB,CAAC;AAmCpE,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,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,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,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;IAC9B,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;;;;;;;;;OAWG;IACH,yBAAyB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AA+iBD,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,IAAI,CAAqB;IACjC;;;;;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,8EAA8E;IAC9E,OAAO,CAAC,kBAAkB,CAAC,CAAgC;IAC3D;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe,CAAK;IAC5B,sFAAsF;IACtF,OAAO,CAAC,iBAAiB,CAMT;IAChB,OAAO,CAAC,WAAW,CACuD;IAC1E,OAAO,CAAC,mBAAmB,CAA+B;IAC1D;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,cAAc,CAAC,CAA2B;IAClD,OAAO,CAAC,mBAAmB,CAAC,CAAgC;IAC5D;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB,CAO5B;gBAEQ,IAAI,EAAE,kBAAkB;IAIpC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0X5B,OAAO,CAAC,4BAA4B;IAQpC;;;;;OAKG;YACW,qBAAqB;IAcnC;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAG3C;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAmC3B,OAAO,CAAC,sBAAsB;YAahB,oBAAoB;IAoBlC;;;;;;;;;;;;OAYG;IACH;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,0BAA0B;IA6ElC;;;;;;;;;;;;;;;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,mDAAmD;IACnD,WAAW,IAAI,OAAO;IAItB,mEAAmE;IACnE,UAAU,IAAI,OAAO;IAIrB,2EAA2E;IAC3E,MAAM,IAAI,OAAO;IAIjB,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;;;;;;;;;OASG;IACH,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAI7D;;;;;;;;;;;;OAYG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgDrE;;;;;;;;OAQG;YACW,iBAAiB;IAwB/B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,0BAA0B;IAiBlC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAM1B,2EAA2E;IAC3E,OAAO,CAAC,6BAA6B;IAUrC;;;;OAIG;IACH,OAAO,CAAC,8BAA8B;IAQtC,yEAAyE;IACzE,OAAO,CAAC,iCAAiC;IAOzC,OAAO,CAAC,iBAAiB;IAazB;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAc/B,OAAO,CAAC,uBAAuB;IAMzB,OAAO,CACX,OAAO,CAAC,EAAE,MAAM,GAAG,4BAA4B,GAC9C,OAAO,CAAC,gBAAgB,CAAC;IAY5B,OAAO,IAAI,IAAI;IA6Bf;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAqBrB;;;;;;;;OAQG;IACH,OAAO,CAAC,6BAA6B;IAiCrC;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAiB7B,uEAAuE;IACvE,OAAO,CAAC,wBAAwB;IAMhC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,sBAAsB;IAwD9B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI;CAmkBzC"}
|
|
@@ -527,6 +527,17 @@ const PROMPT_INACTIVITY_TIMEOUT_MS = Number(process.env.SPECTRAL_PROMPT_INACTIVI
|
|
|
527
527
|
* watchdog) with a descriptive message.
|
|
528
528
|
*/
|
|
529
529
|
const TOOL_EXECUTION_TIMEOUT_MS = Number(process.env.SPECTRAL_TOOL_EXECUTION_TIMEOUT_MS) || 60 * 60 * 1000;
|
|
530
|
+
/**
|
|
531
|
+
* Minimum interval between live `subagent_progress` websocket frames for a
|
|
532
|
+
* single subagent tool invocation. Subagent streaming emits
|
|
533
|
+
* `tool_execution_update` on every token/thinking delta, and the bridge
|
|
534
|
+
* mirrors each one as a full accumulated snapshot. Relaying every delta
|
|
535
|
+
* floods the websocket with redundant O(n²) frames; we coalesce to at most
|
|
536
|
+
* one live frame per interval (trailing edge still flushes the latest state).
|
|
537
|
+
*
|
|
538
|
+
* Overridable via `SPECTRAL_SUBAGENT_PROGRESS_INTERVAL_MS`.
|
|
539
|
+
*/
|
|
540
|
+
const SUBAGENT_PROGRESS_MIN_INTERVAL_MS = Number(process.env.SPECTRAL_SUBAGENT_PROGRESS_INTERVAL_MS) || 100;
|
|
530
541
|
export class AgentBridge {
|
|
531
542
|
session;
|
|
532
543
|
suppressNextAgentEndForGeneration;
|
|
@@ -582,6 +593,13 @@ export class AgentBridge {
|
|
|
582
593
|
boundProjectId = null;
|
|
583
594
|
bindingWatcher;
|
|
584
595
|
bindingWatcherTimer;
|
|
596
|
+
/**
|
|
597
|
+
* Per-toolCallId trailing-edge throttle for `subagent_progress`. Keyed by
|
|
598
|
+
* the parent subagent tool call id so parallel/chain delegations coalesce
|
|
599
|
+
* independently. The pending event is the latest accumulated snapshot; the
|
|
600
|
+
* timer flushes it once the minimum interval has elapsed.
|
|
601
|
+
*/
|
|
602
|
+
subagentProgressThrottle = new Map();
|
|
585
603
|
constructor(opts) {
|
|
586
604
|
this.opts = opts;
|
|
587
605
|
}
|
|
@@ -1502,6 +1520,10 @@ export class AgentBridge {
|
|
|
1502
1520
|
this.promptActivityTimer = undefined;
|
|
1503
1521
|
}
|
|
1504
1522
|
this.clearToolExecutionTimer();
|
|
1523
|
+
// Flush any trailing throttled subagent progress so a turn that ends
|
|
1524
|
+
// (including via abort) never drops the last live snapshot, and clear
|
|
1525
|
+
// stale throttle timers so they can't fire into the next turn.
|
|
1526
|
+
this.flushAllSubagentProgress();
|
|
1505
1527
|
this.promptReject = undefined;
|
|
1506
1528
|
this.activeSubagents = 0;
|
|
1507
1529
|
}
|
|
@@ -1595,6 +1617,74 @@ export class AgentBridge {
|
|
|
1595
1617
|
this.pending.wireEvents.push(event);
|
|
1596
1618
|
this.opts.emit(event);
|
|
1597
1619
|
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Live-only `subagent_progress` relay with a per-toolCallId trailing-edge
|
|
1622
|
+
* throttle. The bridge receives `tool_execution_update` on every subagent
|
|
1623
|
+
* token/thinking delta, each carrying the full accumulated snapshot. We
|
|
1624
|
+
* coalesce those into at most one frame per
|
|
1625
|
+
* `SUBAGENT_PROGRESS_MIN_INTERVAL_MS`, always keeping (and eventually
|
|
1626
|
+
* flushing) the latest snapshot so the frontend's delta extraction still
|
|
1627
|
+
* sees the complete transcript.
|
|
1628
|
+
*/
|
|
1629
|
+
emitSubagentProgressThrottled(se) {
|
|
1630
|
+
const id = se.toolCallId;
|
|
1631
|
+
const now = Date.now();
|
|
1632
|
+
const slot = this.subagentProgressThrottle.get(id) ?? { lastEmitAt: 0 };
|
|
1633
|
+
slot.pending = se;
|
|
1634
|
+
this.subagentProgressThrottle.set(id, slot);
|
|
1635
|
+
const flush = () => {
|
|
1636
|
+
const current = this.subagentProgressThrottle.get(id);
|
|
1637
|
+
if (!current?.pending)
|
|
1638
|
+
return;
|
|
1639
|
+
const pending = current.pending;
|
|
1640
|
+
current.pending = undefined;
|
|
1641
|
+
current.timer = undefined;
|
|
1642
|
+
current.lastEmitAt = Date.now();
|
|
1643
|
+
this.subagentProgressThrottle.set(id, current);
|
|
1644
|
+
this.opts.emit(pending);
|
|
1645
|
+
};
|
|
1646
|
+
const elapsed = now - slot.lastEmitAt;
|
|
1647
|
+
if (elapsed >= SUBAGENT_PROGRESS_MIN_INTERVAL_MS) {
|
|
1648
|
+
if (slot.timer) {
|
|
1649
|
+
clearTimeout(slot.timer);
|
|
1650
|
+
slot.timer = undefined;
|
|
1651
|
+
}
|
|
1652
|
+
flush();
|
|
1653
|
+
}
|
|
1654
|
+
else if (!slot.timer) {
|
|
1655
|
+
slot.timer = setTimeout(flush, SUBAGENT_PROGRESS_MIN_INTERVAL_MS - elapsed);
|
|
1656
|
+
this.subagentProgressThrottle.set(id, slot);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* Flush any pending throttled `subagent_progress` frame immediately (used at
|
|
1661
|
+
* subagent completion and prompt teardown so the latest state is not lost),
|
|
1662
|
+
* then drop the throttle state for that tool call.
|
|
1663
|
+
*/
|
|
1664
|
+
flushSubagentProgress(toolCallId) {
|
|
1665
|
+
const slot = this.subagentProgressThrottle.get(toolCallId);
|
|
1666
|
+
if (!slot)
|
|
1667
|
+
return;
|
|
1668
|
+
if (slot.timer) {
|
|
1669
|
+
clearTimeout(slot.timer);
|
|
1670
|
+
slot.timer = undefined;
|
|
1671
|
+
}
|
|
1672
|
+
if (slot.pending) {
|
|
1673
|
+
const pending = slot.pending;
|
|
1674
|
+
slot.pending = undefined;
|
|
1675
|
+
this.subagentProgressThrottle.delete(toolCallId);
|
|
1676
|
+
this.opts.emit(pending);
|
|
1677
|
+
}
|
|
1678
|
+
else {
|
|
1679
|
+
this.subagentProgressThrottle.delete(toolCallId);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
/** Flush and clear every pending throttled subagent progress frame. */
|
|
1683
|
+
flushAllSubagentProgress() {
|
|
1684
|
+
for (const toolCallId of Array.from(this.subagentProgressThrottle.keys())) {
|
|
1685
|
+
this.flushSubagentProgress(toolCallId);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1598
1688
|
/**
|
|
1599
1689
|
* Finalize the current `pending` assistant message: persist its assembled
|
|
1600
1690
|
* content + wire events (including any tool_call / tool_result events that
|
|
@@ -1856,7 +1946,7 @@ export class AgentBridge {
|
|
|
1856
1946
|
// aborted by PROMPT_INACTIVITY_TIMEOUT_MS (default 10 min) even
|
|
1857
1947
|
// though they are actively making progress.
|
|
1858
1948
|
this.bumpPromptActivity();
|
|
1859
|
-
this.
|
|
1949
|
+
this.emitSubagentProgressThrottled(se);
|
|
1860
1950
|
}
|
|
1861
1951
|
}
|
|
1862
1952
|
return;
|
|
@@ -1874,6 +1964,10 @@ export class AgentBridge {
|
|
|
1874
1964
|
// finishes. The subagent's own timeout already governed the child;
|
|
1875
1965
|
// the parent resumes normal silence-based protection from here.
|
|
1876
1966
|
this.resumePromptActivityAfterSubagent();
|
|
1967
|
+
// Flush any throttled progress that hasn't hit its interval yet so
|
|
1968
|
+
// the frontend never misses the final accumulated state before the
|
|
1969
|
+
// terminal subagent_end/tool_result frames.
|
|
1970
|
+
this.flushSubagentProgress(ev.toolCallId);
|
|
1877
1971
|
const result = ev.result;
|
|
1878
1972
|
const details = result?.details;
|
|
1879
1973
|
if (details?.results && details.results.length > 0) {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FanoutManager — spawns parallel "worker" sessions for a batch of tasks.
|
|
3
|
+
*
|
|
4
|
+
* The main agent calls the `session_fanout` tool with a list of independent
|
|
5
|
+
* tasks (e.g. gap analysis of 20 well-described tasks against the codebase).
|
|
6
|
+
* Each task becomes a REAL session in the current project: a record is
|
|
7
|
+
* created in SQLite up front (so it appears in the Studio session list
|
|
8
|
+
* immediately) and the task prompt is sent through the existing
|
|
9
|
+
* SessionStreamManager, subject to a concurrency semaphore.
|
|
10
|
+
*
|
|
11
|
+
* Fire-and-forget: the manager does not collect results — the user reads
|
|
12
|
+
* each worker's final message directly in its session. State is process-
|
|
13
|
+
* local (no new tables); after a restart of `spectral serve` pending worker
|
|
14
|
+
* turns simply finish like any other session, and the in-memory recursion
|
|
15
|
+
* guard resets (recursion stays blocked at the tool-registration level
|
|
16
|
+
* because spawned sessions never outlive the manager's knowledge of them —
|
|
17
|
+
* see the session-fanout extension).
|
|
18
|
+
*
|
|
19
|
+
* Wiring: `configureFanout()` is called once from the SessionStreamManager
|
|
20
|
+
* constructor (next to `setInterAgentBroker`), which is the single place
|
|
21
|
+
* where both the manager and the SessionStore are available.
|
|
22
|
+
*/
|
|
23
|
+
import type { MetaEvent } from "@aexol/relay-protocol";
|
|
24
|
+
import type { SessionStore } from "../storage.js";
|
|
25
|
+
import type { SessionStreamManager } from "../session-stream.js";
|
|
26
|
+
/** Hard cap on tasks accepted per `start()` call. */
|
|
27
|
+
export declare const MAX_TASKS_PER_FANOUT = 20;
|
|
28
|
+
/** Default number of worker prompts running at the same time. */
|
|
29
|
+
export declare const DEFAULT_MAX_CONCURRENT = 4;
|
|
30
|
+
/** Hard cap on the per-run concurrency requested by the caller. */
|
|
31
|
+
export declare const HARD_CAP_CONCURRENT = 8;
|
|
32
|
+
/** Maximum length of a single task prompt. Longer tasks are skipped. */
|
|
33
|
+
export declare const MAX_PROMPT_LENGTH = 12000;
|
|
34
|
+
/** Maximum length of a task title. */
|
|
35
|
+
export declare const MAX_TITLE_LENGTH = 120;
|
|
36
|
+
/** Maximum length of a task key. */
|
|
37
|
+
export declare const MAX_KEY_LENGTH = 80;
|
|
38
|
+
export interface FanoutTask {
|
|
39
|
+
key: string;
|
|
40
|
+
title: string;
|
|
41
|
+
prompt: string;
|
|
42
|
+
}
|
|
43
|
+
export interface FanoutStartInput {
|
|
44
|
+
parentSessionId: string;
|
|
45
|
+
projectId: string;
|
|
46
|
+
tasks: FanoutTask[];
|
|
47
|
+
maxConcurrent?: number;
|
|
48
|
+
}
|
|
49
|
+
export interface FanoutSessionRef {
|
|
50
|
+
key: string;
|
|
51
|
+
sessionId: string;
|
|
52
|
+
title: string;
|
|
53
|
+
}
|
|
54
|
+
export interface FanoutStartResult {
|
|
55
|
+
runId: string;
|
|
56
|
+
runLabel: string;
|
|
57
|
+
/** Concurrency actually applied after clamping. */
|
|
58
|
+
maxConcurrent: number;
|
|
59
|
+
sessions: FanoutSessionRef[];
|
|
60
|
+
/** Tasks rejected during validation, with a human-readable reason. */
|
|
61
|
+
skipped: Array<{
|
|
62
|
+
key: string;
|
|
63
|
+
reason: string;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
/** Minimal surface of SessionStreamManager used by the fanout. */
|
|
67
|
+
export interface FanoutManagerDeps {
|
|
68
|
+
manager: Pick<SessionStreamManager, "attach" | "prompt">;
|
|
69
|
+
store: Pick<SessionStore, "createSession" | "getProjectByCwd" | "appendMessage">;
|
|
70
|
+
/**
|
|
71
|
+
* Optional meta-event publisher — the same channel the REST dispatcher
|
|
72
|
+
* uses. Publishing `session_created` after each store.createSession is
|
|
73
|
+
* what makes fanout-spawned sessions appear live in connected UIs;
|
|
74
|
+
* without it they only show up after a manual refresh.
|
|
75
|
+
*/
|
|
76
|
+
publishMetaEvent?: (event: MetaEvent) => void;
|
|
77
|
+
}
|
|
78
|
+
/** Error carrying a user-readable reason (surfaced verbatim in the tool result). */
|
|
79
|
+
export declare class FanoutError extends Error {
|
|
80
|
+
}
|
|
81
|
+
export declare class FanoutManager {
|
|
82
|
+
private deps;
|
|
83
|
+
private readonly runs;
|
|
84
|
+
private readonly activeRunsByProject;
|
|
85
|
+
/** Every session ever spawned by a fanout in this process (recursion guard). */
|
|
86
|
+
private readonly spawnedSessionIds;
|
|
87
|
+
private runCounter;
|
|
88
|
+
/** Global concurrency cap across all runs (protects multi-project usage). */
|
|
89
|
+
private readonly globalSemaphore;
|
|
90
|
+
private readonly logger;
|
|
91
|
+
constructor(logger?: {
|
|
92
|
+
error: (...args: unknown[]) => void;
|
|
93
|
+
});
|
|
94
|
+
configure(deps: FanoutManagerDeps): void;
|
|
95
|
+
/**
|
|
96
|
+
* Wire the meta-event publisher (serve.ts, after the manager exists —
|
|
97
|
+
* the deferred wrapper no-ops until the relay is up, so ordering is
|
|
98
|
+
* safe). Without a publisher the tool still works; clients just don't
|
|
99
|
+
* get live session-list updates.
|
|
100
|
+
*/
|
|
101
|
+
setMetaEventPublisher(publish: NonNullable<FanoutManagerDeps["publishMetaEvent"]>): void;
|
|
102
|
+
/** Resolve the project id for a working directory, or null. */
|
|
103
|
+
resolveProjectId(cwd: string): string | null;
|
|
104
|
+
/** True if the session was ever spawned as a fanout worker in this process. */
|
|
105
|
+
isFanoutChild(sessionId: string): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Validate input, create one session per accepted task (immediately
|
|
108
|
+
* visible in the session list) and kick off prompt dispatch in the
|
|
109
|
+
* background. Returns as soon as records exist — never awaits turns.
|
|
110
|
+
*/
|
|
111
|
+
start(input: FanoutStartInput): FanoutStartResult;
|
|
112
|
+
private createWorkerSessions;
|
|
113
|
+
/**
|
|
114
|
+
* Publish `session_created` over the relay meta channel so every
|
|
115
|
+
* subscribed tab refreshes its session list immediately. Mirrors the
|
|
116
|
+
* dispatcher's safePublish: failures are logged, never propagated —
|
|
117
|
+
* the session record already exists.
|
|
118
|
+
*/
|
|
119
|
+
private publishSessionCreated;
|
|
120
|
+
private executeRun;
|
|
121
|
+
}
|
|
122
|
+
export declare function configureFanout(deps: FanoutManagerDeps): FanoutManager;
|
|
123
|
+
export declare function getFanoutManager(): FanoutManager | null;
|
|
124
|
+
/** Test-only: drop the configured singleton. */
|
|
125
|
+
export declare function resetFanoutManager(): void;
|
|
126
|
+
//# sourceMappingURL=fanout-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fanout-manager.d.ts","sourceRoot":"","sources":["../../../src/server/fanout/fanout-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FanoutManager — spawns parallel "worker" sessions for a batch of tasks.
|
|
3
|
+
*
|
|
4
|
+
* The main agent calls the `session_fanout` tool with a list of independent
|
|
5
|
+
* tasks (e.g. gap analysis of 20 well-described tasks against the codebase).
|
|
6
|
+
* Each task becomes a REAL session in the current project: a record is
|
|
7
|
+
* created in SQLite up front (so it appears in the Studio session list
|
|
8
|
+
* immediately) and the task prompt is sent through the existing
|
|
9
|
+
* SessionStreamManager, subject to a concurrency semaphore.
|
|
10
|
+
*
|
|
11
|
+
* Fire-and-forget: the manager does not collect results — the user reads
|
|
12
|
+
* each worker's final message directly in its session. State is process-
|
|
13
|
+
* local (no new tables); after a restart of `spectral serve` pending worker
|
|
14
|
+
* turns simply finish like any other session, and the in-memory recursion
|
|
15
|
+
* guard resets (recursion stays blocked at the tool-registration level
|
|
16
|
+
* because spawned sessions never outlive the manager's knowledge of them —
|
|
17
|
+
* see the session-fanout extension).
|
|
18
|
+
*
|
|
19
|
+
* Wiring: `configureFanout()` is called once from the SessionStreamManager
|
|
20
|
+
* constructor (next to `setInterAgentBroker`), which is the single place
|
|
21
|
+
* where both the manager and the SessionStore are available.
|
|
22
|
+
*/
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Guardrail constants
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/** Hard cap on tasks accepted per `start()` call. */
|
|
27
|
+
export const MAX_TASKS_PER_FANOUT = 20;
|
|
28
|
+
/** Default number of worker prompts running at the same time. */
|
|
29
|
+
export const DEFAULT_MAX_CONCURRENT = 4;
|
|
30
|
+
/** Hard cap on the per-run concurrency requested by the caller. */
|
|
31
|
+
export const HARD_CAP_CONCURRENT = 8;
|
|
32
|
+
/** Maximum length of a single task prompt. Longer tasks are skipped. */
|
|
33
|
+
export const MAX_PROMPT_LENGTH = 12_000;
|
|
34
|
+
/** Maximum length of a task title. */
|
|
35
|
+
export const MAX_TITLE_LENGTH = 120;
|
|
36
|
+
/** Maximum length of a task key. */
|
|
37
|
+
export const MAX_KEY_LENGTH = 80;
|
|
38
|
+
/** Error carrying a user-readable reason (surfaced verbatim in the tool result). */
|
|
39
|
+
export class FanoutError extends Error {
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Semaphore
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
class Semaphore {
|
|
45
|
+
capacity;
|
|
46
|
+
active = 0;
|
|
47
|
+
waiters = [];
|
|
48
|
+
constructor(capacity) {
|
|
49
|
+
this.capacity = capacity;
|
|
50
|
+
}
|
|
51
|
+
/** Resolves with the release function once a slot is available. */
|
|
52
|
+
async acquire() {
|
|
53
|
+
if (this.active < this.capacity) {
|
|
54
|
+
this.active++;
|
|
55
|
+
return () => this.release();
|
|
56
|
+
}
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
this.waiters.push(() => {
|
|
59
|
+
this.active++;
|
|
60
|
+
resolve(() => this.release());
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
release() {
|
|
65
|
+
this.active--;
|
|
66
|
+
const next = this.waiters.shift();
|
|
67
|
+
if (next)
|
|
68
|
+
next();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Manager
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
const noopSubscriber = {
|
|
75
|
+
send: () => { },
|
|
76
|
+
isOpen: () => false,
|
|
77
|
+
};
|
|
78
|
+
export class FanoutManager {
|
|
79
|
+
deps = null;
|
|
80
|
+
runs = new Map();
|
|
81
|
+
activeRunsByProject = new Map();
|
|
82
|
+
/** Every session ever spawned by a fanout in this process (recursion guard). */
|
|
83
|
+
spawnedSessionIds = new Set();
|
|
84
|
+
runCounter = 0;
|
|
85
|
+
/** Global concurrency cap across all runs (protects multi-project usage). */
|
|
86
|
+
globalSemaphore = new Semaphore(HARD_CAP_CONCURRENT);
|
|
87
|
+
logger;
|
|
88
|
+
constructor(logger) {
|
|
89
|
+
this.logger = logger ?? console;
|
|
90
|
+
}
|
|
91
|
+
configure(deps) {
|
|
92
|
+
this.deps = deps;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Wire the meta-event publisher (serve.ts, after the manager exists —
|
|
96
|
+
* the deferred wrapper no-ops until the relay is up, so ordering is
|
|
97
|
+
* safe). Without a publisher the tool still works; clients just don't
|
|
98
|
+
* get live session-list updates.
|
|
99
|
+
*/
|
|
100
|
+
setMetaEventPublisher(publish) {
|
|
101
|
+
if (!this.deps)
|
|
102
|
+
return; // not configured yet — nothing would receive it
|
|
103
|
+
this.deps.publishMetaEvent = publish;
|
|
104
|
+
}
|
|
105
|
+
/** Resolve the project id for a working directory, or null. */
|
|
106
|
+
resolveProjectId(cwd) {
|
|
107
|
+
return this.deps?.store.getProjectByCwd(cwd) ?? null;
|
|
108
|
+
}
|
|
109
|
+
/** True if the session was ever spawned as a fanout worker in this process. */
|
|
110
|
+
isFanoutChild(sessionId) {
|
|
111
|
+
return this.spawnedSessionIds.has(sessionId);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Validate input, create one session per accepted task (immediately
|
|
115
|
+
* visible in the session list) and kick off prompt dispatch in the
|
|
116
|
+
* background. Returns as soon as records exist — never awaits turns.
|
|
117
|
+
*/
|
|
118
|
+
start(input) {
|
|
119
|
+
if (!this.deps) {
|
|
120
|
+
throw new FanoutError("session_fanout is only available while `spectral serve` is running.");
|
|
121
|
+
}
|
|
122
|
+
if (typeof input.parentSessionId !== "string" || input.parentSessionId.length === 0) {
|
|
123
|
+
throw new FanoutError("parentSessionId is required.");
|
|
124
|
+
}
|
|
125
|
+
if (typeof input.projectId !== "string" || input.projectId.length === 0) {
|
|
126
|
+
throw new FanoutError("projectId is required.");
|
|
127
|
+
}
|
|
128
|
+
if (this.isFanoutChild(input.parentSessionId)) {
|
|
129
|
+
throw new FanoutError("Recursion blocked: fanout worker sessions cannot spawn further fanouts.");
|
|
130
|
+
}
|
|
131
|
+
const activeRunId = this.activeRunsByProject.get(input.projectId);
|
|
132
|
+
if (activeRunId) {
|
|
133
|
+
throw new FanoutError(`A fanout run is already active in this project (${activeRunId}). ` +
|
|
134
|
+
"Wait for its workers to finish before starting another one.");
|
|
135
|
+
}
|
|
136
|
+
if (!Array.isArray(input.tasks) || input.tasks.length === 0) {
|
|
137
|
+
throw new FanoutError("`tasks` must be a non-empty array of {key, title, prompt}.");
|
|
138
|
+
}
|
|
139
|
+
if (input.tasks.length > MAX_TASKS_PER_FANOUT) {
|
|
140
|
+
throw new FanoutError(`Too many tasks: ${input.tasks.length} (hard cap ${MAX_TASKS_PER_FANOUT}). ` +
|
|
141
|
+
"Split the work into multiple smaller fanouts over several turns.");
|
|
142
|
+
}
|
|
143
|
+
this.runCounter++;
|
|
144
|
+
const runLabel = `F${this.runCounter}`;
|
|
145
|
+
const { sessions, skipped } = this.createWorkerSessions(input.tasks, input.projectId, runLabel);
|
|
146
|
+
if (sessions.length === 0) {
|
|
147
|
+
this.runCounter--;
|
|
148
|
+
throw new FanoutError("No valid tasks: " + (skipped.map((s) => `${s.key}: ${s.reason}`).join("; ") || "empty input."));
|
|
149
|
+
}
|
|
150
|
+
const maxConcurrent = clampConcurrency(input.maxConcurrent);
|
|
151
|
+
const runId = `fanout_${Date.now()}_${this.runCounter}`;
|
|
152
|
+
const run = {
|
|
153
|
+
runId,
|
|
154
|
+
runLabel,
|
|
155
|
+
projectId: input.projectId,
|
|
156
|
+
parentSessionId: input.parentSessionId,
|
|
157
|
+
childSessionIds: new Set(sessions.map((s) => s.sessionId)),
|
|
158
|
+
finished: false,
|
|
159
|
+
};
|
|
160
|
+
for (const session of sessions) {
|
|
161
|
+
this.spawnedSessionIds.add(session.sessionId);
|
|
162
|
+
}
|
|
163
|
+
this.runs.set(runId, run);
|
|
164
|
+
this.activeRunsByProject.set(input.projectId, runId);
|
|
165
|
+
// Background dispatch — never awaited by the tool (fire-and-forget).
|
|
166
|
+
void this.executeRun(run, input.tasks, sessions, maxConcurrent);
|
|
167
|
+
return { runId, runLabel, maxConcurrent, sessions, skipped };
|
|
168
|
+
}
|
|
169
|
+
// -------------------------------------------------------------------------
|
|
170
|
+
// Internals
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
createWorkerSessions(tasks, projectId, runLabel) {
|
|
173
|
+
const store = this.deps?.store;
|
|
174
|
+
if (!store)
|
|
175
|
+
throw new FanoutError("FanoutManager is not configured.");
|
|
176
|
+
const seenPrompts = new Set();
|
|
177
|
+
const seenKeys = new Set();
|
|
178
|
+
const sessions = [];
|
|
179
|
+
const skipped = [];
|
|
180
|
+
for (const task of tasks) {
|
|
181
|
+
const key = typeof task?.key === "string" ? task.key.trim() : "";
|
|
182
|
+
const title = typeof task?.title === "string" ? task.title.trim() : "";
|
|
183
|
+
const prompt = typeof task?.prompt === "string" ? task.prompt.trim() : "";
|
|
184
|
+
if (!key) {
|
|
185
|
+
skipped.push({ key: "(missing)", reason: "missing key" });
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (!title || !prompt) {
|
|
189
|
+
skipped.push({ key, reason: "missing title or prompt" });
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (key.length > MAX_KEY_LENGTH) {
|
|
193
|
+
skipped.push({ key, reason: `key longer than ${MAX_KEY_LENGTH} chars` });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (title.length > MAX_TITLE_LENGTH) {
|
|
197
|
+
skipped.push({ key, reason: `title longer than ${MAX_TITLE_LENGTH} chars` });
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (prompt.length > MAX_PROMPT_LENGTH) {
|
|
201
|
+
skipped.push({ key, reason: `prompt longer than ${MAX_PROMPT_LENGTH} chars` });
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (seenPrompts.has(prompt)) {
|
|
205
|
+
skipped.push({ key, reason: "duplicate prompt" });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (seenKeys.has(key)) {
|
|
209
|
+
skipped.push({ key, reason: "duplicate key" });
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
seenPrompts.add(prompt);
|
|
213
|
+
seenKeys.add(key);
|
|
214
|
+
try {
|
|
215
|
+
const summary = store.createSession({
|
|
216
|
+
projectId,
|
|
217
|
+
title: `[${runLabel}] ${title}`,
|
|
218
|
+
});
|
|
219
|
+
sessions.push({ key, sessionId: summary.id, title: summary.title });
|
|
220
|
+
this.publishSessionCreated(projectId, summary.id);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
224
|
+
skipped.push({ key, reason: `session creation failed: ${msg}` });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { sessions, skipped };
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Publish `session_created` over the relay meta channel so every
|
|
231
|
+
* subscribed tab refreshes its session list immediately. Mirrors the
|
|
232
|
+
* dispatcher's safePublish: failures are logged, never propagated —
|
|
233
|
+
* the session record already exists.
|
|
234
|
+
*/
|
|
235
|
+
publishSessionCreated(projectId, sessionId) {
|
|
236
|
+
const publish = this.deps?.publishMetaEvent;
|
|
237
|
+
if (!publish)
|
|
238
|
+
return;
|
|
239
|
+
try {
|
|
240
|
+
publish({ type: "session_created", projectId, sessionId });
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
this.logger.error("[fanout] publishMetaEvent(session_created) failed:", err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async executeRun(run, tasks, sessions, maxConcurrent) {
|
|
247
|
+
const deps = this.deps;
|
|
248
|
+
if (!deps)
|
|
249
|
+
return;
|
|
250
|
+
// Prompt lookup: sessions[] is parallel to the accepted (non-skipped) tasks.
|
|
251
|
+
const accepted = new Map();
|
|
252
|
+
for (const ref of sessions)
|
|
253
|
+
accepted.set(ref.key, ref);
|
|
254
|
+
const runSemaphore = new Semaphore(maxConcurrent);
|
|
255
|
+
await Promise.all(sessions.map(async (ref) => {
|
|
256
|
+
const task = tasks.find((t) => t.key === ref.key);
|
|
257
|
+
if (!task)
|
|
258
|
+
return;
|
|
259
|
+
const releaseRun = await runSemaphore.acquire();
|
|
260
|
+
const releaseGlobal = await this.globalSemaphore.acquire();
|
|
261
|
+
try {
|
|
262
|
+
await deps.manager.attach(ref.sessionId, noopSubscriber);
|
|
263
|
+
const content = buildWorkerPrompt(run.runLabel, ref.key, task.prompt, sessions.length);
|
|
264
|
+
// Persist the task message here, not inside prompt(): prompt()
|
|
265
|
+
// waits for the agent bridge to become ready first (bootstrap
|
|
266
|
+
// can take minutes when creation is queued) and only then
|
|
267
|
+
// writes the user message — until that moment the worker
|
|
268
|
+
// session shows an empty message list in Studio even though
|
|
269
|
+
// it is already in the session list. The prompt below then
|
|
270
|
+
// runs with skipPersistence (auto-research pattern) so the
|
|
271
|
+
// message is not written twice.
|
|
272
|
+
let persisted = false;
|
|
273
|
+
try {
|
|
274
|
+
deps.store.appendMessage(ref.sessionId, { role: "user", content });
|
|
275
|
+
persisted = true;
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
this.logger.error(`[fanout:${run.runId}] worker ${ref.key}: pre-persisting the task message failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
279
|
+
}
|
|
280
|
+
await deps.manager.prompt(ref.sessionId, content, undefined, undefined, undefined, persisted ? { skipPersistence: true } : undefined);
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
284
|
+
this.logger.error(`[fanout:${run.runId}] worker ${ref.key} (${ref.sessionId}) failed: ${msg}`);
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
releaseGlobal();
|
|
288
|
+
releaseRun();
|
|
289
|
+
}
|
|
290
|
+
}));
|
|
291
|
+
run.finished = true;
|
|
292
|
+
if (this.activeRunsByProject.get(run.projectId) === run.runId) {
|
|
293
|
+
this.activeRunsByProject.delete(run.projectId);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Prompt building
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
function buildWorkerPrompt(runLabel, key, prompt, total) {
|
|
301
|
+
return [
|
|
302
|
+
`You are a focused worker session spawned by \`session_fanout\` (run ${runLabel}, task ${key}, 1 of ${total}).`,
|
|
303
|
+
"",
|
|
304
|
+
"Work autonomously on the task below. Do not ask questions — make reasonable assumptions and state them.",
|
|
305
|
+
"Your final message is the report the user will read in this session, so make it self-contained.",
|
|
306
|
+
"",
|
|
307
|
+
"--- TASK ---",
|
|
308
|
+
prompt,
|
|
309
|
+
].join("\n");
|
|
310
|
+
}
|
|
311
|
+
function clampConcurrency(raw) {
|
|
312
|
+
const n = typeof raw === "number" ? Math.floor(raw) : DEFAULT_MAX_CONCURRENT;
|
|
313
|
+
if (!Number.isFinite(n))
|
|
314
|
+
return DEFAULT_MAX_CONCURRENT;
|
|
315
|
+
return Math.max(1, Math.min(HARD_CAP_CONCURRENT, n));
|
|
316
|
+
}
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// Singleton (wired from SessionStreamManager's constructor)
|
|
319
|
+
//
|
|
320
|
+
// Stored on globalThis under a registered symbol instead of a module-level
|
|
321
|
+
// variable. In the repo/dev layout the session-fanout extension is loaded
|
|
322
|
+
// from src/ via jiti while the server runs from dist/, so this module exists
|
|
323
|
+
// as TWO instances in one process. A module-level singleton would be set in
|
|
324
|
+
// the dist instance but read as null in the jiti instance; a globalThis slot
|
|
325
|
+
// is shared by both.
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
const FANOUT_SINGLETON_KEY = Symbol.for("aexol.sessionFanoutManager");
|
|
328
|
+
function holder() {
|
|
329
|
+
return globalThis;
|
|
330
|
+
}
|
|
331
|
+
export function configureFanout(deps) {
|
|
332
|
+
const h = holder();
|
|
333
|
+
if (!h[FANOUT_SINGLETON_KEY])
|
|
334
|
+
h[FANOUT_SINGLETON_KEY] = new FanoutManager();
|
|
335
|
+
h[FANOUT_SINGLETON_KEY].configure(deps);
|
|
336
|
+
return h[FANOUT_SINGLETON_KEY];
|
|
337
|
+
}
|
|
338
|
+
export function getFanoutManager() {
|
|
339
|
+
return holder()[FANOUT_SINGLETON_KEY] ?? null;
|
|
340
|
+
}
|
|
341
|
+
/** Test-only: drop the configured singleton. */
|
|
342
|
+
export function resetFanoutManager() {
|
|
343
|
+
delete holder()[FANOUT_SINGLETON_KEY];
|
|
344
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAIH,OAAO,EAAe,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,6 +41,7 @@ import { AgentBridge } from "./agent-bridge.js";
|
|
|
41
41
|
import { SettingsManager, } from "../sdk/coding-agent/index.js";
|
|
42
42
|
import { InterAgentBroker } from "./inter-agent-broker.js";
|
|
43
43
|
import { setInterAgentBroker } from "../memory/inter-agent-broker-singleton.js";
|
|
44
|
+
import { configureFanout } from "./fanout/fanout-manager.js";
|
|
44
45
|
import { CONTEXT_LIMIT_RATIO, DEFAULT_COMPACTION_POLICY, getAutoOlderHistoryContextThresholdTokens, } from "../sdk/coding-agent/core/compaction/policy.js";
|
|
45
46
|
import { getMemoryState, isSourceEntry, rawTokensSinceLastBound, rawTokensSinceLastCompaction, } from "../memory/branch.js";
|
|
46
47
|
import { observationPoolTokens, renderSummary } from "../memory/compaction.js";
|
|
@@ -116,7 +117,7 @@ const STREAMING_MEANINGFUL_EVENT_TYPES = new Set([
|
|
|
116
117
|
* so the actual context inflation is 3-8× the stored token estimate.
|
|
117
118
|
*/
|
|
118
119
|
const REHYDRATION_TAIL_TOKEN_BUDGET = 20_000;
|
|
119
|
-
const UI_HISTORY_TAIL_MESSAGE_LIMIT =
|
|
120
|
+
const UI_HISTORY_TAIL_MESSAGE_LIMIT = 60;
|
|
120
121
|
/**
|
|
121
122
|
* Number of accumulated wire events before flushing the in-flight turn
|
|
122
123
|
* to SQLite. Batch-persisting means a server crash mid-turn only loses
|
|
@@ -541,6 +542,9 @@ export class SessionStreamManager {
|
|
|
541
542
|
// Wire the inter-agent broker singleton so the memory extension can send
|
|
542
543
|
// and receive live observation messages between active sessions.
|
|
543
544
|
setInterAgentBroker(this.broker);
|
|
545
|
+
// Wire the fanout manager singleton so the session-fanout extension can
|
|
546
|
+
// spawn parallel worker sessions through this manager.
|
|
547
|
+
configureFanout({ manager: this, store: this.store });
|
|
544
548
|
}
|
|
545
549
|
/**
|
|
546
550
|
* Update the machine JWT used by all future AgentBridge instances.
|