funolio-agent 0.17.8 → 1.0.3

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 (223) hide show
  1. package/dist/approval.d.ts +7 -6
  2. package/dist/approval.d.ts.map +1 -1
  3. package/dist/approval.js +40 -10
  4. package/dist/approval.js.map +1 -1
  5. package/dist/auth/anthropic-subscription.d.ts +9 -29
  6. package/dist/auth/anthropic-subscription.d.ts.map +1 -1
  7. package/dist/auth/anthropic-subscription.js +12 -133
  8. package/dist/auth/anthropic-subscription.js.map +1 -1
  9. package/dist/auth/auto-detect.d.ts +28 -6
  10. package/dist/auth/auto-detect.d.ts.map +1 -1
  11. package/dist/auth/auto-detect.js +52 -205
  12. package/dist/auth/auto-detect.js.map +1 -1
  13. package/dist/auth/credential-reader.d.ts +24 -4
  14. package/dist/auth/credential-reader.d.ts.map +1 -1
  15. package/dist/auth/credential-reader.js +31 -256
  16. package/dist/auth/credential-reader.js.map +1 -1
  17. package/dist/auth/credential-status.d.ts +7 -27
  18. package/dist/auth/credential-status.d.ts.map +1 -1
  19. package/dist/auth/credential-status.js +7 -95
  20. package/dist/auth/credential-status.js.map +1 -1
  21. package/dist/auth/index.d.ts +9 -2
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +10 -10
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/auth/subscription-runtime.d.ts +19 -23
  26. package/dist/auth/subscription-runtime.d.ts.map +1 -1
  27. package/dist/auth/subscription-runtime.js +24 -292
  28. package/dist/auth/subscription-runtime.js.map +1 -1
  29. package/dist/auth/token-refresh.d.ts +19 -28
  30. package/dist/auth/token-refresh.d.ts.map +1 -1
  31. package/dist/auth/token-refresh.js +26 -464
  32. package/dist/auth/token-refresh.js.map +1 -1
  33. package/dist/backfill.js +2 -2
  34. package/dist/backfill.js.map +1 -1
  35. package/dist/bot-manager.d.ts +5 -6
  36. package/dist/bot-manager.d.ts.map +1 -1
  37. package/dist/bot-manager.js +20 -62
  38. package/dist/bot-manager.js.map +1 -1
  39. package/dist/clerk-model.d.ts +0 -1
  40. package/dist/clerk-model.d.ts.map +1 -1
  41. package/dist/clerk-model.js +24 -50
  42. package/dist/clerk-model.js.map +1 -1
  43. package/dist/commands/configure-provider.js +2 -2
  44. package/dist/commands/configure-provider.js.map +1 -1
  45. package/dist/commands/configure.d.ts.map +1 -1
  46. package/dist/commands/configure.js +10 -14
  47. package/dist/commands/configure.js.map +1 -1
  48. package/dist/commands/start.d.ts.map +1 -1
  49. package/dist/commands/start.js +51 -229
  50. package/dist/commands/start.js.map +1 -1
  51. package/dist/config.d.ts +1 -1
  52. package/dist/config.d.ts.map +1 -1
  53. package/dist/config.js +2 -2
  54. package/dist/config.js.map +1 -1
  55. package/dist/context-compressor.d.ts +6 -1
  56. package/dist/context-compressor.d.ts.map +1 -1
  57. package/dist/context-compressor.js +87 -18
  58. package/dist/context-compressor.js.map +1 -1
  59. package/dist/context-window.d.ts +2 -8
  60. package/dist/context-window.d.ts.map +1 -1
  61. package/dist/context-window.js +4 -4
  62. package/dist/context-window.js.map +1 -1
  63. package/dist/eval/orchestrator-front-door-replay.js +43 -2
  64. package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
  65. package/dist/eval/orchestrator-todo-dispatch-replay.d.ts +2 -0
  66. package/dist/eval/orchestrator-todo-dispatch-replay.d.ts.map +1 -0
  67. package/dist/eval/orchestrator-todo-dispatch-replay.js +253 -0
  68. package/dist/eval/orchestrator-todo-dispatch-replay.js.map +1 -0
  69. package/dist/eval/orchestrator-todo-planning-replay.d.ts +2 -0
  70. package/dist/eval/orchestrator-todo-planning-replay.d.ts.map +1 -0
  71. package/dist/eval/orchestrator-todo-planning-replay.js +247 -0
  72. package/dist/eval/orchestrator-todo-planning-replay.js.map +1 -0
  73. package/dist/eval/policy-detection-replay.d.ts +2 -0
  74. package/dist/eval/policy-detection-replay.d.ts.map +1 -0
  75. package/dist/eval/policy-detection-replay.js +122 -0
  76. package/dist/eval/policy-detection-replay.js.map +1 -0
  77. package/dist/eval/todo-worker-runtime-replay.d.ts +2 -0
  78. package/dist/eval/todo-worker-runtime-replay.d.ts.map +1 -0
  79. package/dist/eval/todo-worker-runtime-replay.js +520 -0
  80. package/dist/eval/todo-worker-runtime-replay.js.map +1 -0
  81. package/dist/integration-tokens.d.ts +6 -0
  82. package/dist/integration-tokens.d.ts.map +1 -1
  83. package/dist/integration-tokens.js +43 -0
  84. package/dist/integration-tokens.js.map +1 -1
  85. package/dist/local-data.d.ts +134 -1
  86. package/dist/local-data.d.ts.map +1 -1
  87. package/dist/local-data.js +711 -18
  88. package/dist/local-data.js.map +1 -1
  89. package/dist/local-db.d.ts.map +1 -1
  90. package/dist/local-db.js +216 -12
  91. package/dist/local-db.js.map +1 -1
  92. package/dist/local-funnel.d.ts.map +1 -1
  93. package/dist/local-funnel.js +7 -0
  94. package/dist/local-funnel.js.map +1 -1
  95. package/dist/local-server.d.ts.map +1 -1
  96. package/dist/local-server.js +119 -96
  97. package/dist/local-server.js.map +1 -1
  98. package/dist/mcp/bridge-server.d.ts.map +1 -1
  99. package/dist/mcp/bridge-server.js +8 -2
  100. package/dist/mcp/bridge-server.js.map +1 -1
  101. package/dist/mcp/manager.d.ts +5 -0
  102. package/dist/mcp/manager.d.ts.map +1 -1
  103. package/dist/mcp/manager.js +131 -2
  104. package/dist/mcp/manager.js.map +1 -1
  105. package/dist/mcp/sync-cli-config.d.ts +5 -0
  106. package/dist/mcp/sync-cli-config.d.ts.map +1 -1
  107. package/dist/mcp/sync-cli-config.js +10 -2
  108. package/dist/mcp/sync-cli-config.js.map +1 -1
  109. package/dist/message-loop.d.ts +1 -10
  110. package/dist/message-loop.d.ts.map +1 -1
  111. package/dist/message-loop.js +179 -250
  112. package/dist/message-loop.js.map +1 -1
  113. package/dist/mqtt-client.d.ts +44 -0
  114. package/dist/mqtt-client.d.ts.map +1 -1
  115. package/dist/mqtt-client.js +2 -2
  116. package/dist/mqtt-client.js.map +1 -1
  117. package/dist/orchestration/front-door-policy.d.ts +26 -9
  118. package/dist/orchestration/front-door-policy.d.ts.map +1 -1
  119. package/dist/orchestration/front-door-policy.js +242 -69
  120. package/dist/orchestration/front-door-policy.js.map +1 -1
  121. package/dist/orchestration/orchestrator-blocked-prompt.d.ts +18 -0
  122. package/dist/orchestration/orchestrator-blocked-prompt.d.ts.map +1 -0
  123. package/dist/orchestration/orchestrator-blocked-prompt.js +46 -0
  124. package/dist/orchestration/orchestrator-blocked-prompt.js.map +1 -0
  125. package/dist/orchestration/orchestrator-final-response-prompt.d.ts +10 -0
  126. package/dist/orchestration/orchestrator-final-response-prompt.d.ts.map +1 -0
  127. package/dist/orchestration/orchestrator-final-response-prompt.js +39 -0
  128. package/dist/orchestration/orchestrator-final-response-prompt.js.map +1 -0
  129. package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
  130. package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
  131. package/dist/orchestration/orchestrator-operating-prompt.js +106 -36
  132. package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
  133. package/dist/orchestration/policy-prompt.d.ts +6 -0
  134. package/dist/orchestration/policy-prompt.d.ts.map +1 -0
  135. package/dist/orchestration/policy-prompt.js +40 -0
  136. package/dist/orchestration/policy-prompt.js.map +1 -0
  137. package/dist/orchestration/worker-operating-prompt.d.ts +16 -0
  138. package/dist/orchestration/worker-operating-prompt.d.ts.map +1 -0
  139. package/dist/orchestration/worker-operating-prompt.js +75 -0
  140. package/dist/orchestration/worker-operating-prompt.js.map +1 -0
  141. package/dist/orchestrator.d.ts +19 -0
  142. package/dist/orchestrator.d.ts.map +1 -1
  143. package/dist/orchestrator.js +614 -54
  144. package/dist/orchestrator.js.map +1 -1
  145. package/dist/policy-detection.d.ts +40 -0
  146. package/dist/policy-detection.d.ts.map +1 -0
  147. package/dist/policy-detection.js +298 -0
  148. package/dist/policy-detection.js.map +1 -0
  149. package/dist/providers/anthropic.d.ts +0 -5
  150. package/dist/providers/anthropic.d.ts.map +1 -1
  151. package/dist/providers/anthropic.js +34 -51
  152. package/dist/providers/anthropic.js.map +1 -1
  153. package/dist/providers/claude-cli.d.ts.map +1 -1
  154. package/dist/providers/claude-cli.js +11 -2
  155. package/dist/providers/claude-cli.js.map +1 -1
  156. package/dist/providers/codex-cli.d.ts.map +1 -1
  157. package/dist/providers/codex-cli.js +121 -2
  158. package/dist/providers/codex-cli.js.map +1 -1
  159. package/dist/providers/index.d.ts +6 -2
  160. package/dist/providers/index.d.ts.map +1 -1
  161. package/dist/providers/index.js.map +1 -1
  162. package/dist/summarization-pipeline.d.ts +1 -4
  163. package/dist/summarization-pipeline.d.ts.map +1 -1
  164. package/dist/summarization-pipeline.js +43 -56
  165. package/dist/summarization-pipeline.js.map +1 -1
  166. package/dist/tools/analyze-image.js +2 -2
  167. package/dist/tools/analyze-image.js.map +1 -1
  168. package/dist/tools/edit-file.d.ts.map +1 -1
  169. package/dist/tools/edit-file.js +16 -1
  170. package/dist/tools/edit-file.js.map +1 -1
  171. package/dist/tools/git-tools.d.ts.map +1 -1
  172. package/dist/tools/git-tools.js +32 -1
  173. package/dist/tools/git-tools.js.map +1 -1
  174. package/dist/tools/index.d.ts.map +1 -1
  175. package/dist/tools/index.js +2 -0
  176. package/dist/tools/index.js.map +1 -1
  177. package/dist/tools/list-directory.d.ts.map +1 -1
  178. package/dist/tools/list-directory.js +23 -0
  179. package/dist/tools/list-directory.js.map +1 -1
  180. package/dist/tools/notify-user.d.ts.map +1 -1
  181. package/dist/tools/notify-user.js +15 -1
  182. package/dist/tools/notify-user.js.map +1 -1
  183. package/dist/tools/read-file.d.ts.map +1 -1
  184. package/dist/tools/read-file.js +3 -1
  185. package/dist/tools/read-file.js.map +1 -1
  186. package/dist/tools/request-mcp-install.js +5 -5
  187. package/dist/tools/request-mcp-install.js.map +1 -1
  188. package/dist/tools/run-command.d.ts.map +1 -1
  189. package/dist/tools/run-command.js +16 -4
  190. package/dist/tools/run-command.js.map +1 -1
  191. package/dist/tools/sandbox.d.ts.map +1 -1
  192. package/dist/tools/sandbox.js +6 -0
  193. package/dist/tools/sandbox.js.map +1 -1
  194. package/dist/tools/schedule-task.d.ts.map +1 -1
  195. package/dist/tools/schedule-task.js +37 -0
  196. package/dist/tools/schedule-task.js.map +1 -1
  197. package/dist/tools/search-codebase.d.ts.map +1 -1
  198. package/dist/tools/search-codebase.js +251 -32
  199. package/dist/tools/search-codebase.js.map +1 -1
  200. package/dist/tools/todo-tasks.d.ts +2 -0
  201. package/dist/tools/todo-tasks.d.ts.map +1 -1
  202. package/dist/tools/todo-tasks.js +203 -6
  203. package/dist/tools/todo-tasks.js.map +1 -1
  204. package/dist/tools/web-fetch.d.ts.map +1 -1
  205. package/dist/tools/web-fetch.js +21 -2
  206. package/dist/tools/web-fetch.js.map +1 -1
  207. package/dist/tools/web-search.d.ts.map +1 -1
  208. package/dist/tools/web-search.js +38 -34
  209. package/dist/tools/web-search.js.map +1 -1
  210. package/dist/types.d.ts +2 -0
  211. package/dist/types.d.ts.map +1 -1
  212. package/dist/wizard-state.d.ts.map +1 -1
  213. package/dist/wizard-state.js +30 -8
  214. package/dist/wizard-state.js.map +1 -1
  215. package/dist/wizard-support.d.ts +2 -2
  216. package/dist/wizard-support.d.ts.map +1 -1
  217. package/dist/wizard-support.js +80 -93
  218. package/dist/wizard-support.js.map +1 -1
  219. package/dist/workflow-engine.d.ts +3 -0
  220. package/dist/workflow-engine.d.ts.map +1 -1
  221. package/dist/workflow-engine.js +111 -82
  222. package/dist/workflow-engine.js.map +1 -1
  223. package/package.json +6 -1
