funolio-agent 1.0.53 → 1.0.75

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 (115) hide show
  1. package/dist/approval.d.ts +1 -6
  2. package/dist/approval.d.ts.map +1 -1
  3. package/dist/approval.js +2 -7
  4. package/dist/approval.js.map +1 -1
  5. package/dist/bot-manager.d.ts +5 -1
  6. package/dist/bot-manager.d.ts.map +1 -1
  7. package/dist/bot-manager.js +23 -13
  8. package/dist/bot-manager.js.map +1 -1
  9. package/dist/cli-session-epoch.d.ts +1 -1
  10. package/dist/cli-session-epoch.d.ts.map +1 -1
  11. package/dist/cli-session-epoch.js +1 -1
  12. package/dist/cli-session-epoch.js.map +1 -1
  13. package/dist/cli-session-registry.d.ts +35 -0
  14. package/dist/cli-session-registry.d.ts.map +1 -0
  15. package/dist/cli-session-registry.js +177 -0
  16. package/dist/cli-session-registry.js.map +1 -0
  17. package/dist/cli.js +62 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/codex-app-server-manager.d.ts +129 -0
  20. package/dist/codex-app-server-manager.d.ts.map +1 -0
  21. package/dist/codex-app-server-manager.js +768 -0
  22. package/dist/codex-app-server-manager.js.map +1 -0
  23. package/dist/commands/init.d.ts.map +1 -1
  24. package/dist/commands/init.js +8 -30
  25. package/dist/commands/init.js.map +1 -1
  26. package/dist/commands/setup.d.ts +4 -1
  27. package/dist/commands/setup.d.ts.map +1 -1
  28. package/dist/commands/setup.js +9 -25
  29. package/dist/commands/setup.js.map +1 -1
  30. package/dist/commands/start.d.ts.map +1 -1
  31. package/dist/commands/start.js +77 -2
  32. package/dist/commands/start.js.map +1 -1
  33. package/dist/completion-marker.d.ts +7 -0
  34. package/dist/completion-marker.d.ts.map +1 -0
  35. package/dist/completion-marker.js +28 -0
  36. package/dist/completion-marker.js.map +1 -0
  37. package/dist/config.d.ts +6 -2
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +15 -3
  40. package/dist/config.js.map +1 -1
  41. package/dist/context-window.d.ts.map +1 -1
  42. package/dist/context-window.js +8 -1
  43. package/dist/context-window.js.map +1 -1
  44. package/dist/live-activity.d.ts +29 -0
  45. package/dist/live-activity.d.ts.map +1 -0
  46. package/dist/live-activity.js +36 -0
  47. package/dist/live-activity.js.map +1 -0
  48. package/dist/local-cli-pty-manager.d.ts +51 -0
  49. package/dist/local-cli-pty-manager.d.ts.map +1 -1
  50. package/dist/local-cli-pty-manager.js +1227 -114
  51. package/dist/local-cli-pty-manager.js.map +1 -1
  52. package/dist/local-data.d.ts +41 -0
  53. package/dist/local-data.d.ts.map +1 -1
  54. package/dist/local-data.js +140 -4
  55. package/dist/local-data.js.map +1 -1
  56. package/dist/local-db.d.ts.map +1 -1
  57. package/dist/local-db.js +55 -1
  58. package/dist/local-db.js.map +1 -1
  59. package/dist/local-server.d.ts +25 -0
  60. package/dist/local-server.d.ts.map +1 -1
  61. package/dist/local-server.js +528 -267
  62. package/dist/local-server.js.map +1 -1
  63. package/dist/message-loop.d.ts +6 -0
  64. package/dist/message-loop.d.ts.map +1 -1
  65. package/dist/message-loop.js +239 -89
  66. package/dist/message-loop.js.map +1 -1
  67. package/dist/mqtt-client.d.ts +10 -1
  68. package/dist/mqtt-client.d.ts.map +1 -1
  69. package/dist/mqtt-client.js +14 -1
  70. package/dist/mqtt-client.js.map +1 -1
  71. package/dist/oauth.d.ts.map +1 -1
  72. package/dist/oauth.js +69 -29
  73. package/dist/oauth.js.map +1 -1
  74. package/dist/orchestration/orchestrator-operating-prompt.d.ts +1 -0
  75. package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
  76. package/dist/orchestration/orchestrator-operating-prompt.js +60 -0
  77. package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
  78. package/dist/orchestration/validation.d.ts +40 -0
  79. package/dist/orchestration/validation.d.ts.map +1 -0
  80. package/dist/orchestration/validation.js +203 -0
  81. package/dist/orchestration/validation.js.map +1 -0
  82. package/dist/orchestrator.d.ts +21 -32
  83. package/dist/orchestrator.d.ts.map +1 -1
  84. package/dist/orchestrator.js +287 -725
  85. package/dist/orchestrator.js.map +1 -1
  86. package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
  87. package/dist/providers/claude-cli-prompt.js +49 -5
  88. package/dist/providers/claude-cli-prompt.js.map +1 -1
  89. package/dist/providers/claude-cli.d.ts.map +1 -1
  90. package/dist/providers/claude-cli.js +56 -5
  91. package/dist/providers/claude-cli.js.map +1 -1
  92. package/dist/providers/codex-cli.d.ts.map +1 -1
  93. package/dist/providers/codex-cli.js +15 -10
  94. package/dist/providers/codex-cli.js.map +1 -1
  95. package/dist/response-guard.js +1 -1
  96. package/dist/response-guard.js.map +1 -1
  97. package/dist/tools/admin-tools.d.ts.map +1 -1
  98. package/dist/tools/admin-tools.js +8 -2
  99. package/dist/tools/admin-tools.js.map +1 -1
  100. package/dist/tools/index.d.ts.map +1 -1
  101. package/dist/tools/index.js +2 -1
  102. package/dist/tools/index.js.map +1 -1
  103. package/dist/tools/search-conversation-history.d.ts +16 -0
  104. package/dist/tools/search-conversation-history.d.ts.map +1 -0
  105. package/dist/tools/search-conversation-history.js +324 -0
  106. package/dist/tools/search-conversation-history.js.map +1 -0
  107. package/dist/wizard-state.d.ts +7 -0
  108. package/dist/wizard-state.d.ts.map +1 -1
  109. package/dist/wizard-state.js +31 -2
  110. package/dist/wizard-state.js.map +1 -1
  111. package/dist/workflow-engine.d.ts +4 -1
  112. package/dist/workflow-engine.d.ts.map +1 -1
  113. package/dist/workflow-engine.js +190 -29
  114. package/dist/workflow-engine.js.map +1 -1
  115. package/package.json +1 -1
@@ -47,7 +47,10 @@ var __importStar = (this && this.__importStar) || (function () {
47
47
  })();
48
48
  Object.defineProperty(exports, "__esModule", { value: true });
49
49
  exports.OrchestratorAgent = void 0;
50
+ exports.buildLocalDesktopOrchestratorRuntime = buildLocalDesktopOrchestratorRuntime;
51
+ const index_1 = require("./providers/index");
50
52
  const status_parser_1 = require("./orchestration/status-parser");
53
+ const validation_1 = require("./orchestration/validation");
51
54
  const front_door_policy_1 = require("./orchestration/front-door-policy");
52
55
  const deterministic_path_1 = require("./orchestration/deterministic-path");
53
56
  const orchestrator_operating_prompt_1 = require("./orchestration/orchestrator-operating-prompt");
@@ -61,6 +64,7 @@ const state_1 = require("./orchestration/state");
61
64
  const orchestrator_profile_1 = require("./orchestrator-profile");
62
65
  const context_window_1 = require("./context-window");
63
66
  const data = __importStar(require("./local-data"));
67
+ const cli_session_epoch_1 = require("./cli-session-epoch");
64
68
  const storage_mode_1 = require("./storage-mode");
65
69
  const fs = __importStar(require("fs"));
66
70
  const os = __importStar(require("os"));
@@ -81,12 +85,130 @@ const ORCHESTRATION_NODE_RETRY_LIMITS = {
81
85
  require_confirmation: 0,
82
86
  };
83
87
  // ─── Orchestrator Agent ──────────────────────────────────────────
