@aexol/spectral 0.9.199 → 0.9.201

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.
Files changed (28) hide show
  1. package/dist/commands/serve.d.ts +2 -0
  2. package/dist/commands/serve.d.ts.map +1 -1
  3. package/dist/commands/serve.js +11 -1
  4. package/dist/extensions/image-generation/index.d.ts.map +1 -1
  5. package/dist/extensions/image-generation/index.js +81 -11
  6. package/dist/extensions/session-fanout/index.d.ts +19 -0
  7. package/dist/extensions/session-fanout/index.d.ts.map +1 -0
  8. package/dist/extensions/session-fanout/index.js +139 -0
  9. package/dist/memory/tools/read-project-observations.d.ts.map +1 -1
  10. package/dist/memory/tools/read-project-observations.js +8 -4
  11. package/dist/memory/tools/write-project-observation.d.ts.map +1 -1
  12. package/dist/memory/tools/write-project-observation.js +5 -1
  13. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
  14. package/dist/sdk/coding-agent/core/extensions/native-extensions.js +10 -0
  15. package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
  16. package/dist/sdk/coding-agent/core/system-prompt.js +2 -1
  17. package/dist/server/fanout/fanout-manager.d.ts +126 -0
  18. package/dist/server/fanout/fanout-manager.d.ts.map +1 -0
  19. package/dist/server/fanout/fanout-manager.js +344 -0
  20. package/dist/server/session-stream.d.ts.map +1 -1
  21. package/dist/server/session-stream.js +4 -0
  22. package/dist/server/storage.d.ts +59 -2
  23. package/dist/server/storage.d.ts.map +1 -1
  24. package/dist/server/storage.js +287 -12
  25. package/dist/server/text/trigrams.d.ts +72 -0
  26. package/dist/server/text/trigrams.d.ts.map +1 -0
  27. package/dist/server/text/trigrams.js +128 -0
  28. package/package.json +1 -1
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAwBH,OAAO,EAAE,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AAWlE,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,CAiiBlF;AA+DD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAa5E"}
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"}
@@ -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: parsed.machineName,
688
+ machineName,
679
689
  });
680
690
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/image-generation/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAmB,YAAY,EAAkB,MAAM,iCAAiC,CAAC;AA4GrG,wBAA8B,wBAAwB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DvF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/image-generation/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAmB,YAAY,EAAkB,MAAM,iCAAiC,CAAC;AAyLrG,wBAA8B,wBAAwB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAwDvF"}
@@ -1,6 +1,6 @@
1
1
  import { Type } from "typebox";