@@ -51,6 +51,11 @@ const status_parser_1 = require("./orchestration/status-parser");
51
51
  const front_door_policy_1 = require("./orchestration/front-door-policy");
52
52
  const deterministic_path_1 = require("./orchestration/deterministic-path");
53
53
  const orchestrator_operating_prompt_1 = require("./orchestration/orchestrator-operating-prompt");
54
+ const policy_prompt_1 = require("./orchestration/policy-prompt");
55
+ const orchestrator_blocked_prompt_1 = require("./orchestration/orchestrator-blocked-prompt");
56
+ const orchestrator_final_response_prompt_1 = require("./orchestration/orchestrator-final-response-prompt");
57
+ const worker_operating_prompt_1 = require("./orchestration/worker-operating-prompt");
58
+ const policy_detection_1 = require("./policy-detection");
54
59
  const execution_contract_1 = require("./execution-contract");
55
60
  const state_1 = require("./orchestration/state");
56
61
  const orchestrator_profile_1 = require("./orchestrator-profile");
@@ -160,41 +165,37 @@ class OrchestratorAgent {
160
165
  const validation = this.validateIntentExecution(prompt, workflowIntent, initialPolicy);
161
166
  if (validation.ok) {
162
167
  this.applyExecutionSpecToState(orchestrationState, validation.executionSpec);
163
- return this.handleOrchestratedRequest(prompt, conversationId, workflowIntent, validation.executionSpec, opts, initialAssignments, initialProject, orchestrationState, selectedOrchestrator);
168
+ return await this.queueWorkflowTodoPlan(prompt, conversationId, workflowIntent, validation.executionSpec, opts, initialAssignments, initialProject, orchestrationState, selectedOrchestrator, template);
164
169
  }
165
170
  }
166
171
  }
