funolio-agent 1.0.7 → 1.0.48
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/agent-config.d.ts +9 -1
- package/dist/agent-config.d.ts.map +1 -1
- package/dist/agent-config.js +4 -1
- package/dist/agent-config.js.map +1 -1
- package/dist/approval.d.ts +1 -0
- package/dist/approval.d.ts.map +1 -1
- package/dist/approval.js +12 -0
- package/dist/approval.js.map +1 -1
- package/dist/auth/auto-detect.d.ts +11 -3
- package/dist/auth/auto-detect.d.ts.map +1 -1
- package/dist/auth/auto-detect.js +136 -168
- package/dist/auth/auto-detect.js.map +1 -1
- package/dist/auth/subscription-runtime.js +1 -1
- package/dist/auth/subscription-runtime.js.map +1 -1
- package/dist/auto-organizer.d.ts.map +1 -1
- package/dist/auto-organizer.js +4 -3
- package/dist/auto-organizer.js.map +1 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +34 -30
- package/dist/backfill.js.map +1 -1
- package/dist/bot-manager.d.ts +4 -8
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +31 -160
- package/dist/bot-manager.js.map +1 -1
- package/dist/clerk-model.d.ts +15 -7
- package/dist/clerk-model.d.ts.map +1 -1
- package/dist/clerk-model.js +78 -43
- package/dist/clerk-model.js.map +1 -1
- package/dist/cli-session-epoch.d.ts +10 -0
- package/dist/cli-session-epoch.d.ts.map +1 -0
- package/dist/cli-session-epoch.js +61 -0
- package/dist/cli-session-epoch.js.map +1 -0
- package/dist/cli.js +7 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/import-history.js +5 -1
- package/dist/commands/import-history.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +30 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pool.js +1 -1
- package/dist/commands/pool.js.map +1 -1
- package/dist/commands/setup.d.ts +37 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +146 -43
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +117 -255
- package/dist/commands/start.js.map +1 -1
- package/dist/config-cleanup.d.ts.map +1 -1
- package/dist/config-cleanup.js +2 -1
- package/dist/config-cleanup.js.map +1 -1
- package/dist/config.d.ts +6 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -18
- package/dist/config.js.map +1 -1
- package/dist/context-window.d.ts +33 -5
- package/dist/context-window.d.ts.map +1 -1
- package/dist/context-window.js +122 -21
- package/dist/context-window.js.map +1 -1
- package/dist/eval/orchestrator-front-door-replay.js +1 -1
- package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
- package/dist/eval/policy-detection-replay.js +1 -1
- package/dist/eval/policy-detection-replay.js.map +1 -1
- package/dist/import-parser-core.d.ts.map +1 -1
- package/dist/import-parser-core.js +74 -8
- package/dist/import-parser-core.js.map +1 -1
- package/dist/integration-tokens.d.ts +1 -6
- package/dist/integration-tokens.d.ts.map +1 -1
- package/dist/integration-tokens.js +38 -40
- package/dist/integration-tokens.js.map +1 -1
- package/dist/local-cli-pty-manager.d.ts +50 -0
- package/dist/local-cli-pty-manager.d.ts.map +1 -0
- package/dist/local-cli-pty-manager.js +645 -0
- package/dist/local-cli-pty-manager.js.map +1 -0
- package/dist/local-data.d.ts +89 -6
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +600 -63
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +74 -1
- package/dist/local-db.js.map +1 -1
- package/dist/local-funnel.d.ts +0 -7
- package/dist/local-funnel.d.ts.map +1 -1
- package/dist/local-funnel.js +22 -30
- package/dist/local-funnel.js.map +1 -1
- package/dist/local-import-worker.d.ts.map +1 -1
- package/dist/local-import-worker.js +49 -4
- package/dist/local-import-worker.js.map +1 -1
- package/dist/local-memory-search.d.ts +1 -0
- package/dist/local-memory-search.d.ts.map +1 -1
- package/dist/local-memory-search.js +107 -21
- package/dist/local-memory-search.js.map +1 -1
- package/dist/local-server.d.ts +21 -0
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +1057 -501
- package/dist/local-server.js.map +1 -1
- package/dist/mcp/bridge-server.d.ts.map +1 -1
- package/dist/mcp/bridge-server.js +2 -1
- package/dist/mcp/bridge-server.js.map +1 -1
- package/dist/mcp/local-memory-server.d.ts +6 -1
- package/dist/mcp/local-memory-server.d.ts.map +1 -1
- package/dist/mcp/local-memory-server.js +38 -13
- package/dist/mcp/local-memory-server.js.map +1 -1
- package/dist/mcp/manager.d.ts +3 -22
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +66 -320
- package/dist/mcp/manager.js.map +1 -1
- package/dist/memory-extraction.d.ts +2 -0
- package/dist/memory-extraction.d.ts.map +1 -1
- package/dist/memory-extraction.js +3 -1
- package/dist/memory-extraction.js.map +1 -1
- package/dist/message-loop.d.ts +1 -3
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +220 -437
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +2 -28
- 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/oauth.d.ts +6 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +91 -0
- package/dist/oauth.js.map +1 -1
- package/dist/orchestration/front-door-policy.d.ts +5 -2
- package/dist/orchestration/front-door-policy.d.ts.map +1 -1
- package/dist/orchestration/front-door-policy.js +25 -28
- package/dist/orchestration/front-door-policy.js.map +1 -1
- package/dist/orchestration/orchestrator-blocked-prompt.d.ts +2 -1
- package/dist/orchestration/orchestrator-blocked-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-blocked-prompt.js +12 -1
- package/dist/orchestration/orchestrator-blocked-prompt.js.map +1 -1
- package/dist/orchestration/orchestrator-final-response-prompt.d.ts +4 -1
- package/dist/orchestration/orchestrator-final-response-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-final-response-prompt.js +9 -7
- package/dist/orchestration/orchestrator-final-response-prompt.js.map +1 -1
- 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 +67 -44
- package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
- package/dist/orchestration/worker-operating-prompt.d.ts +2 -0
- package/dist/orchestration/worker-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/worker-operating-prompt.js +41 -2
- package/dist/orchestration/worker-operating-prompt.js.map +1 -1
- package/dist/orchestrator.d.ts +17 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +328 -166
- package/dist/orchestrator.js.map +1 -1
- package/dist/prompt-template.js +3 -3
- package/dist/prompt-template.js.map +1 -1
- package/dist/providers/anthropic.d.ts +0 -5
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +29 -75
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
- package/dist/providers/claude-cli-prompt.js +22 -6
- package/dist/providers/claude-cli-prompt.js.map +1 -1
- package/dist/providers/claude-cli.d.ts.map +1 -1
- package/dist/providers/claude-cli.js +36 -142
- 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 +148 -74
- package/dist/providers/codex-cli.js.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +4 -2
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +27 -2
- package/dist/providers/openai.js.map +1 -1
- package/dist/runtime-context.d.ts +10 -0
- package/dist/runtime-context.d.ts.map +1 -0
- package/dist/runtime-context.js +30 -0
- package/dist/runtime-context.js.map +1 -0
- package/dist/storage-mode.d.ts +5 -0
- package/dist/storage-mode.d.ts.map +1 -0
- package/dist/storage-mode.js +21 -0
- package/dist/storage-mode.js.map +1 -0
- package/dist/subagent/queue.d.ts.map +1 -1
- package/dist/subagent/queue.js +1 -0
- package/dist/subagent/queue.js.map +1 -1
- package/dist/summarization-pipeline.d.ts +10 -0
- package/dist/summarization-pipeline.d.ts.map +1 -1
- package/dist/summarization-pipeline.js +147 -34
- package/dist/summarization-pipeline.js.map +1 -1
- package/dist/tool-permissions.d.ts +2 -0
- package/dist/tool-permissions.d.ts.map +1 -0
- package/dist/tool-permissions.js +25 -0
- package/dist/tool-permissions.js.map +1 -0
- package/dist/tools/analyze-image.js +2 -2
- package/dist/tools/analyze-image.js.map +1 -1
- package/dist/tools/edit-file.js +3 -3
- package/dist/tools/edit-file.js.map +1 -1
- package/dist/tools/index.d.ts +7 -8
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +106 -60
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-directory.js +7 -4
- package/dist/tools/list-directory.js.map +1 -1
- package/dist/tools/read-file.js +3 -3
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/run-command.js +3 -3
- package/dist/tools/run-command.js.map +1 -1
- package/dist/tools/sandbox.d.ts +10 -5
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +41 -13
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/search-codebase.js +2 -2
- package/dist/tools/search-codebase.js.map +1 -1
- package/dist/tools/search-local-memory.d.ts.map +1 -1
- package/dist/tools/search-local-memory.js +19 -8
- package/dist/tools/search-local-memory.js.map +1 -1
- package/dist/tools/search-memory.d.ts.map +1 -1
- package/dist/tools/search-memory.js +9 -3
- package/dist/tools/search-memory.js.map +1 -1
- package/dist/tools/spawn-subagent.d.ts.map +1 -1
- package/dist/tools/spawn-subagent.js +1 -0
- package/dist/tools/spawn-subagent.js.map +1 -1
- package/dist/tools/write-file.js +3 -3
- package/dist/tools/write-file.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +1 -1
- package/dist/verification/index.js +2 -2
- package/dist/verification/index.js.map +1 -1
- package/dist/wizard-state.d.ts.map +1 -1
- package/dist/wizard-state.js +16 -2
- 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 +88 -99
- package/dist/wizard-support.js.map +1 -1
- package/dist/workflow-engine.d.ts +9 -3
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +378 -82
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +2 -1
package/dist/workflow-engine.js
CHANGED
|
@@ -62,18 +62,47 @@ const safeguards_1 = require("./orchestration/safeguards");
|
|
|
62
62
|
const data = __importStar(require("./local-data"));
|
|
63
63
|
const orchestrator_profile_1 = require("./orchestrator-profile");
|
|
64
64
|
const subscription_runtime_1 = require("./auth/subscription-runtime");
|
|
65
|
+
const runtime_context_1 = require("./runtime-context");
|
|
66
|
+
const local_cli_pty_manager_1 = require("./local-cli-pty-manager");
|
|
67
|
+
const cli_session_epoch_1 = require("./cli-session-epoch");
|
|
65
68
|
// ─── Workflow Engine ─────────────────────────────────────────────
|
|
66
69
|
const MAX_STEP_ATTEMPTS = safeguards_1.SAFEGUARDS.MAX_AGENT_ATTEMPTS;
|
|
67
70
|
const MAX_STEPS = safeguards_1.SAFEGUARDS.MAX_WORKFLOW_STEPS;
|
|
68
71
|
function isInteractiveAuthFailure(text) {
|
|
69
72
|
return /\b(not logged in|please run \/login|unauthorized|invalid api key|authentication required)\b/i.test(text);
|
|
70
73
|
}
|
|
74
|
+
const LOCAL_WORKFLOW_RUNTIME_RETRY_LIMIT = 2;
|
|
75
|
+
function shouldRetrySelectedWorkflowRuntime(err) {
|
|
76
|
+
const text = String(err?.message || err || '').toLowerCase();
|
|
77
|
+
if (!text)
|
|
78
|
+
return false;
|
|
79
|
+
if (/\b(no api key|configure one in settings|not available on this machine|not installed|please run \/login|not logged in|invalid api key)\b/i.test(text)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return /\b(429|rate limit|timeout|timed out|temporar|temporarily|econnreset|etimedout|enotfound|econnrefused|socket hang up|network|try again|overloaded|busy)\b/i.test(text);
|
|
83
|
+
}
|
|
84
|
+
async function pauseWorkflowRuntimeRetry(attempt) {
|
|
85
|
+
const delayMs = attempt <= 1 ? 750 : 1500;
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
87
|
+
}
|
|
88
|
+
function buildWorkflowPtyConversationKey(conversationId, step, workflowContext) {
|
|
89
|
+
if (conversationId)
|
|
90
|
+
return conversationId;
|
|
91
|
+
if (workflowContext?.workflowId)
|
|
92
|
+
return `workflow:${workflowContext.workflowId}`;
|
|
93
|
+
return `adhoc:${step.agentId}:${step.id}`;
|
|
94
|
+
}
|
|
71
95
|
class WorkflowEngine extends events_1.EventEmitter {
|
|
72
96
|
projectDir;
|
|
97
|
+
runtimeMode;
|
|
73
98
|
activeWorkflows = new Map();
|
|
74
|
-
constructor(projectDir) {
|
|
99
|
+
constructor(projectDir, runtimeMode) {
|
|
75
100
|
super();
|
|
76
101
|
this.projectDir = projectDir;
|
|
102
|
+
this.runtimeMode = (0, runtime_context_1.normalizeRuntimeMode)(runtimeMode || (0, runtime_context_1.getDefaultRuntimeMode)());
|
|
103
|
+
}
|
|
104
|
+
getRuntimeMode() {
|
|
105
|
+
return this.runtimeMode;
|
|
77
106
|
}
|
|
78
107
|
/**
|
|
79
108
|
* Execute a workflow: decompose a prompt into steps, route, and execute.
|
|
@@ -82,7 +111,8 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
82
111
|
async execute(prompt, conversationId, agentId, opts) {
|
|
83
112
|
const workflowId = crypto.randomUUID();
|
|
84
113
|
const startedAt = Date.now();
|
|
85
|
-
const
|
|
114
|
+
const effectiveRuntimeMode = (0, runtime_context_1.normalizeRuntimeMode)(opts?.runtimeMode || this.runtimeMode);
|
|
115
|
+
const clerk = (0, clerk_model_1.getClerk)({ runtimeMode: effectiveRuntimeMode });
|
|
86
116
|
const allProfiles = data.listAgentProfiles();
|
|
87
117
|
// Filter out orchestrator profiles by default, but allow explicitly assigned specialists
|
|
88
118
|
// and the selected agent back into the planning pool when needed.
|
|
@@ -100,10 +130,12 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
100
130
|
}
|
|
101
131
|
else {
|
|
102
132
|
// Single agent, single step
|
|
133
|
+
const stepDescription = opts?.stepDescription?.trim()
|
|
134
|
+
|| prompt.slice(0, 100);
|
|
103
135
|
steps = [{
|
|
104
136
|
id: `${workflowId}-0`,
|
|
105
137
|
index: 0,
|
|
106
|
-
description:
|
|
138
|
+
description: stepDescription,
|
|
107
139
|
prompt,
|
|
108
140
|
agentId: profile.id,
|
|
109
141
|
agentName: profile.name,
|
|
@@ -281,7 +313,7 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
281
313
|
`TODO ITEM #${task.id}: ${task.title}`,
|
|
282
314
|
task.details ? `Task details:\n${task.details}` : '',
|
|
283
315
|
'Work only on this TODO item. Do not mark any other item complete.',
|
|
284
|
-
'When your stage is complete, return the structured footer
|
|
316
|
+
'When your stage is complete, return the structured footer required below so routing can continue automatically.',
|
|
285
317
|
].filter(Boolean).join('\n\n');
|
|
286
318
|
const steps = template.steps
|
|
287
319
|
.sort((a, b) => a.order_index - b.order_index)
|
|
@@ -633,7 +665,7 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
633
665
|
? 'Include REASON only when STATUS is FAIL.'
|
|
634
666
|
: 'Include REASON only when STATUS is BLOCKED.';
|
|
635
667
|
return [
|
|
636
|
-
`You are ${profile?.name || 'a
|
|
668
|
+
`You are ${profile?.name || 'a workflow worker'}.`,
|
|
637
669
|
'Complete only the assigned workflow step.',
|
|
638
670
|
roleLine,
|
|
639
671
|
'Use available tools when the task requires creating, changing, or verifying files.',
|
|
@@ -654,8 +686,8 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
654
686
|
? 'Your role is QA. Verify the delivered work, then complete the TODO through the worker tool or create the next fix handoff.'
|
|
655
687
|
: 'Your role is implementer. Do the assigned work, then complete the TODO through the worker tool.';
|
|
656
688
|
return [
|
|
657
|
-
`You are ${profile?.name || 'a
|
|
658
|
-
'You are executing one orchestrated TODO task
|
|
689
|
+
`You are ${profile?.name || 'a workflow worker'}.`,
|
|
690
|
+
'You are executing one orchestrated TODO task.',
|
|
659
691
|
roleLine,
|
|
660
692
|
'Follow the assignment in the user message exactly.',
|
|
661
693
|
'Use available tools when needed.',
|
|
@@ -831,6 +863,7 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
831
863
|
* Handles checkpoint loop-back and footer-driven retry/failure logic.
|
|
832
864
|
*/
|
|
833
865
|
async executeSteps(workflowId, steps, conversationId, opts) {
|
|
866
|
+
const effectiveRuntimeMode = (0, runtime_context_1.normalizeRuntimeMode)(opts?.runtimeMode || this.runtimeMode);
|
|
834
867
|
const completed = new Set();
|
|
835
868
|
const failed = new Set();
|
|
836
869
|
const allowMissingFooterForDirectStep = !opts?.isOrchestrated && !!opts?.disableDecomposition && steps.length === 1;
|
|
@@ -902,6 +935,53 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
902
935
|
loopCount: step.loopCount,
|
|
903
936
|
};
|
|
904
937
|
const result = await this.executeStep(step, fullPrompt, conversationId, opts, opts?.apiKey, wfContext);
|
|
938
|
+
if (opts?.isOrchestrated && opts.taskId) {
|
|
939
|
+
const completedTodo = data.getTodoTask(opts.taskId, 'completed');
|
|
940
|
+
if (completedTodo) {
|
|
941
|
+
step.status = 'completed';
|
|
942
|
+
step.result = completedTodo.output_summary || completedTodo.handoff_prompt || result;
|
|
943
|
+
step.completedAt = Date.now();
|
|
944
|
+
completed.add(step.index);
|
|
945
|
+
this.emitProgress(workflowId, step, steps, 'step-completed', opts?.onProgress, undefined, opts?.onWorkerChunk);
|
|
946
|
+
try {
|
|
947
|
+
const execId = step._execId;
|
|
948
|
+
if (execId) {
|
|
949
|
+
data.updateStepExecution(execId, {
|
|
950
|
+
status: 'completed',
|
|
951
|
+
attempts: step.attempts,
|
|
952
|
+
resultSummary: step.result.length > 200 ? step.result.slice(0, 200) + '...' : step.result,
|
|
953
|
+
completedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
|
|
954
|
+
});
|
|
955
|
+
data.updateWorkflowExecution(workflowId, { completedSteps: completed.size });
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
catch { /* best effort */ }
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const blockedTodo = data.getTodoTask(opts.taskId, 'active');
|
|
962
|
+
if (blockedTodo?.blocker_summary) {
|
|
963
|
+
step.status = 'failed';
|
|
964
|
+
step.result = blockedTodo.blocker_summary;
|
|
965
|
+
step.error = blockedTodo.blocker_summary;
|
|
966
|
+
step.completedAt = Date.now();
|
|
967
|
+
failed.add(step.index);
|
|
968
|
+
this.emitProgress(workflowId, step, steps, 'step-failed', opts?.onProgress, undefined, opts?.onWorkerChunk);
|
|
969
|
+
try {
|
|
970
|
+
const execId = step._execId;
|
|
971
|
+
if (execId) {
|
|
972
|
+
data.updateStepExecution(execId, {
|
|
973
|
+
status: 'failed',
|
|
974
|
+
attempts: step.attempts,
|
|
975
|
+
error: step.error,
|
|
976
|
+
resultSummary: blockedTodo.blocker_summary.slice(0, 200),
|
|
977
|
+
completedAt: new Date().toISOString().replace('T', ' ').replace('Z', ''),
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
catch { /* best effort */ }
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
905
985
|
const footer = (0, status_parser_1.parseStructuredFooter)(result);
|
|
906
986
|
const stepRole = this.inferRoleForStep(step);
|
|
907
987
|
if (stepRole !== 'qa' && (footer.status === 'PASS' || footer.status === 'FAIL')) {
|
|
@@ -1066,7 +1146,7 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1066
1146
|
&& !hasFutureQaStep
|
|
1067
1147
|
&& !opts?.isOrchestrated;
|
|
1068
1148
|
if (shouldRunClerkVerification) {
|
|
1069
|
-
const verifyClerk = (0, clerk_model_1.getClerk)();
|
|
1149
|
+
const verifyClerk = (0, clerk_model_1.getClerk)({ runtimeMode: effectiveRuntimeMode });
|
|
1070
1150
|
if (verifyClerk) {
|
|
1071
1151
|
try {
|
|
1072
1152
|
const verification = await verifyClerk.verifyStepOutput(step.description, step.expectedOutput, result);
|
|
@@ -1237,7 +1317,7 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1237
1317
|
}
|
|
1238
1318
|
else if (step.attempts < step.maxAttempts) {
|
|
1239
1319
|
// Nudge on retry — generate adjusted prompt
|
|
1240
|
-
const nudgeClerk = (0, clerk_model_1.getClerk)();
|
|
1320
|
+
const nudgeClerk = (0, clerk_model_1.getClerk)({ runtimeMode: effectiveRuntimeMode });
|
|
1241
1321
|
if (nudgeClerk) {
|
|
1242
1322
|
try {
|
|
1243
1323
|
const nudge = await nudgeClerk.generateNudge(step.description, step.prompt, err.message);
|
|
@@ -1278,12 +1358,14 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1278
1358
|
* Execute a single workflow step: call LLM with tools, agentic loop.
|
|
1279
1359
|
*/
|
|
1280
1360
|
async executeStep(step, prompt, conversationId, opts, apiKey, workflowContext) {
|
|
1361
|
+
const effectiveRuntimeMode = (0, runtime_context_1.normalizeRuntimeMode)(opts?.runtimeMode || this.runtimeMode);
|
|
1281
1362
|
const profile = data.getAgentProfile(step.agentId);
|
|
1282
|
-
const runtime = await this.resolveStepRuntime(step, profile, apiKey);
|
|
1363
|
+
const runtime = await this.resolveStepRuntime(step, profile, apiKey, opts);
|
|
1283
1364
|
let activeRuntime = runtime;
|
|
1284
1365
|
let llm = (0, index_1.createProvider)(activeRuntime.providerName, {
|
|
1285
1366
|
apiKey: activeRuntime.apiKey,
|
|
1286
1367
|
model: activeRuntime.model,
|
|
1368
|
+
runtimeMode: effectiveRuntimeMode,
|
|
1287
1369
|
...(activeRuntime.requestShape ? { requestShape: activeRuntime.requestShape } : {}),
|
|
1288
1370
|
...(activeRuntime.baseUrl ? { baseUrl: activeRuntime.baseUrl } : {}),
|
|
1289
1371
|
...(activeRuntime.apiQuery ? { apiQuery: activeRuntime.apiQuery } : {}),
|
|
@@ -1295,19 +1377,22 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1295
1377
|
...(opts?.isOrchestrated ? { toolProjectId: opts?.projectId ?? null } : {}),
|
|
1296
1378
|
...(opts?.isOrchestrated ? { currentTodoTaskId: opts?.taskId } : {}),
|
|
1297
1379
|
});
|
|
1298
|
-
const toolDefs = (0, index_2.getAllToolDefinitions)();
|
|
1380
|
+
const toolDefs = (0, index_2.getAllToolDefinitions)(effectiveRuntimeMode);
|
|
1299
1381
|
const workspacePath = opts?.workspacePath?.trim();
|
|
1300
1382
|
const effectiveProjectDir = workspacePath || this.projectDir;
|
|
1383
|
+
const unrestrictedCliProvider = index_1.CLI_PROVIDERS.has(step.provider);
|
|
1301
1384
|
const toolCtx = (0, index_2.createToolContext)(effectiveProjectDir, {
|
|
1302
1385
|
projectId: opts?.projectId ?? null,
|
|
1303
1386
|
currentTodoTaskId: opts?.taskId,
|
|
1304
1387
|
actorType: 'llm',
|
|
1305
1388
|
actorId: step.agentName || step.agentId,
|
|
1389
|
+
runtimeMode: effectiveRuntimeMode,
|
|
1390
|
+
restrictFileAccessToProject: unrestrictedCliProvider ? false : undefined,
|
|
1306
1391
|
});
|
|
1307
1392
|
const isWorkflowWorker = opts?.workerMode !== false;
|
|
1308
1393
|
let systemPrompt = profile?.soul_md
|
|
1309
|
-
|| 'You are
|
|
1310
|
-
const clerk = (0, clerk_model_1.getClerk)();
|
|
1394
|
+
|| 'You are an AI assistant working on a specific task step.';
|
|
1395
|
+
const clerk = (0, clerk_model_1.getClerk)({ runtimeMode: effectiveRuntimeMode });
|
|
1311
1396
|
if (opts?.systemPromptOverride?.trim()) {
|
|
1312
1397
|
systemPrompt = opts.systemPromptOverride.trim();
|
|
1313
1398
|
}
|
|
@@ -1342,12 +1427,27 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1342
1427
|
const messages = [{ role: 'user', content: effectivePrompt }];
|
|
1343
1428
|
const promptChars = effectivePrompt.length;
|
|
1344
1429
|
const systemChars = systemPrompt.length;
|
|
1430
|
+
const isLocalCliSession = effectiveRuntimeMode === 'local_desktop'
|
|
1431
|
+
&& (activeRuntime.providerName === 'claude-cli' || activeRuntime.providerName === 'codex-cli');
|
|
1432
|
+
const hasPersistentConversation = isLocalCliSession && !!conversationId;
|
|
1433
|
+
const cliSessionEpochPlan = hasPersistentConversation
|
|
1434
|
+
? (0, cli_session_epoch_1.selectCliSessionEpoch)(conversationId, step.agentId, activeRuntime.providerName)
|
|
1435
|
+
: { existing: undefined, resumeSessionId: null, resetReason: null };
|
|
1436
|
+
let activeCliSessionId = cliSessionEpochPlan.resumeSessionId;
|
|
1437
|
+
const cliEpochStartedAt = cliSessionEpochPlan.resumeSessionId
|
|
1438
|
+
? (cliSessionEpochPlan.existing?.epoch_started_at || (0, cli_session_epoch_1.localTimestamp)())
|
|
1439
|
+
: (0, cli_session_epoch_1.localTimestamp)();
|
|
1440
|
+
const ptyConversationKey = buildWorkflowPtyConversationKey(conversationId, step, workflowContext);
|
|
1345
1441
|
console.info(`[workflow-engine] step runtime: agent=${step.agentName} provider=${activeRuntime.providerName} model=${activeRuntime.model} runtime=${activeRuntime.runtimeLabel || 'unknown'} promptChars=${promptChars} systemChars=${systemChars} tools=${opts?.disableTools ? 0 : toolDefs.length}`);
|
|
1346
1442
|
// Agentic loop
|
|
1347
1443
|
let iteration = 0;
|
|
1348
1444
|
const MAX_ITERATIONS = safeguards_1.SAFEGUARDS.MAX_WORKFLOW_ITERATIONS;
|
|
1349
1445
|
let incompleteExecutionRecoveries = 0;
|
|
1446
|
+
let selectedRuntimeRetryCount = 0;
|
|
1350
1447
|
while (iteration < MAX_ITERATIONS) {
|
|
1448
|
+
if (opts?.isOrchestrated && this.orchestratedTodoHandled(opts.taskId)) {
|
|
1449
|
+
return this.buildOrchestratedTodoHandledResult(opts.taskId);
|
|
1450
|
+
}
|
|
1351
1451
|
iteration++;
|
|
1352
1452
|
let streamedContent = '';
|
|
1353
1453
|
let lastChunkProgressAt = 0;
|
|
@@ -1364,70 +1464,155 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1364
1464
|
: 'auto';
|
|
1365
1465
|
let response;
|
|
1366
1466
|
try {
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1467
|
+
if (isLocalCliSession) {
|
|
1468
|
+
if (cliSessionEpochPlan.resetReason && iteration === 1 && opts?.onWorkerChunk) {
|
|
1469
|
+
const resetDetail = cliSessionEpochPlan.resetReason === 'turn_limit'
|
|
1470
|
+
? 'Resetting CLI session after reaching the turn limit.'
|
|
1471
|
+
: cliSessionEpochPlan.resetReason === 'token_limit'
|
|
1472
|
+
? 'Resetting CLI session after reaching the context budget.'
|
|
1473
|
+
: 'Resetting CLI session because the runtime changed.';
|
|
1474
|
+
opts.onWorkerChunk({
|
|
1475
|
+
type: opts?.isOrchestrated ? 'worker_chunk' : 'chunk',
|
|
1476
|
+
stepId: step.id,
|
|
1477
|
+
agentName: step.agentName,
|
|
1478
|
+
description: step.description,
|
|
1479
|
+
stepIndex: workflowContext?.stepIndex ?? 0,
|
|
1480
|
+
totalSteps: workflowContext?.totalSteps ?? 1,
|
|
1481
|
+
text: resetDetail,
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
const ptyManager = (0, local_cli_pty_manager_1.getLocalCliPtySessionManager)();
|
|
1485
|
+
let ptyAttempt = 0;
|
|
1486
|
+
while (true) {
|
|
1487
|
+
ptyAttempt++;
|
|
1488
|
+
try {
|
|
1489
|
+
const ptyResult = await ptyManager.runTurn({
|
|
1490
|
+
conversationId: ptyConversationKey,
|
|
1491
|
+
botId: step.agentId,
|
|
1492
|
+
provider: activeRuntime.providerName,
|
|
1493
|
+
cwd: effectiveProjectDir,
|
|
1494
|
+
systemPrompt,
|
|
1495
|
+
messages,
|
|
1496
|
+
forceFreshSession: !hasPersistentConversation || (iteration === 1 && !cliSessionEpochPlan.resumeSessionId),
|
|
1497
|
+
onDetail: async (detail) => {
|
|
1498
|
+
streamedContent += streamedContent ? `\n${detail}` : detail;
|
|
1499
|
+
if (opts?.onWorkerChunk) {
|
|
1500
|
+
opts.onWorkerChunk({
|
|
1501
|
+
type: opts?.isOrchestrated ? 'worker_chunk' : 'chunk',
|
|
1502
|
+
stepId: step.id,
|
|
1503
|
+
agentName: step.agentName,
|
|
1504
|
+
description: step.description,
|
|
1505
|
+
stepIndex: workflowContext?.stepIndex ?? 0,
|
|
1506
|
+
totalSteps: workflowContext?.totalSteps ?? 1,
|
|
1507
|
+
text: detail,
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
const now = Date.now();
|
|
1511
|
+
if (now - lastChunkProgressAt < 8000)
|
|
1512
|
+
return;
|
|
1513
|
+
lastChunkProgressAt = now;
|
|
1514
|
+
const activeSteps = workflowContext?.workflowId
|
|
1515
|
+
? this.getActiveWorkflows().find((w) => w.id === workflowContext.workflowId)?.steps || [step]
|
|
1516
|
+
: [step];
|
|
1517
|
+
this.emitProgress(workflowContext?.workflowId || 'workflow', step, activeSteps, 'step-progress', opts?.onProgress, `${step.agentName} is still working on ${step.description}`);
|
|
1518
|
+
},
|
|
1519
|
+
});
|
|
1520
|
+
if (ptyResult.sessionId) {
|
|
1521
|
+
activeCliSessionId = ptyResult.sessionId;
|
|
1390
1522
|
}
|
|
1523
|
+
response = {
|
|
1524
|
+
content: ptyResult.content || '',
|
|
1525
|
+
usage: ptyResult.usage,
|
|
1526
|
+
};
|
|
1527
|
+
break;
|
|
1391
1528
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1529
|
+
catch (ptyErr) {
|
|
1530
|
+
if (ptyAttempt >= LOCAL_WORKFLOW_RUNTIME_RETRY_LIMIT
|
|
1531
|
+
|| !shouldRetrySelectedWorkflowRuntime(ptyErr)) {
|
|
1532
|
+
throw ptyErr;
|
|
1533
|
+
}
|
|
1534
|
+
console.warn(`[workflow-engine] ${step.agentName} selected runtime failed, retrying the same connection (${ptyAttempt + 1}/${LOCAL_WORKFLOW_RUNTIME_RETRY_LIMIT})`);
|
|
1535
|
+
await pauseWorkflowRuntimeRetry(ptyAttempt);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
else {
|
|
1540
|
+
response = await llm.chat({
|
|
1541
|
+
messages,
|
|
1542
|
+
system: systemPrompt,
|
|
1543
|
+
stream: true,
|
|
1544
|
+
toolChoice,
|
|
1545
|
+
onChunk: async (chunk) => {
|
|
1546
|
+
streamedContent += chunk;
|
|
1547
|
+
// Forward chunks to onWorkerChunk with 150ms/80-char buffer
|
|
1548
|
+
if (opts?.onWorkerChunk) {
|
|
1549
|
+
chunkBuffer += chunk;
|
|
1550
|
+
const now2 = Date.now();
|
|
1551
|
+
if (chunkBuffer.length >= 80 || now2 - lastChunkForwardAt >= 150) {
|
|
1552
|
+
opts.onWorkerChunk({
|
|
1553
|
+
type: opts?.isOrchestrated ? 'worker_chunk' : 'chunk',
|
|
1554
|
+
stepId: step.id,
|
|
1555
|
+
agentName: step.agentName,
|
|
1556
|
+
description: step.description,
|
|
1557
|
+
stepIndex: workflowContext?.stepIndex ?? 0,
|
|
1558
|
+
totalSteps: workflowContext?.totalSteps ?? 1,
|
|
1559
|
+
text: chunkBuffer,
|
|
1560
|
+
});
|
|
1561
|
+
chunkBuffer = '';
|
|
1562
|
+
lastChunkForwardAt = now2;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
// Existing 8-second heartbeat for backward compat
|
|
1566
|
+
const now = Date.now();
|
|
1567
|
+
if (now - lastChunkProgressAt < 8000)
|
|
1568
|
+
return;
|
|
1569
|
+
lastChunkProgressAt = now;
|
|
1570
|
+
const compact = chunk.replace(/\s+/g, ' ').trim();
|
|
1571
|
+
if (!compact && emittedChunkHeartbeat)
|
|
1572
|
+
return;
|
|
1573
|
+
emittedChunkHeartbeat = true;
|
|
1574
|
+
const preview = `${step.agentName} is still working on ${step.description}`;
|
|
1575
|
+
const activeSteps = workflowContext?.workflowId
|
|
1576
|
+
? this.getActiveWorkflows().find((w) => w.id === workflowContext.workflowId)?.steps || [step]
|
|
1577
|
+
: [step];
|
|
1578
|
+
this.emitProgress(workflowContext?.workflowId || 'workflow', step, activeSteps, 'step-progress', opts?.onProgress, preview);
|
|
1579
|
+
},
|
|
1580
|
+
tools: opts?.disableTools ? undefined : toolDefs,
|
|
1581
|
+
cwd: effectiveProjectDir,
|
|
1420
1582
|
});
|
|
1421
|
-
|
|
1583
|
+
// Flush remaining chunk buffer
|
|
1584
|
+
if (opts?.onWorkerChunk && chunkBuffer) {
|
|
1585
|
+
opts.onWorkerChunk({
|
|
1586
|
+
type: opts?.isOrchestrated ? 'worker_chunk' : 'chunk',
|
|
1587
|
+
stepId: step.id,
|
|
1588
|
+
agentName: step.agentName,
|
|
1589
|
+
description: step.description,
|
|
1590
|
+
stepIndex: workflowContext?.stepIndex ?? 0,
|
|
1591
|
+
totalSteps: workflowContext?.totalSteps ?? 1,
|
|
1592
|
+
text: chunkBuffer,
|
|
1593
|
+
});
|
|
1594
|
+
chunkBuffer = '';
|
|
1595
|
+
}
|
|
1422
1596
|
}
|
|
1597
|
+
selectedRuntimeRetryCount = 0;
|
|
1423
1598
|
}
|
|
1424
1599
|
catch (err) {
|
|
1425
1600
|
console.warn(`[workflow-engine] step error: agent=${step.agentName} provider=${activeRuntime.providerName} model=${activeRuntime.model} runtime=${activeRuntime.runtimeLabel || 'unknown'} iteration=${iteration} message=${err?.message || err}`);
|
|
1426
|
-
if (
|
|
1601
|
+
if (effectiveRuntimeMode === 'local_desktop'
|
|
1602
|
+
&& selectedRuntimeRetryCount < (LOCAL_WORKFLOW_RUNTIME_RETRY_LIMIT - 1)
|
|
1603
|
+
&& shouldRetrySelectedWorkflowRuntime(err)) {
|
|
1604
|
+
selectedRuntimeRetryCount++;
|
|
1605
|
+
console.warn(`[workflow-engine] ${step.agentName} selected runtime failed, retrying the same connection (${selectedRuntimeRetryCount + 1}/${LOCAL_WORKFLOW_RUNTIME_RETRY_LIMIT})`);
|
|
1606
|
+
await pauseWorkflowRuntimeRetry(selectedRuntimeRetryCount);
|
|
1607
|
+
iteration--;
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
if (effectiveRuntimeMode !== 'local_desktop' && activeRuntime.fallback) {
|
|
1427
1611
|
activeRuntime = activeRuntime.fallback;
|
|
1428
1612
|
llm = (0, index_1.createProvider)(activeRuntime.providerName, {
|
|
1429
1613
|
apiKey: activeRuntime.apiKey,
|
|
1430
1614
|
model: activeRuntime.model,
|
|
1615
|
+
runtimeMode: effectiveRuntimeMode,
|
|
1431
1616
|
...(activeRuntime.requestShape ? { requestShape: activeRuntime.requestShape } : {}),
|
|
1432
1617
|
...(activeRuntime.baseUrl ? { baseUrl: activeRuntime.baseUrl } : {}),
|
|
1433
1618
|
...(activeRuntime.apiQuery ? { apiQuery: activeRuntime.apiQuery } : {}),
|
|
@@ -1445,6 +1630,23 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1445
1630
|
}
|
|
1446
1631
|
throw err;
|
|
1447
1632
|
}
|
|
1633
|
+
if (hasPersistentConversation && activeCliSessionId && !response.toolCalls?.length) {
|
|
1634
|
+
const nextEpochTurnCount = cliSessionEpochPlan.resumeSessionId
|
|
1635
|
+
? ((cliSessionEpochPlan.existing?.epoch_turn_count || 0) + 1)
|
|
1636
|
+
: 1;
|
|
1637
|
+
data.upsertCliSessionEpoch({
|
|
1638
|
+
conversationId: conversationId,
|
|
1639
|
+
botId: step.agentId,
|
|
1640
|
+
provider: activeRuntime.providerName,
|
|
1641
|
+
sessionId: activeCliSessionId,
|
|
1642
|
+
epochTurnCount: nextEpochTurnCount,
|
|
1643
|
+
lastInputTokens: response.usage?.inputTokens ?? null,
|
|
1644
|
+
lastOutputTokens: response.usage?.outputTokens ?? null,
|
|
1645
|
+
resetReason: cliSessionEpochPlan.resetReason,
|
|
1646
|
+
epochStartedAt: cliEpochStartedAt,
|
|
1647
|
+
lastUsedAt: (0, cli_session_epoch_1.localTimestamp)(),
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1448
1650
|
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
1449
1651
|
messages.push({
|
|
1450
1652
|
role: 'assistant',
|
|
@@ -1452,7 +1654,9 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1452
1654
|
toolCalls: response.toolCalls,
|
|
1453
1655
|
});
|
|
1454
1656
|
for (const tc of response.toolCalls) {
|
|
1455
|
-
const permMode =
|
|
1657
|
+
const permMode = unrestrictedCliProvider
|
|
1658
|
+
? 'autopilot'
|
|
1659
|
+
: (profile?.permission_mode || 'autopilot');
|
|
1456
1660
|
const approval = (0, approval_1.checkPermission)(tc.name, permMode);
|
|
1457
1661
|
if (!approval.approved) {
|
|
1458
1662
|
throw new Error(`APPROVAL_REQUIRED: ${approval.reason}`);
|
|
@@ -1460,7 +1664,7 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1460
1664
|
// Emit tool_call event so UI can show what's happening
|
|
1461
1665
|
if (opts?.onWorkerChunk) {
|
|
1462
1666
|
opts.onWorkerChunk({
|
|
1463
|
-
type: 'tool_call',
|
|
1667
|
+
type: opts?.isOrchestrated ? 'worker_tool_call' : 'tool_call',
|
|
1464
1668
|
stepId: step.id,
|
|
1465
1669
|
agentName: step.agentName,
|
|
1466
1670
|
description: step.description,
|
|
@@ -1477,7 +1681,7 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1477
1681
|
// Emit tool_result event
|
|
1478
1682
|
if (opts?.onWorkerChunk) {
|
|
1479
1683
|
opts.onWorkerChunk({
|
|
1480
|
-
type: 'tool_result',
|
|
1684
|
+
type: opts?.isOrchestrated ? 'worker_tool_result' : 'tool_result',
|
|
1481
1685
|
stepId: step.id,
|
|
1482
1686
|
agentName: step.agentName,
|
|
1483
1687
|
description: step.description,
|
|
@@ -1489,6 +1693,9 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1489
1693
|
toolIsError: !result.success,
|
|
1490
1694
|
});
|
|
1491
1695
|
}
|
|
1696
|
+
if (opts?.isOrchestrated && this.orchestratedTodoHandled(opts.taskId)) {
|
|
1697
|
+
return this.buildOrchestratedTodoHandledResult(opts.taskId);
|
|
1698
|
+
}
|
|
1492
1699
|
}
|
|
1493
1700
|
if (opts?.isOrchestrated && this.orchestratedTodoHandled(opts.taskId)) {
|
|
1494
1701
|
return this.buildOrchestratedTodoHandledResult(opts.taskId);
|
|
@@ -1551,12 +1758,15 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1551
1758
|
this.emitProgress(workflowContext?.workflowId || 'workflow', step, activeSteps, 'step-progress', opts?.onProgress, `${step.agentName} described intended work instead of executing it. Retrying with an explicit tool-use correction.`);
|
|
1552
1759
|
continue;
|
|
1553
1760
|
}
|
|
1554
|
-
if (
|
|
1761
|
+
if (effectiveRuntimeMode !== 'local_desktop'
|
|
1762
|
+
&& activeRuntime.fallback
|
|
1763
|
+
&& activeRuntime.providerName !== activeRuntime.fallback.providerName) {
|
|
1555
1764
|
console.warn(`[workflow-engine] forcing runtime fallback after repeated incomplete execution: agent=${step.agentName} from=${activeRuntime.providerName} to=${activeRuntime.fallback.providerName}`);
|
|
1556
1765
|
activeRuntime = activeRuntime.fallback;
|
|
1557
1766
|
llm = (0, index_1.createProvider)(activeRuntime.providerName, {
|
|
1558
1767
|
apiKey: activeRuntime.apiKey,
|
|
1559
1768
|
model: activeRuntime.model,
|
|
1769
|
+
runtimeMode: effectiveRuntimeMode,
|
|
1560
1770
|
...(activeRuntime.requestShape ? { requestShape: activeRuntime.requestShape } : {}),
|
|
1561
1771
|
...(activeRuntime.baseUrl ? { baseUrl: activeRuntime.baseUrl } : {}),
|
|
1562
1772
|
...(activeRuntime.apiQuery ? { apiQuery: activeRuntime.apiQuery } : {}),
|
|
@@ -1687,12 +1897,13 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1687
1897
|
default: return undefined;
|
|
1688
1898
|
}
|
|
1689
1899
|
}
|
|
1690
|
-
async resolveStepRuntime(step, profile, apiKey) {
|
|
1900
|
+
async resolveStepRuntime(step, profile, apiKey, opts) {
|
|
1901
|
+
const effectiveRuntimeMode = (0, runtime_context_1.normalizeRuntimeMode)(opts?.runtimeMode || this.runtimeMode);
|
|
1691
1902
|
if (step.provider === 'claude-cli') {
|
|
1692
|
-
return this.resolveClaudeWorkflowRuntime(step, profile);
|
|
1903
|
+
return this.resolveClaudeWorkflowRuntime(step, profile, opts?.disallowCliRuntime === true && effectiveRuntimeMode !== 'local_desktop', effectiveRuntimeMode);
|
|
1693
1904
|
}
|
|
1694
1905
|
if (step.provider === 'codex-cli') {
|
|
1695
|
-
return this.resolveCodexWorkflowRuntime(step, profile);
|
|
1906
|
+
return this.resolveCodexWorkflowRuntime(step, profile, opts?.disallowCliRuntime === true && effectiveRuntimeMode !== 'local_desktop', effectiveRuntimeMode);
|
|
1696
1907
|
}
|
|
1697
1908
|
const profileConnection = profile?.provider_connection_id
|
|
1698
1909
|
? data.getProviderConnection(profile.provider_connection_id)
|
|
@@ -1701,6 +1912,22 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1701
1912
|
|| profileConnection?.api_key_enc
|
|
1702
1913
|
|| apiKey
|
|
1703
1914
|
|| this.resolveApiKey(step.provider);
|
|
1915
|
+
if (effectiveRuntimeMode !== 'local_desktop' && !resolvedKey && step.provider === 'openai') {
|
|
1916
|
+
const preferredModel = (0, subscription_runtime_1.resolveSubscriptionApiModel)(step.model || profile?.model, data.findProviderConnection('openai')?.default_model || undefined) || (step.model || profile?.model || 'default').trim() || 'default';
|
|
1917
|
+
const subscriptionRuntime = await (0, subscription_runtime_1.resolveCodexSubscriptionRuntime)({
|
|
1918
|
+
preferredModel,
|
|
1919
|
+
});
|
|
1920
|
+
if (subscriptionRuntime) {
|
|
1921
|
+
return {
|
|
1922
|
+
providerName: subscriptionRuntime.providerName,
|
|
1923
|
+
apiKey: subscriptionRuntime.apiKey,
|
|
1924
|
+
model: subscriptionRuntime.model,
|
|
1925
|
+
runtimeLabel: 'Subscription API (Token)',
|
|
1926
|
+
...(subscriptionRuntime.baseUrl ? { baseUrl: subscriptionRuntime.baseUrl } : {}),
|
|
1927
|
+
...(subscriptionRuntime.apiStyle ? { apiStyle: subscriptionRuntime.apiStyle } : {}),
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1704
1931
|
if (!resolvedKey) {
|
|
1705
1932
|
throw new Error(`No API key for provider ${step.provider}`);
|
|
1706
1933
|
}
|
|
@@ -1711,7 +1938,15 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1711
1938
|
runtimeLabel: 'API Key',
|
|
1712
1939
|
};
|
|
1713
1940
|
}
|
|
1714
|
-
async resolveClaudeWorkflowRuntime(step, profile) {
|
|
1941
|
+
async resolveClaudeWorkflowRuntime(step, profile, disallowCliRuntime = false, runtimeMode = this.runtimeMode) {
|
|
1942
|
+
if (runtimeMode === 'local_desktop') {
|
|
1943
|
+
return {
|
|
1944
|
+
providerName: 'claude-cli',
|
|
1945
|
+
apiKey: 'cli-auth',
|
|
1946
|
+
model: (step.model || profile?.model || 'default').trim() || 'default',
|
|
1947
|
+
runtimeLabel: 'Subscription CLI',
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1715
1950
|
const preferredModel = (0, subscription_runtime_1.resolveSubscriptionApiModel)(step.model || profile?.model, data.findProviderConnection('anthropic')?.default_model || data.findProviderConnection('claude-cli')?.default_model || undefined) || (step.model || profile?.model || 'default').trim() || 'default';
|
|
1716
1951
|
const apiKeyFallback = data.findProviderConnection('anthropic')?.api_key_enc || process.env.ANTHROPIC_API_KEY;
|
|
1717
1952
|
const apiFallbackConfig = apiKeyFallback
|
|
@@ -1722,6 +1957,26 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1722
1957
|
runtimeLabel: 'API Key',
|
|
1723
1958
|
}
|
|
1724
1959
|
: undefined;
|
|
1960
|
+
if (disallowCliRuntime) {
|
|
1961
|
+
const subscriptionRuntime = await (0, subscription_runtime_1.resolveClaudeSubscriptionRuntime)({
|
|
1962
|
+
preferredModel,
|
|
1963
|
+
});
|
|
1964
|
+
if (subscriptionRuntime) {
|
|
1965
|
+
return {
|
|
1966
|
+
providerName: subscriptionRuntime.providerName,
|
|
1967
|
+
apiKey: subscriptionRuntime.apiKey,
|
|
1968
|
+
model: subscriptionRuntime.model,
|
|
1969
|
+
runtimeLabel: 'Subscription API (Token)',
|
|
1970
|
+
...(subscriptionRuntime.authMode ? { authMode: subscriptionRuntime.authMode } : {}),
|
|
1971
|
+
...(subscriptionRuntime.baseUrl ? { baseUrl: subscriptionRuntime.baseUrl } : {}),
|
|
1972
|
+
...(subscriptionRuntime.apiQuery ? { apiQuery: subscriptionRuntime.apiQuery } : {}),
|
|
1973
|
+
...(subscriptionRuntime.extraHeaders ? { extraHeaders: subscriptionRuntime.extraHeaders } : {}),
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
if (apiFallbackConfig)
|
|
1977
|
+
return apiFallbackConfig;
|
|
1978
|
+
throw new Error('No non-CLI Anthropic runtime is available for this workflow step.');
|
|
1979
|
+
}
|
|
1725
1980
|
const cliFallbackConfig = {
|
|
1726
1981
|
providerName: 'claude-cli',
|
|
1727
1982
|
apiKey: 'cli-auth',
|
|
@@ -1731,11 +1986,43 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1731
1986
|
};
|
|
1732
1987
|
return cliFallbackConfig;
|
|
1733
1988
|
}
|
|
1734
|
-
async resolveCodexWorkflowRuntime(step, profile) {
|
|
1989
|
+
async resolveCodexWorkflowRuntime(step, profile, disallowCliRuntime = false, runtimeMode = this.runtimeMode) {
|
|
1990
|
+
if (runtimeMode === 'local_desktop') {
|
|
1991
|
+
return {
|
|
1992
|
+
providerName: 'codex-cli',
|
|
1993
|
+
apiKey: 'cli-auth',
|
|
1994
|
+
model: (step.model || profile?.model || 'default').trim() || 'default',
|
|
1995
|
+
runtimeLabel: 'Subscription CLI',
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1735
1998
|
const preferredModel = (0, subscription_runtime_1.resolveSubscriptionApiModel)(step.model || profile?.model, data.findProviderConnection('openai')?.default_model || data.findProviderConnection('codex-cli')?.default_model || undefined)
|
|
1736
1999
|
|| (step.model || profile?.model || 'default').trim()
|
|
1737
2000
|
|| 'default';
|
|
2001
|
+
const subscriptionRuntime = await (0, subscription_runtime_1.resolveCodexSubscriptionRuntime)({
|
|
2002
|
+
preferredModel,
|
|
2003
|
+
});
|
|
2004
|
+
if (subscriptionRuntime) {
|
|
2005
|
+
return {
|
|
2006
|
+
providerName: subscriptionRuntime.providerName,
|
|
2007
|
+
apiKey: subscriptionRuntime.apiKey,
|
|
2008
|
+
model: subscriptionRuntime.model,
|
|
2009
|
+
runtimeLabel: 'Subscription API (Token)',
|
|
2010
|
+
...(subscriptionRuntime.baseUrl ? { baseUrl: subscriptionRuntime.baseUrl } : {}),
|
|
2011
|
+
...(subscriptionRuntime.apiStyle ? { apiStyle: subscriptionRuntime.apiStyle } : {}),
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
1738
2014
|
const apiKeyFallback = data.findProviderConnection('openai')?.api_key_enc || process.env.OPENAI_API_KEY;
|
|
2015
|
+
if (disallowCliRuntime) {
|
|
2016
|
+
if (apiKeyFallback) {
|
|
2017
|
+
return {
|
|
2018
|
+
providerName: 'openai',
|
|
2019
|
+
apiKey: apiKeyFallback,
|
|
2020
|
+
model: preferredModel,
|
|
2021
|
+
runtimeLabel: 'API Key',
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
throw new Error('No non-CLI OpenAI runtime is available for this workflow step.');
|
|
2025
|
+
}
|
|
1739
2026
|
return {
|
|
1740
2027
|
providerName: 'codex-cli',
|
|
1741
2028
|
apiKey: 'cli-auth',
|
|
@@ -1860,7 +2147,7 @@ class WorkflowEngine extends events_1.EventEmitter {
|
|
|
1860
2147
|
async replanRemainingSteps(steps, failedStep, agents, defaultProfile) {
|
|
1861
2148
|
if (steps.length <= 1)
|
|
1862
2149
|
return null;
|
|
1863
|
-
const clerk = (0, clerk_model_1.getClerk)();
|
|
2150
|
+
const clerk = (0, clerk_model_1.getClerk)({ runtimeMode: this.runtimeMode });
|
|
1864
2151
|
if (!clerk)
|
|
1865
2152
|
return null;
|
|
1866
2153
|
const completedSummary = steps
|
|
@@ -1951,15 +2238,24 @@ Rules:
|
|
|
1951
2238
|
}
|
|
1952
2239
|
exports.WorkflowEngine = WorkflowEngine;
|
|
1953
2240
|
// ─── Singleton ───────────────────────────────────────────────────
|
|
1954
|
-
|
|
1955
|
-
function getWorkflowEngine(projectDir) {
|
|
1956
|
-
|
|
1957
|
-
|
|
2241
|
+
const _workflowEngines = new Map();
|
|
2242
|
+
function getWorkflowEngine(projectDir, runtimeMode) {
|
|
2243
|
+
const effectiveRuntimeMode = (0, runtime_context_1.normalizeRuntimeMode)(runtimeMode || (0, runtime_context_1.getDefaultRuntimeMode)());
|
|
2244
|
+
const existing = _workflowEngines.get(effectiveRuntimeMode);
|
|
2245
|
+
if (existing) {
|
|
2246
|
+
if (!projectDir || existing.projectDir === projectDir) {
|
|
2247
|
+
return existing;
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
if (projectDir) {
|
|
2251
|
+
const engine = new WorkflowEngine(projectDir, effectiveRuntimeMode);
|
|
2252
|
+
_workflowEngines.set(effectiveRuntimeMode, engine);
|
|
2253
|
+
return engine;
|
|
1958
2254
|
}
|
|
1959
|
-
if (!
|
|
2255
|
+
if (!_workflowEngines.size) {
|
|
1960
2256
|
throw new Error('Workflow engine not initialized. Call getWorkflowEngine(projectDir) first.');
|
|
1961
2257
|
}
|
|
1962
|
-
|
|
2258
|
+
throw new Error(`Workflow engine not initialized for runtime ${effectiveRuntimeMode}. Call getWorkflowEngine(projectDir, runtimeMode) first.`);
|
|
1963
2259
|
}
|
|
1964
2260
|
// ─── JSON Parsing Helper ─────────────────────────────────────────
|
|
1965
2261
|
function parseJsonResponse(text) {
|