@aexol/spectral 0.9.199 → 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/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/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 +4 -0
- 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
|
+
}
|
|
@@ -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:
|
|
@@ -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";
|
|
@@ -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.
|