88
+ function resolveLocalProviderCredentials(providerName, profile) {
89
+ if (index_1.CLI_PROVIDERS.has(providerName)) {
90
+ return { apiKey: 'cli-auth' };
91
+ }
92
+ const directConnection = profile?.provider_connection_id
93
+ ? data.getProviderConnection(profile.provider_connection_id)
94
+ : undefined;
95
+ if (directConnection?.oauth_token) {
96
+ return { apiKey: directConnection.oauth_token, authMode: 'oauth-bearer' };
97
+ }
98
+ if (directConnection?.api_key_enc) {
99
+ return { apiKey: directConnection.api_key_enc };
100
+ }
101
+ const providerConnection = data.findProviderConnection(providerName);
102
+ if (providerConnection?.oauth_token) {
103
+ return { apiKey: providerConnection.oauth_token, authMode: 'oauth-bearer' };
104
+ }
105
+ if (providerConnection?.api_key_enc) {
106
+ return { apiKey: providerConnection.api_key_enc };
107
+ }
108
+ if (profile?.api_key_enc) {
109
+ return { apiKey: profile.api_key_enc };
110
+ }
111
+ switch (providerName) {
112
+ case 'anthropic':
113
+ if (process.env.ANTHROPIC_API_KEY)
114
+ return { apiKey: process.env.ANTHROPIC_API_KEY };
115
+ break;
116
+ case 'openai':
117
+ if (process.env.OPENAI_API_KEY)
118
+ return { apiKey: process.env.OPENAI_API_KEY };
119
+ break;
120
+ case 'google':
121
+ if (process.env.GOOGLE_API_KEY)
122
+ return { apiKey: process.env.GOOGLE_API_KEY };
123
+ break;
124
+ }
125
+ throw new Error(`No credentials configured for provider ${providerName}.`);
126
+ }
127
+ function buildLocalDesktopOrchestratorRuntime(selectedBot) {
128
+ if (data.isClerkOrchestratorEnabled()) {
129
+ const providerName = (data.getSetting('clerk_provider') || '').trim();
130
+ const model = (data.getSetting('clerk_model') || '').trim() || 'default';
131
+ if (!providerName) {
132
+ throw new Error('Clerk is selected as orchestrator, but no clerk provider is configured.');
133
+ }
134
+ const credentials = resolveLocalProviderCredentials(providerName);
135
+ const llm = (0, index_1.createProvider)(providerName, {
136
+ apiKey: credentials.apiKey,
137
+ model,
138
+ runtimeMode: 'local_desktop',
139
+ ...(credentials.authMode ? { authMode: credentials.authMode } : {}),
140
+ });
141
+ return {
142
+ kind: 'clerk',
143
+ llm,
144
+ providerName,
145
+ model,
146
+ agentName: 'Clerk',
147
+ botId: null,
148
+ modelLabel: model ? `${model} | Clerk` : 'Clerk',
149
+ };
150
+ }
151
+ const profile = selectedBot || data.getOrchestratorBot() || data.getDefaultAgentProfile();
152
+ if (!profile) {
153
+ throw new Error('No orchestrator or default bot is configured.');
154
+ }
155
+ const credentials = resolveLocalProviderCredentials(profile.provider, profile);
156
+ const llm = (0, index_1.createProvider)(profile.provider, {
157
+ apiKey: credentials.apiKey,
158
+ model: (profile.model || '').trim() || 'default',
159
+ runtimeMode: 'local_desktop',
160
+ ...(credentials.authMode ? { authMode: credentials.authMode } : {}),
161
+ });
162
+ return {
163
+ kind: 'bot',
164
+ llm,
165
+ providerName: profile.provider,
166
+ model: profile.model || null,
167
+ agentName: profile.name,
168
+ botId: profile.id,
169
+ modelLabel: profile.model || profile.name,
170
+ };
171
+ }
84
172
  class OrchestratorAgent {
85
- clerk;
173
+ orchestratorRuntime;
86
174
  workflowEngine;
87
175
  lastResponseMeta = null;
88
- constructor(clerk, workflowEngine) {
89
- this.clerk = clerk;
176
+ constructor(orchestratorRuntime, workflowEngine) {
177
+ const runtimeCandidate = orchestratorRuntime;
178
+ if (runtimeCandidate && typeof runtimeCandidate.kind === 'string') {
179
+ this.orchestratorRuntime = orchestratorRuntime;
180
+ }
181
+ else if (runtimeCandidate && typeof runtimeCandidate.respond === 'function') {
182
+ const runtimeInfo = typeof runtimeCandidate.getRuntimeInfo === 'function'
183
+ ? runtimeCandidate.getRuntimeInfo()
184
+ : {};
185
+ this.orchestratorRuntime = {
186
+ kind: 'clerk',
187
+ llm: {
188
+ chat: async (options) => ({
189
+ content: await runtimeCandidate.respond(String(options.messages?.[0]?.content || ''), String(options.system || '')),
190
+ }),
191
+ },
192
+ providerName: runtimeInfo?.provider || 'openai',
193
+ model: runtimeInfo?.model || null,
194
+ agentName: 'Clerk',
195
+ botId: null,
196
+ modelLabel: runtimeInfo?.model ? `${runtimeInfo.model} | Clerk` : 'Clerk',
197
+ };
198
+ }
199
+ else {
200
+ this.orchestratorRuntime = {
201
+ kind: 'clerk',
202
+ llm: {
203
+ chat: async () => ({ content: '' }),
204
+ },
205
+ providerName: 'openai',
206
+ model: null,
207
+ agentName: 'Orchestrator',
208
+ botId: null,
209
+ modelLabel: null,
210
+ };
211
+ }
90
212
  this.workflowEngine = workflowEngine;
91
213
  }
92
214
  isLocalDesktopRuntime() {
@@ -264,41 +386,14 @@ class OrchestratorAgent {
264
386
  if (frontDoorResult !== null)
265
387
  return frontDoorResult;
266
388
  }
267
- let intent;
268
- let routedPlan;
269
- if (selectedOrchestrator) {
270
- this.reportHiddenRoleProgress(opts, 'intent_classifier', 'Recovering routing from code fallback');
271
- intent = this.fallbackClassifyUserMessage(prompt, routingMode);
272
- orchestrationState.fallbackUsed = true;
273
- if (this.applyClassificationGuards(prompt, intent, routingMode)) {
274
- (0, state_1.markMisrouteCorrected)(orchestrationState);
275
- }
276
- this.reportHiddenRoleProgress(opts, 'intent_classifier', `Classified request as ${this.describeIntentForActivity(intent)}`);
277
- }
278
- else {
279
- this.reportHiddenRoleProgress(opts, 'intent_classifier', 'Classifying request');
280
- (0, state_1.addHelperRoleUsage)(orchestrationState, 'intent_classifier');
281
- routedPlan = await this.decomposeAndRouteUserMessage(prompt, conversationId, opts.projectId, routingMode);
282
- const routedIntent = routedPlan?.intents.length === 1
283
- ? this.buildIntentAnalysisFromRoutedIntent(routedPlan.intents[0])
284
- : undefined;
285
- if (routedIntent) {
286
- intent = routedIntent;
287
- if (this.applyClassificationGuards(prompt, intent, routingMode)) {
288
- (0, state_1.markMisrouteCorrected)(orchestrationState);
289
- }
290
- this.reportHiddenRoleProgress(opts, 'intent_classifier', `Routed single intent as ${this.describeIntentForActivity(intent)}`);
291
- }
292
- else {
293
- this.reportHiddenRoleProgress(opts, 'intent_classifier', 'Classifying request');
294
- intent = await this.classifyUserMessage(prompt, opts.projectId, routingMode);
295
- orchestrationState.fallbackUsed = true;
296
- if (this.applyClassificationGuards(prompt, intent, routingMode)) {
297
- (0, state_1.markMisrouteCorrected)(orchestrationState);
298
- }
299
- this.reportHiddenRoleProgress(opts, 'intent_classifier', `Classified request as ${this.describeIntentForActivity(intent)}`);
300
- }
389
+ this.reportHiddenRoleProgress(opts, 'intent_classifier', selectedOrchestrator ? 'Recovering routing from code fallback' : 'Classifying request');
390
+ (0, state_1.addHelperRoleUsage)(orchestrationState, 'intent_classifier');
391
+ const intent = this.fallbackClassifyUserMessage(prompt, routingMode);
392
+ orchestrationState.fallbackUsed = true;
393
+ if (this.applyClassificationGuards(prompt, intent, routingMode)) {
394
+ (0, state_1.markMisrouteCorrected)(orchestrationState);
301
395
  }
396
+ this.reportHiddenRoleProgress(opts, 'intent_classifier', `Classified request as ${this.describeIntentForActivity(intent)}`);
302
397
  orchestrationState.intent = intent.intent;
303
398
  orchestrationState.taskType = intent.primaryMode;
304
399
  this.recordOrchestrationAudit(orchestrationState, 'understand_request', 'classified', intent.reasoning || `Classified as ${intent.primaryMode}.`, {
@@ -331,11 +426,6 @@ class OrchestratorAgent {
331
426
  if (this.isOrchestratorControlMessage(prompt)) {
332
427
  return this.formatOrchestratorControlResponse(prompt, effectiveAssignments, projectOverview);
333
428
  }
334
- if (routedPlan && routedPlan.intents.length > 1) {
335
- (0, state_1.setOrchestrationPath)(orchestrationState, 'workflow', 'workflow');
336
- this.recordOrchestrationAudit(orchestrationState, 'choose_path', 'path_selected', 'Selected ordered multi-intent workflow path.', { intentCount: routedPlan.intents.length });
337
- return this.handleOrderedIntentSequence(routedPlan, prompt, conversationId, opts, promptAssignments, effectiveProjectId, effectiveProject, effectivePolicy, effectiveAssignments, projectOverview);
338
- }
339
429
  if (this.hasMode(intent, 'POLICY_UPDATE')) {
340
430
  (0, state_1.addHelperRoleUsage)(orchestrationState, 'policy_interpreter');
341
431
  this.reportHiddenRoleProgress(opts, 'policy_interpreter', 'Reviewing requested orchestration policy changes');
@@ -498,437 +588,6 @@ class OrchestratorAgent {
498
588
  }
499
589
  return next;
500
590
  }
501
- async decomposeAndRouteUserMessage(prompt, conversationId, projectId, routingMode) {
502
- try {
503
- const agents = (0, orchestrator_profile_1.filterOutOrchestratorProfiles)(data.listAgentProfiles());
504
- const firstPass = await this.clerk.decomposeUserIntents({
505
- prompt,
506
- agents,
507
- });
508
- if (!firstPass?.intents?.length)
509
- return null;
510
- let plan = firstPass;
511
- if (firstPass.intents.some((intent) => intent.needsContext)) {
512
- const recentTurnsText = this.buildIntentVerifierRecentTurns(conversationId);
513
- const verified = await this.clerk.verifyDecomposedUserIntents({
514
- prompt,
515
- priorPlan: firstPass,
516
- recentTurnsText,
517
- });
518
- if (verified?.intents?.length) {
519
- plan = verified;
520
- }
521
- }
522
- const clarificationIntents = plan.intents.filter((intent) => intent.needsClarification);
523
- if (clarificationIntents.length > 0) {
524
- return {
525
- intents: clarificationIntents.map((intent) => ({
526
- id: intent.id,
527
- request: intent.request,
528
- category: 'CLARIFICATION_NEEDED',
529
- targetAgent: undefined,
530
- dependsOn: [...intent.dependsOn],
531
- requirements: [...intent.requirements],
532
- confidence: intent.confidence || 'MEDIUM',
533
- needsClarification: true,
534
- clarificationQuestions: [...(intent.clarificationQuestions || [])],
535
- reasoning: plan.reasoning || 'Intent verification requires clarification before routing.',
536
- })),
537
- reasoning: plan.reasoning,
538
- };
539
- }
540
- const routed = [];
541
- for (const atom of plan.intents) {
542
- const routedAtom = await this.clerk.routeIntentAtom({
543
- atom,
544
- agents,
545
- routingMode,
546
- });
547
- if (!routedAtom)
548
- return null;
549
- routed.push(routedAtom);
550
- }
551
- const collapsed = this.collapseSingleWorkerPlan(prompt, routed);
552
- return {
553
- intents: collapsed,
554
- reasoning: plan.reasoning,
555
- };
556
- }
557
- catch {
558
- return null;
559
- }
560
- }
561
- buildIntentVerifierRecentTurns(conversationId) {
562
- try {
563
- const context = (0, context_window_1.getPromptContextWindow)(conversationId, 3);
564
- return (0, context_window_1.formatTurnsForPrompt)(context.turns);
565
- }
566
- catch {
567
- return '(no recent messages)';
568
- }
569
- }
570
- collapseSingleWorkerPlan(prompt, intents) {
571
- if (intents.length <= 1)
572
- return intents;
573
- if (this.hasExplicitOrderingLanguage(prompt))
574
- return intents;
575
- if (!this.hasConcreteArtifactDetails(prompt))
576
- return intents;
577
- if (this.countMentionedAgents(prompt) > 1)
578
- return intents;
579
- if (!intents.every((intent) => intent.category === 'ASK_WORKER' || intent.category === 'FULL_WORKFLOW'))
580
- return intents;
581
- const resolvedTargets = Array.from(new Set(intents
582
- .map((intent) => (intent.targetAgent || '').trim().toLowerCase())
583
- .filter(Boolean)));
584
- if (resolvedTargets.length > 1 && this.countMentionedAgents(prompt) > 0)
585
- return intents;
586
- const mergedRequirements = Array.from(new Set(intents
587
- .flatMap((intent) => [intent.request, ...intent.requirements])
588
- .map((item) => item.trim())
589
- .filter(Boolean)));
590
- const collapsedConfidence = intents.some((intent) => (intent.confidence || 'MEDIUM') === 'LOW')
591
- ? 'LOW'
592
- : intents.some((intent) => (intent.confidence || 'MEDIUM') === 'MEDIUM')
593
- ? 'MEDIUM'
594
- : 'HIGH';
595
- return [{
596
- ...intents[0],
597
- category: 'ASK_WORKER',
598
- request: prompt.trim(),
599
- targetAgent: this.countMentionedAgents(prompt) > 0
600
- ? intents.find((intent) => intent.targetAgent)?.targetAgent
601
- : undefined,
602
- dependsOn: [],
603
- requirements: mergedRequirements,
604
- confidence: collapsedConfidence,
605
- needsClarification: intents.some((intent) => intent.needsClarification),
606
- clarificationQuestions: Array.from(new Set(intents.flatMap((intent) => intent.clarificationQuestions || []))),
607
- reasoning: 'Collapsed multiple single-worker fragments into one deliverable for one worker.',
608
- }];
609
- }
610
- hasExplicitOrderingLanguage(prompt) {
611
- return /\b(before|after|then|first|last|next|once that is done|once done|if that succeeds|after that|follow the workflow|workflow|todo|handoff)\b/i.test(prompt);
612
- }
613
- buildIntentAnalysisFromRoutedIntent(routed) {
614
- let primaryMode = 'DIRECT_CONVERSATION';
615
- let userFacingMode = 'DIRECT_RESPONSE';
616
- let targetScope = 'SELF';
617
- let isMultiStep = false;
618
- switch (routed.category) {
619
- case 'ASK_WORKER':
620
- primaryMode = 'PROXY_MODE';
621
- userFacingMode = 'ASK_WORKER';
622
- targetScope = 'ONE_WORKER';
623
- break;
624
- case 'STATUS_CHECK':
625
- primaryMode = 'STATUS_INQUIRY';
626
- userFacingMode = 'STATUS_CHECK';
627
- break;
628
- case 'POLICY_CONFIGURATION':
629
- primaryMode = 'POLICY_UPDATE';
630
- userFacingMode = 'POLICY_CONFIGURATION';
631
- break;
632
- case 'FULL_WORKFLOW':
633
- primaryMode = 'WORKFLOW_MODE';
634
- userFacingMode = 'FULL_WORKFLOW';
635
- targetScope = 'MULTI_WORKER';
636
- isMultiStep = true;
637
- break;
638
- case 'MEMORY_CAPTURE':
639
- primaryMode = 'MEMORY_CAPTURE';
640
- userFacingMode = 'DIRECT_RESPONSE';
641
- break;
642
- default:
643
- primaryMode = 'DIRECT_CONVERSATION';
644
- userFacingMode = 'DIRECT_RESPONSE';
645
- break;
646
- }
647
- const intent = this.inferIntentFromRequest(routed.request, routed.category);
648
- const targetAgent = routed.targetAgent;
649
- return {
650
- primaryMode,
651
- secondaryModes: [],
652
- executionOrder: [primaryMode],
653
- userFacingMode,
654
- targetScope,
655
- confidence: routed.confidence,
656
- intent,
657
- projectMatch: undefined,
658
- topicMatch: undefined,
659
- targetAgent,
660
- isMultiStep,
661
- reasoning: routed.reasoning,
662
- needsClarification: routed.category === 'CLARIFICATION_NEEDED' || routed.needsClarification,
663
- clarificationQuestions: routed.clarificationQuestions,
664
- };
665
- }
666
- inferIntentFromRequest(request, category) {
667
- const normalized = request.toLowerCase();
668
- if (category === 'POLICY_CONFIGURATION')
669
- return 'plan';
670
- if (category === 'STATUS_CHECK')
671
- return 'question';
672
- if (/\b(brainstorm|research|pressure-test|feasibility|is this a good idea|is this sound|evaluate this idea)\b/i.test(normalized)) {
673
- return 'brainstorm';
674
- }
675
- if (/\b(review|qa|test|audit|verify)\b/i.test(normalized))
676
- return 'review';
677
- if (/\b(plan|outline|roadmap|approach)\b/i.test(normalized))
678
- return 'plan';
679
- if (/\b(build|code|implement|create|fix|write|update)\b/i.test(normalized))
680
- return 'build';
681
- if (/\b(what|how|why|when|where|show|list|find|check)\b/i.test(normalized))
682
- return 'question';
683
- return category === 'ASK_WORKER' ? 'discuss' : 'simple';
684
- }
685
- async handleOrderedIntentSequence(routedPlan, originalPrompt, conversationId, opts, promptAssignments, effectiveProjectId, effectiveProject, effectivePolicy, effectiveAssignments, projectOverview) {
686
- const normalizedIntents = this.normalizeOrderedIntentSequence(originalPrompt, routedPlan.intents, effectiveAssignments);
687
- this.reportHiddenRoleProgress(opts, 'intent_classifier', `Decomposed request into ${normalizedIntents.length} ordered intents`);
688
- if (normalizedIntents.length > 1) {
689
- opts.onProgress(this.formatOrderedIntentPlanPreview(normalizedIntents));
690
- }
691
- const persistedPlan = data.createIntentExecutionPlan({
692
- conversationId,
693
- sourcePrompt: originalPrompt,
694
- reasoning: routedPlan.reasoning,
695
- intents: normalizedIntents.map((intent, index) => ({
696
- atomId: intent.id,
697
- orderIndex: index + 1,
698
- request: intent.request,
699
- category: intent.category,
700
- targetAgent: intent.targetAgent,
701
- requirements: intent.requirements,
702
- dependsOn: intent.dependsOn,
703
- confidence: intent.confidence,
704
- needsClarification: intent.needsClarification,
705
- clarificationQuestions: intent.clarificationQuestions,
706
- reasoning: intent.reasoning,
707
- })),
708
- });
709
- data.updateIntentExecutionPlan(persistedPlan.id, { status: 'running' });
710
- const lines = [];
711
- let completedIntents = 0;
712
- for (let index = 0; index < normalizedIntents.length; index += 1) {
713
- const routed = normalizedIntents[index];
714
- const persistedAtom = persistedPlan.atoms[index];
715
- if (routed.category === 'CLARIFICATION_NEEDED' || routed.needsClarification) {
716
- if (persistedAtom) {
717
- data.updateIntentExecutionAtom(persistedAtom.id, {
718
- status: 'blocked',
719
- error: 'Clarification required before execution.',
720
- });
721
- }
722
- data.updateIntentExecutionPlan(persistedPlan.id, {
723
- status: 'blocked',
724
- completedIntents,
725
- });
726
- const clarificationIntent = this.buildIntentAnalysisFromRoutedIntent(routed);
727
- return this.formatClarificationResponse(clarificationIntent, projectOverview);
728
- }
729
- const intent = this.buildIntentAnalysisFromRoutedIntent(routed);
730
- if (persistedAtom) {
731
- data.updateIntentExecutionAtom(persistedAtom.id, {
732
- status: 'running',
733
- startedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
734
- error: null,
735
- });
736
- }
737
- const executionValidation = this.validateIntentExecution(routed.request, intent, effectivePolicy);
738
- if (!executionValidation.ok) {
739
- if (persistedAtom) {
740
- data.updateIntentExecutionAtom(persistedAtom.id, {
741
- status: 'failed',
742
- error: executionValidation.reason,
743
- });
744
- }
745
- data.updateIntentExecutionPlan(persistedPlan.id, {
746
- status: 'failed',
747
- completedIntents,
748
- });
749
- if (executionValidation.clarificationQuestions?.length) {
750
- return this.formatClarificationResponse({ ...intent, clarificationQuestions: executionValidation.clarificationQuestions }, projectOverview);
751
- }
752
- return `I couldn't execute step ${index + 1} safely: ${executionValidation.reason}`;
753
- }
754
- const executionSpec = executionValidation.executionSpec;
755
- const stepHeader = `**Step ${index + 1}:** ${routed.request}`;
756
- if (this.hasMode(intent, 'POLICY_UPDATE')) {
757
- const policyResponse = await this.handlePolicyUpdate(routed.request, conversationId, intent, this.parseRoleAssignments(routed.request), effectiveProjectId, projectOverview, effectivePolicy);
758
- if (persistedAtom) {
759
- data.updateIntentExecutionAtom(persistedAtom.id, {
760
- status: 'completed',
761
- resultSummary: this.summarizeResultForIntentPlan(policyResponse),
762
- completedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
763
- });
764
- }
765
- completedIntents += 1;
766
- if (index < routedPlan.intents.length - 1) {
767
- data.updateIntentExecutionPlan(persistedPlan.id, {
768
- status: 'blocked',
769
- completedIntents,
770
- });
771
- return `${policyResponse}\n\nI paused the remaining ordered steps until the policy change is confirmed.`;
772
- }
773
- data.updateIntentExecutionPlan(persistedPlan.id, {
774
- status: 'completed',
775
- completedIntents,
776
- finishedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
777
- });
778
- return policyResponse;
779
- }
780
- if (this.hasMode(intent, 'STATUS_INQUIRY')) {
781
- const statusResponse = this.formatStatusInquiryResponse(conversationId, effectiveProjectId);
782
- if (persistedAtom) {
783
- data.updateIntentExecutionAtom(persistedAtom.id, {
784
- status: 'completed',
785
- resultSummary: this.summarizeResultForIntentPlan(statusResponse),
786
- completedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
787
- });
788
- }
789
- completedIntents += 1;
790
- lines.push(stepHeader);
791
- lines.push(statusResponse);
792
- continue;
793
- }
794
- if (this.hasMode(intent, 'MEMORY_CAPTURE')) {
795
- const memoryResponse = this.formatMemoryCaptureResponse(routed.request);
796
- if (persistedAtom) {
797
- data.updateIntentExecutionAtom(persistedAtom.id, {
798
- status: 'completed',
799
- resultSummary: this.summarizeResultForIntentPlan(memoryResponse),
800
- completedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
801
- });
802
- }
803
- completedIntents += 1;
804
- lines.push(stepHeader);
805
- lines.push(memoryResponse);
806
- continue;
807
- }
808
- if (executionSpec.executionMode === 'direct') {
809
- const directResponse = this.handleDirectConversation(routed.request, projectOverview, effectiveAssignments);
810
- if (persistedAtom) {
811
- data.updateIntentExecutionAtom(persistedAtom.id, {
812
- status: 'completed',
813
- resultSummary: this.summarizeResultForIntentPlan(directResponse),
814
- completedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
815
- });
816
- }
817
- completedIntents += 1;
818
- lines.push(stepHeader);
819
- lines.push(directResponse);
820
- continue;
821
- }
822
- if (executionSpec.executionMode === 'proxy') {
823
- const proxyPrompt = this.buildSimpleWorkerPrompt(routed.request, executionSpec.targetAgents[0] || intent.targetAgent || 'the worker');
824
- const proxyResult = await this.executeProxyRequest(proxyPrompt, conversationId, intent, opts, executionSpec, effectiveAssignments);
825
- const proxyResponse = proxyResult.response;
826
- if (!proxyResult.ok) {
827
- if (persistedAtom) {
828
- data.updateIntentExecutionAtom(persistedAtom.id, {
829
- status: 'failed',
830
- error: proxyResult.error || proxyResponse,
831
- completedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
832
- });
833
- }
834
- data.updateIntentExecutionPlan(persistedPlan.id, {
835
- status: 'failed',
836
- completedIntents,
837
- finishedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
838
- });
839
- lines.push(stepHeader);
840
- lines.push(proxyResponse);
841
- return lines.join('\n\n');
842
- }
843
- if (persistedAtom) {
844
- data.updateIntentExecutionAtom(persistedAtom.id, {
845
- status: 'completed',
846
- resultSummary: this.summarizeResultForIntentPlan(proxyResponse),
847
- completedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
848
- });
849
- }
850
- completedIntents += 1;
851
- lines.push(stepHeader);
852
- lines.push(proxyResponse);
853
- continue;
854
- }
855
- if (executionSpec.executionMode === 'workflow') {
856
- const workflowResponse = await this.handleOrchestratedRequest(routed.request, conversationId, intent, executionSpec, opts, effectiveAssignments, effectiveProject);
857
- if (persistedAtom) {
858
- data.updateIntentExecutionAtom(persistedAtom.id, {
859
- status: 'completed',
860
- resultSummary: this.summarizeResultForIntentPlan(workflowResponse),
861
- completedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
862
- });
863
- }
864
- completedIntents += 1;
865
- lines.push(stepHeader);
866
- lines.push(workflowResponse);
867
- }
868
- }
869
- data.updateIntentExecutionPlan(persistedPlan.id, {
870
- status: 'completed',
871
- completedIntents,
872
- finishedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
873
- });
874
- return lines.join('\n\n');
875
- }
876
- normalizeOrderedIntentSequence(prompt, intents, assignments) {
877
- const collapsed = this.collapseSingleWorkerPlan(prompt, intents);
878
- const normalized = collapsed.map((intent) => this.applyImplicitWorkerTarget(intent, assignments));
879
- return this.dropRedundantSequentialWorkerHandoffs(normalized);
880
- }
881
- applyImplicitWorkerTarget(intent, assignments) {
882
- if (intent.category !== 'ASK_WORKER' || intent.targetAgent || intent.needsClarification) {
883
- return intent;
884
- }
885
- const targetAgent = this.resolveImplicitWorkerTarget(intent.request, intent.requirements, assignments);
886
- return targetAgent ? { ...intent, targetAgent } : intent;
887
- }
888
- resolveImplicitWorkerTarget(request, requirements, assignments) {
889
- const text = `${request}\n${requirements.join('\n')}`.toLowerCase();
890
- if (/\b(research|brainstorm|feasibility|evaluate|pressure-test|is this sound|idea)\b/i.test(text)) {
891
- return assignments.research || assignments.coding || assignments.qa;
892
- }
893
- if (/\b(qa|review|verify|test|audit|check)\b/i.test(text)) {
894
- return assignments.qa || assignments.coding || assignments.research;
895
- }
896
- if (/\b(build|code|implement|create|fix|write|update|design)\b/i.test(text)) {
897
- return assignments.coding || assignments.qa || assignments.research;
898
- }
899
- return assignments.coding || assignments.research || assignments.qa;
900
- }
901
- dropRedundantSequentialWorkerHandoffs(intents) {
902
- if (intents.length <= 1)
903
- return intents;
904
- const filtered = [];
905
- for (let index = 0; index < intents.length; index += 1) {
906
- const current = intents[index];
907
- const next = intents[index + 1];
908
- const currentText = current.request.toLowerCase();
909
- const nextText = next?.request.toLowerCase() || '';
910
- const redundantFeedbackStep = Boolean(next &&
911
- current.category === 'ASK_WORKER' &&
912
- next.category === 'ASK_WORKER' &&
913
- current.targetAgent &&
914
- next.targetAgent &&
915
- current.targetAgent.toLowerCase() === next.targetAgent.toLowerCase() &&
916
- /\b(give|send|provide|pass along)\b.*\b(feedback|qa)\b/.test(currentText) &&
917
- /\b(fix|implement|apply|address)\b/.test(nextText));
918
- if (!redundantFeedbackStep) {
919
- filtered.push(current);
920
- }
921
- }
922
- return filtered;
923
- }
924
- formatOrderedIntentPlanPreview(intents) {
925
- const lines = [`**Here's my plan (${intents.length} steps):**`];
926
- intents.forEach((intent, index) => {
927
- const agentLabel = intent.targetAgent ? ` (${intent.targetAgent})` : '';
928
- lines.push(`${index + 1}. ${intent.request}${agentLabel}`);
929
- });
930
- return lines.join('\n');
931
- }
932
591
  shouldForceProxy(prompt, intent) {
933
592
  if (intent.primaryMode === 'PROXY_MODE')
934
593
  return true;
@@ -1055,82 +714,6 @@ class OrchestratorAgent {
1055
714
  // Aliases like "claude" → "Ben" cause false matches on every prompt.
1056
715
  return [agent.name.trim()].filter(Boolean);
1057
716
  }
1058
- /**
1059
- * Interpret user intent using the clerk.
1060
- * This is the structured hidden-role classifier for Phase 1.
1061
- */
1062
- async classifyUserMessage(prompt, projectId, routingMode) {
1063
- try {
1064
- const agents = (0, orchestrator_profile_1.filterOutOrchestratorProfiles)(data.listAgentProfiles());
1065
- const projects = data.listProjects();
1066
- const topics = data.listTopics({ limit: 20 });
1067
- const parsed = await this.clerk.classifyOrchestratorIntent({
1068
- prompt,
1069
- agents,
1070
- projects,
1071
- topics,
1072
- projectId,
1073
- routingMode,
1074
- });
1075
- if (parsed) {
1076
- const primaryMode = this.normalizeMode(parsed.primaryMode);
1077
- const secondaryModes = this.normalizeModeList(parsed.secondaryModes);
1078
- const executionOrder = this.normalizeModeList(parsed.executionOrder);
1079
- return {
1080
- primaryMode,
1081
- secondaryModes,
1082
- executionOrder: executionOrder.length > 0
1083
- ? executionOrder
1084
- : [primaryMode, ...secondaryModes.filter((mode) => mode !== primaryMode)],
1085
- userFacingMode: parsed.userFacingMode,
1086
- targetScope: parsed.targetScope,
1087
- confidence: parsed.confidence,
1088
- intent: parsed.intent || 'simple',
1089
- projectMatch: undefined,
1090
- topicMatch: undefined,
1091
- targetAgent: parsed.targetAgent || undefined,
1092
- isMultiStep: Boolean(parsed.isMultiStep),
1093
- reasoning: parsed.reasoning || '',
1094
- needsClarification: Boolean(parsed.needsClarification),
1095
- clarificationQuestions: Array.isArray(parsed.clarificationQuestions) ? parsed.clarificationQuestions.map(String) : undefined,
1096
- };
1097
- }
1098
- }
1099
- catch { /* fall through */ }
1100
- return this.fallbackClassifyUserMessage(prompt, routingMode);
1101
- // Default: treat as simple single-step
1102
- return {
1103
- primaryMode: 'DIRECT_CONVERSATION',
1104
- secondaryModes: [],
1105
- executionOrder: ['DIRECT_CONVERSATION'],
1106
- userFacingMode: 'DIRECT_RESPONSE',
1107
- targetScope: 'SELF',
1108
- confidence: 'LOW',
1109
- intent: 'simple',
1110
- isMultiStep: false,
1111
- reasoning: 'Intent analysis failed - defaulting to direct conversation',
1112
- };
1113
- }
1114
- normalizeMode(raw) {
1115
- const value = String(raw || '').trim().toUpperCase();
1116
- switch (value) {
1117
- case 'POLICY_UPDATE':
1118
- case 'MEMORY_CAPTURE':
1119
- case 'STATUS_INQUIRY':
1120
- case 'PROXY_MODE':
1121
- case 'WORKFLOW_MODE':
1122
- return value;
1123
- default:
1124
- return 'DIRECT_CONVERSATION';
1125
- }
1126
- }
1127
- normalizeModeList(raw) {
1128
- if (!Array.isArray(raw))
1129
- return [];
1130
- return raw
1131
- .map((item) => this.normalizeMode(item))
1132
- .filter((mode, index, list) => list.indexOf(mode) === index);
1133
- }
1134
717
  fallbackClassifyUserMessage(prompt, routingMode) {
1135
718
  const normalized = prompt.toLowerCase();
1136
719
  const targetAgent = this.extractTargetAgentName(prompt);
@@ -1543,9 +1126,20 @@ class OrchestratorAgent {
1543
1126
  const recentSummaryText = contextWindow.summary?.summary_text?.trim()
1544
1127
  ? `${contextWindow.carriedForward ? '(carried forward from the previous conversation in this topic)\n' : ''}${contextWindow.summary.summary_text.trim()}`
1545
1128
  : '(none)';
1546
- // Build specialists list from non-orchestrator agent profiles
1129
+ // Build specialists list include all bots except pure orchestrators.
1130
+ // A bot that is_orchestrator but also has a worker role_class (code, qa, research, etc.)
1131
+ // should still appear as an available worker.
1547
1132
  const allProfiles = data.listAgentProfiles();
1548
- const specialists = (0, orchestrator_profile_1.filterOutOrchestratorProfiles)(allProfiles)
1133
+ const PURE_ORCHESTRATOR_ROLES = new Set(['orchestrator', 'project manager']);
1134
+ const specialists = allProfiles
1135
+ .filter((agent) => {
1136
+ const rc = String(agent.role_class || '').trim().toLowerCase();
1137
+ if (agent.is_orchestrator === 1 && PURE_ORCHESTRATOR_ROLES.has(rc))
1138
+ return false;
1139
+ if (!agent.is_orchestrator && (0, orchestrator_profile_1.isOrchestratorProfile)(agent))
1140
+ return false;
1141
+ return true;
1142
+ })
1549
1143
  .map((agent) => ({
1550
1144
  name: agent.name,
1551
1145
  roleLabel: agent.role_label || agent.role_class || 'general',
@@ -1586,12 +1180,20 @@ class OrchestratorAgent {
1586
1180
  fastPath: false,
1587
1181
  });
1588
1182
  }
1589
- const useSinglePassOrchestrator = this.isLocalDesktopRuntime();
1183
+ const useSinglePassOrchestrator = this.isLocalDesktopRuntime() && this.orchestratorRuntime.kind === 'bot';
1184
+ const localPromptContract = useSinglePassOrchestrator
1185
+ ? (index_1.CLI_PROVIDERS.has(orchestratorBot.provider)
1186
+ && conversationId
1187
+ && !!(0, cli_session_epoch_1.selectCliSessionEpoch)(conversationId, orchestratorBot.id, orchestratorBot.provider).resumeSessionId
1188
+ ? 'cli_recurring'
1189
+ : 'api_or_fresh_cli')
1190
+ : 'legacy';
1590
1191
  const operatingPrompt = (0, orchestrator_operating_prompt_1.buildOrchestratorOperatingPrompt)({
1591
1192
  orchestratorName: orchestratorBot.name,
1592
1193
  primaryRole: orchestratorBot.role_label || orchestratorBot.role_class || null,
1593
1194
  specialists,
1594
1195
  workflowNames,
1196
+ localPromptContract,
1595
1197
  entryMode: frontDoorSignals.explicitOrchestrationRequested ? 'orchestration_bias' : 'normal',
1596
1198
  signalSummary: {
1597
1199
  matchedBots: frontDoorSignals.matchedBots,
@@ -1622,7 +1224,6 @@ class OrchestratorAgent {
1622
1224
  ].join('\n');
1623
1225
  let decision = null;
1624
1226
  this.reportHiddenRoleProgress(opts, 'orchestrator', 'Understanding request');
1625
- opts.onProgress('Understanding request...');
1626
1227
  let understandingHeartbeat = setInterval(() => {
1627
1228
  this.reportHiddenRoleProgress(opts, 'orchestrator', 'Still understanding the request');
1628
1229
  }, 8_000);
@@ -1656,14 +1257,14 @@ class OrchestratorAgent {
1656
1257
  }
1657
1258
  }
1658
1259
  else {
1659
- const result = await this.runNodeWithRetry('understand_request', state, async () => this.workflowEngine.execute(decisionPrompt, null, orchestratorBot.id, {
1660
- disableDecomposition: true,
1661
- disableTools: true,
1260
+ const rawDecision = await this.runNodeWithRetry('understand_request', state, async () => this.runOrchestratorPrompt(decisionPrompt, operatingPrompt, {
1261
+ conversationId: null,
1262
+ orchestratorBot,
1662
1263
  projectId: projectId || null,
1663
1264
  workspacePath: project?.folder?.trim() || undefined,
1664
- systemPromptOverride: operatingPrompt,
1265
+ disableTools: true,
1665
1266
  }));
1666
- decision = (0, orchestrator_operating_prompt_1.parseOrchestratorFrontDoorDecision)(result.mergedResult);
1267
+ decision = (0, orchestrator_operating_prompt_1.parseOrchestratorFrontDoorDecision)(rawDecision);
1667
1268
  }
1668
1269
  }
1669
1270
  catch {
@@ -1831,6 +1432,37 @@ class OrchestratorAgent {
1831
1432
  return response;
1832
1433
  }
1833
1434
  if (decision.mode === 'execute_self') {
1435
+ if (this.orchestratorRuntime.kind === 'clerk') {
1436
+ const fallbackDelegateTarget = roleAssignments.coding
1437
+ || data.getDefaultAgentProfile()?.name
1438
+ || data.listAgentProfiles().find((agent) => !(0, orchestrator_profile_1.isOrchestratorProfile)(agent))?.name;
1439
+ if (fallbackDelegateTarget) {
1440
+ const delegateIntent = {
1441
+ primaryMode: 'PROXY_MODE',
1442
+ secondaryModes: [],
1443
+ executionOrder: ['PROXY_MODE'],
1444
+ userFacingMode: 'ASK_WORKER',
1445
+ targetScope: 'ONE_WORKER',
1446
+ confidence: 'MEDIUM',
1447
+ intent: normalizedDecision.taskType === 'qa'
1448
+ ? 'review'
1449
+ : normalizedDecision.taskType === 'research'
1450
+ ? 'brainstorm'
1451
+ : 'build',
1452
+ targetAgent: fallbackDelegateTarget,
1453
+ isMultiStep: false,
1454
+ reasoning: 'Clerk orchestrator cannot own implementation work directly, so the task falls back to the default worker.',
1455
+ };
1456
+ const delegatePrompt = prompt;
1457
+ const executionSpec = this.buildFrontDoorDelegateExecutionSpec(delegatePrompt, delegateIntent, fallbackDelegateTarget);
1458
+ const guardrailQuestions = this.buildDeterministicGuardrailQuestions(executionSpec, roleAssignments, delegateIntent);
1459
+ if (guardrailQuestions.length > 0) {
1460
+ return this.formatClarificationResponse({ ...delegateIntent, clarificationQuestions: guardrailQuestions }, overview);
1461
+ }
1462
+ this.applyExecutionSpecToState(state, executionSpec);
1463
+ return this.handleProxyRequest(delegatePrompt, conversationId, delegateIntent, opts, executionSpec, roleAssignments, state);
1464
+ }
1465
+ }
1834
1466
  // No guardrails, no confirmation checkpoints, no validation blocking.
1835
1467
  // Ben just does the work — like Claude CLI.
1836
1468
  (0, state_1.setOrchestrationPath)(state, 'direct', 'direct');
@@ -2177,7 +1809,13 @@ class OrchestratorAgent {
2177
1809
  });
2178
1810
  let rawResponse = '';
2179
1811
  try {
2180
- rawResponse = await this.runNodeWithRetry('finalize_response', state, async () => this.respondAsClerk(blockedPrompt, 'You are Clerk. Write the user-facing blocker message after work has already started. Be concise, outcome-first, and do not restate the full original request.'));
1812
+ rawResponse = await this.runNodeWithRetry('finalize_response', state, async () => this.runOrchestratorPrompt(blockedPrompt, `You are ${this.orchestratorRuntime.agentName}. Write the user-facing blocker message after work has already started. Be concise, outcome-first, and do not restate the full original request.`, {
1813
+ conversationId: task.conversation_id || state.conversationId || null,
1814
+ orchestratorBot,
1815
+ projectId: project?.id || opts.projectId || state.projectId || null,
1816
+ workspacePath: project?.folder?.trim() || undefined,
1817
+ disableTools: true,
1818
+ }));
2181
1819
  }
2182
1820
  catch (error) {
2183
1821
  this.recordOrchestrationAudit(state, 'finalize_response', 'blocked', `Blocked final response generation failed: ${error?.message || error}`);
@@ -2208,7 +1846,13 @@ class OrchestratorAgent {
2208
1846
  });
2209
1847
  let response = '';
2210
1848
  try {
2211
- response = await this.runNodeWithRetry('finalize_response', state, async () => this.respondAsClerk(finalPrompt, 'You are Clerk. Write the final user-facing answer after the workflow is complete. Do not restate the full request. Say what was created, where it is, the concise result, and only any important caveat. Do not claim everything was verified, defect-free, or ready for use unless a completed verification step explicitly established that.'));
1849
+ response = await this.runNodeWithRetry('finalize_response', state, async () => this.runOrchestratorPrompt(finalPrompt, `You are ${this.orchestratorRuntime.agentName}. Write the final user-facing answer after the workflow is complete. Do not restate the full request. Say what was created, where it is, the concise result, and only any important caveat. Do not claim everything was verified, defect-free, or ready for use unless a completed verification step explicitly established that.`, {
1850
+ conversationId,
1851
+ orchestratorBot,
1852
+ projectId: project?.id || opts.projectId || state.projectId || null,
1853
+ workspacePath: project?.folder?.trim() || undefined,
1854
+ disableTools: true,
1855
+ }));
2212
1856
  }
2213
1857
  catch (error) {
2214
1858
  this.recordOrchestrationAudit(state, 'finalize_response', 'blocked', `Final response generation failed: ${error?.message || error}`);
@@ -2551,6 +2195,38 @@ class OrchestratorAgent {
2551
2195
  return 'research';
2552
2196
  return 'discuss';
2553
2197
  }
2198
+ async runOrchestratorPrompt(userPrompt, systemPrompt, opts) {
2199
+ if (this.orchestratorRuntime.kind === 'bot' && opts.orchestratorBot) {
2200
+ const result = await this.workflowEngine.execute(userPrompt, opts.conversationId, opts.orchestratorBot.id, {
2201
+ disableDecomposition: true,
2202
+ disableTools: opts.disableTools === true,
2203
+ isOrchestrated: false,
2204
+ projectId: opts.projectId ?? null,
2205
+ workspacePath: opts.workspacePath,
2206
+ persistConversationMessages: false,
2207
+ workerMode: false,
2208
+ systemPromptOverride: systemPrompt,
2209
+ });
2210
+ this.setLastResponseMeta({
2211
+ agentName: opts.orchestratorBot.name,
2212
+ botId: opts.orchestratorBot.id,
2213
+ modelLabel: this.orchestratorRuntime.modelLabel,
2214
+ });
2215
+ return this.stripWorkerProtocol(result.mergedResult || '');
2216
+ }
2217
+ const response = await this.orchestratorRuntime.llm.chat({
2218
+ messages: [{ role: 'user', content: userPrompt }],
2219
+ system: systemPrompt,
2220
+ stream: false,
2221
+ runtimeMode: 'local_desktop',
2222
+ });
2223
+ this.setLastResponseMeta({
2224
+ agentName: this.orchestratorRuntime.agentName,
2225
+ botId: this.orchestratorRuntime.botId,
2226
+ modelLabel: this.orchestratorRuntime.modelLabel,
2227
+ });
2228
+ return String(response.content || '').trim();
2229
+ }
2554
2230
  inferRequestedWorkflowRoles(prompt, roleAssignments) {
2555
2231
  const normalized = String(prompt || '').toLowerCase();
2556
2232
  const explicitResearch = /\b(brain|gpt|research(?:es|ed|ing)?|investigat(?:e|es|ed|ing)|analy[sz](?:e|es|ed|ing)|analysis|sound idea|review the idea|pressure[- ]?test|brainstorm(?:ed|ing)?)\b/.test(normalized)
@@ -2879,31 +2555,6 @@ class OrchestratorAgent {
2879
2555
  }
2880
2556
  return currentProjectId || intentProjectId;
2881
2557
  }
2882
- /**
2883
- * Handle simple requests — route to best agent, return response.
2884
- */
2885
- async handleSimpleRequest(prompt, conversationId, intent, opts) {
2886
- const agents = (0, orchestrator_profile_1.filterOutOrchestratorProfiles)(data.listAgentProfiles());
2887
- if (agents.length === 0)
2888
- return 'No worker agents configured. Please add an agent in Settings.';
2889
- const route = await this.clerk.routeTask(prompt, agents);
2890
- this.reportHiddenRoleProgress(opts, 'dispatch_controller', `Routing request to ${route.agentName}`);
2891
- this.publishOrchestratorStatus(opts, {
2892
- phase: 'delegating',
2893
- steps: [{ index: 0, description: prompt.slice(0, 80), agentName: route.agentName, status: 'running' }],
2894
- currentStep: 0,
2895
- totalSteps: 1,
2896
- });
2897
- // Execute via workflow engine — disable decomposition for simple mode
2898
- const result = await this.workflowEngine.execute(prompt, conversationId, route.agentId, {
2899
- apiKey: this.resolveApiKey(route.provider),
2900
- disableDecomposition: true,
2901
- onWorkerChunk: opts.onWorkerChunk,
2902
- });
2903
- this.publishOrchestratorStatus(opts, { phase: 'complete', totalSteps: 1, currentStep: 1 });
2904
- // Strip worker protocol before returning to user
2905
- return this.stripWorkerProtocol(result.mergedResult);
2906
- }
2907
2558
  /**
2908
2559
  * Handle proxy/discussion requests — forward to specific agent.
2909
2560
  */
@@ -3129,15 +2780,25 @@ class OrchestratorAgent {
3129
2780
  if (state && workflowTemplate) {
3130
2781
  state.preferredWorkflowUsed = true;
3131
2782
  }
3132
- // TODO: planWorkflow uses the cheap clerk (GPT-4o-mini). Per architecture doc,
3133
- // workflow planning should be done by the orchestrator LLM, not the clerk.
3134
- // The clerk should only do summaries, extraction, and compression.
2783
+ const inferredWorkflowRoles = this.inferRequestedWorkflowRoles(prompt, roleAssignments);
3135
2784
  const plannedSteps = !workflowTemplate
3136
- ? await this.clerk.planWorkflow({
3137
- prompt,
3138
- agents,
3139
- roleAssignments,
3140
- }).catch(() => null)
2785
+ ? inferredWorkflowRoles.map((role, index) => {
2786
+ const ownerName = roleAssignments[role] || defaultAgent.name;
2787
+ return {
2788
+ description: role === 'research'
2789
+ ? 'Research and pressure-test the request'
2790
+ : role === 'qa'
2791
+ ? 'Review and QA the completed work'
2792
+ : 'Implement the requested work',
2793
+ agentName: ownerName,
2794
+ expectedOutput: role === 'qa'
2795
+ ? 'PASS if the work meets the request, otherwise FAIL with actionable defects'
2796
+ : role === 'research'
2797
+ ? 'Clear research findings and implementation guidance'
2798
+ : 'Completed implementation that satisfies the request',
2799
+ dependsOn: index > 0 ? [index - 1] : [],
2800
+ };
2801
+ })
3141
2802
  : null;
3142
2803
  let todoTaskId;
3143
2804
  if (executionSpec.allowTodoCreation) {
@@ -3307,6 +2968,36 @@ class OrchestratorAgent {
3307
2968
  }
3308
2969
  return lines.join('\n');
3309
2970
  }
2971
+ /**
2972
+ * Run completion validation on a synthesized response and append warnings
2973
+ * if the validation finds error-severity issues. Non-blocking — always
2974
+ * returns a response, possibly with a validation warning appended.
2975
+ */
2976
+ applyCompletionValidation(synthesized, result, conversationId, executionMode) {
2977
+ try {
2978
+ const auditMessages = conversationId
2979
+ ? data.getMessages(conversationId, { limit: 200 }).map((m) => ({
2980
+ role: m.role,
2981
+ content: m.content,
2982
+ tool_calls_json: m.tool_calls_json,
2983
+ }))
2984
+ : [];
2985
+ const validation = (0, validation_1.validateCompletion)(result, synthesized, auditMessages, executionMode);
2986
+ if (!validation.valid) {
2987
+ console.warn(`[orchestrator] Completion validation failed (confidence=${validation.confidence}):`, JSON.stringify(validation.issues));
2988
+ return synthesized + (0, validation_1.formatValidationWarning)(validation);
2989
+ }
2990
+ if (validation.issues.length > 0) {
2991
+ console.info(`[orchestrator] Completion validation passed with warnings (confidence=${validation.confidence}):`, JSON.stringify(validation.issues));
2992
+ }
2993
+ return synthesized;
2994
+ }
2995
+ catch (err) {
2996
+ // Validation must never block the response
2997
+ console.warn('[orchestrator] Completion validation error (non-blocking):', err);
2998
+ return synthesized;
2999
+ }
3000
+ }
3310
3001
  /**
3311
3002
  * Publish orchestrator status via MQTT for UI rendering.
3312
3003
  */
@@ -3474,21 +3165,6 @@ class OrchestratorAgent {
3474
3165
  }
3475
3166
  return assignments;
3476
3167
  }
3477
- clerkModelLabel() {
3478
- const runtime = this.clerk.getRuntimeInfo();
3479
- if (!runtime.model)
3480
- return 'Clerk';
3481
- return `${runtime.model} | Clerk`;
3482
- }
3483
- async respondAsClerk(userPrompt, systemPrompt) {
3484
- const response = await this.clerk.respond(userPrompt, systemPrompt);
3485
- this.setLastResponseMeta({
3486
- agentName: 'Clerk',
3487
- botId: null,
3488
- modelLabel: this.clerkModelLabel(),
3489
- });
3490
- return response;
3491
- }
3492
3168
  hasRoleAssignments(assignments) {
3493
3169
  return Boolean(assignments.coding || assignments.qa || assignments.research);
3494
3170
  }
@@ -3670,128 +3346,14 @@ class OrchestratorAgent {
3670
3346
  isPolicyConfirmation(prompt) {
3671
3347
  return /\b(yes|yes save it|save it|save this policy|apply it|apply this|confirm|confirmed|do that|sounds right)\b/i.test(prompt.trim());
3672
3348
  }
3673
- determinePolicyScope(prompt, projectId) {
3674
- if (!projectId)
3675
- return 'user';
3676
- if (/\b(for|in|on|within)\s+(this|the)\s+project\b/i.test(prompt))
3677
- return 'project';
3678
- const project = data.getProject(projectId);
3679
- if (project && new RegExp(`\\b${project.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i').test(prompt)) {
3680
- return 'project';
3681
- }
3682
- return 'user';
3683
- }
3684
- extractPolicyPatch(prompt, assignments, currentPolicy) {
3685
- const patch = {};
3686
- if (/\b(?:you|o|orchestrator|project manager)\s+never\s+code(?:s)?\b|\bnot code\b/i.test(prompt))
3687
- patch.allowOrchestratorCode = false;
3688
- if (/\b(?:you|o|orchestrator|project manager)\s+never\s+qa\b|\bnot qa\b/i.test(prompt))
3689
- patch.allowOrchestratorQa = false;
3690
- if (/\b(?:you|o|orchestrator|project manager)\s+never\s+deploy(?:s)?\b|\bnot deploy\b/i.test(prompt))
3691
- patch.allowOrchestratorDeploy = false;
3692
- if (/\b(?:you|o|orchestrator|project manager)\s+never\s+simulate(?:s)?\b|\bdo not simulate\b|\bnot simulate\b/i.test(prompt))
3693
- patch.allowOrchestratorSimulation = false;
3694
- if (/\bcasual\b/i.test(prompt) && /\bdirect\b/i.test(prompt))
3695
- patch.tone = 'casual_direct';
3696
- else if (/\bcasual\b/i.test(prompt))
3697
- patch.tone = 'casual';
3698
- else if (/\bdirect\b/i.test(prompt))
3699
- patch.tone = 'direct';
3700
- if (/\bstrict\b/i.test(prompt))
3701
- patch.policyEnforcementLevel = 'strict';
3702
- if (/\bno more than (\d+)\s+times\b/i.test(prompt)) {
3703
- patch.coderRetryLimit = Number(prompt.match(/\bno more than (\d+)\s+times\b/i)?.[1] || currentPolicy.coderRetryLimit || 2);
3704
- }
3705
- if (/\b30\s*seconds\b/i.test(prompt))
3706
- patch.statusReportingMode = 'event_plus_heartbeat';
3707
- if (assignments.research)
3708
- patch.defaultIdeaReviewer = assignments.research;
3709
- if (assignments.coding)
3710
- patch.defaultCoder = assignments.coding;
3711
- if (assignments.qa)
3712
- patch.defaultQa = assignments.qa;
3713
- return patch;
3714
- }
3715
- sanitizeInterpretedPolicyPatch(prompt, patch) {
3716
- const sanitized = {};
3717
- const normalized = prompt.toLowerCase();
3718
- const mentionsCodePolicy = /\b(?:you|o|orchestrator|project manager)\b[\s\S]{0,30}\b(code|codes|coding)\b/.test(normalized);
3719
- const mentionsQaPolicy = /\b(?:you|o|orchestrator|project manager)\b[\s\S]{0,30}\b(qa|review|test|testing)\b/.test(normalized);
3720
- const mentionsDeployPolicy = /\b(?:you|o|orchestrator|project manager)\b[\s\S]{0,30}\bdeploy(?:s|ing)?\b/.test(normalized);
3721
- const mentionsSimulationPolicy = /\b(?:you|o|orchestrator|project manager)\b[\s\S]{0,30}\bsimulat(?:e|es|ing|ion)\b/.test(normalized);
3722
- if (patch.allowOrchestratorCode !== undefined && mentionsCodePolicy)
3723
- sanitized.allowOrchestratorCode = patch.allowOrchestratorCode;
3724
- if (patch.allowOrchestratorQa !== undefined && mentionsQaPolicy)
3725
- sanitized.allowOrchestratorQa = patch.allowOrchestratorQa;
3726
- if (patch.allowOrchestratorDeploy !== undefined && mentionsDeployPolicy)
3727
- sanitized.allowOrchestratorDeploy = patch.allowOrchestratorDeploy;
3728
- if (patch.allowOrchestratorSimulation !== undefined && mentionsSimulationPolicy)
3729
- sanitized.allowOrchestratorSimulation = patch.allowOrchestratorSimulation;
3730
- if (patch.defaultIdeaReviewer)
3731
- sanitized.defaultIdeaReviewer = patch.defaultIdeaReviewer;
3732
- if (patch.defaultCoder)
3733
- sanitized.defaultCoder = patch.defaultCoder;
3734
- if (patch.defaultBackendCoder)
3735
- sanitized.defaultBackendCoder = patch.defaultBackendCoder;
3736
- if (patch.defaultQa)
3737
- sanitized.defaultQa = patch.defaultQa;
3738
- if (patch.coderRetryLimit !== undefined)
3739
- sanitized.coderRetryLimit = patch.coderRetryLimit;
3740
- if (patch.statusReportingMode)
3741
- sanitized.statusReportingMode = patch.statusReportingMode;
3742
- if (patch.tone)
3743
- sanitized.tone = patch.tone;
3744
- if (patch.policyEnforcementLevel)
3745
- sanitized.policyEnforcementLevel = patch.policyEnforcementLevel;
3746
- return sanitized;
3747
- }
3748
- summarizePolicyPatch(patch) {
3749
- const lines = [];
3750
- if (patch.allowOrchestratorCode === false)
3751
- lines.push('O should not code');
3752
- if (patch.allowOrchestratorQa === false)
3753
- lines.push('O should not QA');
3754
- if (patch.allowOrchestratorDeploy === false)
3755
- lines.push('O should not deploy');
3756
- if (patch.allowOrchestratorSimulation === false)
3757
- lines.push('O should not simulate work');
3758
- if (patch.defaultIdeaReviewer)
3759
- lines.push(`${patch.defaultIdeaReviewer} should review ideas by default`);
3760
- if (patch.defaultCoder)
3761
- lines.push(`${patch.defaultCoder} should code by default`);
3762
- if (patch.defaultBackendCoder)
3763
- lines.push(`${patch.defaultBackendCoder} should handle backend coding by default`);
3764
- if (patch.defaultQa)
3765
- lines.push(`${patch.defaultQa} should QA by default`);
3766
- if (patch.coderRetryLimit !== undefined)
3767
- lines.push(`Coder retry limit should be ${patch.coderRetryLimit}`);
3768
- if (patch.statusReportingMode)
3769
- lines.push(`Status reporting mode should be ${patch.statusReportingMode}`);
3770
- if (patch.tone)
3771
- lines.push(`Tone should be ${patch.tone.replace(/_/g, ' ')}`);
3772
- if (patch.policyEnforcementLevel)
3773
- lines.push(`Enforcement should be ${patch.policyEnforcementLevel}`);
3774
- return lines;
3775
- }
3776
3349
  async handlePolicyUpdate(prompt, conversationId, intent, assignments, projectId, overview, currentPolicy) {
3777
- const interpreted = await this.clerk.interpretPolicyUpdate({
3778
- prompt,
3779
- currentPolicy,
3780
- projectName: overview?.project.name,
3781
- }).catch(() => null);
3782
3350
  const agentNames = data.listAgentProfiles().map((agent) => agent.name);
3783
- const sanitizedInterpretedPatch = (0, policy_detection_1.sanitizePolicyPatch)(prompt, interpreted?.patch || {}, currentPolicy);
3784
- const patch = {
3785
- ...(0, policy_detection_1.extractPolicyPatchFromPrompt)(prompt, currentPolicy, { agentNames }),
3786
- ...sanitizedInterpretedPatch,
3787
- };
3788
- const changes = interpreted?.summaryLines?.length
3789
- ? interpreted.summaryLines
3790
- : (0, policy_detection_1.summarizePolicyPatch)(patch);
3351
+ const patch = (0, policy_detection_1.extractPolicyPatchFromPrompt)(prompt, currentPolicy, { agentNames });
3352
+ const changes = (0, policy_detection_1.summarizePolicyPatch)(patch);
3791
3353
  if (changes.length === 0) {
3792
3354
  return this.formatPolicyUpdateResponse(prompt, intent, assignments, overview);
3793
3355
  }
3794
- const scope = interpreted?.scope || (0, policy_detection_1.determinePolicyScope)(prompt, overview?.project.name || undefined);
3356
+ const scope = (0, policy_detection_1.determinePolicyScope)(prompt, overview?.project.name || undefined);
3795
3357
  data.setPendingOrchestrationPolicy({
3796
3358
  conversationId,
3797
3359
  projectId: scope === 'project' ? projectId || null : null,