2
- import { mkdirSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
2
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { extname, join, resolve } from "node:path";
4
4
  import { generateImages, getImageModel, getImageModels } from "../../sdk/ai/index.js";
5
5
  // App-layer routing helpers (mirror aexol-mcp.ts): obtain backendUrl +
6
6
  // machineJwt the same way agent-bridge.ts gets them for text models, so image
@@ -9,13 +9,23 @@ import { generateImages, getImageModel, getImageModels } from "../../sdk/ai/inde
9
9
  import { getApiUrl, readConfig } from "../../config.js";
10
10
  import { loadMachine } from "../../relay/machine-store.js";
11
11
  import { isJwtExpired } from "../../relay/registration.js";
12
+ const imageReferenceSchema = Type.Union([
13
+ Type.String({
14
+ description: "Path to a reference image file (relative to the project working directory or absolute).",
15
+ }),
16
+ Type.Object({
17
+ data: Type.String({ description: "Reference image as raw base64 or data URL." }),
18
+ mimeType: Type.Optional(Type.String({ default: "image/png", description: "Reference image MIME type." })),
19
+ }, { description: "Reference image provided inline as base64 data." }),
20
+ Type.Object({
21
+ path: Type.String({ description: "Path to a reference image file (relative to the project working directory or absolute)." }),
22
+ mimeType: Type.Optional(Type.String({ description: "Reference image MIME type. Defaults to the type detected from the file extension." })),
23
+ }, { description: "Reference image provided as a file path." }),
24
+ ]);
12
25
  const imageGenerateSchema = Type.Object({
13
26
  prompt: Type.String({ description: "Text prompt describing the image to generate." }),
14
27
  model: Type.Optional(Type.String({ default: "google/gemini-3.1-flash-lite-image", description: "OpenRouter image model id." })),
15
- images: Type.Optional(Type.Array(Type.Object({
16
- data: Type.String({ description: "Reference image as raw base64 or data URL." }),
17
- mimeType: Type.Optional(Type.String({ default: "image/png", description: "Reference image MIME type." })),
18
- }), { description: "Optional reference images for image-to-image generation." })),
28
+ images: Type.Optional(Type.Array(imageReferenceSchema, { description: "Optional reference images for image-to-image generation. Each element may be a file path string, an object { data: <base64 or data URL>, mimeType?: string }, or an object { path: <file path>, mimeType?: string }." })),
19
29
  n: Type.Optional(Type.Number({ minimum: 1, maximum: 10, default: 1 })),
20
30
  resolution: Type.Optional(Type.String({ description: "Resolution tier, e.g. 512, 1K, 2K, 4K." })),
21
31
  aspect_ratio: Type.Optional(Type.String({ description: "Aspect ratio, e.g. 1:1, 16:9, 9:16, auto." })),
@@ -40,6 +50,70 @@ function extensionForMimeType(mimeType) {
40
50
  return "webp";
41
51
  return "png";
42
52
  }
53
+ function mimeTypeForPath(filePath) {
54
+ switch (extname(filePath).toLowerCase()) {
55
+ case ".png":
56
+ return "image/png";
57
+ case ".jpg":
58
+ case ".jpeg":
59
+ return "image/jpeg";
60
+ case ".webp":
61
+ return "image/webp";
62
+ case ".gif":
63
+ return "image/gif";
64
+ default:
65
+ return "image/png";
66
+ }
67
+ }
68
+ function readReferenceImageFromPath(filePath, mimeType) {
69
+ let data;
70
+ try {
71
+ data = readFileSync(filePath).toString("base64");
72
+ }
73
+ catch (error) {
74
+ const code = error?.code;
75
+ if (code === "ENOENT") {
76
+ throw new Error(`Reference image file does not exist: ${filePath}`);
77
+ }
78
+ if (code === "EACCES" || code === "EPERM") {
79
+ throw new Error(`Permission denied while reading reference image: ${filePath}`);
80
+ }
81
+ const detail = error instanceof Error ? error.message : String(error);
82
+ throw new Error(`Failed to read reference image: ${filePath} (${detail})`);
83
+ }
84
+ return {
85
+ type: "image",
86
+ data,
87
+ mimeType: mimeType ?? mimeTypeForPath(filePath),
88
+ };
89
+ }
90
+ function normalizeReferenceImages(images, cwd) {
91
+ if (!images)
92
+ return [];
93
+ return images.map((image, index) => {
94
+ if (typeof image === "string") {
95
+ return readReferenceImageFromPath(resolve(cwd, image), undefined);
96
+ }
97
+ if (typeof image !== "object" || image === null) {
98
+ throw new Error(`Invalid reference image at index ${index}: expected a file path string or an object with "data" or "path".`);
99
+ }
100
+ const reference = image;
101
+ if (typeof reference.data === "string" && typeof reference.path === "string") {
102
+ throw new Error(`Invalid reference image at index ${index}: provide only one of "data" (inline base64) or "path" (file path), not both.`);
103
+ }
104
+ if (typeof reference.data === "string") {
105
+ return {
106
+ type: "image",
107
+ data: reference.data,
108
+ mimeType: reference.mimeType ?? "image/png",
109
+ };
110
+ }
111
+ if (typeof reference.path === "string") {
112
+ return readReferenceImageFromPath(resolve(cwd, reference.path), reference.mimeType);
113
+ }
114
+ throw new Error(`Invalid reference image at index ${index}: expected a file path string or an object with "data" or "path".`);
115
+ });
116
+ }
43
117
  function slugFromPrompt(prompt) {
44
118
  const slug = prompt
45
119
  .normalize("NFKD")
@@ -120,11 +194,7 @@ export default async function imageGenerationExtension(ext) {
120
194
  const context = {
121
195
  input: [
122
196
  { type: "text", text: input.prompt },
123
- ...(input.images ?? []).map((image) => ({
124
- type: "image",
125
- data: image.data,
126
- mimeType: image.mimeType ?? "image/png",
127
- })),
197
+ ...normalizeReferenceImages(input.images, ctx.cwd),
128
198
  ],
129
199
  };
130
200
  // Route through the backend image proxy (POST /image/generation) using
@@ -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":"read-project-observations.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/read-project-observations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAGhF,eAAO,MAAM,mCAAmC,8BAA8B,CAAC;AAE/E,eAAO,MAAM,2BAA2B;;iFAwFtC,CAAC;AAEH,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAE3E"}
1
+ {"version":3,"file":"read-project-observations.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/read-project-observations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAGhF,eAAO,MAAM,mCAAmC,8BAA8B,CAAC;AAE/E,eAAO,MAAM,2BAA2B;;iFA4FtC,CAAC;AAEH,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAE3E"}
@@ -9,22 +9,26 @@ export const readProjectObservationsTool = defineTool({
9
9
  "and per-working-directory (cwd). " +
10
10
  "Use this when you need to recall project conventions, past decisions, " +
11
11
  "file locations, or user preferences before editing or creating files. " +
12
- "Uses simple substring matching be specific with your query terms. " +
13
- "Returns up to 20 most recent matching observations with relevance tags. " +
12
+ "Uses n-gram matching: multi-word queries match observations containing all terms; " +
13
+ "typo-tolerant fallback when nothing matches exactly. " +
14
+ "Returns up to 20 best-matching observations with relevance tags. " +
14
15
  "Results are local to this machine and cwd; they are NOT synced across machines or teams.",
15
16
  promptSnippet: "Use read_project_observations(<query>) to search past observations across sessions in this local project working directory.",
16
17
  promptGuidelines: [
17
18
  "Results are per-machine + per-cwd and cross-session; they are NOT shared across machines or teams.",
18
19
  "Use read_project_observations before creating new files to find conventions for that file kind.",
19
20
  "Use read_project_observations before editing a file to find past context about that file or its directory.",
20
- "Be specific: search for file paths, technology names, or pattern keywords.",
21
+ "Search for file paths, technology names, or pattern keywords; multi-word queries match observations containing all terms.",
21
22
  "Do not use for broad topic searches — keep queries narrow for relevant results.",
22
23
  "Results include relevance tags (critical/high/medium/low) to help you prioritize.",
24
+ "Results are ranked by match quality, relevance tag, and recency.",
23
25
  ],
24
26
  parameters: Type.Object({
25
27
  query: Type.String({
26
28
  minLength: 1,
27
- description: "Substring to search for in past observation content. " +
29
+ description: "Search terms to look for in past observation content. " +
30
+ "Multi-word queries match observations containing all terms; " +
31
+ "minor typos are tolerated when nothing matches exactly. " +
28
32
  "Use file paths (e.g. 'src/auth/middleware'), technology names, " +
29
33
  "or pattern keywords (e.g. 'convention', 'api route').",
30
34
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"write-project-observation.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/write-project-observation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAIhF,eAAO,MAAM,mCAAmC,8BAA8B,CAAC;AAE/E,eAAO,MAAM,2BAA2B;;;iFA2EtC,CAAC;AAEH,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAE3E"}
1
+ {"version":3,"file":"write-project-observation.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/write-project-observation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAIhF,eAAO,MAAM,mCAAmC,8BAA8B,CAAC;AAE/E,eAAO,MAAM,2BAA2B;;;iFA+EtC,CAAC;AAEH,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAE3E"}
@@ -11,7 +11,9 @@ export const writeProjectObservationTool = defineTool({
11
11
  "they are shared across sessions on this machine but are NOT synced across machines or teams. " +
12
12
  "Use this to persist discovered conventions, architectural rules, " +
13
13
  "gotchas, and directory purposes. Observations are deduplicated by " +
14
- "content hash — writing the same fact twice is harmless (idempotent). " +
14
+ "content hash — writing the same fact twice is harmless (idempotent) " +
15
+ "and near-duplicate rewordings (very similar content) automatically " +
16
+ "replace the older observation. " +
15
17
  "Future sessions in this working directory can discover these via read_project_observations.",
16
18
  promptSnippet: "Use write_project_observation(content, relevance) to persist local, cross-session knowledge for this working directory.",
17
19
  promptGuidelines: [
@@ -22,6 +24,8 @@ export const writeProjectObservationTool = defineTool({
22
24
  "Use relevance='medium' for useful patterns and directory purposes.",
23
25
  "Content must be a single, self-contained sentence — no markdown, no lists.",
24
26
  "Deduplication is automatic — same content = same ID → INSERT OR REPLACE.",
27
+ "Near-duplicate rewordings (very similar content) replace the older observation automatically — no need to delete or rewrite existing facts first.",
28
+ "A rewording that replaces a near-duplicate gets a new id; observation ids shared before the replacement may become unresolvable.",
25
29
  ],
26
30
  parameters: Type.Object({
27
31
  content: Type.String({
@@ -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,EAuItD,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"}
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,CA2L3E"}
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
- Prefer taking action over over-analyzing. If you have a reasonable next step, take it; don't loop on planning or re-reading the same files. Rely on error feedback from commands/tests to correct course rather than trying to predict every failure in advance.
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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"}