167
- // ─── Single-Call Orchestrator ─────────────────────────
168
- // Ben always does the work first. If user named another bot, delegate AFTER.
169
- if (selectedOrchestrator) {
170
- orchestrationState.intent = 'execute_self';
171
- (0, state_1.setOrchestrationPath)(orchestrationState, 'direct', 'direct');
172
- this.recordOrchestrationAudit(orchestrationState, 'choose_path', 'path_selected', 'Orchestrator executes directly (single-call model).');
173
- // Check for delegation targets before executing
174
- const mentionedOtherBots = this.getMentionedAgents(prompt)
175
- .filter((a) => a.id !== selectedOrchestrator.id);
176
- // Step 1: Ben does the work — if delegating, tell Ben to skip the delegate's role
177
- let executionPrompt = prompt;
178
- if (mentionedOtherBots.length > 0) {
179
- const botNames = mentionedOtherBots.map((b) => b.name).join(', ');
180
- const role = this.inferDelegationRole(prompt);
181
- executionPrompt = `${prompt}\n\n[System: ${botNames} will handle ${role} after you finish. Do YOUR work only — do not perform ${role} yourself. Complete your task and return your deliverable.]`;
182
- }
183
- const directResult = await this.executeOrchestratorOwnedWork(executionPrompt, conversationId, selectedOrchestrator, initialAssignments, initialProject, opts, orchestrationState);
184
- if (mentionedOtherBots.length > 0 && directResult.ok) {
185
- const delegateBot = mentionedOtherBots[0];
186
- this.recordOrchestrationAudit(orchestrationState, 'delegate_specialist', 'delegated', `Delegating to ${delegateBot.name} after orchestrator completed work.`, { delegateBot: delegateBot.name });
187
- try {
188
- const delegateResponse = await this.executeDelegation(directResult.response, prompt, delegateBot, conversationId, initialProject, opts, orchestrationState);
189
- return delegateResponse;
190
- }
191
- catch (err) {
192
- // If delegation fails, still return Ben's result
193
- this.recordOrchestrationAudit(orchestrationState, 'delegate_specialist', 'blocked', `Delegation to ${delegateBot.name} failed: ${err?.message || err}. Returning orchestrator result.`);
194
- return directResult.response;
195
- }
172
+ const namedWorkflowTemplate = selectedOrchestrator
173
+ ? this.resolveWorkflowTemplateByPrompt(conversationProjectId, prompt)
174
+ : undefined;
175
+ if (namedWorkflowTemplate && selectedOrchestrator) {
176
+ orchestrationState.intent = 'workflow';
177
+ this.recordOrchestrationAudit(orchestrationState, 'choose_path', 'path_selected', `User explicitly named workflow template: ${namedWorkflowTemplate.name}`, { workflowTemplateId: namedWorkflowTemplate.id });
178
+ const workflowIntent = {
179
+ primaryMode: 'WORKFLOW_MODE',
180
+ secondaryModes: [],
181
+ executionOrder: ['WORKFLOW_MODE'],
182
+ userFacingMode: 'FULL_WORKFLOW',
183
+ targetScope: 'MULTI_WORKER',
184
+ confidence: 'HIGH',
185
+ intent: 'build',
186
+ isMultiStep: true,
187
+ reasoning: `User named workflow template: ${namedWorkflowTemplate.name}`,
188
+ };
189
+ const validation = this.validateIntentExecution(prompt, workflowIntent, initialPolicy);
190
+ if (validation.ok) {
191
+ this.applyExecutionSpecToState(orchestrationState, validation.executionSpec);
192
+ return await this.queueWorkflowTodoPlan(prompt, conversationId, workflowIntent, validation.executionSpec, opts, initialAssignments, initialProject, orchestrationState, selectedOrchestrator, namedWorkflowTemplate);
196
193
  }
197
- return directResult.response;
194
+ }
195
+ if (selectedOrchestrator) {
196
+ const frontDoorResult = await this.handleOrchestratorFrontDoor(prompt, conversationId, opts, selectedOrchestrator, conversationProjectId, initialProject, initialPolicy, promptAssignments, initialAssignments, initialOverview, orchestrationState);
197
+ if (frontDoorResult !== null)
198
+ return frontDoorResult;
198
199
  }
199
200
  this.reportHiddenRoleProgress(opts, 'intent_classifier', 'Classifying request');
200
201
  (0, state_1.addHelperRoleUsage)(orchestrationState, 'intent_classifier');
@@ -1458,7 +1459,13 @@ class OrchestratorAgent {
1458
1459
  ? `${contextWindow.carriedForward ? '(carried forward from the previous conversation in this topic)\n' : ''}${contextWindow.summary.summary_text.trim()}`
1459
1460
  : '(none)';
1460
1461
  // Build specialists list from non-orchestrator agent profiles
1461
- const specialists = (0, orchestrator_profile_1.filterOutOrchestratorProfiles)(data.listAgentProfiles())
1462
+ const allProfiles = data.listAgentProfiles();
1463
+ const specialists = (0, orchestrator_profile_1.filterOutOrchestratorProfiles)(allProfiles)
1464
+ .map((agent) => ({
1465
+ name: agent.name,
1466
+ roleLabel: agent.role_label || agent.role_class || 'general',
1467
+ }));
1468
+ const signalSpecialists = allProfiles
1462
1469
  .map((agent) => ({
1463
1470
  name: agent.name,
1464
1471
  roleLabel: agent.role_label || agent.role_class || 'general',
@@ -1473,17 +1480,44 @@ class OrchestratorAgent {
1473
1480
  }
1474
1481
  catch { /* table may not exist yet */ }
1475
1482
  }
1483
+ const frontDoorSignals = (0, front_door_policy_1.extractFrontDoorSignals)({
1484
+ prompt,
1485
+ specialists: signalSpecialists,
1486
+ workflowNames,
1487
+ roleAssignments,
1488
+ });
1489
+ const normalizeName = (value) => String(value || '').trim().toLowerCase();
1476
1490
  const operatingPrompt = (0, orchestrator_operating_prompt_1.buildOrchestratorOperatingPrompt)({
1477
1491
  orchestratorName: orchestratorBot.name,
1478
1492
  primaryRole: orchestratorBot.role_label || orchestratorBot.role_class || null,
1479
1493
  specialists,
1480
1494
  workflowNames,
1495
+ entryMode: frontDoorSignals.explicitOrchestrationRequested ? 'orchestration_bias' : 'normal',
1496
+ signalSummary: {
1497
+ matchedBots: frontDoorSignals.matchedBots,
1498
+ matchedRoles: frontDoorSignals.matchedRoles,
1499
+ matchedWorkflows: frontDoorSignals.matchedWorkflowNames,
1500
+ explicitTodoKeyword: frontDoorSignals.explicitTodoKeyword,
1501
+ explicitWorkflowKeyword: frontDoorSignals.explicitWorkflowKeyword,
1502
+ explicitDelegationKeyword: frontDoorSignals.explicitDelegationKeyword,
1503
+ },
1481
1504
  projectName: overview?.project.name || project?.name || null,
1482
1505
  projectFolder: project?.folder || overview?.project.folder || null,
1506
+ effectivePolicy: (0, policy_prompt_1.buildEffectivePolicyPromptSection)(policy, {
1507
+ heading: '',
1508
+ defaultLine: 'No confirmed special policy is set.',
1509
+ }),
1483
1510
  recentSummary: recentSummaryText,
1484
1511
  lastFiveTurns: recentTurnsText,
1485
1512
  });
1486
- const decisionPrompt = [`User prompt:\n${prompt}`, '', 'Return strict JSON only.'].join('\n');
1513
+ const decisionPrompt = [
1514
+ 'This is a routing decision only. Do not perform the task.',
1515
+ 'Do not claim to have created files, completed work, or used tools.',
1516
+ '',
1517
+ `User prompt:\n${prompt}`,
1518
+ '',
1519
+ 'Return strict JSON only.',
1520
+ ].join('\n');
1487
1521
  let decision = null;
1488
1522
  this.reportHiddenRoleProgress(opts, 'orchestrator', 'Understanding request');
1489
1523
  opts.onProgress('Understanding request...');
@@ -1506,6 +1540,66 @@ class OrchestratorAgent {
1506
1540
  finally {
1507
1541
  clearInterval(understandingHeartbeat);
1508
1542
  }
1543
+ if (!decision) {
1544
+ const fallbackTaskType = (0, front_door_policy_1.inferFrontDoorTaskType)(prompt);
1545
+ const promptAssignedRoles = [promptAssignments.coding, promptAssignments.qa, promptAssignments.research].filter(Boolean).length;
1546
+ if (frontDoorSignals.preferredMode === 'workflow') {
1547
+ decision = {
1548
+ mode: 'workflow',
1549
+ reason: frontDoorSignals.matchedWorkflowNames.length > 0
1550
+ ? 'Code-based front door recovered routing by matching a named workflow.'
1551
+ : 'Code-based front door recovered routing from explicit TODO/workflow signals.',
1552
+ workflow_request: prompt,
1553
+ };
1554
+ }
1555
+ else if (fallbackTaskType === 'mixed' || promptAssignedRoles >= 2 || frontDoorSignals.matchedBots.length >= 2 || frontDoorSignals.matchedRoles.length >= 2) {
1556
+ decision = {
1557
+ mode: 'workflow',
1558
+ reason: 'Code-based front door recovered routing for a multi-worker or staged worker task.',
1559
+ workflow_request: prompt,
1560
+ };
1561
+ }
1562
+ else if (frontDoorSignals.preferredMode === 'delegate' && frontDoorSignals.preferredTarget) {
1563
+ const delegateRole = frontDoorSignals.matchedRoles[0]
1564
+ || (normalizeName(frontDoorSignals.preferredTarget) === normalizeName(roleAssignments.qa) ? 'qa'
1565
+ : normalizeName(frontDoorSignals.preferredTarget) === normalizeName(roleAssignments.research) ? 'research'
1566
+ : 'coding');
1567
+ decision = {
1568
+ mode: 'delegate',
1569
+ reason: 'Code-based front door recovered routing from an explicit single-worker TODO/workflow signal.',
1570
+ delegate_target: frontDoorSignals.preferredTarget,
1571
+ delegate_role: delegateRole,
1572
+ delegate_request: prompt,
1573
+ };
1574
+ }
1575
+ else if (fallbackTaskType === 'coding' && roleAssignments.coding) {
1576
+ decision = {
1577
+ mode: 'delegate',
1578
+ reason: 'Code-based front door recovered routing for a coding task owned by the coding worker.',
1579
+ delegate_target: roleAssignments.coding,
1580
+ delegate_role: 'coding',
1581
+ delegate_request: prompt,
1582
+ };
1583
+ }
1584
+ else if (fallbackTaskType === 'qa' && roleAssignments.qa) {
1585
+ decision = {
1586
+ mode: 'delegate',
1587
+ reason: 'Code-based front door recovered routing for a QA task owned by the QA worker.',
1588
+ delegate_target: roleAssignments.qa,
1589
+ delegate_role: 'qa',
1590
+ delegate_request: prompt,
1591
+ };
1592
+ }
1593
+ else if (fallbackTaskType === 'research' && roleAssignments.research) {
1594
+ decision = {
1595
+ mode: 'delegate',
1596
+ reason: 'Code-based front door recovered routing for a research task owned by the research worker.',
1597
+ delegate_target: roleAssignments.research,
1598
+ delegate_role: 'research',
1599
+ delegate_request: prompt,
1600
+ };
1601
+ }
1602
+ }
1509
1603
  if (!decision) {
1510
1604
  this.recordOrchestrationAudit(state, 'understand_request', 'blocked', 'Orchestrator-first decision phase did not return valid JSON. Falling back to legacy clerk chain.', { featureFlag: 'orchestrator_v2_enabled' });
1511
1605
  return null;
@@ -1520,6 +1614,8 @@ class OrchestratorAgent {
1520
1614
  orchestratorRoleClass: orchestratorBot.role_class,
1521
1615
  promptAssignments,
1522
1616
  roleAssignments,
1617
+ specialists: signalSpecialists,
1618
+ workflowNames,
1523
1619
  highRisk: highRiskPrompt,
1524
1620
  });
1525
1621
  if (normalizedDecision.corrected) {
@@ -1538,10 +1634,28 @@ class OrchestratorAgent {
1538
1634
  explicitWorkflowRequested: normalizedDecision.explicitWorkflowRequested,
1539
1635
  corrected: normalizedDecision.corrected,
1540
1636
  });
1541
- // Clarify should never reach here (parser + policy convert to execute_self),
1542
- // but if it somehow does, treat it as execute_self — just do the work.
1543
1637
  if (decision.mode === 'clarify') {
1544
- decision.mode = 'execute_self';
1638
+ return this.formatClarificationResponse({
1639
+ primaryMode: normalizedDecision.explicitWorkflowRequested ? 'WORKFLOW_MODE' : 'DIRECT_CONVERSATION',
1640
+ secondaryModes: [],
1641
+ executionOrder: [normalizedDecision.explicitWorkflowRequested ? 'WORKFLOW_MODE' : 'DIRECT_CONVERSATION'],
1642
+ userFacingMode: normalizedDecision.explicitWorkflowRequested ? 'FULL_WORKFLOW' : 'DIRECT_RESPONSE',
1643
+ targetScope: normalizedDecision.explicitWorkflowRequested ? 'MULTI_WORKER' : 'SELF',
1644
+ confidence: 'MEDIUM',
1645
+ intent: normalizedDecision.taskType === 'qa'
1646
+ ? 'review'
1647
+ : normalizedDecision.taskType === 'research'
1648
+ ? 'brainstorm'
1649
+ : normalizedDecision.taskType === 'coding' || normalizedDecision.taskType === 'workflow' || normalizedDecision.taskType === 'mixed'
1650
+ ? 'build'
1651
+ : 'question',
1652
+ isMultiStep: normalizedDecision.explicitWorkflowRequested,
1653
+ reasoning: decision.reason,
1654
+ needsClarification: true,
1655
+ clarificationQuestions: decision.clarification_questions?.length
1656
+ ? decision.clarification_questions
1657
+ : ['What exactly should I route to the workflow?'],
1658
+ }, overview);
1545
1659
  }
1546
1660
  if (decision.mode === 'respond') {
1547
1661
  const response = decision.response?.trim() || this.handleDirectConversation(prompt, overview, roleAssignments, state);
@@ -1596,7 +1710,7 @@ class OrchestratorAgent {
1596
1710
  if (executionSpec.requiresConfirmation) {
1597
1711
  return this.createConfirmationCheckpoint(delegatePrompt, conversationId, delegateIntent, executionSpec, projectId, roleAssignments, state);
1598
1712
  }
1599
- return this.handleProxyRequest(delegatePrompt, conversationId, delegateIntent, opts, executionSpec, roleAssignments, state);
1713
+ return await this.queueDelegateTodo(delegatePrompt, conversationId, delegateIntent, opts, executionSpec, roleAssignments, state, orchestratorBot, delegateTarget);
1600
1714
  }
1601
1715
  if (decision.mode === 'workflow') {
1602
1716
  const workflowObjective = decision.workflow_request?.trim() || prompt;
@@ -1626,7 +1740,7 @@ class OrchestratorAgent {
1626
1740
  return this.createConfirmationCheckpoint(prompt, conversationId, workflowIntent, validation.executionSpec, projectId, roleAssignments, state);
1627
1741
  }
1628
1742
  this.recordOrchestrationAudit(state, 'run_workflow', 'delegated', 'Workflow execution is using the original user prompt as the execution source of truth.', { workflowObjective });
1629
- return this.handleOrchestratedRequest(prompt, conversationId, workflowIntent, validation.executionSpec, opts, roleAssignments, project, state, orchestratorBot);
1743
+ return await this.queueWorkflowTodoPlan(prompt, conversationId, workflowIntent, validation.executionSpec, opts, roleAssignments, project, state, orchestratorBot);
1630
1744
  }
1631
1745
  return null;
1632
1746
  }
@@ -1702,6 +1816,438 @@ class OrchestratorAgent {
1702
1816
  locked: true,
1703
1817
  };
1704
1818
  }
1819
+ async queueDelegateTodo(prompt, conversationId, intent, opts, executionSpec, roleAssignments, state, orchestratorBot, delegateTarget) {
1820
+ const projectId = opts.projectId || state.projectId || null;
1821
+ const project = projectId ? data.getProject(projectId) : undefined;
1822
+ const targetAgent = this.findAgentByName(delegateTarget, project?.id || undefined);
1823
+ if (!targetAgent) {
1824
+ return this.formatClarificationResponse({
1825
+ ...intent,
1826
+ needsClarification: true,
1827
+ clarificationQuestions: [`I could not resolve the target bot "${delegateTarget}". Which worker should own this task?`],
1828
+ }, projectId ? data.getProjectOverview(projectId) : undefined);
1829
+ }
1830
+ const step = this.buildSingleDelegateTodoStep(prompt, targetAgent, orchestratorBot, project?.name || null, project?.folder || null, data.getEffectiveOrchestrationPolicy(projectId || undefined));
1831
+ const task = data.addTodoTask({
1832
+ projectId,
1833
+ conversationId,
1834
+ title: step.title,
1835
+ details: step.details,
1836
+ participants: [step.owner.name],
1837
+ successCriteria: step.successCriteria,
1838
+ ownerBotId: step.owner.id,
1839
+ ownerName: step.owner.name,
1840
+ taskType: step.taskType,
1841
+ profileName: 'Programming',
1842
+ taskPrompt: step.taskPrompt,
1843
+ actor: { actorType: 'orchestrator', actorId: orchestratorBot.id },
1844
+ });
1845
+ this.markConversationOrchestrated(conversationId);
1846
+ this.recordOrchestrationAudit(state, 'delegate_specialist', 'delegated', `Queued delegated TODO for ${targetAgent.name}.`, {
1847
+ todoTaskId: task.id,
1848
+ targetAgent: targetAgent.name,
1849
+ taskType: step.taskType,
1850
+ });
1851
+ (0, state_1.addDelegateTarget)(state, targetAgent.name);
1852
+ this.applyExecutionSpecToState(state, executionSpec);
1853
+ if (opts.autoDispatchTodos === false) {
1854
+ state.finalResponseDraft = [
1855
+ `I created 1 TODO for ${targetAgent.name}.`,
1856
+ '',
1857
+ `1. ${step.title}`,
1858
+ ` Role: ${step.taskType}`,
1859
+ ` Owner: ${targetAgent.name}`,
1860
+ ].join('\n');
1861
+ return state.finalResponseDraft;
1862
+ }
1863
+ return await this.dispatchQueuedTodoChain(conversationId, opts, state, orchestratorBot, project, { taskIds: [task.id], workflowName: null });
1864
+ }
1865
+ async queueWorkflowTodoPlan(prompt, conversationId, intent, executionSpec, opts, roleAssignments, project, state, orchestratorBot, workflowTemplate) {
1866
+ const projectId = project?.id || opts.projectId || state.projectId || null;
1867
+ const effectivePolicy = data.getEffectiveOrchestrationPolicy(projectId || undefined);
1868
+ const plannedSteps = workflowTemplate
1869
+ ? this.buildTodoStepsFromTemplate(prompt, workflowTemplate, orchestratorBot, project?.name || null, project?.folder || null, effectivePolicy)
1870
+ : this.buildTodoStepsFromAssignments(prompt, roleAssignments, intent, orchestratorBot, projectId || undefined, project?.name || null, project?.folder || null, effectivePolicy);
1871
+ if (plannedSteps.length === 0) {
1872
+ const overview = projectId ? data.getProjectOverview(projectId) : undefined;
1873
+ return this.formatClarificationResponse({
1874
+ ...intent,
1875
+ needsClarification: true,
1876
+ clarificationQuestions: ['I do not have enough worker assignments to build the TODO workflow. Which bots should handle the requested stages?'],
1877
+ }, overview);
1878
+ }
1879
+ const createdTasks = [];
1880
+ for (let index = 0; index < plannedSteps.length; index += 1) {
1881
+ const step = plannedSteps[index];
1882
+ const next = plannedSteps[index + 1];
1883
+ const task = data.addTodoTask({
1884
+ projectId,
1885
+ conversationId,
1886
+ title: step.title,
1887
+ details: step.details,
1888
+ participants: [step.owner.name],
1889
+ successCriteria: step.successCriteria,
1890
+ ownerBotId: step.owner.id,
1891
+ ownerName: step.owner.name,
1892
+ taskType: step.taskType,
1893
+ profileName: 'Programming',
1894
+ nextWorkerBotId: next?.owner.id || null,
1895
+ nextWorkerName: next?.owner.name || null,
1896
+ nextWorkerRole: next ? (next.owner.role_label || next.owner.role_class || null) : null,
1897
+ taskPrompt: step.taskPrompt,
1898
+ actor: { actorType: 'orchestrator', actorId: orchestratorBot.id },
1899
+ });
1900
+ createdTasks.push(task);
1901
+ (0, state_1.addDelegateTarget)(state, step.owner.name);
1902
+ }
1903
+ this.markConversationOrchestrated(conversationId);
1904
+ this.applyExecutionSpecToState(state, executionSpec);
1905
+ this.recordOrchestrationAudit(state, 'run_workflow', 'delegated', workflowTemplate
1906
+ ? `Queued TODO workflow from template "${workflowTemplate.name}".`
1907
+ : 'Queued TODO workflow from explicit orchestration request.', {
1908
+ todoTaskIds: createdTasks.map((task) => task.id),
1909
+ workflowTemplateId: workflowTemplate?.id || null,
1910
+ stepCount: createdTasks.length,
1911
+ });
1912
+ if (opts.autoDispatchTodos === false) {
1913
+ const lines = [
1914
+ workflowTemplate
1915
+ ? `I created a ${createdTasks.length}-step TODO workflow from "${workflowTemplate.name}".`
1916
+ : `I created a ${createdTasks.length}-step TODO workflow.`,
1917
+ '',
1918
+ ];
1919
+ for (const [index, step] of plannedSteps.entries()) {
1920
+ lines.push(`${index + 1}. ${step.title}`);
1921
+ lines.push(` Owner: ${step.owner.name}`);
1922
+ lines.push(` Role: ${step.taskType}`);
1923
+ }
1924
+ state.finalResponseDraft = lines.join('\n');
1925
+ return state.finalResponseDraft;
1926
+ }
1927
+ return await this.dispatchQueuedTodoChain(conversationId, opts, state, orchestratorBot, project, {
1928
+ taskIds: createdTasks.map((task) => task.id),
1929
+ workflowName: workflowTemplate?.name || null,
1930
+ });
1931
+ }
1932
+ async dispatchQueuedTodoChain(conversationId, opts, state, orchestratorBot, project, context) {
1933
+ const maxSteps = 20;
1934
+ for (let iteration = 0; iteration < maxSteps; iteration += 1) {
1935
+ const task = data.getNextActiveTodoForConversation(conversationId);
1936
+ if (!task) {
1937
+ return await this.finalizeQueuedTodoChain(conversationId, opts, state, orchestratorBot, project, context.workflowName);
1938
+ }
1939
+ const owner = task.owner_bot_id
1940
+ ? data.getAgentProfile(task.owner_bot_id)
1941
+ : this.findAgentByName(task.owner_name || undefined, project?.id || undefined);
1942
+ if (!owner) {
1943
+ const message = `I could not resolve the worker assigned to TODO #${task.id}.`;
1944
+ state.finalResponseDraft = message;
1945
+ this.recordOrchestrationAudit(state, 'delegate_specialist', 'blocked', message, { taskId: task.id, ownerName: task.owner_name || null });
1946
+ return message;
1947
+ }
1948
+ const taskPrompt = task.task_prompt?.trim() || task.details || task.title;
1949
+ const result = await this.runNodeWithRetry('run_workflow', state, async () => this.workflowEngine.execute(taskPrompt, conversationId, owner.id, {
1950
+ apiKey: this.resolveApiKey(owner.provider),
1951
+ taskId: task.id,
1952
+ disableDecomposition: true,
1953
+ isOrchestrated: true,
1954
+ projectId: project?.id || opts.projectId || state.projectId || null,
1955
+ workspacePath: project?.folder?.trim() || undefined,
1956
+ persistConversationMessages: false,
1957
+ onWorkerChunk: opts.onWorkerChunk,
1958
+ onProgress: (progress) => {
1959
+ if (progress.event === 'step-started') {
1960
+ this.reportHiddenRoleProgress(opts, 'dispatch_controller', `${owner.name} is working on TODO: ${task.title}`);
1961
+ }
1962
+ else if (progress.event === 'step-completed') {
1963
+ this.reportHiddenRoleProgress(opts, 'dispatch_controller', `${owner.name} completed TODO: ${task.title}`);
1964
+ }
1965
+ else if (progress.event === 'step-failed') {
1966
+ this.reportHiddenRoleProgress(opts, 'dispatch_controller', `${owner.name} encountered an issue while working on TODO: ${task.title}`);
1967
+ }
1968
+ },
1969
+ }));
1970
+ const stillActive = data.getTodoTask(task.id, 'active');
1971
+ const completed = data.getTodoTask(task.id, 'completed');
1972
+ if (stillActive?.blocker_summary && stillActive.blocker_question) {
1973
+ this.recordOrchestrationAudit(state, 'run_workflow', 'blocked', `${owner.name} blocked on TODO: ${task.title}`, { taskId: task.id, blockerSummary: stillActive.blocker_summary });
1974
+ return await this.handleBlockedTodoTask(stillActive, opts, state, orchestratorBot, project);
1975
+ }
1976
+ if (!completed) {
1977
+ const error = result.steps.find((step) => step.error)?.error || this.stripWorkerProtocol(result.mergedResult || '') || 'Worker did not complete the queued task.';
1978
+ const message = `${owner.name} encountered an issue: ${error}`;
1979
+ state.finalResponseDraft = message;
1980
+ this.recordOrchestrationAudit(state, 'run_workflow', 'blocked', message, { taskId: task.id });
1981
+ return message;
1982
+ }
1983
+ }
1984
+ const message = 'The workflow exceeded the automatic dispatch limit before completing.';
1985
+ state.finalResponseDraft = message;
1986
+ this.recordOrchestrationAudit(state, 'run_workflow', 'blocked', message, { maxSteps: 20 });
1987
+ return message;
1988
+ }
1989
+ async handleBlockedTodoTask(task, opts, state, orchestratorBot, project) {
1990
+ const policy = data.getEffectiveOrchestrationPolicy(project?.id || opts.projectId || state.projectId || undefined);
1991
+ const artifacts = data.listTodoArtifactsForTask(task.id, 'active').map((item) => item.path_or_ref);
1992
+ const workerRole = task.task_type || task.next_worker_role || 'general';
1993
+ const blockedPrompt = (0, orchestrator_blocked_prompt_1.buildBlockedWorkerOrchestratorPrompt)({
1994
+ orchestratorName: orchestratorBot.name,
1995
+ workerName: task.owner_name || 'Worker',
1996
+ workerRole,
1997
+ taskTitle: task.title,
1998
+ taskPrompt: task.task_prompt || task.details || task.title,
1999
+ projectId: project?.id || opts.projectId || state.projectId || undefined,
2000
+ projectName: project?.name || null,
2001
+ workspacePath: project?.folder || null,
2002
+ effectivePolicy: policy,
2003
+ artifactRefs: artifacts,
2004
+ blockerSummary: task.blocker_summary || 'Worker is blocked.',
2005
+ checkedContext: task.blocker_checked || 'No checked context was recorded.',
2006
+ userQuestion: task.blocker_question || 'What should happen next?',
2007
+ });
2008
+ const response = await this.runNodeWithRetry('finalize_response', state, async () => {
2009
+ const result = await this.workflowEngine.execute(blockedPrompt, task.conversation_id || state.conversationId || '', orchestratorBot.id, {
2010
+ apiKey: this.resolveApiKey(orchestratorBot.provider),
2011
+ disableDecomposition: true,
2012
+ disableTools: true,
2013
+ workerMode: false,
2014
+ persistConversationMessages: false,
2015
+ onWorkerChunk: opts.onWorkerChunk,
2016
+ workspacePath: project?.folder?.trim() || undefined,
2017
+ });
2018
+ return this.stripWorkerProtocol(result.mergedResult || '');
2019
+ });
2020
+ state.finalResponseDraft = response;
2021
+ return response;
2022
+ }
2023
+ async finalizeQueuedTodoChain(conversationId, opts, state, orchestratorBot, project, workflowName) {
2024
+ const completedTasks = data.listCompletedTodoTasksForConversation(conversationId);
2025
+ if (completedTasks.length === 0) {
2026
+ const fallback = workflowName
2027
+ ? `The "${workflowName}" workflow finished without any completed worker output.`
2028
+ : 'The queued workflow finished without any completed worker output.';
2029
+ state.finalResponseDraft = fallback;
2030
+ return fallback;
2031
+ }
2032
+ const policy = data.getEffectiveOrchestrationPolicy(project?.id || opts.projectId || state.projectId || undefined);
2033
+ const finalPrompt = (0, orchestrator_final_response_prompt_1.buildOrchestratorFinalResponsePrompt)({
2034
+ orchestratorName: orchestratorBot.name,
2035
+ projectName: project?.name || null,
2036
+ workspacePath: project?.folder || null,
2037
+ effectivePolicy: policy,
2038
+ completedTasks,
2039
+ });
2040
+ const response = await this.runNodeWithRetry('finalize_response', state, async () => {
2041
+ const result = await this.workflowEngine.execute(finalPrompt, conversationId, orchestratorBot.id, {
2042
+ apiKey: this.resolveApiKey(orchestratorBot.provider),
2043
+ disableDecomposition: true,
2044
+ disableTools: true,
2045
+ workerMode: false,
2046
+ persistConversationMessages: false,
2047
+ onWorkerChunk: opts.onWorkerChunk,
2048
+ workspacePath: project?.folder?.trim() || undefined,
2049
+ });
2050
+ return this.stripWorkerProtocol(result.mergedResult || '');
2051
+ });
2052
+ state.finalResponseDraft = response;
2053
+ this.recordOrchestrationAudit(state, 'finalize_response', 'completed', workflowName
2054
+ ? `Completed automatic TODO dispatch for workflow "${workflowName}".`
2055
+ : 'Completed automatic TODO dispatch.', { completedTaskCount: completedTasks.length });
2056
+ return response;
2057
+ }
2058
+ buildSingleDelegateTodoStep(prompt, owner, orchestratorBot, projectName, workspacePath, effectivePolicy) {
2059
+ const taskType = this.normalizeTaskTypeForWorker(owner, 'coding');
2060
+ return {
2061
+ title: `${owner.name}: ${this.summarizeTodoTitle(prompt)}`,
2062
+ details: `Delegated by ${orchestratorBot.name}. Complete this single-worker request directly.`,
2063
+ owner,
2064
+ taskType,
2065
+ taskPrompt: this.buildWorkerTaskPrompt({
2066
+ originalPrompt: prompt,
2067
+ stepInstruction: 'Handle this delegated request directly.',
2068
+ owner,
2069
+ nextWorker: null,
2070
+ projectName,
2071
+ workspacePath,
2072
+ effectivePolicy,
2073
+ }),
2074
+ successCriteria: `Complete the delegated ${taskType} task and return a concise result summary.`,
2075
+ };
2076
+ }
2077
+ buildTodoStepsFromTemplate(prompt, workflowTemplate, orchestratorBot, projectName, workspacePath, effectivePolicy) {
2078
+ const steps = workflowTemplate.steps
2079
+ .sort((a, b) => a.order_index - b.order_index)
2080
+ .map((step) => {
2081
+ const owner = data.getAgentProfile(step.agent_id);
2082
+ if (!owner)
2083
+ return null;
2084
+ const taskType = this.normalizeTaskTypeForWorker(owner, step.is_checkpoint === 1 ? 'qa' : null);
2085
+ return {
2086
+ owner,
2087
+ instruction: step.instruction?.trim() || `Complete ${owner.name}'s workflow step.`,
2088
+ taskType,
2089
+ };
2090
+ })
2091
+ .filter((item) => Boolean(item));
2092
+ return steps.map((step, index) => ({
2093
+ title: `${step.owner.name}: ${this.summarizeTodoTitle(step.instruction)}`,
2094
+ details: `Workflow template step ${index + 1} from ${workflowTemplate.name}.`,
2095
+ owner: step.owner,
2096
+ taskType: step.taskType,
2097
+ taskPrompt: this.buildWorkerTaskPrompt({
2098
+ originalPrompt: prompt,
2099
+ stepInstruction: step.instruction,
2100
+ owner: step.owner,
2101
+ nextWorker: steps[index + 1]?.owner || null,
2102
+ projectName,
2103
+ workspacePath,
2104
+ effectivePolicy,
2105
+ }),
2106
+ successCriteria: `Complete step ${index + 1} of "${workflowTemplate.name}" and hand off the necessary output.`,
2107
+ }));
2108
+ }
2109
+ buildTodoStepsFromAssignments(prompt, roleAssignments, intent, orchestratorBot, projectId, projectName, workspacePath, effectivePolicy) {
2110
+ const roleOrder = this.inferWorkflowRoleOrder(prompt, roleAssignments, intent);
2111
+ const owners = roleOrder
2112
+ .map((role) => {
2113
+ const agentName = roleAssignments[role];
2114
+ const agent = this.findAgentByName(agentName, projectId);
2115
+ if (!agent)
2116
+ return null;
2117
+ return { role, agent };
2118
+ })
2119
+ .filter((item) => Boolean(item));
2120
+ return owners.map((item, index) => ({
2121
+ title: `${item.agent.name}: ${this.defaultTodoTitleForRole(item.role, prompt)}`,
2122
+ details: `Workflow step assigned by ${orchestratorBot.name}.`,
2123
+ owner: item.agent,
2124
+ taskType: this.normalizeTaskTypeForWorker(item.agent, item.role === 'research' ? 'research' : item.role === 'qa' ? 'qa' : 'coding'),
2125
+ taskPrompt: this.buildWorkerTaskPrompt({
2126
+ originalPrompt: prompt,
2127
+ stepInstruction: this.defaultStepInstructionForRole(item.role, prompt),
2128
+ owner: item.agent,
2129
+ nextWorker: owners[index + 1]?.agent || null,
2130
+ projectName: projectName || null,
2131
+ workspacePath: workspacePath || null,
2132
+ effectivePolicy: effectivePolicy || data.getEffectiveOrchestrationPolicy(projectId),
2133
+ }),
2134
+ successCriteria: this.defaultSuccessCriteriaForRole(item.role),
2135
+ }));
2136
+ }
2137
+ inferWorkflowRoleOrder(prompt, assignments, intent) {
2138
+ const explicitResearch = /\b(research|brainstorm|plan|planning|analyze|investigate|design|architect)\b/i.test(prompt);
2139
+ const explicitCoding = /\b(code|codes|coded|coding|build|builds|building|implement|implements|implementing|create|creates|creating|fix|fixes|fixing|update|updates|updating|edit|editing|change|changes|changing)\b/i.test(prompt);
2140
+ const explicitQa = /\b(qa|review|test|verify|verification|audit)\b/i.test(prompt);
2141
+ const requestedRoles = [];
2142
+ if (explicitResearch && assignments.research)
2143
+ requestedRoles.push('research');
2144
+ if (explicitCoding && assignments.coding)
2145
+ requestedRoles.push('coding');
2146
+ if (explicitQa && assignments.qa)
2147
+ requestedRoles.push('qa');
2148
+ if (requestedRoles.length > 0)
2149
+ return requestedRoles;
2150
+ if (intent.intent === 'review' && assignments.qa)
2151
+ return ['qa'];
2152
+ if (intent.intent === 'brainstorm' && assignments.research)
2153
+ return ['research'];
2154
+ const fallback = [];
2155
+ if (assignments.research && /\b(research|brainstorm|plan|planning)\b/i.test(prompt))
2156
+ fallback.push('research');
2157
+ if (assignments.coding)
2158
+ fallback.push('coding');
2159
+ if (assignments.qa)
2160
+ fallback.push('qa');
2161
+ if (fallback.length > 0)
2162
+ return Array.from(new Set(fallback));
2163
+ return [];
2164
+ }
2165
+ normalizeTaskTypeForWorker(owner, fallback) {
2166
+ const roleClass = String(owner.role_class || owner.role_label || '').trim().toLowerCase();
2167
+ if (roleClass === 'qa' || roleClass === 'review')
2168
+ return 'qa';
2169
+ if (roleClass === 'research' || roleClass === 'brainstorm' || roleClass === 'planning' || roleClass === 'plan')
2170
+ return 'research';
2171
+ if (roleClass === 'verify' || roleClass === 'verification')
2172
+ return 'verify';
2173
+ if (roleClass === 'deploy' || roleClass === 'deployment')
2174
+ return 'deploy';
2175
+ if (roleClass === 'coding' || roleClass === 'code' || roleClass === 'builder' || roleClass === 'developer' || roleClass === 'engineer')
2176
+ return 'coding';
2177
+ return (fallback || 'coding');
2178
+ }
2179
+ buildWorkerTaskPrompt(input) {
2180
+ const workerRole = String(input.owner.role_label || input.owner.role_class || 'general').trim() || 'general';
2181
+ const composedTask = [
2182
+ `Step instruction: ${input.stepInstruction}`,
2183
+ '',
2184
+ 'Original user request:',
2185
+ input.originalPrompt,
2186
+ ].join('\n');
2187
+ return (0, worker_operating_prompt_1.buildWorkerOperatingPrompt)({
2188
+ workerName: input.owner.name,
2189
+ workerRole,
2190
+ taskType: this.normalizeTaskTypeForWorker(input.owner, null),
2191
+ taskTitle: input.stepInstruction,
2192
+ taskPrompt: composedTask,
2193
+ projectName: input.projectName,
2194
+ workspacePath: input.workspacePath,
2195
+ effectivePolicy: (0, policy_prompt_1.buildEffectivePolicyPromptSection)(input.effectivePolicy, {
2196
+ heading: '',
2197
+ defaultLine: 'No confirmed special policy is set.',
2198
+ }),
2199
+ nextWorkerName: input.nextWorker?.name || null,
2200
+ nextWorkerRole: input.nextWorker ? String(input.nextWorker.role_label || input.nextWorker.role_class || 'general') : null,
2201
+ });
2202
+ }
2203
+ defaultTodoTitleForRole(role, prompt) {
2204
+ if (role === 'research')
2205
+ return `Research and plan: ${this.summarizeTodoTitle(prompt)}`;
2206
+ if (role === 'qa')
2207
+ return `QA and verify: ${this.summarizeTodoTitle(prompt)}`;
2208
+ return `Build and implement: ${this.summarizeTodoTitle(prompt)}`;
2209
+ }
2210
+ defaultStepInstructionForRole(role, prompt) {
2211
+ if (role === 'research') {
2212
+ return 'Research, brainstorm, or plan the work so the implementation handoff is specific and actionable.';
2213
+ }
2214
+ if (role === 'qa') {
2215
+ return 'Review and QA the completed work against the original request. If issues remain, prepare a fix handoff for the previous worker.';
2216
+ }
2217
+ return `Implement the requested work directly. Keep the original request in scope: ${prompt}`;
2218
+ }
2219
+ defaultSuccessCriteriaForRole(role) {
2220
+ if (role === 'research')
2221
+ return 'Produce a clear handoff that gives the next worker enough detail to execute without guessing.';
2222
+ if (role === 'qa')
2223
+ return 'Verify the work against the request and prepare the next handoff if fixes are needed.';
2224
+ return 'Complete the requested implementation and prepare the next worker handoff.';
2225
+ }
2226
+ summarizeTodoTitle(text) {
2227
+ const normalized = String(text || '').replace(/\s+/g, ' ').trim();
2228
+ if (!normalized)
2229
+ return 'Assigned task';
2230
+ return normalized.length <= 72 ? normalized : `${normalized.slice(0, 69).trimEnd()}...`;
2231
+ }
2232
+ markConversationOrchestrated(conversationId) {
2233
+ try {
2234
+ const db = data.getDb();
2235
+ db.prepare("UPDATE conversation SET routing_mode = 'orchestrated' WHERE id = ?").run(conversationId);
2236
+ }
2237
+ catch { /* best effort */ }
2238
+ }
2239
+ findAgentByName(name, projectId) {
2240
+ const normalized = String(name || '').trim().toLowerCase();
2241
+ if (!normalized)
2242
+ return undefined;
2243
+ const project = projectId ? data.getProject(projectId) : undefined;
2244
+ const scopedIds = new Set(project?.bot_ids || []);
2245
+ const allAgents = data.listAgentProfiles();
2246
+ const scopedMatch = allAgents.find((agent) => scopedIds.has(agent.id) && agent.name.trim().toLowerCase() === normalized);
2247
+ if (scopedMatch)
2248
+ return scopedMatch;
2249
+ return allAgents.find((agent) => agent.name.trim().toLowerCase() === normalized);
2250
+ }
1705
2251
  async executeOrchestratorOwnedWork(prompt, conversationId, orchestratorBot, roleAssignments, project, opts, state, options) {
1706
2252
  this.recordOrchestrationAudit(state, 'execute_direct', 'executed', `Selected orchestrator bot ${orchestratorBot.name} kept the work.`, { orchestratorBotId: orchestratorBot.id, orchestratorBotName: orchestratorBot.name });
1707
2253
  this.reportHiddenRoleProgress(opts, 'orchestrator', `${orchestratorBot.name} is working on the request`);
@@ -1710,17 +2256,14 @@ class OrchestratorAgent {
1710
2256
  isOrchestrated: false,
1711
2257
  projectId: project?.id || state.projectId || null,
1712
2258
  workspacePath: project?.folder?.trim() || undefined,
1713
- persistConversationMessages: true,
2259
+ // The orchestrator owns the visible reply and persists the final
2260
+ // assistant message after post-processing. Letting the worker path
2261
+ // also persist would create duplicate assistant messages.
2262
+ persistConversationMessages: false,
1714
2263
  workerMode: false,
1715
2264
  onWorkerChunk: opts.onWorkerChunk,
1716
2265
  onProgress: (progress) => {
1717
- if (progress.event === 'step-started') {
1718
- this.reportHiddenRoleProgress(opts, 'orchestrator', `${progress.step.agentName} is working on the kept task`);
1719
- }
1720
- else if (progress.event === 'step-completed') {
1721
- this.reportHiddenRoleProgress(opts, 'orchestrator', `${progress.step.agentName} completed the kept task`);
1722
- }
1723
- else if (progress.event === 'step-failed') {
2266
+ if (progress.event === 'step-failed') {
1724
2267
  this.reportHiddenRoleProgress(opts, 'orchestrator', `${progress.step.agentName} hit an issue while working on the kept task`);
1725
2268
  }
1726
2269
  },
@@ -2696,8 +3239,6 @@ class OrchestratorAgent {
2696
3239
  const conn = data.findProviderConnection(provider);
2697
3240
  if (conn?.api_key_enc)
2698
3241
  return conn.api_key_enc;
2699
- if (conn?.oauth_token)
2700
- return conn.oauth_token;
2701
3242
  }
2702
3243
  catch { /* fall through */ }
2703
3244
  switch (provider) {
@@ -2754,11 +3295,11 @@ class OrchestratorAgent {
2754
3295
  }
2755
3296
  parseRoleAssignments(prompt) {
2756
3297
  const assignments = {};
2757
- const agents = (0, orchestrator_profile_1.filterOutOrchestratorProfiles)(data.listAgentProfiles());
3298
+ const agents = data.listAgentProfiles();
2758
3299
  const rolePatterns = [
2759
3300
  { role: 'coding', patterns: [/\bcodes?\b/i, /\bcoding\b/i, /\bimplement(s|ing)?\b/i, /\bbuild(s|ing)?\b/i, /\bcreate(s|d|ing)?\b/i, /\bwrite(s|n)?\b/i, /\bdeveloper\b/i] },
2760
3301
  { role: 'qa', patterns: [/\bqa\b/i, /\btest(s|ing)?\b/i, /\breview(s|ing)?\b/i, /\bverify\b|\bverification\b/i, /\bpass\/fail\b/i] },
2761
- { role: 'research', patterns: [/\bresearch(es|ing)?\b/i, /\banaly(s|z)e(s|d|ing)?\b/i, /\binvestigate(s|d|ing)?\b/i, /\bbrainstorm\b/i, /\bidea is sound\b/i, /\bsound\b/i, /\bpressure[- ]?test\b/i, /\brecommend(?:s|ed|ing)?\b/i] },
3302
+ { role: 'research', patterns: [/\bresearch(es|ing)?\b/i, /\banaly(s|z)e(s|d|ing)?\b/i, /\binvestigate(s|d|ing)?\b/i, /\bbrainstorm\b/i, /\bdesign(s|ed|ing)?\b/i, /\bplan(?:s|ned|ning)?\b/i, /\barchitect(?:s|ed|ing)?\b/i, /\bidea is sound\b/i, /\bsound\b/i, /\bpressure[- ]?test\b/i, /\brecommend(?:s|ed|ing)?\b/i] },
2762
3303
  ];
2763
3304
  const clauses = prompt
2764
3305
  .split(/[\n,;\.]+|\band\b|\bwhile\b|\bthen\b|->/gi)
@@ -3078,18 +3619,19 @@ class OrchestratorAgent {
3078
3619
  currentPolicy,
3079
3620
  projectName: overview?.project.name,
3080
3621
  }).catch(() => null);
3081
- const sanitizedInterpretedPatch = this.sanitizeInterpretedPolicyPatch(prompt, interpreted?.patch || {});
3622
+ const agentNames = data.listAgentProfiles().map((agent) => agent.name);
3623
+ const sanitizedInterpretedPatch = (0, policy_detection_1.sanitizePolicyPatch)(prompt, interpreted?.patch || {}, currentPolicy);
3082
3624
  const patch = {
3083
- ...this.extractPolicyPatch(prompt, assignments, currentPolicy),
3625
+ ...(0, policy_detection_1.extractPolicyPatchFromPrompt)(prompt, currentPolicy, { agentNames }),
3084
3626
  ...sanitizedInterpretedPatch,
3085
3627
  };
3086
3628
  const changes = interpreted?.summaryLines?.length
3087
3629
  ? interpreted.summaryLines
3088
- : this.summarizePolicyPatch(patch);
3630
+ : (0, policy_detection_1.summarizePolicyPatch)(patch);
3089
3631
  if (changes.length === 0) {
3090
3632
  return this.formatPolicyUpdateResponse(prompt, intent, assignments, overview);
3091
3633
  }
3092
- const scope = interpreted?.scope || this.determinePolicyScope(prompt, projectId);
3634
+ const scope = interpreted?.scope || (0, policy_detection_1.determinePolicyScope)(prompt, overview?.project.name || undefined);
3093
3635
  data.setPendingOrchestrationPolicy({
3094
3636
  conversationId,
3095
3637
  projectId: scope === 'project' ? projectId || null : null,
@@ -3142,6 +3684,24 @@ class OrchestratorAgent {
3142
3684
  return data.getWorkflowTemplate(templates[0].id);
3143
3685
  return undefined;
3144
3686
  }
3687
+ resolveWorkflowTemplateByPrompt(projectId, prompt) {
3688
+ if (!projectId)
3689
+ return undefined;
3690
+ const normalizedPrompt = String(prompt || '').trim().toLowerCase();
3691
+ if (!/\b(todo|workflow)\b/i.test(normalizedPrompt))
3692
+ return undefined;
3693
+ const templates = data.listWorkflowTemplates(projectId).filter((template) => template.enabled === 1);
3694
+ for (const template of templates) {
3695
+ const name = String(template.name || '').trim().toLowerCase();
3696
+ if (!name)
3697
+ continue;
3698
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3699
+ if (new RegExp(`(^|[^a-z0-9])${escaped}([^a-z0-9]|$)`, 'i').test(normalizedPrompt)) {
3700
+ return data.getWorkflowTemplate(template.id);
3701
+ }
3702
+ }
3703
+ return undefined;
3704
+ }
3145
3705
  persistRoleAssignments(projectId, conversationId, agentId, assignments) {
3146
3706
  const merged = this.mergeRoleAssignments({}, this.loadRoleAssignments(projectId), this.deriveRoleAssignmentsFromProject(projectId), assignments);
3147
3707
  data.setProjectSetting(projectId, ORCHESTRATOR_ROLE_SETTING_KEY, JSON.stringify(merged));