openfox 2.0.113 → 2.0.115
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/CHANGELOG.md +43 -0
- package/dist/CHANGELOG.md +43 -0
- package/dist/agent-defaults/builder.agent.md +1 -0
- package/dist/agent-defaults/planner.agent.md +1 -0
- package/dist/{backend-P3GHHLHJ.js → backend-LVYHLGQJ.js} +2 -2
- package/dist/{chat-handler-PEK5JDHO.js → chat-handler-5SP265CR.js} +32 -26
- package/dist/{chunk-HKD4IMOE.js → chunk-27NABDXB.js} +2 -2
- package/dist/{chunk-FF2EKUQO.js → chunk-2FCLCUVR.js} +6 -6
- package/dist/{chunk-ONONXV3N.js → chunk-37QLXLML.js} +4 -4
- package/dist/{chunk-YB6WYVNY.js → chunk-3KBRMBTP.js} +2 -2
- package/dist/{chunk-YGPPFVLJ.js → chunk-3NPLERPQ.js} +17 -17
- package/dist/{chunk-XQR76MQ5.js → chunk-3PG65AXG.js} +18 -3
- package/dist/chunk-3QQAIUZI.js +957 -0
- package/dist/{chunk-PWXBIUEY.js → chunk-5PT4UMRR.js} +89 -1
- package/dist/chunk-5V3BF67X.js +7 -0
- package/dist/{chunk-6UPBZ2ZR.js → chunk-6KPJLELC.js} +4 -4
- package/dist/chunk-6RGACY4E.js +1081 -0
- package/dist/{chunk-BNFGDTEU.js → chunk-AESOGRFK.js} +2 -2
- package/dist/{chunk-YHEQTVFV.js → chunk-AZHWLLPP.js} +1 -1
- package/dist/{chunk-TCGWZBTS.js → chunk-BVVCRIVM.js} +4 -4
- package/dist/{chunk-EMI3YYLP.js → chunk-BWVO5WFK.js} +4 -4
- package/dist/{chunk-CP44U7BT.js → chunk-ERNWAKUB.js} +7 -7
- package/dist/{chunk-WWTYWLDM.js → chunk-HD6Y77RO.js} +2 -2
- package/dist/{chunk-4NMGX5F2.js → chunk-I7FY2LTR.js} +2 -2
- package/dist/{chunk-5VYSKVZT.js → chunk-JFK4GKQD.js} +321 -54
- package/dist/{chunk-43M7QZMX.js → chunk-JGWHRYWG.js} +4 -4
- package/dist/{chunk-Y64PEWKP.js → chunk-JLAR2CMV.js} +2 -2
- package/dist/{chunk-BYSUSSSC.js → chunk-LNNBLB6G.js} +48 -151
- package/dist/{chunk-V4664YKR.js → chunk-LTYL6ZGH.js} +2 -2
- package/dist/{chunk-R2MTWC7C.js → chunk-M6X6AIEG.js} +6 -4
- package/dist/{chunk-VGRVPYZ5.js → chunk-O6DU226D.js} +2 -2
- package/dist/{chunk-TEQEDOWI.js → chunk-OCBBAEMT.js} +288 -316
- package/dist/{chunk-SNLLOXCM.js → chunk-OCSTEGK7.js} +3 -3
- package/dist/{chunk-HGOTZWON.js → chunk-OKVHMKE2.js} +60 -1064
- package/dist/chunk-OSXPQSLW.js +128 -0
- package/dist/{chunk-O2YSYW2F.js → chunk-TFF5T3K3.js} +10 -10
- package/dist/{chunk-3R43CMLS.js → chunk-TGLVWAFC.js} +11 -11
- package/dist/{chunk-L737Y63F.js → chunk-TOKQ2UVG.js} +2 -2
- package/dist/{chunk-AZFZE36I.js → chunk-VWEATA7R.js} +18 -8
- package/dist/{chunk-BKCW3JLL.js → chunk-X25Q6RAV.js} +1 -1
- package/dist/{chunk-JR54MDZI.js → chunk-YX3OQBNX.js} +3 -3
- package/dist/{chunk-EC5T5A6O.js → chunk-ZTOG6LJD.js} +3 -3
- package/dist/cli/dev.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/client-KE7EDI2D.js +16 -0
- package/dist/{client-pure-VFZIIK4S.js → client-pure-66Z6FWS6.js} +2 -2
- package/dist/command-defaults/mark-done.command.md +14 -0
- package/dist/compactor-SKVC2VNL.js +32 -0
- package/dist/{dynamic-context-F2XTQJR5.js → dynamic-context-QNB4ELLP.js} +10 -9
- package/dist/{events-224IO5NJ.js → events-5PLO4QJU.js} +9 -7
- package/dist/{folding-YTTTVS56.js → folding-CJWYFUKD.js} +7 -5
- package/dist/http-client-KLODRQC2.js +11 -0
- package/dist/{inspect-proxy-B7LGGTM7.js → inspect-proxy-K6QKEYDT.js} +4 -4
- package/dist/launch-EOXVWLOP.js +52 -0
- package/dist/{manager-4QGXI6WZ.js → manager-LZ3JQ443.js} +5 -5
- package/dist/{model-overrides-OY2YKUNV.js → model-overrides-5UVW3KTE.js} +4 -4
- package/dist/orchestrator-ED3KFPXO.js +63 -0
- package/dist/package.json +3 -3
- package/dist/{path-security-PFMZY7IA.js → path-security-B6EZC63L.js} +13 -11
- package/dist/{platform-QNGCKX4L.js → platform-6RPPBHKH.js} +4 -4
- package/dist/{processor-NYPJ5PFA.js → processor-226ZOTRI.js} +31 -25
- package/dist/{project-creator-AWP2JUFS.js → project-creator-VBFE35NI.js} +3 -3
- package/dist/{projects-C4353IQK.js → projects-AXHZOBR2.js} +5 -3
- package/dist/{protocol-CBT1d98i.d.ts → protocol-JdxRpPxM.d.ts} +21 -3
- package/dist/{protocol-LZZ7C4VQ.js → protocol-RKSWKNXI.js} +3 -3
- package/dist/provider/index.d.ts +4 -4
- package/dist/{provider-PIRPN2CC.js → provider-DL4ZWQY7.js} +8 -8
- package/dist/{provider-manager-GNEPR6UN.js → provider-manager-6CLRMGDE.js} +8 -8
- package/dist/{registry-VCKDSVKA.js → registry-5XI5NDBF.js} +6 -4
- package/dist/{serve-6DRHQLCG.js → serve-DARV4NJG.js} +36 -33
- package/dist/server/index.d.ts +13 -6
- package/dist/server/index.js +35 -32
- package/dist/server-LQJ7MBDS.js +55 -0
- package/dist/service-4JKTGPRG.js +25 -0
- package/dist/{session-overrides-RNKV35MU.js → session-overrides-AZKV6BRD.js} +8 -6
- package/dist/{sessions-D5CEPQGU.js → sessions-752IZIIK.js} +7 -5
- package/dist/{settings-BNRNX6IW.js → settings-2AHDKCD7.js} +3 -3
- package/dist/shared/index.d.ts +3 -3
- package/dist/shared/index.js +1 -1
- package/dist/skill-defaults/workflows/SKILL.md +24 -6
- package/dist/tasks-XJXHU623.js +217 -0
- package/dist/{tools-2B363F7L.js → tools-QYYNFMGU.js} +31 -23
- package/dist/{types-cVfqOj6M.d.ts → types-CIVhQMvD.d.ts} +1 -1
- package/dist/{types-BAxh20ZL.d.ts → types-Df7NM9b5.d.ts} +76 -1
- package/dist/{update-IQMRYN7J.js → update-2REP2WB6.js} +2 -2
- package/dist/web/assets/{index-CjWC6EQ4.css → index-BmILAZrT.css} +1 -1
- package/dist/web/assets/index-M7ToECa4.js +324 -0
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/dist/workflow-defaults/default.workflow.json +5 -3
- package/dist/{workspace-YUNHV4WS.js → workspace-RMZVTFJD.js} +4 -4
- package/package.json +3 -3
- package/dist/chunk-B2PHPBFU.js +0 -7
- package/dist/client-3LJGD6KY.js +0 -16
- package/dist/compactor-ZBNKANHV.js +0 -30
- package/dist/http-client-YC3UA5OZ.js +0 -11
- package/dist/orchestrator-JKBY7B3P.js +0 -57
- package/dist/server-HUHFJW7U.js +0 -48
- package/dist/web/assets/index-DBUtUSHK.js +0 -323
|
@@ -0,0 +1,957 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runAgentTurn
|
|
3
|
+
} from "./chunk-3NPLERPQ.js";
|
|
4
|
+
import {
|
|
5
|
+
executeSubAgent,
|
|
6
|
+
getToolRegistryForAgent
|
|
7
|
+
} from "./chunk-JFK4GKQD.js";
|
|
8
|
+
import {
|
|
9
|
+
TurnMetrics,
|
|
10
|
+
createMessageStartEvent
|
|
11
|
+
} from "./chunk-YX3OQBNX.js";
|
|
12
|
+
import {
|
|
13
|
+
checkAborted,
|
|
14
|
+
decodeUtf8,
|
|
15
|
+
spawnShellProcess
|
|
16
|
+
} from "./chunk-ZTOG6LJD.js";
|
|
17
|
+
import {
|
|
18
|
+
terminateProcessTree
|
|
19
|
+
} from "./chunk-I72ACQ6R.js";
|
|
20
|
+
import {
|
|
21
|
+
gitSpawnEnv
|
|
22
|
+
} from "./chunk-J7GAZGZT.js";
|
|
23
|
+
import {
|
|
24
|
+
findWorkflowById,
|
|
25
|
+
loadAllWorkflows,
|
|
26
|
+
loadDefaultWorkflows,
|
|
27
|
+
loadProjectWorkflows,
|
|
28
|
+
loadUserWorkflows,
|
|
29
|
+
normalizeWorkflowScope
|
|
30
|
+
} from "./chunk-6RGACY4E.js";
|
|
31
|
+
import {
|
|
32
|
+
getCurrentContextWindowId,
|
|
33
|
+
getEventStore
|
|
34
|
+
} from "./chunk-ERNWAKUB.js";
|
|
35
|
+
import {
|
|
36
|
+
createChatMessageMessage,
|
|
37
|
+
createSessionRunningMessage
|
|
38
|
+
} from "./chunk-I7FY2LTR.js";
|
|
39
|
+
import {
|
|
40
|
+
findAgentById,
|
|
41
|
+
getRuntimeConfig,
|
|
42
|
+
loadAllAgentsDefault,
|
|
43
|
+
resolveDefaultAgentId
|
|
44
|
+
} from "./chunk-LNNBLB6G.js";
|
|
45
|
+
import {
|
|
46
|
+
logger
|
|
47
|
+
} from "./chunk-K44MW7JJ.js";
|
|
48
|
+
import {
|
|
49
|
+
computeSessionStats
|
|
50
|
+
} from "./chunk-VUQCQXXJ.js";
|
|
51
|
+
import {
|
|
52
|
+
createServerMessage
|
|
53
|
+
} from "./chunk-X25Q6RAV.js";
|
|
54
|
+
import {
|
|
55
|
+
getGlobalConfigDir
|
|
56
|
+
} from "./chunk-CQGTEGKL.js";
|
|
57
|
+
|
|
58
|
+
// src/server/workflows/types.ts
|
|
59
|
+
var TERMINAL_DONE = "$done";
|
|
60
|
+
var TERMINAL_BLOCKED = "$blocked";
|
|
61
|
+
|
|
62
|
+
// src/server/git/diff.ts
|
|
63
|
+
import { spawn } from "child_process";
|
|
64
|
+
function getGitDiffFiles(cwd) {
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
const env = gitSpawnEnv();
|
|
67
|
+
const diffProc = spawn("git", ["diff", "--name-status", "HEAD"], {
|
|
68
|
+
cwd,
|
|
69
|
+
env,
|
|
70
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
71
|
+
windowsHide: true
|
|
72
|
+
});
|
|
73
|
+
const statusProc = spawn("git", ["status", "--porcelain"], {
|
|
74
|
+
cwd,
|
|
75
|
+
env,
|
|
76
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
77
|
+
windowsHide: true
|
|
78
|
+
});
|
|
79
|
+
let diffStdout = "";
|
|
80
|
+
let statusStdout = "";
|
|
81
|
+
let diffExited = false;
|
|
82
|
+
let statusExited = false;
|
|
83
|
+
let diffCode = null;
|
|
84
|
+
let statusCode = null;
|
|
85
|
+
const collect = () => {
|
|
86
|
+
if (!diffExited || !statusExited) return;
|
|
87
|
+
const files = [];
|
|
88
|
+
if (diffCode === 0) {
|
|
89
|
+
for (const line of diffStdout.split("\n")) {
|
|
90
|
+
if (!line.trim()) continue;
|
|
91
|
+
const [statusChar, ...pathParts] = line.split(" ");
|
|
92
|
+
const path = pathParts.join(" ") || statusChar || "";
|
|
93
|
+
if (!path) continue;
|
|
94
|
+
const status = statusChar === "A" ? "added" : statusChar === "D" ? "deleted" : "modified";
|
|
95
|
+
files.push({ path, status, additions: 0, deletions: 0 });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (statusCode === 0) {
|
|
99
|
+
for (const line of statusStdout.split("\n")) {
|
|
100
|
+
if (!line.startsWith("?? ")) continue;
|
|
101
|
+
const path = line.slice(3).trim();
|
|
102
|
+
if (!path) continue;
|
|
103
|
+
files.push({ path, status: "added", additions: 0, deletions: 0 });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
resolve(files);
|
|
107
|
+
};
|
|
108
|
+
diffProc.stdout.on("data", (data) => {
|
|
109
|
+
diffStdout += data.toString();
|
|
110
|
+
});
|
|
111
|
+
statusProc.stdout.on("data", (data) => {
|
|
112
|
+
statusStdout += data.toString();
|
|
113
|
+
});
|
|
114
|
+
diffProc.on("close", (code) => {
|
|
115
|
+
diffExited = true;
|
|
116
|
+
diffCode = code;
|
|
117
|
+
collect();
|
|
118
|
+
});
|
|
119
|
+
statusProc.on("close", (code) => {
|
|
120
|
+
statusExited = true;
|
|
121
|
+
statusCode = code;
|
|
122
|
+
collect();
|
|
123
|
+
});
|
|
124
|
+
diffProc.on("error", () => {
|
|
125
|
+
diffExited = true;
|
|
126
|
+
diffCode = 1;
|
|
127
|
+
collect();
|
|
128
|
+
});
|
|
129
|
+
statusProc.on("error", () => {
|
|
130
|
+
statusExited = true;
|
|
131
|
+
statusCode = 1;
|
|
132
|
+
collect();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async function formatGitDiffFiles(cwd) {
|
|
137
|
+
const files = await getGitDiffFiles(cwd);
|
|
138
|
+
if (files.length === 0) return "(none)";
|
|
139
|
+
return files.map((f) => `- ${f.path} (${f.status})`).join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/server/workflows/shell.ts
|
|
143
|
+
function executeShellCommand(command, cwd, timeout, signal) {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
if (checkAborted(signal)) {
|
|
146
|
+
reject(new Error("Aborted"));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const proc = spawnShellProcess(command, cwd, signal);
|
|
150
|
+
const stdoutChunks = [];
|
|
151
|
+
const stderrChunks = [];
|
|
152
|
+
let killed = false;
|
|
153
|
+
const timer = setTimeout(() => {
|
|
154
|
+
killed = true;
|
|
155
|
+
void terminateProcessTree(proc);
|
|
156
|
+
resolve({
|
|
157
|
+
exitCode: 1,
|
|
158
|
+
stdout: decodeUtf8(stdoutChunks),
|
|
159
|
+
stderr: decodeUtf8(stderrChunks) + "\nCommand timed out",
|
|
160
|
+
success: false
|
|
161
|
+
});
|
|
162
|
+
}, timeout);
|
|
163
|
+
const onAbort = () => {
|
|
164
|
+
if (!killed) {
|
|
165
|
+
killed = true;
|
|
166
|
+
void terminateProcessTree(proc, { immediate: true });
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
reject(new Error("Aborted"));
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
signal?.addEventListener("abort", onAbort);
|
|
172
|
+
proc.stdout?.on("data", (data) => {
|
|
173
|
+
stdoutChunks.push(data);
|
|
174
|
+
});
|
|
175
|
+
proc.stderr?.on("data", (data) => {
|
|
176
|
+
stderrChunks.push(data);
|
|
177
|
+
});
|
|
178
|
+
proc.on("close", (code) => {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
signal?.removeEventListener("abort", onAbort);
|
|
181
|
+
if (killed) return;
|
|
182
|
+
const exitCode = code ?? 1;
|
|
183
|
+
resolve({
|
|
184
|
+
exitCode,
|
|
185
|
+
stdout: decodeUtf8(stdoutChunks),
|
|
186
|
+
stderr: decodeUtf8(stderrChunks),
|
|
187
|
+
success: exitCode === 0
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
proc.on("error", (err) => {
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
signal?.removeEventListener("abort", onAbort);
|
|
193
|
+
if (killed) return;
|
|
194
|
+
resolve({ exitCode: 1, stdout: decodeUtf8(stdoutChunks), stderr: err.message, success: false });
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/server/workflows/executor.ts
|
|
200
|
+
var TEMPLATE_VARIABLES = [
|
|
201
|
+
{ name: "workdir", description: "Working directory of the session" },
|
|
202
|
+
{ name: "reason", description: 'Human-readable reason (e.g. "2 criteria remaining")' },
|
|
203
|
+
{
|
|
204
|
+
name: "stepOutput",
|
|
205
|
+
description: "Structured output from the previous step (content, stdout, stderr, exitCode, etc.)"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "verifierFindings",
|
|
209
|
+
description: "@deprecated Use stepOutput.content instead. Output from the last sub-agent step"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "previousStepOutput",
|
|
213
|
+
description: "@deprecated Use stepOutput.stdout instead. Output from the last shell step"
|
|
214
|
+
},
|
|
215
|
+
{ name: "criteriaCount", description: "Total number of criteria" },
|
|
216
|
+
{ name: "pendingCount", description: "Number of pending/failed criteria" },
|
|
217
|
+
{ name: "criteriaList", description: "Formatted list of all criteria with status" },
|
|
218
|
+
{ name: "modifiedFiles", description: "List of modified files" }
|
|
219
|
+
];
|
|
220
|
+
function formatCriteriaList(entries) {
|
|
221
|
+
if (entries.length === 0) return "(none)";
|
|
222
|
+
return entries.map((e) => {
|
|
223
|
+
const status = e.status === "passed" ? "[PASSED]" : e.status === "completed" ? "[NEEDS VERIFICATION]" : e.status === "failed" ? "[FAILED]" : "[NOT COMPLETED]";
|
|
224
|
+
return `- **${e.id}** ${status}: ${e.description}`;
|
|
225
|
+
}).join("\n");
|
|
226
|
+
}
|
|
227
|
+
async function formatModifiedFiles(workdir) {
|
|
228
|
+
return formatGitDiffFiles(workdir);
|
|
229
|
+
}
|
|
230
|
+
function resolveTemplate(template, ctx) {
|
|
231
|
+
let result = template;
|
|
232
|
+
for (const { name } of TEMPLATE_VARIABLES) {
|
|
233
|
+
if (name === "stepOutput" || name === "verifierFindings" || name === "previousStepOutput") continue;
|
|
234
|
+
const value = String(ctx[name]);
|
|
235
|
+
result = result.replace(new RegExp(`\\{\\{${name}\\}\\}`, "g"), value);
|
|
236
|
+
}
|
|
237
|
+
result = result.replace(/\{\{stepOutput\.(\w+)\}\}/g, (_, key) => ctx.stepOutput[key] ?? "");
|
|
238
|
+
result = result.replace(/\{\{verifierFindings\}\}/g, ctx.stepOutput["content"] ?? "");
|
|
239
|
+
result = result.replace(/\{\{previousStepOutput\}\}/g, ctx.stepOutput["stdout"] ?? "");
|
|
240
|
+
for (const [key, value] of Object.entries(ctx.params)) {
|
|
241
|
+
result = result.replaceAll(`{{${key}}}`, value);
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
function evaluateCondition(condition, stepOutcome, metadataEntries) {
|
|
246
|
+
switch (condition.type) {
|
|
247
|
+
case "step_result":
|
|
248
|
+
if (!stepOutcome) return false;
|
|
249
|
+
return stepOutcome.result === condition.result;
|
|
250
|
+
case "metadata_all_match": {
|
|
251
|
+
if (!metadataEntries) return false;
|
|
252
|
+
const entries = metadataEntries[condition.key];
|
|
253
|
+
if (!entries || entries.length === 0) return true;
|
|
254
|
+
return entries.every((e) => e[condition.field] === condition.value);
|
|
255
|
+
}
|
|
256
|
+
case "metadata_all_in": {
|
|
257
|
+
if (!metadataEntries) return false;
|
|
258
|
+
const entries = metadataEntries[condition.key];
|
|
259
|
+
if (!entries || entries.length === 0) return true;
|
|
260
|
+
return entries.every((e) => condition.values.includes(e[condition.field]));
|
|
261
|
+
}
|
|
262
|
+
case "always":
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function findMatchingTransition(transitions, stepOutcome, metadataEntries) {
|
|
267
|
+
for (const transition of transitions) {
|
|
268
|
+
if (evaluateCondition(transition.when, stepOutcome, metadataEntries)) {
|
|
269
|
+
return transition;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
var DEFAULT_USER_RESULT = "continue";
|
|
275
|
+
var CONTINUE_CHOICE_ID = "continue";
|
|
276
|
+
function userStepChoices(step) {
|
|
277
|
+
const seen = /* @__PURE__ */ new Set();
|
|
278
|
+
const choices = [];
|
|
279
|
+
for (const t of step.transitions) {
|
|
280
|
+
if (t.when.type === "step_result") {
|
|
281
|
+
const id = t.when.result;
|
|
282
|
+
if (seen.has(id)) continue;
|
|
283
|
+
seen.add(id);
|
|
284
|
+
choices.push({ id, label: id, goto: t.goto });
|
|
285
|
+
} else if (t.when.type === "always") {
|
|
286
|
+
if (seen.has(CONTINUE_CHOICE_ID)) continue;
|
|
287
|
+
seen.add(CONTINUE_CHOICE_ID);
|
|
288
|
+
choices.push({ id: CONTINUE_CHOICE_ID, label: "Continue", goto: t.goto });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return choices;
|
|
292
|
+
}
|
|
293
|
+
function emitWorkflowMessage(eventStore, sessionId, content, windowOptions, onMessage) {
|
|
294
|
+
const msgId = crypto.randomUUID();
|
|
295
|
+
eventStore.append(
|
|
296
|
+
sessionId,
|
|
297
|
+
createMessageStartEvent(msgId, "user", content, {
|
|
298
|
+
...windowOptions ?? {},
|
|
299
|
+
isSystemGenerated: true,
|
|
300
|
+
messageKind: "correction",
|
|
301
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
eventStore.append(sessionId, { type: "message.done", data: { messageId: msgId } });
|
|
305
|
+
if (onMessage) {
|
|
306
|
+
onMessage(
|
|
307
|
+
createChatMessageMessage({
|
|
308
|
+
id: msgId,
|
|
309
|
+
role: "user",
|
|
310
|
+
content,
|
|
311
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
312
|
+
isSystemGenerated: true,
|
|
313
|
+
messageKind: "correction",
|
|
314
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return msgId;
|
|
319
|
+
}
|
|
320
|
+
function injectGenericKickoff(sessionId) {
|
|
321
|
+
const eventStore = getEventStore();
|
|
322
|
+
const windowOpts = getCurrentWindowMessageOptions(sessionId);
|
|
323
|
+
const msgId = crypto.randomUUID();
|
|
324
|
+
eventStore.append(
|
|
325
|
+
sessionId,
|
|
326
|
+
createMessageStartEvent(msgId, "user", "Proceed with the current step.", {
|
|
327
|
+
...windowOpts ?? {},
|
|
328
|
+
isSystemGenerated: true,
|
|
329
|
+
messageKind: "auto-prompt",
|
|
330
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
331
|
+
})
|
|
332
|
+
);
|
|
333
|
+
eventStore.append(sessionId, { type: "message.done", data: { messageId: msgId } });
|
|
334
|
+
}
|
|
335
|
+
function getCurrentWindowMessageOptions(sessionId) {
|
|
336
|
+
const contextWindowId = getCurrentContextWindowId(sessionId);
|
|
337
|
+
return contextWindowId ? { contextWindowId } : void 0;
|
|
338
|
+
}
|
|
339
|
+
function buildReason(metadataEntries) {
|
|
340
|
+
const entries = metadataEntries?.["criteria"] ?? [];
|
|
341
|
+
const remaining = entries.filter((e) => e.status !== "passed");
|
|
342
|
+
return `${remaining.length} criteria remaining`;
|
|
343
|
+
}
|
|
344
|
+
async function executeWorkflow(workflow, options, subGroup) {
|
|
345
|
+
const { sessionManager, sessionId, llmClient, signal, onMessage } = options;
|
|
346
|
+
const eventStore = getEventStore();
|
|
347
|
+
const startTime = performance.now();
|
|
348
|
+
let iterations = 0;
|
|
349
|
+
const activeSteps = subGroup ? workflow.steps.filter((s) => s.subGroup === subGroup) : workflow.steps;
|
|
350
|
+
const resumeFromStep = options.resumeFromStep;
|
|
351
|
+
const isResume = !!resumeFromStep;
|
|
352
|
+
let resumeConsumed = false;
|
|
353
|
+
let currentStepId = isResume ? resumeFromStep : subGroup ? activeSteps[0]?.id ?? workflow.entryStep : workflow.entryStep;
|
|
354
|
+
let lastStepOutput = options.initialStepOutput ?? {};
|
|
355
|
+
const firstEntryForStep = /* @__PURE__ */ new Set();
|
|
356
|
+
const messagesBeforeWorkflow = sessionManager.requireSession(sessionId).messages.length;
|
|
357
|
+
const activeStepIds = new Set(activeSteps.map((s) => s.id));
|
|
358
|
+
const activeSubGroups = /* @__PURE__ */ new Set();
|
|
359
|
+
if (subGroup) {
|
|
360
|
+
activeSubGroups.add(subGroup);
|
|
361
|
+
}
|
|
362
|
+
const stepsById = /* @__PURE__ */ new Map();
|
|
363
|
+
for (const step of workflow.steps) {
|
|
364
|
+
stepsById.set(step.id, step);
|
|
365
|
+
}
|
|
366
|
+
if (resumeFromStep) {
|
|
367
|
+
const targetStep = stepsById.get(resumeFromStep);
|
|
368
|
+
if (!targetStep) {
|
|
369
|
+
return {
|
|
370
|
+
finalAction: {
|
|
371
|
+
type: "BLOCKED",
|
|
372
|
+
reason: `Resume target step "${resumeFromStep}" not found in workflow "${workflow.metadata.id}"`,
|
|
373
|
+
blockedCriteria: []
|
|
374
|
+
},
|
|
375
|
+
iterations: 0,
|
|
376
|
+
totalTime: (performance.now() - startTime) / 1e3
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
logger.debug("Workflow executor starting", { sessionId, workflow: workflow.metadata.id });
|
|
381
|
+
let executionId;
|
|
382
|
+
if (isResume) {
|
|
383
|
+
const activeExec = sessionManager.getActiveWorkflowExecution(sessionId);
|
|
384
|
+
if (activeExec) {
|
|
385
|
+
executionId = activeExec.id;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (workflow.startCondition && workflow.startCondition.type !== "always") {
|
|
389
|
+
const session = sessionManager.requireSession(sessionId);
|
|
390
|
+
const conditionMet = evaluateCondition(
|
|
391
|
+
workflow.startCondition,
|
|
392
|
+
null,
|
|
393
|
+
session.metadataEntries
|
|
394
|
+
);
|
|
395
|
+
if (!conditionMet) {
|
|
396
|
+
logger.debug("Workflow start condition not met", { sessionId, condition: workflow.startCondition.type });
|
|
397
|
+
return {
|
|
398
|
+
finalAction: {
|
|
399
|
+
type: "BLOCKED",
|
|
400
|
+
reason: `Start condition not met: ${workflow.startCondition.type}`,
|
|
401
|
+
blockedCriteria: []
|
|
402
|
+
},
|
|
403
|
+
iterations: 0,
|
|
404
|
+
totalTime: (performance.now() - startTime) / 1e3
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (!isResume) {
|
|
409
|
+
const startMsgId = crypto.randomUUID();
|
|
410
|
+
const startWindowOpts = getCurrentWindowMessageOptions(sessionId);
|
|
411
|
+
eventStore.append(
|
|
412
|
+
sessionId,
|
|
413
|
+
createMessageStartEvent(
|
|
414
|
+
startMsgId,
|
|
415
|
+
"user",
|
|
416
|
+
JSON.stringify({
|
|
417
|
+
workflowName: workflow.metadata.name,
|
|
418
|
+
workflowId: workflow.metadata.id,
|
|
419
|
+
workflowColor: workflow.metadata.color
|
|
420
|
+
}),
|
|
421
|
+
{ ...startWindowOpts ?? {}, isSystemGenerated: true, messageKind: "workflow-started" }
|
|
422
|
+
)
|
|
423
|
+
);
|
|
424
|
+
eventStore.append(sessionId, { type: "message.done", data: { messageId: startMsgId } });
|
|
425
|
+
executionId = crypto.randomUUID();
|
|
426
|
+
sessionManager.startWorkflow(
|
|
427
|
+
sessionId,
|
|
428
|
+
executionId,
|
|
429
|
+
workflow.metadata.id,
|
|
430
|
+
workflow.metadata.name,
|
|
431
|
+
workflow.metadata.color,
|
|
432
|
+
options.params ?? {},
|
|
433
|
+
subGroup
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (options.userMessage) {
|
|
437
|
+
sessionManager.addMessage(sessionId, {
|
|
438
|
+
role: "user",
|
|
439
|
+
content: options.userMessage.content,
|
|
440
|
+
...options.userMessage.attachments ? { attachments: options.userMessage.attachments } : {}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
while (iterations < workflow.settings.maxIterations) {
|
|
444
|
+
if (signal?.aborted) {
|
|
445
|
+
logger.debug("Workflow executor aborted \u2014 preserving execution", { sessionId, iterations });
|
|
446
|
+
return {
|
|
447
|
+
finalAction: { type: "RUN_BUILDER", reason: "Aborted" },
|
|
448
|
+
iterations,
|
|
449
|
+
totalTime: (performance.now() - startTime) / 1e3
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
iterations++;
|
|
453
|
+
const step = stepsById.get(currentStepId);
|
|
454
|
+
if (!step) {
|
|
455
|
+
logger.error("Workflow step not found", { sessionId, stepId: currentStepId });
|
|
456
|
+
return {
|
|
457
|
+
finalAction: { type: "BLOCKED", reason: `Step "${currentStepId}" not found in workflow`, blockedCriteria: [] },
|
|
458
|
+
iterations,
|
|
459
|
+
totalTime: (performance.now() - startTime) / 1e3
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
const session = sessionManager.requireSession(sessionId);
|
|
463
|
+
const currentWindowMessageOptions = getCurrentWindowMessageOptions(sessionId);
|
|
464
|
+
const criteriaEntries = session.metadataEntries["criteria"] ?? [];
|
|
465
|
+
const templateCtx = {
|
|
466
|
+
workdir: sessionManager.getEffectiveWorkdir(sessionId),
|
|
467
|
+
reason: buildReason(session.metadataEntries),
|
|
468
|
+
verifierFindings: lastStepOutput["content"] ?? "",
|
|
469
|
+
previousStepOutput: lastStepOutput["stdout"] ?? "",
|
|
470
|
+
criteriaCount: criteriaEntries.length,
|
|
471
|
+
pendingCount: criteriaEntries.filter((e) => e.status !== "passed").length,
|
|
472
|
+
criteriaList: formatCriteriaList(criteriaEntries),
|
|
473
|
+
modifiedFiles: await formatModifiedFiles(sessionManager.getEffectiveWorkdir(sessionId)),
|
|
474
|
+
stepOutput: lastStepOutput,
|
|
475
|
+
params: options.params ?? {}
|
|
476
|
+
};
|
|
477
|
+
sessionManager.setPhase(sessionId, step.phase);
|
|
478
|
+
if (executionId) {
|
|
479
|
+
sessionManager.updateWorkflowStep(
|
|
480
|
+
sessionId,
|
|
481
|
+
executionId,
|
|
482
|
+
step.id,
|
|
483
|
+
step.name,
|
|
484
|
+
workflow.metadata.id,
|
|
485
|
+
workflow.metadata.name,
|
|
486
|
+
workflow.metadata.color
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if (step.type === "agent") {
|
|
490
|
+
const agentStep = step;
|
|
491
|
+
sessionManager.setMode(sessionId, agentStep.agentId ?? resolveDefaultAgentId());
|
|
492
|
+
}
|
|
493
|
+
logger.debug("Workflow step executing", { sessionId, iteration: iterations, stepId: step.id, stepType: step.type });
|
|
494
|
+
let stepOutcome = null;
|
|
495
|
+
switch (step.type) {
|
|
496
|
+
case "agent": {
|
|
497
|
+
const agentStep = step;
|
|
498
|
+
const STEP_DONE_PROMPT = "\n\nOnce you're done, call step_done()";
|
|
499
|
+
const STEP_DONE_NUDGE = "You haven't called step_done(). If you haven't finished the task, continue and when you're finished call step_done()";
|
|
500
|
+
let promptContent;
|
|
501
|
+
let nudgeContent;
|
|
502
|
+
const isResumingCurrentStep = isResume && step.id === resumeFromStep && !resumeConsumed;
|
|
503
|
+
if (!firstEntryForStep.has(step.id) && agentStep.prompt && !isResumingCurrentStep) {
|
|
504
|
+
const resolvedPrompt = resolveTemplate(agentStep.prompt, templateCtx);
|
|
505
|
+
promptContent = resolvedPrompt + STEP_DONE_PROMPT;
|
|
506
|
+
const promptMsgId = crypto.randomUUID();
|
|
507
|
+
const msgMetadata = { type: "workflow", name: "Workflow", color: "#f59e0b" };
|
|
508
|
+
eventStore.append(
|
|
509
|
+
sessionId,
|
|
510
|
+
createMessageStartEvent(promptMsgId, "user", promptContent, {
|
|
511
|
+
...currentWindowMessageOptions ?? {},
|
|
512
|
+
isSystemGenerated: true,
|
|
513
|
+
messageKind: "auto-prompt",
|
|
514
|
+
metadata: msgMetadata
|
|
515
|
+
})
|
|
516
|
+
);
|
|
517
|
+
eventStore.append(sessionId, { type: "message.done", data: { messageId: promptMsgId } });
|
|
518
|
+
if (onMessage) {
|
|
519
|
+
onMessage(
|
|
520
|
+
createChatMessageMessage({
|
|
521
|
+
id: promptMsgId,
|
|
522
|
+
role: "user",
|
|
523
|
+
content: promptContent,
|
|
524
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
525
|
+
isSystemGenerated: true,
|
|
526
|
+
messageKind: "auto-prompt",
|
|
527
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
528
|
+
})
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
} else if (firstEntryForStep.has(step.id) && !isResumingCurrentStep) {
|
|
532
|
+
const parts = [];
|
|
533
|
+
if (agentStep.nudgePrompt) {
|
|
534
|
+
const resolvedNudge = resolveTemplate(agentStep.nudgePrompt, templateCtx);
|
|
535
|
+
parts.push(resolvedNudge);
|
|
536
|
+
}
|
|
537
|
+
parts.push(STEP_DONE_NUDGE);
|
|
538
|
+
nudgeContent = parts.join("\n\n");
|
|
539
|
+
emitWorkflowMessage(eventStore, sessionId, nudgeContent, currentWindowMessageOptions, onMessage);
|
|
540
|
+
}
|
|
541
|
+
const turnMetrics = new TurnMetrics();
|
|
542
|
+
const es = getEventStore();
|
|
543
|
+
const append = (event) => es.append(sessionId, event);
|
|
544
|
+
let stepDoneCalled = false;
|
|
545
|
+
const agentResult = await runAgentTurn(
|
|
546
|
+
{
|
|
547
|
+
sessionManager,
|
|
548
|
+
sessionId,
|
|
549
|
+
llmClient,
|
|
550
|
+
...options.statsIdentity ? { statsIdentity: options.statsIdentity } : {},
|
|
551
|
+
...signal ? { signal } : {},
|
|
552
|
+
...onMessage ? { onMessage } : {}
|
|
553
|
+
},
|
|
554
|
+
turnMetrics,
|
|
555
|
+
agentStep.agentId ?? resolveDefaultAgentId(),
|
|
556
|
+
append,
|
|
557
|
+
{
|
|
558
|
+
...!firstEntryForStep.has(step.id) && !agentStep.prompt && !isResumingCurrentStep ? { injectKickoff: () => injectGenericKickoff(sessionId) } : {},
|
|
559
|
+
onToolExecuted: (toolCall, toolResult) => {
|
|
560
|
+
if (toolCall.name === "step_done" && toolResult.success) {
|
|
561
|
+
stepDoneCalled = true;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
firstEntryForStep.add(step.id);
|
|
567
|
+
resumeConsumed = true;
|
|
568
|
+
const agentReturnValue = agentResult.returnValueResult ?? "completed";
|
|
569
|
+
lastStepOutput = {
|
|
570
|
+
...agentResult.returnValueContent ? { content: agentResult.returnValueContent } : {},
|
|
571
|
+
...agentResult.returnValueResult ? { result: agentResult.returnValueResult } : {},
|
|
572
|
+
stepDoneCalled: String(stepDoneCalled)
|
|
573
|
+
};
|
|
574
|
+
stepOutcome = { result: agentReturnValue, output: lastStepOutput };
|
|
575
|
+
if (!stepDoneCalled) {
|
|
576
|
+
logger.debug("step_done not called, looping agent step", {
|
|
577
|
+
sessionId,
|
|
578
|
+
stepId: step.id,
|
|
579
|
+
iteration: iterations
|
|
580
|
+
});
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
case "sub_agent": {
|
|
586
|
+
const subStep = step;
|
|
587
|
+
const turnMetrics = new TurnMetrics();
|
|
588
|
+
const promptTemplate = subStep.prompt ?? "Perform your task.";
|
|
589
|
+
const resolvedPrompt = resolveTemplate(promptTemplate, templateCtx);
|
|
590
|
+
const allAgents = await loadAllAgentsDefault(sessionManager.getProjectWorkdir(sessionId));
|
|
591
|
+
const agentDef = findAgentById(subStep.subAgentType, allAgents);
|
|
592
|
+
if (!agentDef) {
|
|
593
|
+
logger.error("Sub-agent definition not found", { subAgentType: subStep.subAgentType });
|
|
594
|
+
stepOutcome = { result: "error", output: {} };
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
const toolRegistry = getToolRegistryForAgent(agentDef);
|
|
598
|
+
const filteredToolRegistry = {
|
|
599
|
+
tools: toolRegistry.tools.filter((t) => t.name !== "step_done"),
|
|
600
|
+
definitions: toolRegistry.definitions.filter((d) => d.type === "function" && d.function.name !== "step_done"),
|
|
601
|
+
execute: toolRegistry.execute
|
|
602
|
+
};
|
|
603
|
+
const result = await executeSubAgent({
|
|
604
|
+
subAgentType: subStep.subAgentType,
|
|
605
|
+
prompt: resolvedPrompt,
|
|
606
|
+
sessionManager,
|
|
607
|
+
sessionId,
|
|
608
|
+
llmClient,
|
|
609
|
+
toolRegistry: filteredToolRegistry,
|
|
610
|
+
turnMetrics,
|
|
611
|
+
statsIdentity: options.statsIdentity ?? {
|
|
612
|
+
providerId: "",
|
|
613
|
+
providerName: "",
|
|
614
|
+
backend: "unknown",
|
|
615
|
+
model: llmClient.getModel()
|
|
616
|
+
},
|
|
617
|
+
...signal ? { signal } : {},
|
|
618
|
+
...onMessage ? { onMessage } : {}
|
|
619
|
+
});
|
|
620
|
+
lastStepOutput = { content: result.content ?? "", ...result.result ? { result: result.result } : {} };
|
|
621
|
+
stepOutcome = { result: result.result ?? "success", output: lastStepOutput };
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
case "shell": {
|
|
625
|
+
const shellStep = step;
|
|
626
|
+
const command = resolveTemplate(shellStep.command, templateCtx);
|
|
627
|
+
const timeout = shellStep.timeout ?? 6e4;
|
|
628
|
+
const successCodes = shellStep.successExitCodes ?? [0];
|
|
629
|
+
const shellMsgId = crypto.randomUUID();
|
|
630
|
+
eventStore.append(
|
|
631
|
+
sessionId,
|
|
632
|
+
createMessageStartEvent(shellMsgId, "user", `Running: \`${command}\``, {
|
|
633
|
+
...currentWindowMessageOptions ?? {},
|
|
634
|
+
isSystemGenerated: true,
|
|
635
|
+
messageKind: "auto-prompt",
|
|
636
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
637
|
+
})
|
|
638
|
+
);
|
|
639
|
+
if (onMessage) {
|
|
640
|
+
onMessage(
|
|
641
|
+
createChatMessageMessage({
|
|
642
|
+
id: shellMsgId,
|
|
643
|
+
role: "user",
|
|
644
|
+
content: `Running: \`${command}\``,
|
|
645
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
646
|
+
isSystemGenerated: true,
|
|
647
|
+
messageKind: "auto-prompt",
|
|
648
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
649
|
+
})
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
const result = await executeShellCommand(
|
|
653
|
+
command,
|
|
654
|
+
sessionManager.getEffectiveWorkdir(sessionId),
|
|
655
|
+
timeout,
|
|
656
|
+
signal
|
|
657
|
+
);
|
|
658
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
659
|
+
lastStepOutput = { stdout: result.stdout ?? "", stderr: result.stderr ?? "", exitCode: String(result.exitCode) };
|
|
660
|
+
const outputContent = output ? `Exit code: ${result.exitCode}
|
|
661
|
+
\`\`\`
|
|
662
|
+
${output.slice(0, 1e4)}
|
|
663
|
+
\`\`\`` : `Exit code: ${result.exitCode}`;
|
|
664
|
+
eventStore.append(sessionId, { type: "message.done", data: { messageId: shellMsgId } });
|
|
665
|
+
const outputMsgId = crypto.randomUUID();
|
|
666
|
+
eventStore.append(
|
|
667
|
+
sessionId,
|
|
668
|
+
createMessageStartEvent(outputMsgId, "user", outputContent, {
|
|
669
|
+
...currentWindowMessageOptions ?? {},
|
|
670
|
+
isSystemGenerated: true,
|
|
671
|
+
messageKind: "correction"
|
|
672
|
+
})
|
|
673
|
+
);
|
|
674
|
+
eventStore.append(sessionId, { type: "message.done", data: { messageId: outputMsgId } });
|
|
675
|
+
if (onMessage) {
|
|
676
|
+
onMessage(
|
|
677
|
+
createChatMessageMessage({
|
|
678
|
+
id: outputMsgId,
|
|
679
|
+
role: "user",
|
|
680
|
+
content: outputContent,
|
|
681
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
682
|
+
isSystemGenerated: true,
|
|
683
|
+
messageKind: "correction"
|
|
684
|
+
})
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
stepOutcome = { result: successCodes.includes(result.exitCode) ? "success" : "failure", output: lastStepOutput };
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
case "user": {
|
|
691
|
+
if (resumeFromStep === step.id) {
|
|
692
|
+
const choice = options.userChoice;
|
|
693
|
+
const result = choice === void 0 || choice === CONTINUE_CHOICE_ID ? DEFAULT_USER_RESULT : choice;
|
|
694
|
+
stepOutcome = { result, output: lastStepOutput };
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
if (executionId) {
|
|
698
|
+
const choices = userStepChoices(step).map((choice) => {
|
|
699
|
+
const target = choice.goto ? stepsById.get(choice.goto) : void 0;
|
|
700
|
+
return target ? { ...choice, nextStepName: target.name } : choice;
|
|
701
|
+
});
|
|
702
|
+
sessionManager.waitAtStep(
|
|
703
|
+
sessionId,
|
|
704
|
+
executionId,
|
|
705
|
+
step.id,
|
|
706
|
+
step.name,
|
|
707
|
+
lastStepOutput,
|
|
708
|
+
workflow.metadata.id,
|
|
709
|
+
workflow.metadata.name,
|
|
710
|
+
workflow.metadata.color,
|
|
711
|
+
choices
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
logger.debug("Workflow paused at user step", { sessionId, stepId: step.id, stepName: step.name });
|
|
715
|
+
return {
|
|
716
|
+
finalAction: {
|
|
717
|
+
type: "WAITING",
|
|
718
|
+
reason: `Paused at user step: ${step.name}`,
|
|
719
|
+
workflowId: workflow.metadata.id,
|
|
720
|
+
stepId: step.id,
|
|
721
|
+
stepOutput: lastStepOutput
|
|
722
|
+
},
|
|
723
|
+
iterations,
|
|
724
|
+
totalTime: (performance.now() - startTime) / 1e3
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const refreshedSession = sessionManager.requireSession(sessionId);
|
|
729
|
+
const candidates = subGroup ? step.transitions.filter((t) => !t.subGroup || activeSubGroups.has(t.subGroup)) : step.transitions;
|
|
730
|
+
const fired = findMatchingTransition(candidates, stepOutcome, refreshedSession.metadataEntries);
|
|
731
|
+
let nextStepId = fired ? fired.goto : TERMINAL_BLOCKED;
|
|
732
|
+
if (subGroup && nextStepId !== TERMINAL_DONE && nextStepId !== TERMINAL_BLOCKED && !activeStepIds.has(nextStepId)) {
|
|
733
|
+
if (fired && fired.subGroup && activeSubGroups.has(fired.subGroup)) {
|
|
734
|
+
activeStepIds.add(nextStepId);
|
|
735
|
+
const targetStep = stepsById.get(nextStepId);
|
|
736
|
+
if (targetStep?.subGroup) {
|
|
737
|
+
activeSubGroups.add(targetStep.subGroup);
|
|
738
|
+
}
|
|
739
|
+
} else {
|
|
740
|
+
nextStepId = TERMINAL_DONE;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (nextStepId === TERMINAL_DONE) {
|
|
744
|
+
sessionManager.setPhase(sessionId, "done");
|
|
745
|
+
if (executionId) {
|
|
746
|
+
sessionManager.completeWorkflow(
|
|
747
|
+
sessionId,
|
|
748
|
+
executionId,
|
|
749
|
+
workflow.metadata.id,
|
|
750
|
+
workflow.metadata.name,
|
|
751
|
+
workflow.metadata.color
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
const totalTimeSeconds = Math.round((performance.now() - startTime) / 100) / 10;
|
|
755
|
+
const completedSession = sessionManager.requireSession(sessionId);
|
|
756
|
+
const workflowMessages = completedSession.messages.slice(messagesBeforeWorkflow);
|
|
757
|
+
const workflowStats = computeSessionStats(workflowMessages);
|
|
758
|
+
const totalToolCalls = workflowMessages.reduce((sum, m) => sum + (m.toolCalls?.length ?? 0), 0);
|
|
759
|
+
const taskCompletedData = {
|
|
760
|
+
summary: null,
|
|
761
|
+
iterations,
|
|
762
|
+
totalTimeSeconds,
|
|
763
|
+
totalToolCalls,
|
|
764
|
+
totalTokensGenerated: workflowStats?.generationTokens ?? 0,
|
|
765
|
+
avgGenerationSpeed: workflowStats?.avgGenerationSpeed ?? 0,
|
|
766
|
+
responseCount: workflowStats?.responseCount ?? 0,
|
|
767
|
+
llmCallCount: workflowStats?.llmCallCount ?? 0,
|
|
768
|
+
criteria: [],
|
|
769
|
+
workflowName: workflow.metadata.name,
|
|
770
|
+
workflowId: workflow.metadata.id,
|
|
771
|
+
...workflow.metadata.color ? { workflowColor: workflow.metadata.color } : {}
|
|
772
|
+
};
|
|
773
|
+
eventStore.append(sessionId, { type: "task.completed", data: taskCompletedData });
|
|
774
|
+
const markerMsgId = crypto.randomUUID();
|
|
775
|
+
eventStore.append(
|
|
776
|
+
sessionId,
|
|
777
|
+
createMessageStartEvent(markerMsgId, "user", JSON.stringify(taskCompletedData), {
|
|
778
|
+
...currentWindowMessageOptions ?? {},
|
|
779
|
+
isSystemGenerated: true,
|
|
780
|
+
messageKind: "task-completed"
|
|
781
|
+
})
|
|
782
|
+
);
|
|
783
|
+
eventStore.append(sessionId, { type: "message.done", data: { messageId: markerMsgId } });
|
|
784
|
+
logger.debug("Workflow executor complete", { sessionId, iterations });
|
|
785
|
+
const doneAction = { type: "DONE" };
|
|
786
|
+
return {
|
|
787
|
+
finalAction: doneAction,
|
|
788
|
+
iterations,
|
|
789
|
+
totalTime: totalTimeSeconds
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
if (nextStepId === TERMINAL_BLOCKED) {
|
|
793
|
+
sessionManager.setPhase(sessionId, "blocked");
|
|
794
|
+
if (executionId) {
|
|
795
|
+
sessionManager.blockWorkflow(
|
|
796
|
+
sessionId,
|
|
797
|
+
executionId,
|
|
798
|
+
workflow.metadata.id,
|
|
799
|
+
workflow.metadata.name,
|
|
800
|
+
workflow.metadata.color
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
const reason = "No matching transition";
|
|
804
|
+
emitWorkflowMessage(eventStore, sessionId, `Runner blocked: ${reason}`, currentWindowMessageOptions, onMessage);
|
|
805
|
+
logger.warn("Workflow executor blocked", { sessionId, iterations, reason });
|
|
806
|
+
const blockedAction = { type: "BLOCKED", reason, blockedCriteria: [] };
|
|
807
|
+
return {
|
|
808
|
+
finalAction: blockedAction,
|
|
809
|
+
iterations,
|
|
810
|
+
totalTime: (performance.now() - startTime) / 1e3
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
currentStepId = nextStepId;
|
|
814
|
+
}
|
|
815
|
+
logger.warn("Workflow executor max iterations reached", { sessionId, iterations });
|
|
816
|
+
return {
|
|
817
|
+
finalAction: {
|
|
818
|
+
type: "BLOCKED",
|
|
819
|
+
reason: `Max iterations (${workflow.settings.maxIterations}) reached`,
|
|
820
|
+
blockedCriteria: []
|
|
821
|
+
},
|
|
822
|
+
iterations,
|
|
823
|
+
totalTime: (performance.now() - startTime) / 1e3
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/server/runner/orchestrator.ts
|
|
828
|
+
async function runOrchestrator(options) {
|
|
829
|
+
const runtimeConfig = getRuntimeConfig();
|
|
830
|
+
const workflowId = options.workflowId ?? runtimeConfig.activeWorkflowId ?? "default";
|
|
831
|
+
const configDir = getGlobalConfigDir(runtimeConfig.mode ?? "production");
|
|
832
|
+
const session = options.sessionManager.requireSession(options.sessionId);
|
|
833
|
+
const projectDir = session.workdir;
|
|
834
|
+
const scope = normalizeWorkflowScope(options.scope);
|
|
835
|
+
let workflow;
|
|
836
|
+
if (scope === "builtin") {
|
|
837
|
+
workflow = findWorkflowById(workflowId, await loadDefaultWorkflows());
|
|
838
|
+
} else if (scope === "user") {
|
|
839
|
+
workflow = findWorkflowById(workflowId, await loadUserWorkflows(configDir));
|
|
840
|
+
} else if (scope === "project") {
|
|
841
|
+
workflow = projectDir ? findWorkflowById(workflowId, await loadProjectWorkflows(projectDir)) : void 0;
|
|
842
|
+
} else {
|
|
843
|
+
workflow = void 0;
|
|
844
|
+
}
|
|
845
|
+
if (!workflow) {
|
|
846
|
+
const workflows = await loadAllWorkflows(configDir, projectDir);
|
|
847
|
+
workflow = findWorkflowById(workflowId, workflows);
|
|
848
|
+
}
|
|
849
|
+
if (!workflow) {
|
|
850
|
+
throw new Error(`Workflow "${workflowId}" not found`);
|
|
851
|
+
}
|
|
852
|
+
const requiredParams = (workflow.metadata.parameters ?? []).filter((p) => p.required);
|
|
853
|
+
const suppliedParams = options.params ?? {};
|
|
854
|
+
const missing = requiredParams.filter((p) => !(p.id in suppliedParams));
|
|
855
|
+
if (missing.length > 0) {
|
|
856
|
+
const names = missing.map((p) => p.label || p.id).join(", ");
|
|
857
|
+
throw new Error(`Missing required parameter${missing.length > 1 ? "s" : ""}: ${names}`);
|
|
858
|
+
}
|
|
859
|
+
logger.debug("Using workflow executor", {
|
|
860
|
+
sessionId: options.sessionId,
|
|
861
|
+
workflow: workflow.metadata.id,
|
|
862
|
+
subGroup: options.subGroup
|
|
863
|
+
});
|
|
864
|
+
return executeWorkflow(workflow, options, options.subGroup);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// src/server/runner/launch.ts
|
|
868
|
+
var activeRuns = /* @__PURE__ */ new Map();
|
|
869
|
+
function abortRunnerRun(sessionId) {
|
|
870
|
+
const controller = activeRuns.get(sessionId);
|
|
871
|
+
if (!controller) return false;
|
|
872
|
+
controller.abort();
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
function launchWorkflowRun(deps, payload) {
|
|
876
|
+
const { sessionManager, sessionId, controller, llmClient, statsIdentity, broadcastForSession, onFinished } = deps;
|
|
877
|
+
const signal = controller.signal;
|
|
878
|
+
activeRuns.set(sessionId, controller);
|
|
879
|
+
sessionManager.setRunning(sessionId, true);
|
|
880
|
+
broadcastForSession(sessionId, createSessionRunningMessage(true));
|
|
881
|
+
const launchAttachments = payload.attachments;
|
|
882
|
+
const hasUserContent = payload.content && payload.content.trim().length > 0;
|
|
883
|
+
const hasUserAttachments = launchAttachments && launchAttachments.length > 0;
|
|
884
|
+
runOrchestrator({
|
|
885
|
+
sessionManager,
|
|
886
|
+
sessionId,
|
|
887
|
+
llmClient,
|
|
888
|
+
statsIdentity,
|
|
889
|
+
scope: normalizeWorkflowScope(payload.scope),
|
|
890
|
+
...payload.workflowId ? { workflowId: payload.workflowId } : {},
|
|
891
|
+
...payload.subGroup ? { subGroup: payload.subGroup } : {},
|
|
892
|
+
...payload.params ? { params: payload.params } : {},
|
|
893
|
+
...payload.resumeFrom ? { resumeFromStep: payload.resumeFrom } : {},
|
|
894
|
+
...payload.stepOutput ? { initialStepOutput: payload.stepOutput } : {},
|
|
895
|
+
...payload.userChoice ? { userChoice: payload.userChoice } : {},
|
|
896
|
+
...payload.resumeFrom ? (() => {
|
|
897
|
+
const exec = sessionManager.getActiveWorkflowExecution(sessionId);
|
|
898
|
+
if (!exec) return {};
|
|
899
|
+
if (exec.status === "waiting") {
|
|
900
|
+
const resumed = sessionManager.resumeWorkflow(
|
|
901
|
+
sessionId,
|
|
902
|
+
exec.id,
|
|
903
|
+
exec.workflowId,
|
|
904
|
+
exec.workflowName,
|
|
905
|
+
exec.workflowColor
|
|
906
|
+
);
|
|
907
|
+
if (resumed) {
|
|
908
|
+
return {
|
|
909
|
+
params: resumed.params,
|
|
910
|
+
initialStepOutput: resumed.stepOutput,
|
|
911
|
+
...exec.currentStepId ? { resumeFromStep: exec.currentStepId } : {},
|
|
912
|
+
...exec.subGroup ? { subGroup: exec.subGroup } : {}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
...Object.keys(exec.params).length > 0 ? { params: exec.params } : {},
|
|
918
|
+
...Object.keys(exec.stepOutput).length > 0 ? { initialStepOutput: exec.stepOutput } : {},
|
|
919
|
+
...exec.currentStepId ? { resumeFromStep: exec.currentStepId } : {},
|
|
920
|
+
...exec.subGroup ? { subGroup: exec.subGroup } : {}
|
|
921
|
+
};
|
|
922
|
+
})() : {},
|
|
923
|
+
...hasUserContent || hasUserAttachments ? {
|
|
924
|
+
userMessage: {
|
|
925
|
+
content: payload.content ?? "",
|
|
926
|
+
...hasUserAttachments ? { attachments: launchAttachments } : {}
|
|
927
|
+
}
|
|
928
|
+
} : {},
|
|
929
|
+
signal,
|
|
930
|
+
onMessage: (msg) => broadcastForSession(sessionId, msg)
|
|
931
|
+
// For path confirmation dialogs
|
|
932
|
+
}).catch((error) => {
|
|
933
|
+
if (error instanceof Error && error.message === "Aborted") {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
937
|
+
logger.error("Runner error", { error: errorMessage, sessionId });
|
|
938
|
+
broadcastForSession(sessionId, createServerMessage("chat.error", { error: errorMessage, recoverable: false }));
|
|
939
|
+
}).finally(() => {
|
|
940
|
+
if (activeRuns.get(sessionId) === controller) {
|
|
941
|
+
activeRuns.delete(sessionId);
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
sessionManager.setRunning(sessionId, false);
|
|
945
|
+
broadcastForSession(sessionId, createSessionRunningMessage(false));
|
|
946
|
+
onFinished?.();
|
|
947
|
+
} catch {
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
export {
|
|
953
|
+
TEMPLATE_VARIABLES,
|
|
954
|
+
abortRunnerRun,
|
|
955
|
+
launchWorkflowRun
|
|
956
|
+
};
|
|
957
|
+
//# sourceMappingURL=chunk-3QQAIUZI.js.map
|