nexus-agents 2.104.0 → 2.105.0

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.
@@ -8,7 +8,7 @@ import {
8
8
  checkSqlite,
9
9
  defaultConfig,
10
10
  initDataDirectories
11
- } from "./chunk-5YMSKNUG.js";
11
+ } from "./chunk-OESO6AQL.js";
12
12
  import {
13
13
  probeAllClis
14
14
  } from "./chunk-VIHUOFIT.js";
@@ -1987,4 +1987,4 @@ export {
1987
1987
  setupCommand,
1988
1988
  setupCommandAsync
1989
1989
  };
1990
- //# sourceMappingURL=chunk-4KTPDSFZ.js.map
1990
+ //# sourceMappingURL=chunk-75BXDWVG.js.map
@@ -53,6 +53,13 @@ function totalTokensFromUsage(usage) {
53
53
  const total = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
54
54
  return total > 0 ? total : void 0;
55
55
  }
56
+ function tokenSplitFromUsage(usage) {
57
+ if (usage === void 0) return void 0;
58
+ const tokensIn = usage.inputTokens ?? 0;
59
+ const tokensOut = usage.outputTokens ?? 0;
60
+ if (tokensIn + tokensOut === 0) return void 0;
61
+ return { tokensIn, tokensOut };
62
+ }
56
63
  function adaptCompositeRouter(compositeRouter) {
57
64
  return {
58
65
  async executeTask(task) {
@@ -64,12 +71,16 @@ function adaptCompositeRouter(compositeRouter) {
64
71
  if (result.ok) {
65
72
  const cli = resolveCliFromModelString(result.value.model);
66
73
  const tokensUsed = totalTokensFromUsage(result.value.usage);
74
+ const model = result.value.model;
75
+ const split = tokenSplitFromUsage(result.value.usage);
67
76
  return {
68
77
  ok: true,
69
78
  value: {
70
79
  text: result.value.text,
71
80
  ...cli !== void 0 && { cli },
72
- ...tokensUsed !== void 0 && { tokensUsed }
81
+ ...tokensUsed !== void 0 && { tokensUsed },
82
+ ...model !== void 0 && { model },
83
+ ...split !== void 0 && { tokensIn: split.tokensIn, tokensOut: split.tokensOut }
73
84
  },
74
85
  error: { message: "" }
75
86
  };
@@ -129,7 +140,10 @@ async function dispatchWithRateLimitRetry(router, task, expertType, start) {
129
140
  expertType,
130
141
  durationMs,
131
142
  ...result.value.cli !== void 0 && { cli: result.value.cli },
132
- ...result.value.tokensUsed !== void 0 && { tokensUsed: result.value.tokensUsed }
143
+ ...result.value.tokensUsed !== void 0 && { tokensUsed: result.value.tokensUsed },
144
+ ...result.value.model !== void 0 && { model: result.value.model },
145
+ ...result.value.tokensIn !== void 0 && { tokensIn: result.value.tokensIn },
146
+ ...result.value.tokensOut !== void 0 && { tokensOut: result.value.tokensOut }
133
147
  };
134
148
  }
135
149
  const isRateLimit = isRateLimitText(result.error.message);
@@ -196,6 +210,7 @@ ${prompt}`;
196
210
  export {
197
211
  shutdownExpertBridge,
198
212
  totalTokensFromUsage,
213
+ tokenSplitFromUsage,
199
214
  executeExpert
200
215
  };
201
- //# sourceMappingURL=chunk-47FQ3BU7.js.map
216
+ //# sourceMappingURL=chunk-BMM6OX2K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pipeline/expert-bridge.ts"],"sourcesContent":["/**\n * Expert Bridge — Programmatic access to the execute_expert pipeline (#1693)\n *\n * Provides a clean wrapper for calling experts with the full pipeline:\n * timeout, fallback cascade, degradation detection, heartbeat, outcome recording.\n *\n * DRY: reuses createBuiltInExpert + CompositeRouter instead of reimplementing.\n *\n * @module pipeline/expert-bridge\n */\n\nimport { createLogger, getTimeProvider } from '../core/index.js';\nimport type { BuiltInExpertType } from '../agents/experts/expert-config.js';\nimport { isRateLimitText } from '../adapters/rate-limit-detector.js';\nimport { resolveCliSlot } from '../config/model-availability.js';\nimport type { CliNameLiteral } from '../config/model-capabilities-types.js';\n\n/**\n * Resolves a CLI slot from the model string a (CLI or API) adapter returned.\n * Known models resolve to their exact slot; **unknown** models — new releases\n * or API/openrouter models not in the curated registry — fall back to the\n * vendor-derived slot (#3317 / #3293) so the outcome is recorded against a real\n * slot instead of being dropped with an undefined cli (the api-mode gap).\n * Returns undefined only when no model is present (the bridge failed before\n * dispatch — no execution to attribute).\n */\nfunction resolveCliFromModelString(model: string | undefined): CliNameLiteral | undefined {\n return resolveCliSlot(model);\n}\n\nconst logger = createLogger({ component: 'expert-bridge' });\n\n/** Base delay for rate limit retry backoff (ms). Scales linearly: 3s, 6s, 9s. */\nconst RATE_LIMIT_BASE_DELAY_MS = 3000;\n\n/** Result of an expert execution. */\nexport interface ExpertBridgeResult {\n readonly success: boolean;\n readonly text: string;\n readonly expertType: BuiltInExpertType;\n readonly durationMs: number;\n readonly error?: string;\n /**\n * CLI that actually executed the task, resolved from the underlying\n * `CliResponse.model` via `getCliForModelId`. Undefined when the bridge\n * failed before dispatch (no adapters / circuit-open / rate-limit cap).\n * Callers writing to OutcomeStore should use this rather than hardcoding\n * a cli — see #2823 (#1154 regression).\n */\n readonly cli?: CliNameLiteral;\n /**\n * Total tokens (input + output) the underlying CLI/adapter reported for this\n * call, when available (#3396). Best-effort: `CliResponse.usage` is optional\n * — CLI-subprocess paths whose `extractUsage` returns null leave this\n * undefined. Consumers (budget enforcement #3395, model.called attribution\n * #3387, routing-experience metrics) must tolerate `undefined`.\n */\n readonly tokensUsed?: number;\n /**\n * Concrete model id the underlying adapter reported (`CliResponse.model`),\n * when present (#3387). Distinct from {@link cli} (the slot): one CLI can run\n * several models. Undefined when the adapter didn't report a model or the\n * bridge failed before dispatch. Required to emit a `model.called` event.\n */\n readonly model?: string;\n /**\n * Input/output token split from the adapter's `CliResponse.usage` (#3387),\n * when reported. Best-effort like {@link tokensUsed}; both undefined together\n * when no usage was available. `tokensIn + tokensOut` reconciles with\n * `tokensUsed` (single source of truth — both derive from the same record).\n */\n readonly tokensIn?: number;\n readonly tokensOut?: number;\n}\n\n/**\n * Execute an expert task with the full nexus-agents expert pipeline.\n *\n * Creates a built-in expert, executes via CompositeRouter (for intelligent\n * CLI routing), and records outcomes. Falls back gracefully on failure.\n *\n * @param expertType - Built-in expert type (code, architecture, security, qa, etc.)\n * @param prompt - Task prompt for the expert\n * @returns Expert result with text output\n */\n/** Minimal router interface for the bridge. */\ninterface RouterLike {\n executeTask(task: { content: string; options?: Record<string, unknown> | undefined }): Promise<{\n ok: boolean;\n value: {\n text: string;\n cli?: CliNameLiteral;\n tokensUsed?: number;\n model?: string;\n tokensIn?: number;\n tokensOut?: number;\n };\n error: { message: string };\n }>;\n}\n\n// Cached router — lazily initialized, reused across calls within a session\nlet cachedRouter: RouterLike | null = null;\n\n// Cached MCP config — generated once, reused across expert calls (#1708)\nlet cachedMcpConfigPath: string | null = null;\n// Cached cleanup for the cached config's tempdir (closes #2946). Previously\n// the cleanup returned by `generateMcpConfig` was thrown away, so\n// `/tmp/nexus-mcp-XXXXXX/` accumulated one entry per MCP server lifetime.\n// Stored here + invoked by `shutdownExpertBridge()` from the server's\n// graceful-shutdown path.\nlet cachedMcpConfigCleanup: (() => Promise<void>) | null = null;\n// Coalesces concurrent init under voter fan-out (closes #2969). consensus_vote\n// fans out N=7 callers on cold start; without this each one ran the full init\n// including a mkdtemp() that the loser N-1 instances never cleaned up.\nlet mcpConfigInitPromise: Promise<string | null> | null = null;\n\n/** Get or create cached MCP config path for expert CLI sessions (#1708). */\nasync function getMcpConfigPath(): Promise<string | null> {\n if (cachedMcpConfigPath !== null) return cachedMcpConfigPath;\n mcpConfigInitPromise ??= (async (): Promise<string | null> => {\n try {\n const { generateMcpConfig } = await import('../cli-adapters/child-mcp-config.js');\n const config = await generateMcpConfig();\n cachedMcpConfigPath = config.configPath;\n cachedMcpConfigCleanup = config.cleanup;\n return cachedMcpConfigPath;\n } catch {\n mcpConfigInitPromise = null; // allow retry on next call\n return null; // MCP config not available — experts run without tools\n }\n })();\n return mcpConfigInitPromise;\n}\n\n/**\n * Removes the cached MCP-config tempdir (closes #2946). Invoke from the\n * server's graceful-shutdown path so stale nexus-mcp-* tempdirs (under\n * the OS tmpdir, see child-mcp-config.ts) don't accumulate across daemon\n * restarts. Idempotent; safe to call multiple times. Never throws —\n * cleanup failures are logged and swallowed.\n */\nexport async function shutdownExpertBridge(): Promise<void> {\n const cleanup = cachedMcpConfigCleanup;\n if (cleanup === null) return;\n cachedMcpConfigCleanup = null;\n cachedMcpConfigPath = null;\n mcpConfigInitPromise = null;\n try {\n await cleanup();\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n logger.debug('Expert-bridge MCP-config cleanup failed', { error: msg });\n }\n}\n\n/** Cached circuit breaker for health monitoring (#1766). */\nlet cachedCircuitBreaker: {\n getHealthStatus(): {\n systemHealthy: boolean;\n healthyCount: number;\n clis: ReadonlyArray<{ name: string; healthy: boolean }>;\n };\n} | null = null;\n\n/**\n * Adapt a CompositeRouter to the narrower RouterLike interface used by this\n * bridge. Previously did `as unknown as RouterLike` which hid any structural\n * mismatch between CompositeRouter's `Result<CliResponse, CliError>` and\n * RouterLike's flat `{ ok, value: { text }, error: { message } }` shape.\n * If CliResponse renames `.text` → `.output` (or similar), this adapter\n * breaks at compile time instead of silently returning wrong data (#1921).\n */\n/**\n * Total tokens from a best-effort `CliResponse.usage` record (#3396). Prefers\n * the reported `totalTokens`; falls back to input+output; returns undefined\n * when no usage was reported (so callers can distinguish \"0 tokens\" — which\n * never happens for a real call — from \"unknown\").\n */\nexport function totalTokensFromUsage(\n usage: { totalTokens?: number; inputTokens?: number; outputTokens?: number } | undefined\n): number | undefined {\n if (usage === undefined) return undefined;\n if (typeof usage.totalTokens === 'number') return usage.totalTokens;\n const total = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);\n return total > 0 ? total : undefined;\n}\n\n/**\n * Per-direction token split from a best-effort `CliResponse.usage` record\n * (#3387). Unlike {@link totalTokensFromUsage} this keeps `tokensIn`/`tokensOut`\n * separate — the granularity `ModelCalledEvent` requires for attribution.\n * Returns undefined when no usage was reported or both directions are zero (no\n * real call), so callers skip emitting a noise event instead of recording\n * zeros. Reconciles with the total: `tokensIn + tokensOut === totalTokensFromUsage`.\n */\nexport function tokenSplitFromUsage(\n usage: { inputTokens?: number; outputTokens?: number; totalTokens?: number } | undefined\n): { tokensIn: number; tokensOut: number } | undefined {\n if (usage === undefined) return undefined;\n const tokensIn = usage.inputTokens ?? 0;\n const tokensOut = usage.outputTokens ?? 0;\n if (tokensIn + tokensOut === 0) return undefined;\n return { tokensIn, tokensOut };\n}\n\nfunction adaptCompositeRouter(\n compositeRouter: import('../cli-adapters/composite-router.js').ICompositeRouter\n): RouterLike {\n return {\n async executeTask(task): Promise<{\n ok: boolean;\n value: {\n text: string;\n cli?: CliNameLiteral;\n tokensUsed?: number;\n model?: string;\n tokensIn?: number;\n tokensOut?: number;\n };\n error: { message: string };\n }> {\n const cliTask: import('../cli-adapters/types.js').CliTask = {\n content: task.content,\n ...(task.options !== undefined ? { options: task.options } : {}),\n };\n const result = await compositeRouter.executeTask(cliTask);\n if (result.ok) {\n // #2823: surface which CLI actually executed so callers writing to\n // OutcomeStore don't have to hardcode 'claude' (the bug #1154 fixed\n // and that regressed into the pipeline/ tree). Derive via the\n // canonical model→cli mapping in the registry — if the underlying\n // adapter didn't set `model` or the model isn't in the registry,\n // cli stays undefined and downstream code can skip the record\n // rather than lie.\n const cli = resolveCliFromModelString(result.value.model);\n // #3396: surface token usage (best-effort) so budget enforcement,\n // attribution, and routing-experience metrics get real numbers instead\n // of zeros. `usage` is optional and `totalTokens` may be absent — fall\n // back to input+output, and leave undefined when no usage was reported.\n const tokensUsed = totalTokensFromUsage(result.value.usage);\n // #3387: also surface the concrete model + per-direction token split so\n // a meaningful `model.called` event can be emitted. Both derive from the\n // same CliResponse, so tokensIn+tokensOut reconciles with tokensUsed.\n const model = result.value.model;\n const split = tokenSplitFromUsage(result.value.usage);\n return {\n ok: true,\n value: {\n text: result.value.text,\n ...(cli !== undefined && { cli }),\n ...(tokensUsed !== undefined && { tokensUsed }),\n ...(model !== undefined && { model }),\n ...(split !== undefined && { tokensIn: split.tokensIn, tokensOut: split.tokensOut }),\n },\n error: { message: '' },\n };\n }\n return { ok: false, value: { text: '' }, error: { message: result.error.message } };\n },\n };\n}\n\n// Coalesces concurrent router init the same way mcpConfigInitPromise does\n// (closes #2969). N=7 voter fan-out previously ran createAllAdapters() N times\n// — N sets of CLI probe subprocesses, all but one discarded.\nlet routerInitPromise: Promise<RouterLike | null> | null = null;\n\n/** Get or create a cached CompositeRouter with circuit breaker monitoring. */\nasync function getRouter(): Promise<RouterLike | null> {\n if (cachedRouter !== null) return cachedRouter;\n routerInitPromise ??= (async (): Promise<RouterLike | null> => {\n const { createAllAdapters } = await import('../cli-adapters/factory.js');\n const { createCompositeRouter } = await import('../cli-adapters/composite-router.js');\n const adapters = createAllAdapters();\n if (adapters.size === 0) {\n routerInitPromise = null; // allow retry once adapters become available\n return null;\n }\n cachedRouter = adaptCompositeRouter(createCompositeRouter(adapters));\n\n // Initialize circuit breaker monitoring (#1766)\n try {\n const { createCliCircuitBreakerIntegration } =\n await import('../cli-adapters/cli-circuit-breaker.js');\n cachedCircuitBreaker = createCliCircuitBreakerIntegration([...adapters.values()]);\n } catch (error: unknown) {\n // Circuit breaker not available — continue without it. Log so we can\n // notice if initialization silently stops working (#1913 Class B).\n const msg = error instanceof Error ? error.message : String(error);\n logger.debug('Circuit breaker init failed; continuing without it', { error: msg });\n }\n\n return cachedRouter;\n })();\n return routerInitPromise;\n}\n\n/** Check CLI health before dispatch (#1766). */\nfunction checkCircuitHealth(): { healthy: boolean; message: string } {\n if (cachedCircuitBreaker === null) return { healthy: true, message: '' };\n const status = cachedCircuitBreaker.getHealthStatus();\n if (!status.systemHealthy) {\n return {\n healthy: false,\n message: `All CLI circuits open (${String(status.healthyCount)}/${String(status.clis.length)} healthy)`,\n };\n }\n return { healthy: true, message: '' };\n}\n\n/** Dispatch task to router with rate limit retry (#1802). */\nasync function dispatchWithRateLimitRetry(\n router: RouterLike,\n task: { content: string; options?: Record<string, unknown> | undefined },\n expertType: BuiltInExpertType,\n start: number\n): Promise<ExpertBridgeResult> {\n const maxAttempts = 3;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const result = await router.executeTask(task);\n const durationMs = getTimeProvider().now() - start;\n\n if (result.ok) {\n logger.info('Expert executed successfully', {\n expertType,\n durationMs,\n cli: result.value.cli,\n });\n return {\n success: true,\n text: result.value.text,\n expertType,\n durationMs,\n ...(result.value.cli !== undefined && { cli: result.value.cli }),\n ...(result.value.tokensUsed !== undefined && { tokensUsed: result.value.tokensUsed }),\n ...(result.value.model !== undefined && { model: result.value.model }),\n ...(result.value.tokensIn !== undefined && { tokensIn: result.value.tokensIn }),\n ...(result.value.tokensOut !== undefined && { tokensOut: result.value.tokensOut }),\n };\n }\n\n const isRateLimit = isRateLimitText(result.error.message);\n if (isRateLimit && attempt < maxAttempts - 1) {\n const backoffMs = RATE_LIMIT_BASE_DELAY_MS * (attempt + 1);\n logger.warn('Expert rate limited, retrying', { expertType, attempt: attempt + 1, backoffMs });\n await new Promise((resolve) => setTimeout(resolve, backoffMs));\n continue;\n }\n\n logger.warn('Expert execution failed', { expertType, error: result.error.message });\n return { success: false, text: '', expertType, durationMs, error: result.error.message };\n }\n\n return {\n success: false,\n text: '',\n expertType,\n durationMs: getTimeProvider().now() - start,\n error: 'Max retry attempts exceeded',\n };\n}\n\nexport async function executeExpert(\n expertType: BuiltInExpertType,\n prompt: string\n): Promise<ExpertBridgeResult> {\n const start = getTimeProvider().now();\n try {\n const { BUILT_IN_EXPERTS } = await import('../agents/experts/expert-config.js');\n const config = BUILT_IN_EXPERTS[expertType];\n const fullPrompt = `${config.systemPrompt}\\n\\n${prompt}`;\n\n const router = await getRouter();\n if (router === null) {\n return {\n success: false,\n text: `[No adapters] ${prompt}`,\n expertType,\n durationMs: getTimeProvider().now() - start,\n error: 'No CLI adapters available',\n };\n }\n\n // Check circuit breaker health before dispatch (#1766)\n const health = checkCircuitHealth();\n if (!health.healthy) {\n logger.warn('Circuit breaker: all CLIs unavailable', { expertType, reason: health.message });\n return {\n success: false,\n text: '',\n expertType,\n durationMs: getTimeProvider().now() - start,\n error: health.message,\n };\n }\n\n // Pass MCP config so CLI experts can call nexus-agents tools (#1708)\n const mcpConfigPath = await getMcpConfigPath();\n const task: { content: string; options?: Record<string, unknown> | undefined } = {\n content: fullPrompt,\n };\n if (mcpConfigPath !== null) task.options = { mcpConfigPath };\n\n return await dispatchWithRateLimitRetry(router, task, expertType, start);\n } catch (error) {\n const durationMs = getTimeProvider().now() - start;\n const msg = error instanceof Error ? error.message : String(error);\n logger.warn('Expert bridge error', { expertType, error: msg });\n return { success: false, text: '', expertType, durationMs, error: msg };\n }\n}\n"],"mappings":"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","names":[]}
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-46GXDOWQ.js";
10
10
  import {
11
11
  executeExpert
12
- } from "./chunk-47FQ3BU7.js";
12
+ } from "./chunk-BMM6OX2K.js";
13
13
  import {
14
14
  EventTopics,
15
15
  JobStatusSchema,
@@ -30,7 +30,7 @@ import {
30
30
  writeJobComplete,
31
31
  writeJobFailed,
32
32
  writeJobPending
33
- } from "./chunk-2MM5MC45.js";
33
+ } from "./chunk-MKSP4M55.js";
34
34
  import {
35
35
  REGISTRY_PATH,
36
36
  getProjectRoot,
@@ -93,7 +93,7 @@ import {
93
93
  DEFAULT_TASK_TTL_MS,
94
94
  DEFAULT_TOOL_RATE_LIMITS,
95
95
  clampTaskTtl
96
- } from "./chunk-5YMSKNUG.js";
96
+ } from "./chunk-OESO6AQL.js";
97
97
  import {
98
98
  DEFAULTS
99
99
  } from "./chunk-I7UDM43R.js";
@@ -28218,6 +28218,22 @@ function emitStageFailed(options) {
28218
28218
  error: options.error
28219
28219
  });
28220
28220
  }
28221
+ function emitModelCalled(options) {
28222
+ const bus = resolveBus(options.bus);
28223
+ if (bus === void 0) return;
28224
+ bus.emit({
28225
+ type: "model.called",
28226
+ timestamp: Date.now(),
28227
+ executionId: options.executionId,
28228
+ cli: options.cli,
28229
+ model: options.model,
28230
+ tokensIn: options.tokensIn,
28231
+ tokensOut: options.tokensOut,
28232
+ durationMs: options.durationMs,
28233
+ ...options.agentId !== void 0 && { agentId: options.agentId },
28234
+ ...options.role !== void 0 && { role: options.role }
28235
+ });
28236
+ }
28221
28237
  function emitPipelineStageEvent(prefix, stage, status, details) {
28222
28238
  const executionId = `${prefix}-${stage}`;
28223
28239
  if (status === "started") {
@@ -42842,7 +42858,7 @@ var VALID_TEMPLATES = /* @__PURE__ */ new Set([
42842
42858
  ]);
42843
42859
  async function classifyWithLLM(task) {
42844
42860
  try {
42845
- const { executeExpert: executeExpert2 } = await import("./expert-bridge-7R4AU4DE.js");
42861
+ const { executeExpert: executeExpert2 } = await import("./expert-bridge-MQURDKJI.js");
42846
42862
  const prompt = [
42847
42863
  "Classify this task into exactly one pipeline template.",
42848
42864
  "Templates: dev (implementation/bug fix/refactor), research (investigate/evaluate/compare),",
@@ -43729,7 +43745,7 @@ function recordOutcome(args) {
43729
43745
  logger40.debug("Failed to record outcome", { taskId: args.taskId, error: String(error) });
43730
43746
  }
43731
43747
  }
43732
- async function runExpert(guard, expertType, prompt) {
43748
+ async function runExpert(guard, expertType, prompt, executionId) {
43733
43749
  if (guard.isExhausted()) {
43734
43750
  return {
43735
43751
  success: false,
@@ -43741,8 +43757,22 @@ async function runExpert(guard, expertType, prompt) {
43741
43757
  }
43742
43758
  const result = await executeExpert(expertType, prompt);
43743
43759
  guard.record(result.tokensUsed);
43760
+ maybeEmitModelCalled(executionId, result);
43744
43761
  return result;
43745
43762
  }
43763
+ function maybeEmitModelCalled(executionId, result) {
43764
+ if (!result.success || executionId === void 0) return;
43765
+ if (result.cli === void 0 || result.model === void 0) return;
43766
+ if (result.tokensIn === void 0 || result.tokensOut === void 0) return;
43767
+ emitModelCalled({
43768
+ executionId,
43769
+ cli: result.cli,
43770
+ model: result.model,
43771
+ tokensIn: result.tokensIn,
43772
+ tokensOut: result.tokensOut,
43773
+ durationMs: result.durationMs
43774
+ });
43775
+ }
43746
43776
  var cachedMemory = null;
43747
43777
  var memoryInitPromise = null;
43748
43778
  async function initPipelineMemory() {
@@ -43899,14 +43929,16 @@ function createAgentStages(config = {}) {
43899
43929
  "research",
43900
43930
  `Use research_discover to find papers and repos related to:
43901
43931
 
43902
- ${task}${memoryCtx}`
43932
+ ${task}${memoryCtx}`,
43933
+ "research"
43903
43934
  );
43904
43935
  const analyze = await runExpert(
43905
43936
  guard,
43906
43937
  "research",
43907
43938
  `Use research_analyze focus=gaps to identify what is missing for:
43908
43939
 
43909
- ${task}`
43940
+ ${task}`,
43941
+ "research"
43910
43942
  );
43911
43943
  const combined = [discover.text, analyze.text].filter(Boolean).join("\n\n");
43912
43944
  const totalMs = discover.durationMs + analyze.durationMs;
@@ -43948,7 +43980,7 @@ ${task}
43948
43980
 
43949
43981
  ${contextBlock}`;
43950
43982
  await postProgress(config, "Plan", feedback !== void 0 ? "Revising..." : "Planning...");
43951
- const r = await runExpert(guard, "architecture", prompt);
43983
+ const r = await runExpert(guard, "architecture", prompt, "plan");
43952
43984
  emitStageEvent2("plan", r.success ? "completed" : "failed", { durationMs: r.durationMs });
43953
43985
  recordOutcome({
43954
43986
  taskId: "plan",
@@ -43966,7 +43998,7 @@ ${contextBlock}`;
43966
43998
  const strategy = config.votingStrategy ?? "higher_order";
43967
43999
  await postProgress(config, "Vote", `Running consensus with ${strategy} strategy...`);
43968
44000
  try {
43969
- const { executeVoting } = await import("./consensus-vote-7HEHHH5S.js");
44001
+ const { executeVoting } = await import("./consensus-vote-VUNGPXXS.js");
43970
44002
  const votingResult = await executeVoting(
43971
44003
  {
43972
44004
  proposal: buildVoteProposal(plan, research),
@@ -44023,7 +44055,8 @@ ${contextBlock}`;
44023
44055
  `Decompose into tasks.
44024
44056
  Return JSON: [{id,title,description,assignedTo}]
44025
44057
 
44026
- ${plan}`
44058
+ ${plan}`,
44059
+ "decompose"
44027
44060
  );
44028
44061
  const tasks = parseTasksFromResponse(r.text, plan);
44029
44062
  emitStageEvent2("decompose", "completed", { durationMs: r.durationMs });
@@ -44049,7 +44082,8 @@ QA feedback: ${task.feedback}` : "";
44049
44082
  `Implement:
44050
44083
 
44051
44084
  ${task.title}
44052
- ${task.description}${fb}`
44085
+ ${task.description}${fb}`,
44086
+ task.id
44053
44087
  );
44054
44088
  emitStageEvent2(`impl-${task.id}`, r.success ? "completed" : "failed", {
44055
44089
  durationMs: r.durationMs
@@ -44078,7 +44112,8 @@ Task: ${task.title}
44078
44112
  Impl:
44079
44113
  ${implementation.slice(0, 3e3)}
44080
44114
 
44081
- Verdict: PASS/NEEDS_WORK/REJECT`
44115
+ Verdict: PASS/NEEDS_WORK/REJECT`,
44116
+ task.id
44082
44117
  );
44083
44118
  const review = parseQaFromResponse(r.text);
44084
44119
  emitStageEvent2(`qa-${task.id}`, review.verdict === "pass" ? "completed" : "failed", {
@@ -50883,4 +50918,4 @@ export {
50883
50918
  detectBackend,
50884
50919
  createTaskTracker
50885
50920
  };
50886
- //# sourceMappingURL=chunk-LM3BH55G.js.map
50921
+ //# sourceMappingURL=chunk-FTFFYAY6.js.map