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.
- package/dist/approval.d.ts +7 -6
- package/dist/approval.d.ts.map +1 -1
- package/dist/approval.js +40 -10
- package/dist/approval.js.map +1 -1
- package/dist/auth/anthropic-subscription.d.ts +9 -29
- package/dist/auth/anthropic-subscription.d.ts.map +1 -1
- package/dist/auth/anthropic-subscription.js +12 -133
- package/dist/auth/anthropic-subscription.js.map +1 -1
- package/dist/auth/auto-detect.d.ts +28 -6
- package/dist/auth/auto-detect.d.ts.map +1 -1
- package/dist/auth/auto-detect.js +52 -205
- package/dist/auth/auto-detect.js.map +1 -1
- package/dist/auth/credential-reader.d.ts +24 -4
- package/dist/auth/credential-reader.d.ts.map +1 -1
- package/dist/auth/credential-reader.js +31 -256
- package/dist/auth/credential-reader.js.map +1 -1
- package/dist/auth/credential-status.d.ts +7 -27
- package/dist/auth/credential-status.d.ts.map +1 -1
- package/dist/auth/credential-status.js +7 -95
- package/dist/auth/credential-status.js.map +1 -1
- package/dist/auth/index.d.ts +9 -2
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +10 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/subscription-runtime.d.ts +19 -23
- package/dist/auth/subscription-runtime.d.ts.map +1 -1
- package/dist/auth/subscription-runtime.js +24 -292
- package/dist/auth/subscription-runtime.js.map +1 -1
- package/dist/auth/token-refresh.d.ts +19 -28
- package/dist/auth/token-refresh.d.ts.map +1 -1
- package/dist/auth/token-refresh.js +26 -464
- package/dist/auth/token-refresh.js.map +1 -1
- package/dist/backfill.js +2 -2
- package/dist/backfill.js.map +1 -1
- package/dist/bot-manager.d.ts +5 -6
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +20 -62
- package/dist/bot-manager.js.map +1 -1
- package/dist/clerk-model.d.ts +0 -1
- package/dist/clerk-model.d.ts.map +1 -1
- package/dist/clerk-model.js +24 -50
- package/dist/clerk-model.js.map +1 -1
- package/dist/commands/configure-provider.js +2 -2
- package/dist/commands/configure-provider.js.map +1 -1
- package/dist/commands/configure.d.ts.map +1 -1
- package/dist/commands/configure.js +10 -14
- package/dist/commands/configure.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +51 -229
- package/dist/commands/start.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/context-compressor.d.ts +6 -1
- package/dist/context-compressor.d.ts.map +1 -1
- package/dist/context-compressor.js +87 -18
- package/dist/context-compressor.js.map +1 -1
- package/dist/context-window.d.ts +2 -8
- package/dist/context-window.d.ts.map +1 -1
- package/dist/context-window.js +4 -4
- package/dist/context-window.js.map +1 -1
- package/dist/eval/orchestrator-front-door-replay.js +43 -2
- package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
- package/dist/eval/orchestrator-todo-dispatch-replay.d.ts +2 -0
- package/dist/eval/orchestrator-todo-dispatch-replay.d.ts.map +1 -0
- package/dist/eval/orchestrator-todo-dispatch-replay.js +253 -0
- package/dist/eval/orchestrator-todo-dispatch-replay.js.map +1 -0
- package/dist/eval/orchestrator-todo-planning-replay.d.ts +2 -0
- package/dist/eval/orchestrator-todo-planning-replay.d.ts.map +1 -0
- package/dist/eval/orchestrator-todo-planning-replay.js +247 -0
- package/dist/eval/orchestrator-todo-planning-replay.js.map +1 -0
- package/dist/eval/policy-detection-replay.d.ts +2 -0
- package/dist/eval/policy-detection-replay.d.ts.map +1 -0
- package/dist/eval/policy-detection-replay.js +122 -0
- package/dist/eval/policy-detection-replay.js.map +1 -0
- package/dist/eval/todo-worker-runtime-replay.d.ts +2 -0
- package/dist/eval/todo-worker-runtime-replay.d.ts.map +1 -0
- package/dist/eval/todo-worker-runtime-replay.js +520 -0
- package/dist/eval/todo-worker-runtime-replay.js.map +1 -0
- package/dist/integration-tokens.d.ts +6 -0
- package/dist/integration-tokens.d.ts.map +1 -1
- package/dist/integration-tokens.js +43 -0
- package/dist/integration-tokens.js.map +1 -1
- package/dist/local-data.d.ts +134 -1
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +711 -18
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +216 -12
- package/dist/local-db.js.map +1 -1
- package/dist/local-funnel.d.ts.map +1 -1
- package/dist/local-funnel.js +7 -0
- package/dist/local-funnel.js.map +1 -1
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +119 -96
- package/dist/local-server.js.map +1 -1
- package/dist/mcp/bridge-server.d.ts.map +1 -1
- package/dist/mcp/bridge-server.js +8 -2
- package/dist/mcp/bridge-server.js.map +1 -1
- package/dist/mcp/manager.d.ts +5 -0
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +131 -2
- package/dist/mcp/manager.js.map +1 -1
- package/dist/mcp/sync-cli-config.d.ts +5 -0
- package/dist/mcp/sync-cli-config.d.ts.map +1 -1
- package/dist/mcp/sync-cli-config.js +10 -2
- package/dist/mcp/sync-cli-config.js.map +1 -1
- package/dist/message-loop.d.ts +1 -10
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +179 -250
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +44 -0
- package/dist/mqtt-client.d.ts.map +1 -1
- package/dist/mqtt-client.js +2 -2
- package/dist/mqtt-client.js.map +1 -1
- package/dist/orchestration/front-door-policy.d.ts +26 -9
- package/dist/orchestration/front-door-policy.d.ts.map +1 -1
- package/dist/orchestration/front-door-policy.js +242 -69
- package/dist/orchestration/front-door-policy.js.map +1 -1
- package/dist/orchestration/orchestrator-blocked-prompt.d.ts +18 -0
- package/dist/orchestration/orchestrator-blocked-prompt.d.ts.map +1 -0
- package/dist/orchestration/orchestrator-blocked-prompt.js +46 -0
- package/dist/orchestration/orchestrator-blocked-prompt.js.map +1 -0
- package/dist/orchestration/orchestrator-final-response-prompt.d.ts +10 -0
- package/dist/orchestration/orchestrator-final-response-prompt.d.ts.map +1 -0
- package/dist/orchestration/orchestrator-final-response-prompt.js +39 -0
- package/dist/orchestration/orchestrator-final-response-prompt.js.map +1 -0
- package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
- package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.js +106 -36
- package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
- package/dist/orchestration/policy-prompt.d.ts +6 -0
- package/dist/orchestration/policy-prompt.d.ts.map +1 -0
- package/dist/orchestration/policy-prompt.js +40 -0
- package/dist/orchestration/policy-prompt.js.map +1 -0
- package/dist/orchestration/worker-operating-prompt.d.ts +16 -0
- package/dist/orchestration/worker-operating-prompt.d.ts.map +1 -0
- package/dist/orchestration/worker-operating-prompt.js +75 -0
- package/dist/orchestration/worker-operating-prompt.js.map +1 -0
- package/dist/orchestrator.d.ts +19 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +614 -54
- package/dist/orchestrator.js.map +1 -1
- package/dist/policy-detection.d.ts +40 -0
- package/dist/policy-detection.d.ts.map +1 -0
- package/dist/policy-detection.js +298 -0
- package/dist/policy-detection.js.map +1 -0
- package/dist/providers/anthropic.d.ts +0 -5
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +34 -51
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/claude-cli.d.ts.map +1 -1
- package/dist/providers/claude-cli.js +11 -2
- package/dist/providers/claude-cli.js.map +1 -1
- package/dist/providers/codex-cli.d.ts.map +1 -1
- package/dist/providers/codex-cli.js +121 -2
- package/dist/providers/codex-cli.js.map +1 -1
- package/dist/providers/index.d.ts +6 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/summarization-pipeline.d.ts +1 -4
- package/dist/summarization-pipeline.d.ts.map +1 -1
- package/dist/summarization-pipeline.js +43 -56
- package/dist/summarization-pipeline.js.map +1 -1
- package/dist/tools/analyze-image.js +2 -2
- package/dist/tools/analyze-image.js.map +1 -1
- package/dist/tools/edit-file.d.ts.map +1 -1
- package/dist/tools/edit-file.js +16 -1
- package/dist/tools/edit-file.js.map +1 -1
- package/dist/tools/git-tools.d.ts.map +1 -1
- package/dist/tools/git-tools.js +32 -1
- package/dist/tools/git-tools.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-directory.d.ts.map +1 -1
- package/dist/tools/list-directory.js +23 -0
- package/dist/tools/list-directory.js.map +1 -1
- package/dist/tools/notify-user.d.ts.map +1 -1
- package/dist/tools/notify-user.js +15 -1
- package/dist/tools/notify-user.js.map +1 -1
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +3 -1
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/request-mcp-install.js +5 -5
- package/dist/tools/request-mcp-install.js.map +1 -1
- package/dist/tools/run-command.d.ts.map +1 -1
- package/dist/tools/run-command.js +16 -4
- package/dist/tools/run-command.js.map +1 -1
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +6 -0
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/schedule-task.d.ts.map +1 -1
- package/dist/tools/schedule-task.js +37 -0
- package/dist/tools/schedule-task.js.map +1 -1
- package/dist/tools/search-codebase.d.ts.map +1 -1
- package/dist/tools/search-codebase.js +251 -32
- package/dist/tools/search-codebase.js.map +1 -1
- package/dist/tools/todo-tasks.d.ts +2 -0
- package/dist/tools/todo-tasks.d.ts.map +1 -1
- package/dist/tools/todo-tasks.js +203 -6
- package/dist/tools/todo-tasks.js.map +1 -1
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +21 -2
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +38 -34
- package/dist/tools/web-search.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wizard-state.d.ts.map +1 -1
- package/dist/wizard-state.js +30 -8
- package/dist/wizard-state.js.map +1 -1
- package/dist/wizard-support.d.ts +2 -2
- package/dist/wizard-support.d.ts.map +1 -1
- package/dist/wizard-support.js +80 -93
- package/dist/wizard-support.js.map +1 -1
- package/dist/workflow-engine.d.ts +3 -0
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +111 -82
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +6 -1
package/dist/orchestrator.js
CHANGED
|
@@ -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.
|
|
168
|
+
return await this.queueWorkflowTodoPlan(prompt, conversationId, workflowIntent, validation.executionSpec, opts, initialAssignments, initialProject, orchestrationState, selectedOrchestrator, template);
|
|
164
169
|
}
|
|
165
170
|
}
|
|
166
171
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
this.recordOrchestrationAudit(orchestrationState, 'choose_path', 'path_selected',
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
this.
|
|
187
|
-
|
|
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
|
-
|
|
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
|
|
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 = [
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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-
|
|
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 =
|
|
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
|
|
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
|
-
...
|
|
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
|
-
:
|
|
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 ||
|
|
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));
|