@agimon-ai/doompi-workflow 0.0.1-alpha.96 → 0.0.1-alpha.98
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/README.md +7 -0
- package/dist/apiContracts.cjs +3 -0
- package/dist/apiContracts.cjs.map +1 -1
- package/dist/apiContracts.d.mts.map +1 -1
- package/dist/apiContracts.mjs +3 -0
- package/dist/apiContracts.mjs.map +1 -1
- package/dist/extensions/pi.cjs +102 -644
- package/dist/extensions/pi.cjs.map +1 -1
- package/dist/extensions/pi.d.mts.map +1 -1
- package/dist/extensions/pi.mjs +49 -591
- package/dist/extensions/pi.mjs.map +1 -1
- package/dist/extensions/server.cjs +1718 -608
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.mts.map +1 -1
- package/dist/extensions/server.mjs +1720 -610
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +644 -218
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +10 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{workflowLaunchCommand-Bhr-PvdD.mjs → workflowLaunchCommand-BLrxmzdd.mjs} +14 -7
- package/dist/workflowLaunchCommand-BLrxmzdd.mjs.map +1 -0
- package/dist/{workflowLaunchCommand-7y8xg5YF.cjs → workflowLaunchCommand-DJyDB96d.cjs} +14 -7
- package/dist/workflowLaunchCommand-DJyDB96d.cjs.map +1 -0
- package/dist/workflowTerminal-Bb4bLtLu.cjs +962 -0
- package/dist/workflowTerminal-Bb4bLtLu.cjs.map +1 -0
- package/dist/workflowTerminal-QzufjHCa.mjs +730 -0
- package/dist/workflowTerminal-QzufjHCa.mjs.map +1 -0
- package/package.json +10 -10
- package/src/prompts/doompi-author-workflow/SKILL.md +26 -0
- package/src/prompts/doompi-use-workflow/SKILL.md +1 -1
- package/src/types/webWorkflowTerminal.ts +16 -0
- package/src/types/webWorkflows.ts +20 -0
- package/dist/workflowLaunchCommand-7y8xg5YF.cjs.map +0 -1
- package/dist/workflowLaunchCommand-Bhr-PvdD.mjs.map +0 -1
- package/dist/workflowTerminal-Bj4KKpoA.mjs +0 -148
- package/dist/workflowTerminal-Bj4KKpoA.mjs.map +0 -1
- package/dist/workflowTerminal-WM_cpxIO.cjs +0 -200
- package/dist/workflowTerminal-WM_cpxIO.cjs.map +0 -1
package/dist/extensions/pi.cjs
CHANGED
|
@@ -2,207 +2,27 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_workflowTerminal = require("../workflowTerminal-
|
|
6
|
-
const require_workflowLaunchCommand = require("../workflowLaunchCommand-
|
|
5
|
+
const require_workflowTerminal = require("../workflowTerminal-Bb4bLtLu.cjs");
|
|
6
|
+
const require_workflowLaunchCommand = require("../workflowLaunchCommand-DJyDB96d.cjs");
|
|
7
7
|
let _agimon_ai_doompi_core_piExtension = require("@agimon-ai/doompi-core/piExtension");
|
|
8
8
|
let _agimon_ai_doompi_core_extensionFile = require("@agimon-ai/doompi-core/extensionFile");
|
|
9
9
|
let _agimon_ai_doompi_core_skills = require("@agimon-ai/doompi-core/skills");
|
|
10
10
|
let _agimon_ai_doompi_core_uiHub = require("@agimon-ai/doompi-core/uiHub");
|
|
11
|
-
let node_async_hooks = require("node:async_hooks");
|
|
12
11
|
let _agimon_ai_doompi_core_childProcess = require("@agimon-ai/doompi-core/childProcess");
|
|
13
12
|
let node_fs = require("node:fs");
|
|
14
13
|
let node_fs_promises = require("node:fs/promises");
|
|
15
14
|
let node_path = require("node:path");
|
|
16
15
|
let _agimon_ai_workflow_mcp = require("@agimon-ai/workflow-mcp");
|
|
17
16
|
let zod = require("zod");
|
|
17
|
+
let node_crypto = require("node:crypto");
|
|
18
18
|
let _agimon_ai_doompi_ui_toolChrome = require("@agimon-ai/doompi-ui/toolChrome");
|
|
19
19
|
let node_child_process = require("node:child_process");
|
|
20
|
-
let node_crypto = require("node:crypto");
|
|
21
20
|
let node_module = require("node:module");
|
|
22
21
|
let _agimon_ai_doompi_core_narration = require("@agimon-ai/doompi-core/narration");
|
|
23
22
|
let _agimon_ai_doompi_core_readiness = require("@agimon-ai/doompi-core/readiness");
|
|
24
23
|
let _agimon_ai_doompi_minor_mode = require("@agimon-ai/doompi-minor-mode");
|
|
25
24
|
let _agimon_ai_doompi_telemetry = require("@agimon-ai/doompi-telemetry");
|
|
26
25
|
let _earendil_works_pi_tui = require("@earendil-works/pi-tui");
|
|
27
|
-
//#region src/constants/workflow.ts
|
|
28
|
-
const PACKAGE_SOURCE = "@agimon-ai/doompi-workflow";
|
|
29
|
-
const LEADER_LABEL = "workflows";
|
|
30
|
-
const LEADER_ENABLE_ACTION = "workflow.enable";
|
|
31
|
-
const LEADER_DISABLE_ACTION = "workflow.disable";
|
|
32
|
-
const LEADER_MANAGE_ACTION = "workflow.manage";
|
|
33
|
-
const LEADER_RECOVER_ACTION = "workflow.recover";
|
|
34
|
-
const LEADER_CATALOG_ACTION = "workflow.catalog";
|
|
35
|
-
const LEADER_DETAIL = "multi-step agent runs";
|
|
36
|
-
const LIST_WORKFLOWS_TOOL_NAME = "list_workflows";
|
|
37
|
-
const LAUNCH_WORKFLOW_TOOL_NAME = "launch_workflow";
|
|
38
|
-
const WORKFLOW_RUN_TOOL_NAME$1 = "workflow_run";
|
|
39
|
-
const WORKFLOW_PI_TOOL_NAMES = [
|
|
40
|
-
LIST_WORKFLOWS_TOOL_NAME,
|
|
41
|
-
LAUNCH_WORKFLOW_TOOL_NAME,
|
|
42
|
-
WORKFLOW_RUN_TOOL_NAME$1
|
|
43
|
-
];
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/services/workflowFence/index.ts
|
|
46
|
-
const DISPATCHER_AGENT_NAME = "agiflow-dispatcher";
|
|
47
|
-
const SESSION_START_EVENT = "session_start";
|
|
48
|
-
const INACTIVE_RUNTIME_MESSAGE = "The workflow runtime is no longer active.";
|
|
49
|
-
const CLEANUP_SCOPE = Symbol("doom-workflow-cleanup");
|
|
50
|
-
const DISPATCHER_TOOL_NAMES = /* @__PURE__ */ new Set(["list_workflows", "launch_workflow"]);
|
|
51
|
-
const ROOT_SCOPED_TOOL_NAMES = /* @__PURE__ */ new Set(["launch_workflow"]);
|
|
52
|
-
const WORKFLOW_RUN_TOOL_NAME = "workflow_run";
|
|
53
|
-
function boundValue(target, property) {
|
|
54
|
-
const value = Reflect.get(target, property, target);
|
|
55
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
56
|
-
}
|
|
57
|
-
function withParentSession(ctx, parentSessionId) {
|
|
58
|
-
const sessionManager = new Proxy(ctx.sessionManager, { get(target, property) {
|
|
59
|
-
if (property === "getSessionId") return () => parentSessionId;
|
|
60
|
-
return boundValue(target, property);
|
|
61
|
-
} });
|
|
62
|
-
return new Proxy(ctx, { get(target, property) {
|
|
63
|
-
if (property === "sessionManager") return sessionManager;
|
|
64
|
-
return boundValue(target, property);
|
|
65
|
-
} });
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* A dispatcher child launches workflows for its root session and never runs one
|
|
69
|
-
* itself, so `workflow_run` stays off its surface even if something registers it.
|
|
70
|
-
*/
|
|
71
|
-
function dispatcherToolRestriction() {
|
|
72
|
-
return (incoming) => incoming.filter((name) => name !== WORKFLOW_RUN_TOOL_NAME);
|
|
73
|
-
}
|
|
74
|
-
/** Restrict a dispatcher child to discovery and root-owned launch. */
|
|
75
|
-
function dispatcherTools(tools, parentSessionId, environment) {
|
|
76
|
-
const rootSessionId = parentSessionId ? (0, _agimon_ai_doompi_core_childProcess.resolveRootSessionId)(parentSessionId, environment) : void 0;
|
|
77
|
-
return tools.filter((tool) => DISPATCHER_TOOL_NAMES.has(tool.name)).map((tool) => {
|
|
78
|
-
if (!rootSessionId || !ROOT_SCOPED_TOOL_NAMES.has(tool.name)) return tool;
|
|
79
|
-
return {
|
|
80
|
-
...tool,
|
|
81
|
-
execute: (toolCallId, params, signal, onUpdate, ctx) => tool.execute(toolCallId, params, signal, onUpdate, withParentSession(ctx, rootSessionId))
|
|
82
|
-
};
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
function resolveDispatcherParentSession(environment) {
|
|
86
|
-
return (0, _agimon_ai_doompi_core_childProcess.readChildProcessContext)(environment)?.parentSessionId ?? (environment[_agimon_ai_doompi_core_childProcess.SUBAGENT_PARENT_SESSION_ENV]?.trim() || void 0);
|
|
87
|
-
}
|
|
88
|
-
function isWorkflowDispatcherProcess(environment) {
|
|
89
|
-
if ((0, _agimon_ai_doompi_core_childProcess.readChildProcessContext)(environment)?.mode === DISPATCHER_AGENT_NAME) return true;
|
|
90
|
-
return environment.PI_SUBAGENT_CHILD_AGENT === DISPATCHER_AGENT_NAME && Boolean(environment[_agimon_ai_doompi_core_childProcess.SUBAGENT_PARENT_SESSION_ENV]?.trim());
|
|
91
|
-
}
|
|
92
|
-
/** Fence every callback retained by Pi after this package root is replaced. */
|
|
93
|
-
function createWorkflowFence(host, signal) {
|
|
94
|
-
const scope = new node_async_hooks.AsyncLocalStorage();
|
|
95
|
-
let active = true;
|
|
96
|
-
let disposing = false;
|
|
97
|
-
let generation = 0;
|
|
98
|
-
let lastSessionStartEvent;
|
|
99
|
-
let lastSessionStartContext;
|
|
100
|
-
const isCurrentInvocation = () => {
|
|
101
|
-
const currentScope = scope.getStore();
|
|
102
|
-
if (!active || signal?.aborted && currentScope !== CLEANUP_SCOPE) return false;
|
|
103
|
-
return currentScope === void 0 || currentScope === CLEANUP_SCOPE || currentScope === generation;
|
|
104
|
-
};
|
|
105
|
-
const ownsContext = (contextGeneration) => {
|
|
106
|
-
const currentScope = scope.getStore();
|
|
107
|
-
if (!active || signal?.aborted && currentScope !== CLEANUP_SCOPE) return false;
|
|
108
|
-
return currentScope === CLEANUP_SCOPE || contextGeneration === generation && (currentScope === void 0 || currentScope === contextGeneration);
|
|
109
|
-
};
|
|
110
|
-
const guardContext = (context, contextGeneration) => {
|
|
111
|
-
const guardedUi = new Proxy(context.ui, { get(target, property) {
|
|
112
|
-
const value = Reflect.get(target, property, target);
|
|
113
|
-
if (typeof value !== "function") return value;
|
|
114
|
-
return (...args) => {
|
|
115
|
-
if (ownsContext(contextGeneration)) return Reflect.apply(value, target, args);
|
|
116
|
-
if (property === "confirm") return Promise.resolve(false);
|
|
117
|
-
if (property === "custom" || property === "editor" || property === "input" || property === "select") return Promise.resolve(void 0);
|
|
118
|
-
};
|
|
119
|
-
} });
|
|
120
|
-
return new Proxy(context, { get(target, property) {
|
|
121
|
-
if (property === "ui") return guardedUi;
|
|
122
|
-
return boundValue(target, property);
|
|
123
|
-
} });
|
|
124
|
-
};
|
|
125
|
-
const tool = (definition) => {
|
|
126
|
-
return {
|
|
127
|
-
...definition,
|
|
128
|
-
execute: async (toolCallId, params, signal, onUpdate, context) => {
|
|
129
|
-
const executionGeneration = generation;
|
|
130
|
-
if (!active || disposing || signal?.aborted) throw new Error(INACTIVE_RUNTIME_MESSAGE);
|
|
131
|
-
const guardedContext = guardContext(context, executionGeneration);
|
|
132
|
-
const guardedUpdate = onUpdate ? (update) => {
|
|
133
|
-
if (active && executionGeneration === generation && !signal?.aborted) onUpdate(update);
|
|
134
|
-
} : void 0;
|
|
135
|
-
const result = await scope.run(executionGeneration, () => definition.execute(toolCallId, params, signal, guardedUpdate, guardedContext));
|
|
136
|
-
if (!active || disposing || executionGeneration !== generation) throw new Error(INACTIVE_RUNTIME_MESSAGE);
|
|
137
|
-
return result;
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
const command = (name, options) => {
|
|
142
|
-
return [name, {
|
|
143
|
-
...options,
|
|
144
|
-
handler: async (args, context) => {
|
|
145
|
-
const executionGeneration = generation;
|
|
146
|
-
if (!active || disposing) return;
|
|
147
|
-
await scope.run(executionGeneration, () => options.handler(args, guardContext(context, executionGeneration)));
|
|
148
|
-
}
|
|
149
|
-
}];
|
|
150
|
-
};
|
|
151
|
-
const shortcut = (key, options) => {
|
|
152
|
-
return [key, {
|
|
153
|
-
...options,
|
|
154
|
-
handler: (context) => {
|
|
155
|
-
const executionGeneration = generation;
|
|
156
|
-
if (!active || disposing) return;
|
|
157
|
-
return scope.run(executionGeneration, () => options.handler(guardContext(context, executionGeneration)));
|
|
158
|
-
}
|
|
159
|
-
}];
|
|
160
|
-
};
|
|
161
|
-
const events = (handlers) => Object.fromEntries(Object.entries(handlers).map(([eventName, handler]) => [eventName, (event, context) => {
|
|
162
|
-
if (!active || disposing) return void 0;
|
|
163
|
-
if (eventName === SESSION_START_EVENT && (event !== lastSessionStartEvent || context !== lastSessionStartContext)) {
|
|
164
|
-
generation += 1;
|
|
165
|
-
lastSessionStartEvent = event;
|
|
166
|
-
lastSessionStartContext = context;
|
|
167
|
-
}
|
|
168
|
-
const eventGeneration = generation;
|
|
169
|
-
return scope.run(eventGeneration, () => handler(event, guardContext(context, eventGeneration)));
|
|
170
|
-
}]));
|
|
171
|
-
return {
|
|
172
|
-
pi: new Proxy(host, { get(target, property) {
|
|
173
|
-
if (property === "sendMessage") return (...args) => {
|
|
174
|
-
if (isCurrentInvocation()) target.sendMessage(...args);
|
|
175
|
-
};
|
|
176
|
-
if (property === "sendUserMessage") return (...args) => {
|
|
177
|
-
if (isCurrentInvocation()) target.sendUserMessage(...args);
|
|
178
|
-
};
|
|
179
|
-
if (property === "exec") return (...args) => isCurrentInvocation() ? target.exec(...args) : Promise.resolve({
|
|
180
|
-
code: 1,
|
|
181
|
-
killed: false,
|
|
182
|
-
stderr: INACTIVE_RUNTIME_MESSAGE,
|
|
183
|
-
stdout: ""
|
|
184
|
-
});
|
|
185
|
-
return boundValue(target, property);
|
|
186
|
-
} }),
|
|
187
|
-
tool,
|
|
188
|
-
command,
|
|
189
|
-
shortcut,
|
|
190
|
-
events,
|
|
191
|
-
beginDisposal() {
|
|
192
|
-
if (disposing || !active) return;
|
|
193
|
-
disposing = true;
|
|
194
|
-
generation += 1;
|
|
195
|
-
},
|
|
196
|
-
finishDisposal() {
|
|
197
|
-
active = false;
|
|
198
|
-
disposing = false;
|
|
199
|
-
generation += 1;
|
|
200
|
-
},
|
|
201
|
-
isCurrentInvocation,
|
|
202
|
-
runCleanup: (operation) => scope.run(CLEANUP_SCOPE, operation)
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
//#endregion
|
|
206
26
|
//#region src/schemas/workflowPi.ts
|
|
207
27
|
const RUN_SELECTOR_FIELDS = {
|
|
208
28
|
runKey: zod.z.string().min(1).describe("Run key of the workflow run."),
|
|
@@ -273,392 +93,6 @@ const workflowCatalogPageSchema = zod.z.object({
|
|
|
273
93
|
}).passthrough()).describe("Workflow definitions on this page.")
|
|
274
94
|
}).passthrough();
|
|
275
95
|
//#endregion
|
|
276
|
-
//#region src/services/piToolBridge/index.ts
|
|
277
|
-
/**
|
|
278
|
-
* Environment variable stamped onto every run launched from a Pi session.
|
|
279
|
-
*
|
|
280
|
-
* `RunWorkflowService` forwards `env` to the inner CLI invocation across the
|
|
281
|
-
* `launch-command` delegation boundary and persists it on the run record, so
|
|
282
|
-
* this survives backgrounding and identifies the launching session afterwards.
|
|
283
|
-
*/
|
|
284
|
-
const PI_SESSION_ENV = "PI_SESSION_ID";
|
|
285
|
-
/** Operator override for the per-session launch ceiling. */
|
|
286
|
-
const MAX_CONCURRENT_ENV = "WORKFLOW_MCP_MAX_CONCURRENT";
|
|
287
|
-
const DEFAULT_MAX_CONCURRENT = 5;
|
|
288
|
-
/** Resolve the per-session launch ceiling from the environment. */
|
|
289
|
-
function resolveMaxConcurrent(env = process.env) {
|
|
290
|
-
const parsed = Number(env[MAX_CONCURRENT_ENV]);
|
|
291
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT;
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* True when this session launched the run.
|
|
295
|
-
*
|
|
296
|
-
* Fails closed on a missing session id: an unstamped record (a CLI launch) and
|
|
297
|
-
* a call arriving before the session id is known both match nothing, so no run
|
|
298
|
-
* ever leaks across sessions through an undefined-equals-undefined comparison.
|
|
299
|
-
*/
|
|
300
|
-
function isSessionRun(record, sessionId) {
|
|
301
|
-
return sessionId !== void 0 && record.env?.["PI_SESSION_ID"] === sessionId;
|
|
302
|
-
}
|
|
303
|
-
/** Runs launched by one Pi session that are still running. */
|
|
304
|
-
function runsForSession(records, sessionId) {
|
|
305
|
-
return records.filter((record) => isSessionRun(record, sessionId) && record.stage === "running");
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* What the caller is told when the registry, not the launcher, acknowledged a
|
|
309
|
-
* launch.
|
|
310
|
-
*
|
|
311
|
-
* Leads with the run key because it is the handle every other workflow action
|
|
312
|
-
* takes, and it is no longer available any other way: the launcher's own output
|
|
313
|
-
* is not waited for, so nothing else in this result carries it.
|
|
314
|
-
*/
|
|
315
|
-
function launchedRunSummary(record) {
|
|
316
|
-
return [
|
|
317
|
-
`Started ${record.displayName} in workspace ${record.workspace}.`,
|
|
318
|
-
`Run key: ${record.runKey}`,
|
|
319
|
-
"The run is registered and going. This call returned at that point rather than waiting for the launcher process to exit."
|
|
320
|
-
].join("\n");
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* What the caller is told when the launch was handed off but no run has
|
|
324
|
-
* registered inside the acknowledgement budget.
|
|
325
|
-
*
|
|
326
|
-
* Deliberately not an error: the launcher is slow rather than broken often
|
|
327
|
-
* enough that failing here would strand runs that go on to start normally.
|
|
328
|
-
* There is no run key to offer yet, so it points at the surfaces that do not
|
|
329
|
-
* need one.
|
|
330
|
-
*/
|
|
331
|
-
function launchHandoffSummary(workflowPath) {
|
|
332
|
-
return [
|
|
333
|
-
`Launch handed off for ${workflowPath}, but no run has registered yet.`,
|
|
334
|
-
"Nothing was cancelled: the launcher is still starting, and a run that registers later still reports back here when it finishes.",
|
|
335
|
-
"Do not launch it again. Ask the user to check the active workflow list if they need to see it sooner."
|
|
336
|
-
].join("\n");
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Escape sequences a terminal consumes rather than prints.
|
|
340
|
-
*
|
|
341
|
-
* Built from a string with the rule silenced, the way `doom-runner`'s own
|
|
342
|
-
* scrubber does it, because matching the escape byte is the entire job. Not
|
|
343
|
-
* imported from there: that package's published build is a stale artifact
|
|
344
|
-
* missing the export, and a launch notice should not need another package's
|
|
345
|
-
* build to be current.
|
|
346
|
-
*/
|
|
347
|
-
const ESCAPE_SEQUENCE = new RegExp([
|
|
348
|
-
"\\u001B\\[[0-9;?]*[ -/]*[@-~]",
|
|
349
|
-
"\\u001B\\][^\\u0007\\u001B]*(?:\\u0007|\\u001B\\\\)",
|
|
350
|
-
"\\u001B[@-Z\\\\-_]"
|
|
351
|
-
].join("|"), "g");
|
|
352
|
-
/** Rows made only of box-drawing: separators that carry nothing once trimmed. */
|
|
353
|
-
const RULE_ROW = /^[\s─-╿]*$/u;
|
|
354
|
-
const NOTICE_LINE_LIMIT = 3;
|
|
355
|
-
/**
|
|
356
|
-
* Widest a notice row may be before it is clipped.
|
|
357
|
-
*
|
|
358
|
-
* A three-line cap does not bound a toast on its own: the engine announces its
|
|
359
|
-
* delegation by echoing the whole shell command, temp script path included, and
|
|
360
|
-
* one line of that wraps across three rows of a narrow terminal and shoves the
|
|
361
|
-
* transcript around. Clipped at a width every terminal can hold instead.
|
|
362
|
-
*/
|
|
363
|
-
const NOTICE_WIDTH_LIMIT = 100;
|
|
364
|
-
/**
|
|
365
|
-
* Cut a tool result down to something a toast can hold.
|
|
366
|
-
*
|
|
367
|
-
* The launch result used to reach `ui.notify` whole, and for a workflow that
|
|
368
|
-
* executes in this process that result is the engine's entire console log:
|
|
369
|
-
* banner, run directory, job tree, rules, colour. Pi rendered every line of it
|
|
370
|
-
* over the transcript, which reads as a broken screen rather than as output.
|
|
371
|
-
* Escape sequences go first: the log carries colour the notification would
|
|
372
|
-
* render as literal text, and a stray clear or cursor-move would do to the
|
|
373
|
-
* screen exactly what this function exists to prevent.
|
|
374
|
-
*/
|
|
375
|
-
function launchNotice(text, limit = NOTICE_LINE_LIMIT) {
|
|
376
|
-
const lines = text.replace(ESCAPE_SEQUENCE, "").split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0 && !RULE_ROW.test(line)).map((line) => line.length > NOTICE_WIDTH_LIMIT ? `${line.slice(0, 99)}…` : line);
|
|
377
|
-
if (lines.length === 0) return "Workflow started.";
|
|
378
|
-
if (lines.length <= limit) return lines.join("\n");
|
|
379
|
-
return [...lines.slice(0, limit), `… ${lines.length - limit} more lines, in the run's own output.`].join("\n");
|
|
380
|
-
}
|
|
381
|
-
/** Flatten MCP tool content to text for Pi. */
|
|
382
|
-
function toolResultText(result) {
|
|
383
|
-
return result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n").trim();
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Convert an MCP tool result into a Pi tool result.
|
|
387
|
-
*
|
|
388
|
-
* Pi signals tool failure by throwing: the agent loop catches it and builds the
|
|
389
|
-
* error result. So an `isError` MCP result becomes a thrown error rather than a
|
|
390
|
-
* successful result carrying a flag Pi would ignore.
|
|
391
|
-
*/
|
|
392
|
-
function toAgentToolResult(tool, result, errorOptions = []) {
|
|
393
|
-
const text = toolResultText(result) || "No output.";
|
|
394
|
-
if (result.isError) throw new Error(withOptions(text, errorOptions));
|
|
395
|
-
return {
|
|
396
|
-
content: [{
|
|
397
|
-
type: "text",
|
|
398
|
-
text
|
|
399
|
-
}],
|
|
400
|
-
details: { tool }
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* Attach a short menu of next steps to a problem.
|
|
405
|
-
*
|
|
406
|
-
* An error with no route forward is where an agent invents one: retrying a
|
|
407
|
-
* capacity limit, relaunching a run that should be recovered, or silently
|
|
408
|
-
* giving up on work the user is waiting for. Naming two or three real options
|
|
409
|
-
* turns an error into a decision, and the decision belongs to the user.
|
|
410
|
-
*/
|
|
411
|
-
function withOptions(problem, options) {
|
|
412
|
-
if (options.length === 0) return problem;
|
|
413
|
-
return [
|
|
414
|
-
problem,
|
|
415
|
-
"",
|
|
416
|
-
"Options, put these to the user rather than picking one yourself:",
|
|
417
|
-
...options.map(bullet)
|
|
418
|
-
].join("\n");
|
|
419
|
-
}
|
|
420
|
-
function bullet(option) {
|
|
421
|
-
return `- ${option}`;
|
|
422
|
-
}
|
|
423
|
-
/** The job and step a run died on, preferring the progress log to the record. */
|
|
424
|
-
function failurePosition(record, jobs) {
|
|
425
|
-
const failedJob = jobs.find((job) => job.status === "failed");
|
|
426
|
-
const failedStep = failedJob?.steps.find((step) => step.status === "failed");
|
|
427
|
-
return {
|
|
428
|
-
job: failedJob?.name ?? record.failedJob,
|
|
429
|
-
step: failedStep?.name,
|
|
430
|
-
reason: failedStep?.reason ?? failedJob?.reason
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Describe a finished run for the agent that launched it.
|
|
435
|
-
*
|
|
436
|
-
* Carries the job identifiers back so a dispatching agent can match the run to
|
|
437
|
-
* the work item it launched it for without another lookup. When the run failed,
|
|
438
|
-
* carries the failing step and a set of options too: the agent is being told
|
|
439
|
-
* about this out of band, with no user question in flight, so without them it
|
|
440
|
-
* has to guess whether to recover, relaunch, or report.
|
|
441
|
-
*/
|
|
442
|
-
function finishedRunSummary(record, jobs = []) {
|
|
443
|
-
const outcome = record.stage === "completed" ? "completed" : `ended in ${record.stage}`;
|
|
444
|
-
const failure = failurePosition(record, jobs);
|
|
445
|
-
const lines = [
|
|
446
|
-
`Workflow run ${record.runKey} in workspace ${record.workspace} ${outcome}.`,
|
|
447
|
-
record.workflowId ? `Workflow: ${record.workflowId}` : void 0,
|
|
448
|
-
failure.job ? `Failed job: ${failure.job}${failure.step ? ` (step: ${failure.step})` : ""}` : void 0,
|
|
449
|
-
record.errorMessage ? `Error: ${record.errorMessage}` : void 0,
|
|
450
|
-
failure.reason && failure.reason !== record.errorMessage ? `Step reported: ${failure.reason}` : void 0
|
|
451
|
-
];
|
|
452
|
-
const jobId = record.env?.AGIFLOW_JOB_ID;
|
|
453
|
-
if (jobId) lines.push(`Agiflow job: ${record.env?.AGIFLOW_JOB_KIND ?? "unknown"} ${jobId}`);
|
|
454
|
-
const summary = lines.filter((line) => Boolean(line)).join("\n");
|
|
455
|
-
if (record.stage === "completed") return summary;
|
|
456
|
-
if (record.outcome === "interrupted") return withOptions(summary, [`workflow_run {"action":"recover","runKey":${JSON.stringify(record.runKey)}}: pick it up where it stopped.`, "Leave it stopped and move on."]);
|
|
457
|
-
const diagnosticText = [record.errorMessage, failure.reason].filter(Boolean).join("\n");
|
|
458
|
-
if (diagnosticText.includes("JOB_ALREADY_CLAIMED")) return withOptions(summary, ["Report contention: another worker owns this job. Do not unlock, release, or retry the claim automatically."]);
|
|
459
|
-
if (diagnosticText.includes("WORKFLOW_NOT_OWNED") || /release failed/i.test(diagnosticText)) return withOptions(summary, ["Inspect the current workflow ownership and ask the user before any release or unlock. Never force another worker’s lock."]);
|
|
460
|
-
if (/running workflow not found/i.test(diagnosticText)) return withOptions(summary, [`workflow_run {"action":"status","runKey":${JSON.stringify(record.runKey)}}: re-check current state before retrying.`, "Report the recorded terminal state when no running owner remains."]);
|
|
461
|
-
return withOptions(summary, [
|
|
462
|
-
`workflow_run {"action":"tail","runKey":${JSON.stringify(record.runKey)}}: read the run's own output first, when the error above does not explain the failure.`,
|
|
463
|
-
`workflow_run {"action":"recover","runKey":${JSON.stringify(record.runKey)}}: resume from the failed job, keeping the work already done. Fits a transient or external cause.`,
|
|
464
|
-
"Report the failure and stop, when it needs a code change or a decision you cannot make."
|
|
465
|
-
]);
|
|
466
|
-
}
|
|
467
|
-
//#endregion
|
|
468
|
-
//#region src/services/workflowExecution/index.ts
|
|
469
|
-
const AGIFLOW_JOB_ID_ENV = "AGIFLOW_JOB_ID";
|
|
470
|
-
const AGIFLOW_JOB_KIND_ENV = "AGIFLOW_JOB_KIND";
|
|
471
|
-
const AGIFLOW_PROJECT_ID_ENV = "AGIFLOW_PROJECT_ID";
|
|
472
|
-
/**
|
|
473
|
-
* What workflow mode does to the tool surface: nothing but hide its own tools.
|
|
474
|
-
*
|
|
475
|
-
* The tools are registered for the whole session because Pi has no other way to
|
|
476
|
-
* add one later, so the mode is expressed as a restriction rather than as a
|
|
477
|
-
* registration.
|
|
478
|
-
*/
|
|
479
|
-
function workflowToolRestriction(enabled) {
|
|
480
|
-
const hidden = new Set(WORKFLOW_PI_TOOL_NAMES);
|
|
481
|
-
return (incoming) => enabled ? incoming : incoming.filter((name) => !hidden.has(name));
|
|
482
|
-
}
|
|
483
|
-
/** How often the registry is asked whether the launch has produced a run. */
|
|
484
|
-
const LAUNCH_ACK_POLL_MS = 500;
|
|
485
|
-
/**
|
|
486
|
-
* How long to wait for that before answering without a run key.
|
|
487
|
-
*
|
|
488
|
-
* Long enough to cover a launcher that is merely slow, short enough that a
|
|
489
|
-
* caller is never left holding a turn open for a launcher that will not answer.
|
|
490
|
-
*/
|
|
491
|
-
const LAUNCH_ACK_TIMEOUT_MS = 15e3;
|
|
492
|
-
function reportProgress(onUpdate, tool, message) {
|
|
493
|
-
onUpdate?.({
|
|
494
|
-
content: [{
|
|
495
|
-
type: "text",
|
|
496
|
-
text: message
|
|
497
|
-
}],
|
|
498
|
-
details: { tool }
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
function appendGuidance(result, guidance) {
|
|
502
|
-
return {
|
|
503
|
-
...result,
|
|
504
|
-
content: [...result.content, {
|
|
505
|
-
type: "text",
|
|
506
|
-
text: guidance
|
|
507
|
-
}]
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
function statusAction(runKey) {
|
|
511
|
-
return `workflow_run {"action":"status","runKey":${JSON.stringify(runKey)}}`;
|
|
512
|
-
}
|
|
513
|
-
function actionGuidance(action, runKey) {
|
|
514
|
-
const status = statusAction(runKey);
|
|
515
|
-
if (action === "stop") return [`Next: call ${status} to confirm the run reached a terminal stage.`];
|
|
516
|
-
if (action === "recover") return [`Report the recovery failure and do not retry it blindly: call ${status} first.`, "Ask the user before launching fresh work when the recorded recovery is no longer valid."];
|
|
517
|
-
return [`Next: call ${status} to verify the recorded execution state.`];
|
|
518
|
-
}
|
|
519
|
-
const launchFailureOptions = [
|
|
520
|
-
"Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.",
|
|
521
|
-
"list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.",
|
|
522
|
-
"workflow_run with action status: check whether an earlier attempt is already running before launching again."
|
|
523
|
-
];
|
|
524
|
-
/**
|
|
525
|
-
* An Error, whatever the launch rejected with.
|
|
526
|
-
*
|
|
527
|
-
* A rejection reaches here as `unknown` because it crossed a promise boundary,
|
|
528
|
-
* and rethrowing that as-is hands Pi's tool layer something it cannot read a
|
|
529
|
-
* message or a stack off. The original is kept as the cause rather than
|
|
530
|
-
* flattened into a string.
|
|
531
|
-
*/
|
|
532
|
-
function asError(value) {
|
|
533
|
-
return value instanceof Error ? value : new Error(String(value), { cause: value });
|
|
534
|
-
}
|
|
535
|
-
/** Resolves to undefined, so a race against it reads as "nothing settled yet". */
|
|
536
|
-
function sleep(ms) {
|
|
537
|
-
return new Promise((settle) => {
|
|
538
|
-
setTimeout(() => settle(void 0), ms).unref?.();
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Wait for whichever answer arrives first: the launch settling, the run
|
|
543
|
-
* appearing in the registry, or the budget running out.
|
|
544
|
-
*
|
|
545
|
-
* A launch that delegates to a terminal launcher settles when that launcher's
|
|
546
|
-
* whole process chain closes, which on a loaded machine has been measured
|
|
547
|
-
* minutes after the run itself was registered and working. The registry is the
|
|
548
|
-
* earlier and more truthful signal: a run recorded there is a run that started.
|
|
549
|
-
*/
|
|
550
|
-
async function awaitLaunchAck(settled, dependencies, query) {
|
|
551
|
-
const findLaunchedRun = dependencies.findLaunchedRun;
|
|
552
|
-
if (!findLaunchedRun) return {
|
|
553
|
-
kind: "settled",
|
|
554
|
-
outcome: await settled
|
|
555
|
-
};
|
|
556
|
-
const pollMs = dependencies.launchAckPollMs ?? LAUNCH_ACK_POLL_MS;
|
|
557
|
-
const deadline = Date.now() + (dependencies.launchAckTimeoutMs ?? LAUNCH_ACK_TIMEOUT_MS);
|
|
558
|
-
for (;;) {
|
|
559
|
-
const outcome = await Promise.race([settled, sleep(pollMs)]);
|
|
560
|
-
if (outcome) return {
|
|
561
|
-
kind: "settled",
|
|
562
|
-
outcome
|
|
563
|
-
};
|
|
564
|
-
const record = await findLaunchedRun(query).catch(() => void 0);
|
|
565
|
-
if (record) return {
|
|
566
|
-
kind: "record",
|
|
567
|
-
record
|
|
568
|
-
};
|
|
569
|
-
if (Date.now() >= deadline) return { kind: "handoff" };
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Carry a failure that lands after the caller was told the run started.
|
|
574
|
-
*
|
|
575
|
-
* Answering early trades the launcher's exit code for timeliness. Dropping that
|
|
576
|
-
* code entirely is not part of the trade: a launch that fails a minute later
|
|
577
|
-
* would otherwise leave a user waiting on a run that never existed.
|
|
578
|
-
*/
|
|
579
|
-
function reportLateFailure(settled, dependencies, ctx) {
|
|
580
|
-
settled.then((outcome) => {
|
|
581
|
-
if (outcome.kind === "error") {
|
|
582
|
-
dependencies.onLateFailure?.(asError(outcome.error), ctx);
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
if (outcome.result.isError) dependencies.onLateFailure?.(new Error(toolResultText(outcome.result) || "Workflow launch failed."), ctx);
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
function createWorkflowLaunchExecutor(dependencies) {
|
|
589
|
-
return { async execute(input, ctx, onUpdate) {
|
|
590
|
-
const sessionId = (0, _agimon_ai_doompi_core_childProcess.resolveRootSessionId)(ctx.sessionManager.getSessionId(), dependencies.environment);
|
|
591
|
-
dependencies.observeSession?.(sessionId);
|
|
592
|
-
const agiflowJobKind = input.env?.[AGIFLOW_JOB_KIND_ENV]?.trim();
|
|
593
|
-
const agiflowJobId = input.env?.[AGIFLOW_JOB_ID_ENV]?.trim();
|
|
594
|
-
const explicitProjectId = input.env?.[AGIFLOW_PROJECT_ID_ENV]?.trim();
|
|
595
|
-
if (Boolean(agiflowJobKind) !== Boolean(agiflowJobId)) throw new Error("Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.");
|
|
596
|
-
if (agiflowJobKind && agiflowJobKind !== "task" && agiflowJobKind !== "work-unit") throw new Error("Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.");
|
|
597
|
-
if (agiflowJobKind && !explicitProjectId) throw new Error("Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.");
|
|
598
|
-
if (agiflowJobKind && !input.prompt?.trim()) throw new Error("Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.");
|
|
599
|
-
reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, "Checking workflow capacity...");
|
|
600
|
-
const maxConcurrent = resolveMaxConcurrent(dependencies.environment);
|
|
601
|
-
const active = await dependencies.activeRunCount?.() ?? 0;
|
|
602
|
-
if (active >= maxConcurrent) throw new Error(withOptions(`This session is at capacity: ${active}/${maxConcurrent} workflows running.`, [
|
|
603
|
-
"Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.",
|
|
604
|
-
"workflow_run with action status: show what is running, so the user can judge whether something is stuck.",
|
|
605
|
-
"workflow_run with action stop: free a slot by stopping a run the user no longer wants."
|
|
606
|
-
]));
|
|
607
|
-
if (input.runner) {
|
|
608
|
-
const rejection = dependencies.rejectRunner?.(input.workflowPath, input.runner);
|
|
609
|
-
if (rejection) throw new Error(rejection);
|
|
610
|
-
}
|
|
611
|
-
reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, `Launching workflow ${input.workflowPath}...`);
|
|
612
|
-
const workflowEnv = { ...input.env };
|
|
613
|
-
const dispatcherContextFile = dependencies.environment.AGIFLOW_DISPATCH_CONTEXT_FILE;
|
|
614
|
-
const dispatcherProjectId = dependencies.environment[AGIFLOW_PROJECT_ID_ENV]?.trim();
|
|
615
|
-
if (dispatcherContextFile && agiflowJobKind && dispatcherProjectId && explicitProjectId && dispatcherProjectId !== explicitProjectId) throw new Error(`Agiflow workflow project identity conflicts with the dispatcher context (${explicitProjectId} versus ${dispatcherProjectId}).`);
|
|
616
|
-
if (dispatcherContextFile) for (const key of [
|
|
617
|
-
"AGIFLOW_ORGANIZATION_ID",
|
|
618
|
-
"AGIFLOW_PROJECT_ID",
|
|
619
|
-
"AGIFLOW_DEVICE_ID",
|
|
620
|
-
"BACKEND_AGIFLOW_API_ENDPOINT",
|
|
621
|
-
"AGIFLOW_DISPATCH_SECRET_FILE"
|
|
622
|
-
]) {
|
|
623
|
-
const hostValue = dependencies.environment[key];
|
|
624
|
-
if (hostValue) workflowEnv[key] = hostValue;
|
|
625
|
-
}
|
|
626
|
-
const since = Date.now();
|
|
627
|
-
const settled = dependencies.trackPendingRun(dependencies.runTool.execute({
|
|
628
|
-
...input,
|
|
629
|
-
env: {
|
|
630
|
-
...workflowEnv,
|
|
631
|
-
[PI_SESSION_ENV]: sessionId
|
|
632
|
-
}
|
|
633
|
-
})).then((value) => ({
|
|
634
|
-
kind: "value",
|
|
635
|
-
result: value
|
|
636
|
-
}), (error) => ({
|
|
637
|
-
kind: "error",
|
|
638
|
-
error
|
|
639
|
-
}));
|
|
640
|
-
const ack = await awaitLaunchAck(settled, dependencies, {
|
|
641
|
-
sessionId,
|
|
642
|
-
since,
|
|
643
|
-
workflowPath: input.workflowPath
|
|
644
|
-
});
|
|
645
|
-
if (ack.kind === "settled") {
|
|
646
|
-
const outcome = ack.outcome;
|
|
647
|
-
if (outcome.kind === "error") throw asError(outcome.error);
|
|
648
|
-
const result = outcome.result;
|
|
649
|
-
if (result.isError) throw new Error(withOptions(toolResultText(result) || "Workflow launch failed.", launchFailureOptions));
|
|
650
|
-
await dependencies.onLaunch?.(ctx);
|
|
651
|
-
return result;
|
|
652
|
-
}
|
|
653
|
-
reportLateFailure(settled, dependencies, ctx);
|
|
654
|
-
await dependencies.onLaunch?.(ctx);
|
|
655
|
-
return { content: [{
|
|
656
|
-
type: "text",
|
|
657
|
-
text: ack.kind === "record" ? launchedRunSummary(ack.record) : launchHandoffSummary(input.workflowPath)
|
|
658
|
-
}] };
|
|
659
|
-
} };
|
|
660
|
-
}
|
|
661
|
-
//#endregion
|
|
662
96
|
//#region src/services/workflowTools/index.ts
|
|
663
97
|
const PI_LAUNCH_FIELDS = {
|
|
664
98
|
workflowPath: true,
|
|
@@ -677,7 +111,7 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
677
111
|
const recoverTool = dependencies.recoverTool ?? feature.createRecoverTool();
|
|
678
112
|
const controlTool = dependencies.controlTool ?? feature.createControlTool();
|
|
679
113
|
const trackPendingRun = dependencies.trackPendingRun ?? ((run) => run);
|
|
680
|
-
const launchExecutor = dependencies.launchExecutor ?? createWorkflowLaunchExecutor({
|
|
114
|
+
const launchExecutor = dependencies.launchExecutor ?? require_workflowTerminal.createWorkflowLaunchExecutor({
|
|
681
115
|
environment: dependencies.environment,
|
|
682
116
|
activeRunCount: dependencies.activeRunCount,
|
|
683
117
|
onLaunch: dependencies.onLaunch,
|
|
@@ -688,23 +122,23 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
688
122
|
});
|
|
689
123
|
return [
|
|
690
124
|
{
|
|
691
|
-
name: LIST_WORKFLOWS_TOOL_NAME,
|
|
125
|
+
name: require_workflowTerminal.LIST_WORKFLOWS_TOOL_NAME,
|
|
692
126
|
label: "List Workflows",
|
|
693
127
|
description: "List the workflow definitions available in this repository.",
|
|
694
128
|
promptSnippet: "List available workflow definitions",
|
|
695
129
|
parameters: zod.z.toJSONSchema(listWorkflowsTool.getInputSchema()),
|
|
696
130
|
renderShell: "self",
|
|
697
|
-
...renderers(LIST_WORKFLOWS_TOOL_NAME),
|
|
131
|
+
...renderers(require_workflowTerminal.LIST_WORKFLOWS_TOOL_NAME),
|
|
698
132
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
699
133
|
const input = params;
|
|
700
|
-
return toAgentToolResult(LIST_WORKFLOWS_TOOL_NAME, await listWorkflowsTool.execute({
|
|
134
|
+
return require_workflowTerminal.toAgentToolResult(require_workflowTerminal.LIST_WORKFLOWS_TOOL_NAME, await listWorkflowsTool.execute({
|
|
701
135
|
...input,
|
|
702
136
|
directory: input?.directory === void 0 ? ctx.cwd : typeof input.directory === "string" ? (0, node_path.resolve)(ctx.cwd, input.directory) : input.directory
|
|
703
137
|
}));
|
|
704
138
|
}
|
|
705
139
|
},
|
|
706
140
|
{
|
|
707
|
-
name: LAUNCH_WORKFLOW_TOOL_NAME,
|
|
141
|
+
name: require_workflowTerminal.LAUNCH_WORKFLOW_TOOL_NAME,
|
|
708
142
|
label: "Launch Workflow",
|
|
709
143
|
description: "Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.",
|
|
710
144
|
promptSnippet: "Start a workflow run and return its identifier",
|
|
@@ -719,15 +153,16 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
719
153
|
],
|
|
720
154
|
parameters: zod.z.toJSONSchema(runTool.getInputSchema().pick(PI_LAUNCH_FIELDS)),
|
|
721
155
|
renderShell: "self",
|
|
722
|
-
...renderers(LAUNCH_WORKFLOW_TOOL_NAME),
|
|
156
|
+
...renderers(require_workflowTerminal.LAUNCH_WORKFLOW_TOOL_NAME),
|
|
723
157
|
async execute(_toolCallId, params, _signal, onUpdate, ctx) {
|
|
724
158
|
const input = params;
|
|
725
159
|
const result = await launchExecutor.execute(input, ctx, onUpdate);
|
|
726
|
-
|
|
160
|
+
const adapted = require_workflowTerminal.toAgentToolResult(require_workflowTerminal.LAUNCH_WORKFLOW_TOOL_NAME, result, require_workflowTerminal.launchFailureOptions);
|
|
161
|
+
return require_workflowTerminal.appendGuidance(adapted, "Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.");
|
|
727
162
|
}
|
|
728
163
|
},
|
|
729
164
|
{
|
|
730
|
-
name: WORKFLOW_RUN_TOOL_NAME
|
|
165
|
+
name: require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME,
|
|
731
166
|
label: "Workflow Run",
|
|
732
167
|
description: "Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.",
|
|
733
168
|
promptSnippet: "Inspect or control a workflow run",
|
|
@@ -742,7 +177,7 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
742
177
|
],
|
|
743
178
|
parameters: zod.z.toJSONSchema(workflowRunInputSchema),
|
|
744
179
|
renderShell: "self",
|
|
745
|
-
...renderers(WORKFLOW_RUN_TOOL_NAME
|
|
180
|
+
...renderers(require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME),
|
|
746
181
|
async execute(_toolCallId, rawParams, _signal, onUpdate, ctx) {
|
|
747
182
|
const input = workflowRunInputSchema.parse(rawParams);
|
|
748
183
|
const sessionId = (0, _agimon_ai_doompi_core_childProcess.resolveRootSessionId)(ctx.sessionManager.getSessionId(), dependencies.environment);
|
|
@@ -762,13 +197,13 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
762
197
|
type: "text",
|
|
763
198
|
text: JSON.stringify(record, null, 2)
|
|
764
199
|
}],
|
|
765
|
-
details: { tool: WORKFLOW_RUN_TOOL_NAME
|
|
200
|
+
details: { tool: require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME }
|
|
766
201
|
};
|
|
767
202
|
}
|
|
768
203
|
if (input.action === "tail") {
|
|
769
204
|
if (!dependencies.tailRun) throw new Error("Reading workflow output is not available in this session.");
|
|
770
205
|
const record = await requireOwnedRun();
|
|
771
|
-
reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME
|
|
206
|
+
require_workflowTerminal.reportProgress(onUpdate, require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME, `Reading output for workflow ${input.runKey}...`);
|
|
772
207
|
await dependencies.tailRun(selector, ctx);
|
|
773
208
|
return {
|
|
774
209
|
content: [{
|
|
@@ -779,42 +214,42 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
779
214
|
"Raw launcher output is hidden from chat. Use action follow or open to view it."
|
|
780
215
|
].join("\n")
|
|
781
216
|
}],
|
|
782
|
-
details: { tool: WORKFLOW_RUN_TOOL_NAME
|
|
217
|
+
details: { tool: require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME }
|
|
783
218
|
};
|
|
784
219
|
}
|
|
785
220
|
if (input.action === "recovery-evidence") {
|
|
786
221
|
if (!dependencies.readRecoveryEvidence) throw new Error("Reading workflow recovery evidence is not available in this session.");
|
|
787
|
-
reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME
|
|
222
|
+
require_workflowTerminal.reportProgress(onUpdate, require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME, `Reading recovery evidence for workflow ${input.runKey}...`);
|
|
788
223
|
return {
|
|
789
224
|
content: [{
|
|
790
225
|
type: "text",
|
|
791
226
|
text: await dependencies.readRecoveryEvidence(selector, ctx)
|
|
792
227
|
}],
|
|
793
|
-
details: { tool: WORKFLOW_RUN_TOOL_NAME
|
|
228
|
+
details: { tool: require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME }
|
|
794
229
|
};
|
|
795
230
|
}
|
|
796
231
|
if (input.action === "follow") {
|
|
797
232
|
if (!dependencies.followRun) throw new Error("Following workflow output is not available in this session.");
|
|
798
233
|
await requireOwnedRun();
|
|
799
|
-
reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME
|
|
234
|
+
require_workflowTerminal.reportProgress(onUpdate, require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME, `Following workflow ${input.runKey}...`);
|
|
800
235
|
return {
|
|
801
236
|
content: [{
|
|
802
237
|
type: "text",
|
|
803
238
|
text: await dependencies.followRun(selector, ctx)
|
|
804
239
|
}],
|
|
805
|
-
details: { tool: WORKFLOW_RUN_TOOL_NAME
|
|
240
|
+
details: { tool: require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME }
|
|
806
241
|
};
|
|
807
242
|
}
|
|
808
243
|
if (input.action === "open") {
|
|
809
244
|
if (!dependencies.openRun) throw new Error("Opening a workflow launcher is not available in this session.");
|
|
810
245
|
await requireOwnedRun();
|
|
811
|
-
reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME
|
|
246
|
+
require_workflowTerminal.reportProgress(onUpdate, require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME, `Opening workflow ${input.runKey}...`);
|
|
812
247
|
return {
|
|
813
248
|
content: [{
|
|
814
249
|
type: "text",
|
|
815
250
|
text: await dependencies.openRun(selector, ctx)
|
|
816
251
|
}],
|
|
817
|
-
details: { tool: WORKFLOW_RUN_TOOL_NAME
|
|
252
|
+
details: { tool: require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME }
|
|
818
253
|
};
|
|
819
254
|
}
|
|
820
255
|
if (input.action === "recover") {
|
|
@@ -824,21 +259,21 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
824
259
|
runKey: recoverable.runKey,
|
|
825
260
|
workspace: recoverable.workspace
|
|
826
261
|
};
|
|
827
|
-
reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME
|
|
262
|
+
require_workflowTerminal.reportProgress(onUpdate, require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME, `Recovering workflow ${input.runKey}...`);
|
|
828
263
|
if (!input.dryRun) {
|
|
829
264
|
const delegated = await dependencies.delegateRecovery?.({
|
|
830
265
|
...recoverySelector,
|
|
831
266
|
runner: input.runner
|
|
832
267
|
}, ctx);
|
|
833
268
|
if (delegated) {
|
|
834
|
-
const guidance = actionGuidance(input.action, input.runKey);
|
|
269
|
+
const guidance = require_workflowTerminal.actionGuidance(input.action, input.runKey);
|
|
835
270
|
await dependencies.onLaunch?.(ctx);
|
|
836
271
|
return {
|
|
837
272
|
content: [{
|
|
838
273
|
type: "text",
|
|
839
274
|
text: [delegated, guidance[0]].join("\n\n")
|
|
840
275
|
}],
|
|
841
|
-
details: { tool: WORKFLOW_RUN_TOOL_NAME
|
|
276
|
+
details: { tool: require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME }
|
|
842
277
|
};
|
|
843
278
|
}
|
|
844
279
|
}
|
|
@@ -850,14 +285,14 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
850
285
|
workspace: recoverySelector.workspace
|
|
851
286
|
};
|
|
852
287
|
const result = await trackPendingRun(recoverTool.execute(recoverInput));
|
|
853
|
-
const guidance = actionGuidance(input.action, input.runKey);
|
|
854
|
-
const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME
|
|
288
|
+
const guidance = require_workflowTerminal.actionGuidance(input.action, input.runKey);
|
|
289
|
+
const adapted = require_workflowTerminal.toAgentToolResult(require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME, result, guidance);
|
|
855
290
|
await dependencies.onLaunch?.(ctx);
|
|
856
|
-
return appendGuidance(adapted, guidance[0]);
|
|
291
|
+
return require_workflowTerminal.appendGuidance(adapted, guidance[0]);
|
|
857
292
|
}
|
|
858
293
|
const ownedRun = await requireOwnedRun();
|
|
859
294
|
if (!ownedRun.runId) throw new Error(`Workflow ${input.runKey} has no controllable run generation.`);
|
|
860
|
-
reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME
|
|
295
|
+
require_workflowTerminal.reportProgress(onUpdate, require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME, `Requesting ${input.action} for workflow ${input.runKey}...`);
|
|
861
296
|
const result = await controlTool.execute({
|
|
862
297
|
action: input.action,
|
|
863
298
|
expectedRunId: ownedRun.runId,
|
|
@@ -865,8 +300,9 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
865
300
|
runKey: input.runKey,
|
|
866
301
|
workspace: input.workspace
|
|
867
302
|
});
|
|
868
|
-
const guidance = actionGuidance(input.action, input.runKey);
|
|
869
|
-
|
|
303
|
+
const guidance = require_workflowTerminal.actionGuidance(input.action, input.runKey);
|
|
304
|
+
const adapted = require_workflowTerminal.toAgentToolResult(require_workflowTerminal.WORKFLOW_RUN_TOOL_NAME, result, guidance);
|
|
305
|
+
return require_workflowTerminal.appendGuidance(adapted, guidance[0]);
|
|
870
306
|
}
|
|
871
307
|
}
|
|
872
308
|
];
|
|
@@ -876,8 +312,8 @@ function createWorkflowTools(dependencies, renderers = () => ({})) {
|
|
|
876
312
|
const LEADER_BINDING_PREFIX = "doom-workflow";
|
|
877
313
|
const GROUP_SEGMENT = {
|
|
878
314
|
key: "w",
|
|
879
|
-
label: LEADER_LABEL,
|
|
880
|
-
detail: LEADER_DETAIL,
|
|
315
|
+
label: require_workflowTerminal.LEADER_LABEL,
|
|
316
|
+
detail: require_workflowTerminal.LEADER_DETAIL,
|
|
881
317
|
order: 50
|
|
882
318
|
};
|
|
883
319
|
/**
|
|
@@ -896,7 +332,7 @@ function workflowLeaderBindings(mode) {
|
|
|
896
332
|
label: "list",
|
|
897
333
|
detail: "browse and launch workflows"
|
|
898
334
|
}],
|
|
899
|
-
action: { name: LEADER_CATALOG_ACTION }
|
|
335
|
+
action: { name: require_workflowTerminal.LEADER_CATALOG_ACTION }
|
|
900
336
|
},
|
|
901
337
|
{
|
|
902
338
|
id: `${LEADER_BINDING_PREFIX}.manage`,
|
|
@@ -905,7 +341,7 @@ function workflowLeaderBindings(mode) {
|
|
|
905
341
|
label: "runs",
|
|
906
342
|
detail: "runs in this session"
|
|
907
343
|
}],
|
|
908
|
-
action: { name: LEADER_MANAGE_ACTION }
|
|
344
|
+
action: { name: require_workflowTerminal.LEADER_MANAGE_ACTION }
|
|
909
345
|
},
|
|
910
346
|
mode ? {
|
|
911
347
|
id: `${LEADER_BINDING_PREFIX}.disable`,
|
|
@@ -915,7 +351,7 @@ function workflowLeaderBindings(mode) {
|
|
|
915
351
|
detail: "take back workflow tools",
|
|
916
352
|
tone: "exit"
|
|
917
353
|
}],
|
|
918
|
-
action: { name: LEADER_DISABLE_ACTION }
|
|
354
|
+
action: { name: require_workflowTerminal.LEADER_DISABLE_ACTION }
|
|
919
355
|
} : {
|
|
920
356
|
id: `${LEADER_BINDING_PREFIX}.enable`,
|
|
921
357
|
path: [GROUP_SEGMENT, {
|
|
@@ -923,7 +359,7 @@ function workflowLeaderBindings(mode) {
|
|
|
923
359
|
label: "enter",
|
|
924
360
|
detail: "give the agent workflow tools"
|
|
925
361
|
}],
|
|
926
|
-
action: { name: LEADER_ENABLE_ACTION }
|
|
362
|
+
action: { name: require_workflowTerminal.LEADER_ENABLE_ACTION }
|
|
927
363
|
},
|
|
928
364
|
{
|
|
929
365
|
id: `${LEADER_BINDING_PREFIX}.recover`,
|
|
@@ -932,13 +368,13 @@ function workflowLeaderBindings(mode) {
|
|
|
932
368
|
label: "recover",
|
|
933
369
|
detail: "adopt a failed run"
|
|
934
370
|
}],
|
|
935
|
-
action: { name: LEADER_RECOVER_ACTION }
|
|
371
|
+
action: { name: require_workflowTerminal.LEADER_RECOVER_ACTION }
|
|
936
372
|
}
|
|
937
373
|
];
|
|
938
374
|
}
|
|
939
375
|
function registerLeaderContribution(hub, enabled = false) {
|
|
940
376
|
const contribution = hub.registerLeader({
|
|
941
|
-
source: PACKAGE_SOURCE,
|
|
377
|
+
source: require_workflowTerminal.PACKAGE_SOURCE,
|
|
942
378
|
bindings: workflowLeaderBindings(enabled)
|
|
943
379
|
});
|
|
944
380
|
return {
|
|
@@ -1456,13 +892,13 @@ function dispatchInputs(workflow) {
|
|
|
1456
892
|
return parsed.data.inputs ?? {};
|
|
1457
893
|
}
|
|
1458
894
|
function launchResultError(result) {
|
|
1459
|
-
return new Error(toolResultText(result) || "Workflow launch failed.");
|
|
895
|
+
return new Error(require_workflowTerminal.toolResultText(result) || "Workflow launch failed.");
|
|
1460
896
|
}
|
|
1461
897
|
function parseWorkflowCatalogPage(result) {
|
|
1462
898
|
if (result.isError) throw launchResultError(result);
|
|
1463
899
|
let value;
|
|
1464
900
|
try {
|
|
1465
|
-
value = JSON.parse(toolResultText(result));
|
|
901
|
+
value = JSON.parse(require_workflowTerminal.toolResultText(result));
|
|
1466
902
|
} catch (cause) {
|
|
1467
903
|
throw new Error("Workflow catalog returned invalid JSON.", { cause });
|
|
1468
904
|
}
|
|
@@ -1573,11 +1009,15 @@ function summarizeWorkflowFile(workflowPath, options) {
|
|
|
1573
1009
|
try {
|
|
1574
1010
|
const workflow = options.parseWorkflow(workflowPath);
|
|
1575
1011
|
const runners = options.compatibleRunners(workflow);
|
|
1012
|
+
const commands = (0, _agimon_ai_workflow_mcp.compatibleCommands)(workflow);
|
|
1013
|
+
const choices = Object.keys(workflow.choices ?? {});
|
|
1576
1014
|
return {
|
|
1577
1015
|
triggers: Object.keys(workflowTriggerBlock(workflow)),
|
|
1578
1016
|
inputs: catalogInputs(workflow),
|
|
1579
1017
|
jobs: catalogJobs(workflow),
|
|
1580
|
-
...runners ? { runners } : {}
|
|
1018
|
+
...runners ? { runners } : {},
|
|
1019
|
+
...commands ? { commands } : {},
|
|
1020
|
+
...choices.length > 0 ? { choices } : {}
|
|
1581
1021
|
};
|
|
1582
1022
|
} catch (cause) {
|
|
1583
1023
|
return {
|
|
@@ -1597,11 +1037,25 @@ async function launchWorkflowEntry(options, selected, ctx) {
|
|
|
1597
1037
|
runner = await options.ui.select("Select a workflow runner", runners);
|
|
1598
1038
|
if (runner === void 0) return void 0;
|
|
1599
1039
|
}
|
|
1040
|
+
const commands = (0, _agimon_ai_workflow_mcp.compatibleCommands)(workflow) ?? [];
|
|
1041
|
+
let command;
|
|
1042
|
+
if (commands.length > 1) {
|
|
1043
|
+
command = await options.ui.select("Select the command agent steps run through", commands);
|
|
1044
|
+
if (command === void 0) return void 0;
|
|
1045
|
+
}
|
|
1046
|
+
const choices = Object.keys(workflow.choices ?? {});
|
|
1047
|
+
let choice;
|
|
1048
|
+
if (choices.length > 1) {
|
|
1049
|
+
choice = await options.ui.select("Select a model choice", choices);
|
|
1050
|
+
if (choice === void 0) return void 0;
|
|
1051
|
+
}
|
|
1600
1052
|
const triggerInputs = await collectWorkflowInputs(workflow, options.ui);
|
|
1601
1053
|
if (triggerInputs === void 0) return void 0;
|
|
1602
1054
|
const launchInput = {
|
|
1603
1055
|
workflowPath: selected.path,
|
|
1604
1056
|
...runner === void 0 ? {} : { runner },
|
|
1057
|
+
...command === void 0 ? {} : { command },
|
|
1058
|
+
...choice === void 0 ? {} : { choice },
|
|
1605
1059
|
...typeof workflow.workspace === "string" ? { workspace: workflow.workspace } : {},
|
|
1606
1060
|
...triggerInputs
|
|
1607
1061
|
};
|
|
@@ -3706,7 +3160,7 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
3706
3160
|
const restrictionListeners = /* @__PURE__ */ new Set();
|
|
3707
3161
|
const modeContribution = (0, _agimon_ai_doompi_minor_mode.defineMinorMode)({
|
|
3708
3162
|
descriptor: {
|
|
3709
|
-
source: PACKAGE_SOURCE,
|
|
3163
|
+
source: require_workflowTerminal.PACKAGE_SOURCE,
|
|
3710
3164
|
id: MODE_ID,
|
|
3711
3165
|
label: MODE_LABEL,
|
|
3712
3166
|
description: "Workflow discovery, launch, inspection, control, and recovery tools.",
|
|
@@ -3874,7 +3328,7 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
3874
3328
|
if (!compatible || compatible.includes(runner)) return void 0;
|
|
3875
3329
|
return `Runner ${JSON.stringify(runner)} is not available across this workflow. Compatible runners: ${compatible.join(", ") || "none"}.`;
|
|
3876
3330
|
};
|
|
3877
|
-
const launchExecutor = createWorkflowLaunchExecutor({
|
|
3331
|
+
const launchExecutor = require_workflowTerminal.createWorkflowLaunchExecutor({
|
|
3878
3332
|
environment: options.environment,
|
|
3879
3333
|
activeRunCount: async () => sessionRunCount,
|
|
3880
3334
|
observeSession,
|
|
@@ -3883,10 +3337,12 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
3883
3337
|
trackPendingRun,
|
|
3884
3338
|
...options.launchAckPollMs === void 0 ? {} : { launchAckPollMs: options.launchAckPollMs },
|
|
3885
3339
|
...options.launchAckTimeoutMs === void 0 ? {} : { launchAckTimeoutMs: options.launchAckTimeoutMs },
|
|
3886
|
-
findLaunchedRun: async ({ sessionId, since, workflowPath }) => {
|
|
3340
|
+
findLaunchedRun: async ({ launchId, sessionId, since, workflowPath }) => {
|
|
3887
3341
|
const expectedId = workflowIdFor(workflowPath);
|
|
3888
3342
|
const matches = (record) => {
|
|
3889
|
-
if (!isSessionRun(record, sessionId) || record.stale) return false;
|
|
3343
|
+
if (!require_workflowTerminal.isSessionRun(record, sessionId) || record.stale) return false;
|
|
3344
|
+
const carried = record.env?.[require_workflowTerminal.WORKFLOW_LAUNCH_ID_ENV];
|
|
3345
|
+
if (carried !== void 0) return carried === launchId;
|
|
3890
3346
|
if (record.workflowId && record.workflowId !== expectedId) return false;
|
|
3891
3347
|
const startedAt = Date.parse(record.startedAt);
|
|
3892
3348
|
return Number.isFinite(startedAt) && startedAt >= since - LAUNCH_ACK_CLOCK_SKEW_MS;
|
|
@@ -3897,7 +3353,7 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
3897
3353
|
},
|
|
3898
3354
|
onLateFailure: (error, ctx) => {
|
|
3899
3355
|
const detail = error instanceof Error ? error.message : String(error);
|
|
3900
|
-
ctx.ui.notify(launchNotice(`Workflow launch failed after it was reported started.\n${detail}`), "warning");
|
|
3356
|
+
ctx.ui.notify(require_workflowTerminal.launchNotice(`Workflow launch failed after it was reported started.\n${detail}`), "warning");
|
|
3901
3357
|
},
|
|
3902
3358
|
onLaunch: async (ctx) => {
|
|
3903
3359
|
await getTelemetry(ctx).recordEvent(LAUNCH_REQUESTED_EVENT, { outcome: "requested" });
|
|
@@ -3959,7 +3415,7 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
3959
3415
|
*/
|
|
3960
3416
|
const startRunControl = async (ctx) => {
|
|
3961
3417
|
const sessionId = (0, _agimon_ai_doompi_core_childProcess.resolveRootSessionId)(ctx.sessionManager.getSessionId(), options.environment);
|
|
3962
|
-
const control = feature.createRunControl({ recordFilter: (candidate) => isSessionRun(candidate, sessionId) });
|
|
3418
|
+
const control = feature.createRunControl({ recordFilter: (candidate) => require_workflowTerminal.isSessionRun(candidate, sessionId) });
|
|
3963
3419
|
runControl = control;
|
|
3964
3420
|
const controlIsCurrent = () => isRuntimeActive() && ctx === sessionCtx && runControl === control;
|
|
3965
3421
|
const controlDisposers = [
|
|
@@ -4076,7 +3532,7 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4076
3532
|
if (!refreshIsCurrent()) return;
|
|
4077
3533
|
const sessionId = (0, _agimon_ai_doompi_core_childProcess.resolveRootSessionId)(ctx.sessionManager.getSessionId(), options.environment);
|
|
4078
3534
|
const rootProcess = isRootProcess(ctx, sessionId);
|
|
4079
|
-
const sessionRecords = records.filter((record) => isSessionRun(record, sessionId));
|
|
3535
|
+
const sessionRecords = records.filter((record) => require_workflowTerminal.isSessionRun(record, sessionId));
|
|
4080
3536
|
const running = sessionRecords.filter((record) => record.stage === RUNNING_STATUS && !record.stale);
|
|
4081
3537
|
const failed = sessionRecords.filter((record) => record.stage === ERROR_STAGE);
|
|
4082
3538
|
if (activeRevisionAtRead === activeRunRevision) replaceActiveRecords(running);
|
|
@@ -4086,7 +3542,7 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4086
3542
|
"workflow.failed_count": failed.length,
|
|
4087
3543
|
mode: workflowMode ? "on" : "off"
|
|
4088
3544
|
});
|
|
4089
|
-
const mine = runsForSession(records, sessionId).filter((record) => !record.stale);
|
|
3545
|
+
const mine = require_workflowTerminal.runsForSession(records, sessionId).filter((record) => !record.stale);
|
|
4090
3546
|
sessionRunCount = mine.length;
|
|
4091
3547
|
publishModeWidget();
|
|
4092
3548
|
forgetLauncherScreens(new Set(sessionRecords.map((record) => runIdentity(record))));
|
|
@@ -4119,10 +3575,10 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4119
3575
|
if (rootProcess && pendingTerminalRuns.size > 0) try {
|
|
4120
3576
|
const terminalRecords = [...pendingTerminalRuns.values()];
|
|
4121
3577
|
const summaries = await Promise.all(terminalRecords.map(async (record) => {
|
|
4122
|
-
if (record.stage === COMPLETED_STATUS) return finishedRunSummary(record, []);
|
|
3578
|
+
if (record.stage === COMPLETED_STATUS) return require_workflowTerminal.finishedRunSummary(record, []);
|
|
4123
3579
|
const identity = runIdentity(record);
|
|
4124
3580
|
const events = runEvents.get(identity) ?? progressEventsForRun(record, await (0, _agimon_ai_workflow_mcp.readWorkflowProgress)(registry.runDirectoryFor(record)));
|
|
4125
|
-
return finishedRunSummary(record, (0, _agimon_ai_workflow_mcp.summarizeWorkflowProgress)(events));
|
|
3581
|
+
return require_workflowTerminal.finishedRunSummary(record, (0, _agimon_ai_workflow_mcp.summarizeWorkflowProgress)(events));
|
|
4126
3582
|
}));
|
|
4127
3583
|
if (!refreshIsCurrent()) return;
|
|
4128
3584
|
pi.sendMessage({
|
|
@@ -4422,7 +3878,7 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4422
3878
|
const selection = await ctx.ui.custom((tui, theme, _keybindings, done) => new WorkflowInspectorComponent(tui, theme, {
|
|
4423
3879
|
list: async () => {
|
|
4424
3880
|
const sessionId = currentSessionId();
|
|
4425
|
-
const records = (await listAllRuns(registry)).filter((record) => record.stage === RUNNING_STATUS && !record.stale && isSessionRun(record, sessionId));
|
|
3881
|
+
const records = (await listAllRuns(registry)).filter((record) => record.stage === RUNNING_STATUS && !record.stale && require_workflowTerminal.isSessionRun(record, sessionId));
|
|
4426
3882
|
inspectorRecords = new Map(records.map((record) => [runIdentity(record), record]));
|
|
4427
3883
|
return Promise.all(records.map(async (record) => ({
|
|
4428
3884
|
displayName: record.displayName,
|
|
@@ -4466,10 +3922,10 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4466
3922
|
*/
|
|
4467
3923
|
const requireRun = async (input, sessionId = currentSessionId()) => {
|
|
4468
3924
|
observeSession(sessionId);
|
|
4469
|
-
const matches = (await listAllRuns(registry, input.workspace)).filter((candidate) => candidate.runKey === input.runKey && isSessionRun(candidate, sessionId));
|
|
4470
|
-
if (matches.length > 1) throw new Error(withOptions(`Workflow run key ${JSON.stringify(input.runKey)} exists in more than one workspace in this session.`, ["Retry with the workspace from the run identity."]));
|
|
3925
|
+
const matches = (await listAllRuns(registry, input.workspace)).filter((candidate) => candidate.runKey === input.runKey && require_workflowTerminal.isSessionRun(candidate, sessionId));
|
|
3926
|
+
if (matches.length > 1) throw new Error(require_workflowTerminal.withOptions(`Workflow run key ${JSON.stringify(input.runKey)} exists in more than one workspace in this session.`, ["Retry with the workspace from the run identity."]));
|
|
4471
3927
|
const record = matches[0];
|
|
4472
|
-
if (!record) throw new Error(withOptions(`No workflow run matches ${JSON.stringify(input.runKey)} in this session.`, ["workflow_run with action status: use a run key from this session before retrying.", "Tell the user this session did not launch that run rather than guessing a similar key. A run launched by another Pi session, or from the command line, is managed there or with the workflow-mcp CLI."]));
|
|
3928
|
+
if (!record) throw new Error(require_workflowTerminal.withOptions(`No workflow run matches ${JSON.stringify(input.runKey)} in this session.`, ["workflow_run with action status: use a run key from this session before retrying.", "Tell the user this session did not launch that run rather than guessing a similar key. A run launched by another Pi session, or from the command line, is managed there or with the workflow-mcp CLI."]));
|
|
4473
3929
|
return record;
|
|
4474
3930
|
};
|
|
4475
3931
|
/**
|
|
@@ -4482,9 +3938,9 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4482
3938
|
*/
|
|
4483
3939
|
const requireRecoverableRun = async (input) => {
|
|
4484
3940
|
const matches = (await listAllRuns(registry, input.workspace)).filter((candidate) => candidate.runKey === input.runKey && candidate.stage === ERROR_STAGE);
|
|
4485
|
-
if (matches.length > 1) throw new Error(withOptions(`Failed workflow run key ${JSON.stringify(input.runKey)} exists in more than one workspace.`, ["Retry with the workspace from the recovery picker or run identity."]));
|
|
3941
|
+
if (matches.length > 1) throw new Error(require_workflowTerminal.withOptions(`Failed workflow run key ${JSON.stringify(input.runKey)} exists in more than one workspace.`, ["Retry with the workspace from the recovery picker or run identity."]));
|
|
4486
3942
|
const record = matches[0];
|
|
4487
|
-
if (!record) throw new Error(withOptions(`No failed workflow run matches ${JSON.stringify(input.runKey)}.`, ["Use the recovery picker to select an interrupted or failed run, or retry with its workspace.", "Do not recover a running or completed workflow."]));
|
|
3943
|
+
if (!record) throw new Error(require_workflowTerminal.withOptions(`No failed workflow run matches ${JSON.stringify(input.runKey)}.`, ["Use the recovery picker to select an interrupted or failed run, or retry with its workspace.", "Do not recover a running or completed workflow."]));
|
|
4488
3944
|
return record;
|
|
4489
3945
|
};
|
|
4490
3946
|
const readRecoveryEvidenceTail = async (record) => {
|
|
@@ -4621,8 +4077,8 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4621
4077
|
runKey: record.runKey,
|
|
4622
4078
|
workspace: record.workspace
|
|
4623
4079
|
});
|
|
4624
|
-
if (result.isError) throw new Error(toolResultText(result) || `Unable to ${action.toLowerCase()} workflow.`);
|
|
4625
|
-
ctx.ui.notify(toolResultText(result) || `${action} requested for ${record.runKey}.`, "info");
|
|
4080
|
+
if (result.isError) throw new Error(require_workflowTerminal.toolResultText(result) || `Unable to ${action.toLowerCase()} workflow.`);
|
|
4081
|
+
ctx.ui.notify(require_workflowTerminal.toolResultText(result) || `${action} requested for ${record.runKey}.`, "info");
|
|
4626
4082
|
await refreshStatus(ctx, false);
|
|
4627
4083
|
record = await requireRun(record);
|
|
4628
4084
|
}
|
|
@@ -4679,7 +4135,7 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4679
4135
|
const entry = byPath.get(row.key);
|
|
4680
4136
|
if (!entry) return;
|
|
4681
4137
|
launchWorkflowEntry(launchOptions, entry, ctx).then((result) => {
|
|
4682
|
-
if (result) ctx.ui.notify(launchNotice(toolResultText(result)), "info");
|
|
4138
|
+
if (result) ctx.ui.notify(require_workflowTerminal.launchNotice(require_workflowTerminal.toolResultText(result)), "info");
|
|
4683
4139
|
}, (error) => {
|
|
4684
4140
|
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
4685
4141
|
});
|
|
@@ -4725,15 +4181,17 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4725
4181
|
const input = {
|
|
4726
4182
|
workflowPath: entry.path,
|
|
4727
4183
|
...parsed.runner === void 0 ? {} : { runner: parsed.runner },
|
|
4184
|
+
...parsed.command === void 0 ? {} : { command: parsed.command },
|
|
4185
|
+
...parsed.choice === void 0 ? {} : { choice: parsed.choice },
|
|
4728
4186
|
...typeof workflow.workspace === "string" ? { workspace: workflow.workspace } : {},
|
|
4729
4187
|
...parsed.prompt === void 0 ? {} : { prompt: parsed.prompt },
|
|
4730
4188
|
...Object.keys(parsed.inputs).length === 0 ? {} : { inputs: parsed.inputs }
|
|
4731
4189
|
};
|
|
4732
4190
|
const result = await launchExecutor.execute(input, ctx);
|
|
4733
|
-
ctx.ui.notify(launchNotice(toolResultText(result)), "info");
|
|
4191
|
+
ctx.ui.notify(require_workflowTerminal.launchNotice(require_workflowTerminal.toolResultText(result)), "info");
|
|
4734
4192
|
};
|
|
4735
4193
|
const commands = [[WORKFLOW_LAUNCH_COMMAND, {
|
|
4736
|
-
description: "Launch a workflow: /workflow-launch <workflow> [runner=x] [key=value …] [prompt]",
|
|
4194
|
+
description: "Launch a workflow: /workflow-launch <workflow> [runner=x] [command=x] [choice=x] [key=value …] [prompt]",
|
|
4737
4195
|
handler: async (args, ctx) => {
|
|
4738
4196
|
await waitForReadiness();
|
|
4739
4197
|
try {
|
|
@@ -4880,25 +4338,25 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4880
4338
|
services.push((cordis) => {
|
|
4881
4339
|
cordis.inject([_agimon_ai_doompi_core_uiHub.DOOM_UI_HUB_SERVICE], (uiContext) => {
|
|
4882
4340
|
const dispose = (0, _agimon_ai_doompi_core_uiHub.requireDoomUiHub)(uiContext).registerLeaderActions({
|
|
4883
|
-
source: PACKAGE_SOURCE,
|
|
4341
|
+
source: require_workflowTerminal.PACKAGE_SOURCE,
|
|
4884
4342
|
handlers: {
|
|
4885
|
-
[LEADER_ENABLE_ACTION]: async (ctx) => {
|
|
4343
|
+
[require_workflowTerminal.LEADER_ENABLE_ACTION]: async (ctx) => {
|
|
4886
4344
|
await waitForReadiness();
|
|
4887
4345
|
return applyWorkflowMode(true, ctx);
|
|
4888
4346
|
},
|
|
4889
|
-
[LEADER_DISABLE_ACTION]: async (ctx) => {
|
|
4347
|
+
[require_workflowTerminal.LEADER_DISABLE_ACTION]: async (ctx) => {
|
|
4890
4348
|
await waitForReadiness();
|
|
4891
4349
|
return applyWorkflowMode(false, ctx);
|
|
4892
4350
|
},
|
|
4893
|
-
[LEADER_MANAGE_ACTION]: async (ctx) => {
|
|
4351
|
+
[require_workflowTerminal.LEADER_MANAGE_ACTION]: async (ctx) => {
|
|
4894
4352
|
await waitForReadiness();
|
|
4895
4353
|
return openInspector(ctx, true);
|
|
4896
4354
|
},
|
|
4897
|
-
[LEADER_CATALOG_ACTION]: async (ctx) => {
|
|
4355
|
+
[require_workflowTerminal.LEADER_CATALOG_ACTION]: async (ctx) => {
|
|
4898
4356
|
await waitForReadiness();
|
|
4899
4357
|
return openWorkflowCatalog(ctx);
|
|
4900
4358
|
},
|
|
4901
|
-
[LEADER_RECOVER_ACTION]: async (ctx) => {
|
|
4359
|
+
[require_workflowTerminal.LEADER_RECOVER_ACTION]: async (ctx) => {
|
|
4902
4360
|
await waitForReadiness();
|
|
4903
4361
|
return openRecoveryHandoff(ctx);
|
|
4904
4362
|
}
|
|
@@ -4956,7 +4414,7 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
4956
4414
|
readinessHandle = (async () => {
|
|
4957
4415
|
if (previousReadiness) await Promise.allSettled([previousReadiness.then((handle) => handle.wait())]);
|
|
4958
4416
|
if (!ownsSession()) throw new Error("Workflow session initialization was superseded.");
|
|
4959
|
-
return coordinator.start(PACKAGE_SOURCE, `${runtimeGeneration}:${generation}`, async (signal) => {
|
|
4417
|
+
return coordinator.start(require_workflowTerminal.PACKAGE_SOURCE, `${runtimeGeneration}:${generation}`, async (signal) => {
|
|
4960
4418
|
const retired = () => ownReadinessAbort.signal.aborted || !ownsSession();
|
|
4961
4419
|
signal.throwIfAborted();
|
|
4962
4420
|
if (retired()) return { value: void 0 };
|
|
@@ -5102,9 +4560,9 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
5102
4560
|
resources_discover: onResourcesDiscover
|
|
5103
4561
|
},
|
|
5104
4562
|
toolRestrictions: [{
|
|
5105
|
-
source: PACKAGE_SOURCE,
|
|
4563
|
+
source: require_workflowTerminal.PACKAGE_SOURCE,
|
|
5106
4564
|
get restrict() {
|
|
5107
|
-
return workflowToolRestriction(workflowMode);
|
|
4565
|
+
return require_workflowTerminal.workflowToolRestriction(workflowMode);
|
|
5108
4566
|
},
|
|
5109
4567
|
subscribe(listener) {
|
|
5110
4568
|
restrictionListeners.add(listener);
|
|
@@ -5119,8 +4577,8 @@ function createWorkflowPiRuntime(pi, options) {
|
|
|
5119
4577
|
//#region src/extensions/workspaces/sessions/(backend)/_lib/index.cli.ts
|
|
5120
4578
|
var index_cli_default = (({ pi, context, options, signal }) => {
|
|
5121
4579
|
const environment = options?.environment ?? Object.freeze({ ...process.env });
|
|
5122
|
-
const dispatcher = isWorkflowDispatcherProcess(environment);
|
|
5123
|
-
const fence = createWorkflowFence(pi, signal);
|
|
4580
|
+
const dispatcher = require_workflowTerminal.isWorkflowDispatcherProcess(environment);
|
|
4581
|
+
const fence = require_workflowTerminal.createWorkflowFence(pi, signal);
|
|
5124
4582
|
let workflowMode = false;
|
|
5125
4583
|
let leader;
|
|
5126
4584
|
const runtime = createWorkflowPiRuntime(fence.pi, {
|
|
@@ -5143,13 +4601,13 @@ var index_cli_default = (({ pi, context, options, signal }) => {
|
|
|
5143
4601
|
isError: renderContext.isError
|
|
5144
4602
|
}, theme)
|
|
5145
4603
|
}));
|
|
5146
|
-
const selectedTools = dispatcher ? dispatcherTools(nativeTools, resolveDispatcherParentSession(environment), environment) : nativeTools;
|
|
4604
|
+
const selectedTools = dispatcher ? require_workflowTerminal.dispatcherTools(nativeTools, require_workflowTerminal.resolveDispatcherParentSession(environment), environment) : nativeTools;
|
|
5147
4605
|
return {
|
|
5148
4606
|
services: [...runtime.services ?? [], (cordis) => {
|
|
5149
4607
|
cordis.inject([_agimon_ai_doompi_core_skills.DOOM_SKILL_SOURCES_SERVICE], (skillContext) => {
|
|
5150
4608
|
const directory = require_workflowTerminal.workflowSkillDirectory();
|
|
5151
4609
|
const contribution = (0, _agimon_ai_doompi_core_skills.requireDoomSkillSourcesService)(skillContext).register({
|
|
5152
|
-
source: PACKAGE_SOURCE,
|
|
4610
|
+
source: require_workflowTerminal.PACKAGE_SOURCE,
|
|
5153
4611
|
directories: [directory]
|
|
5154
4612
|
});
|
|
5155
4613
|
return () => contribution.dispose();
|
|
@@ -5174,11 +4632,11 @@ var index_cli_default = (({ pi, context, options, signal }) => {
|
|
|
5174
4632
|
shortcuts: runtime.shortcuts?.map(([key, shortcut]) => fence.shortcut(key, shortcut)),
|
|
5175
4633
|
events: fence.events(runtime.events ?? {}),
|
|
5176
4634
|
toolRestrictions: [...runtime.toolRestrictions ?? [], ...dispatcher ? [{
|
|
5177
|
-
source: `${PACKAGE_SOURCE}/dispatcher`,
|
|
5178
|
-
restrict: dispatcherToolRestriction()
|
|
4635
|
+
source: `${require_workflowTerminal.PACKAGE_SOURCE}/dispatcher`,
|
|
4636
|
+
restrict: require_workflowTerminal.dispatcherToolRestriction()
|
|
5179
4637
|
}] : []],
|
|
5180
4638
|
resources: dispatcher ? [] : [{
|
|
5181
|
-
source: PACKAGE_SOURCE,
|
|
4639
|
+
source: require_workflowTerminal.PACKAGE_SOURCE,
|
|
5182
4640
|
moduleUrl: require("url").pathToFileURL(__filename).href,
|
|
5183
4641
|
skills: [{
|
|
5184
4642
|
name: "doompi-author-workflow",
|