@agimon-ai/po-workflow 0.0.0-alpha.56 → 0.0.0-alpha.57
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 +98 -2
- package/dist/{RecoverWorkflowService-C1lWL4fC.cjs → RecoverWorkflowService-DL5itVWA.cjs} +61 -6
- package/dist/RecoverWorkflowService-DL5itVWA.cjs.map +1 -0
- package/dist/{RecoverWorkflowService-D_AK7tzm.mjs → RecoverWorkflowService-DnR8EQc1.mjs} +56 -7
- package/dist/RecoverWorkflowService-DnR8EQc1.mjs.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/extensions/native.mjs +1036 -459
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +210 -34
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.mjs +212 -36
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +62 -50
- package/dist/extensions/web.mjs.map +1 -1
- package/llms.txt +2 -2
- package/package.json +8 -8
- package/src/prompts/codemode-how-to-workflow/SKILL.md +31 -2
- package/src/prompts/help-how-to-workflow/SKILL.md +42 -0
- package/dist/RecoverWorkflowService-C1lWL4fC.cjs.map +0 -1
- package/dist/RecoverWorkflowService-D_AK7tzm.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -49,6 +49,101 @@ needs, and `run` and `interactiveRun` steps each get their own RMUX pane. Each r
|
|
|
49
49
|
log in `engine.log` in its run directory. See `docs/automation.md` for the full contract and the
|
|
50
50
|
CLI fallback.
|
|
51
51
|
|
|
52
|
+
## Job-specific tools
|
|
53
|
+
|
|
54
|
+
Managed Po agent steps can receive a workflow-owned `jobTools` code-mode namespace and a pinned
|
|
55
|
+
`workflow-job-tools` skill. Select a folder at workflow level as a default, or override it per job:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
name: summarize
|
|
59
|
+
runConfig:
|
|
60
|
+
majorMode: copilot
|
|
61
|
+
tools: ./tools/shared
|
|
62
|
+
jobs:
|
|
63
|
+
report:
|
|
64
|
+
tools: ./tools/report
|
|
65
|
+
steps:
|
|
66
|
+
- name: Summarize
|
|
67
|
+
customRun: Read workflow-job-tools, discover jobTools, then summarize this job.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The folder must contain JavaScript `index.mjs` and a non-empty `SKILL.md`. Paths are resolved relative
|
|
71
|
+
to the YAML that declares them, before imports and job-template inheritance. Empty paths, control
|
|
72
|
+
characters and URL specifiers are rejected. Parsing, listing and inspection never execute the module.
|
|
73
|
+
A job's main steps, `preJob` and `postJob` use its selection or the workflow default. `repair.tools`
|
|
74
|
+
overrides the default for repair triage. Global phases use the workflow default.
|
|
75
|
+
|
|
76
|
+
Only trusted, managed Po `customRun` and in-process `interactiveRun` agent sessions receive these
|
|
77
|
+
tools, not shell steps, ordinary sessions, or external Claude/Codex CLI runners. Each session uses its
|
|
78
|
+
captured workflow config. Global `pre` can run before the worktree YAML is reparsed, so its tools are
|
|
79
|
+
not necessarily selected from that worktree. Step `cwd`, `WORKTREE_PATH` and environment variables do
|
|
80
|
+
not select or resolve the tools folder.
|
|
81
|
+
|
|
82
|
+
`index.mjs` default-exports a factory, called once per managed session with:
|
|
83
|
+
|
|
84
|
+
- `sessionId` and the admitted working directory `cwd`.
|
|
85
|
+
- `workflow`: the trusted `PoWorkflowContextService`, including the captured config, job/step identity,
|
|
86
|
+
admitted `jobEnvironment` and `stepEnvironment`, and workflow state reader.
|
|
87
|
+
- Injected `fs` and `shell` capabilities.
|
|
88
|
+
- `signal`: the session tools lifetime, aborted on session disposal or mounting failure.
|
|
89
|
+
|
|
90
|
+
Return the existing `PoCodemodeNamespace` contract: name `jobTools`, a non-empty one-line `summary`
|
|
91
|
+
(maximum 600 characters), TypeScript `declarations` for discovery, and `bind(run)`. Bind returns flat
|
|
92
|
+
named async methods with JSON arguments and results. Each exec gets a fresh binding; keep per-session
|
|
93
|
+
state inside the factory, not at module scope. Validate method inputs and honour `run.signal` in long
|
|
94
|
+
operations. Use `run.onDispose` for exec-owned cleanup. The host refuses foreign sessions and calls
|
|
95
|
+
after cancellation/disposal, and detaches JSON values in both directions. Return `null`, not
|
|
96
|
+
`undefined`, for no output.
|
|
97
|
+
|
|
98
|
+
A minimal runnable `tools/report/index.mjs`:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
export default function createJobTools({ sessionId, workflow, signal }) {
|
|
102
|
+
signal.throwIfAborted();
|
|
103
|
+
return {
|
|
104
|
+
name: 'jobTools',
|
|
105
|
+
summary: 'Describe the current workflow job with a supplied label.',
|
|
106
|
+
declarations: `declare const jobTools: {
|
|
107
|
+
describe(input: { label: string }): Promise<{ job: string; label: string }>;
|
|
108
|
+
};`,
|
|
109
|
+
bind(run) {
|
|
110
|
+
if (run.sessionId !== sessionId) throw new Error('Wrong session.');
|
|
111
|
+
return {
|
|
112
|
+
async describe(input) {
|
|
113
|
+
run.signal.throwIfAborted();
|
|
114
|
+
if (!input || typeof input.label !== 'string' || !input.label.trim())
|
|
115
|
+
throw new Error('label must be a non-empty string.');
|
|
116
|
+
return { job: workflow.job, label: input.label.trim() };
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Corresponding `tools/report/SKILL.md`:
|
|
125
|
+
|
|
126
|
+
```md
|
|
127
|
+
# Report tools
|
|
128
|
+
|
|
129
|
+
Use jobTools.describe to label the current job before writing its summary.
|
|
130
|
+
Discover jobTools for the current signature, then call it through exec.
|
|
131
|
+
Do not guess a label or repeat an effect after cancellation without checking its outcome.
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Agents read the pinned skill, call `discover({ namespace: 'jobTools' })`, then use
|
|
135
|
+
`exec` with `return await jobTools.describe({ label: 'Verification' });`. Keep guidance in `SKILL.md`
|
|
136
|
+
and signatures in `declarations`. Optional namespace `approval` policies are preserved and enforced
|
|
137
|
+
by the existing code-mode approval path; each policy must name a returned method. An approval request
|
|
138
|
+
is not permission to bypass the normal approval flow.
|
|
139
|
+
|
|
140
|
+
Tools are trusted workflow code running with the host's privileges, not sandboxed code. Review the
|
|
141
|
+
folder before launching. Native import caches modules by file URL; editing `index.mjs` at the same
|
|
142
|
+
path requires a host restart. There is no direct TypeScript loading, hot reload, or external CLI
|
|
143
|
+
bridge. A missing file or invalid factory fails the managed step session rather than silently
|
|
144
|
+
omitting its selected tools. The namespace and skill mount and dispose together; disposing them does
|
|
145
|
+
not stop the owning workflow or change its recovery rules.
|
|
146
|
+
|
|
52
147
|
## Launch and monitor
|
|
53
148
|
|
|
54
149
|
Core tools are:
|
|
@@ -98,8 +193,9 @@ adopts the eligible failure, validates the evidence, and transfers ownership to
|
|
|
98
193
|
Live controls remain scoped to the owning session. Recovery does not launch a second copy beside a
|
|
99
194
|
still-running job and does not guarantee that parent-session work survives shutdown.
|
|
100
195
|
|
|
101
|
-
The package ships two skills: pinned, ungated `codemode-how-to-workflow` for
|
|
102
|
-
and unpinned, Help-gated `help-how-to-workflow` for authoring and
|
|
196
|
+
The package ships two skills: pinned, ungated `codemode-how-to-workflow` for workflow code-mode
|
|
197
|
+
namespaces and selected job tools, and unpinned, Help-gated `help-how-to-workflow` for authoring and
|
|
198
|
+
maintenance. A selected tools folder also supplies the managed session's pinned `workflow-job-tools` skill.
|
|
103
199
|
|
|
104
200
|
## Workflow sessions
|
|
105
201
|
|
|
@@ -12,6 +12,7 @@ let node_fs = require("node:fs");
|
|
|
12
12
|
let node_fs_promises = require("node:fs/promises");
|
|
13
13
|
node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises, 1);
|
|
14
14
|
let node_readline = require("node:readline");
|
|
15
|
+
let node_url = require("node:url");
|
|
15
16
|
let proper_lockfile = require("proper-lockfile");
|
|
16
17
|
let node_perf_hooks = require("node:perf_hooks");
|
|
17
18
|
let node_os = require("node:os");
|
|
@@ -250,7 +251,9 @@ const PreBlockSchema = zod.z.object({
|
|
|
250
251
|
steps: zod.z.array(StepSchema).optional()
|
|
251
252
|
});
|
|
252
253
|
const JobHookBlockSchema = zod.z.object({ steps: zod.z.array(StepSchema).optional() });
|
|
254
|
+
const WorkflowToolsPathSchema = zod.z.string().min(1).refine((value) => value.trim().length > 0 && !/^[a-z][a-z0-9+.-]*:/iu.test(value.trimStart()) && value.split("").every((character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127), "Workflow tools must be a local filesystem path without URL schemes or control characters.").describe("Tools folder relative to its declaring YAML file, containing index.mjs and SKILL.md.");
|
|
253
255
|
const JobSchema = zod.z.object({
|
|
256
|
+
tools: WorkflowToolsPathSchema.optional(),
|
|
254
257
|
"runs-on": zod.z.string().optional(),
|
|
255
258
|
needs: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional(),
|
|
256
259
|
extends: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional(),
|
|
@@ -329,6 +332,7 @@ const RunDirectorySchema = zod.z.object({
|
|
|
329
332
|
});
|
|
330
333
|
const WorkflowSchema = zod.z.object({
|
|
331
334
|
name: zod.z.string().optional(),
|
|
335
|
+
tools: WorkflowToolsPathSchema.optional(),
|
|
332
336
|
tags: zod.z.array(zod.z.string().trim().min(1)).optional().describe("Tags used to categorize and filter workflow definitions."),
|
|
333
337
|
workspace: zod.z.string().optional(),
|
|
334
338
|
runner: zod.z.string().optional().describe("Default runner key for step command maps when no CLI runner is provided."),
|
|
@@ -1771,6 +1775,21 @@ var WorkflowRegistryService = class {
|
|
|
1771
1775
|
ownerSessionId
|
|
1772
1776
|
});
|
|
1773
1777
|
}
|
|
1778
|
+
/** A person's Stop remains terminal even when the owning process is already gone. */
|
|
1779
|
+
async requestStaleStop(workspace, runKey, reason, expectedRunId, ownerSessionId) {
|
|
1780
|
+
const request = {
|
|
1781
|
+
reason: reason?.trim() || void 0,
|
|
1782
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1783
|
+
runId: expectedRunId,
|
|
1784
|
+
schemaVersion: 1
|
|
1785
|
+
};
|
|
1786
|
+
await this.reconcileStaleRunningRecord(this.resolveWorkspace(workspace), this.slugifySegment(runKey, FALLBACK_RUN_KEY), {
|
|
1787
|
+
runId: expectedRunId,
|
|
1788
|
+
ownerSessionId,
|
|
1789
|
+
stop: request
|
|
1790
|
+
});
|
|
1791
|
+
return request;
|
|
1792
|
+
}
|
|
1774
1793
|
/**
|
|
1775
1794
|
* Ensure the stage directories exist for the workspace.
|
|
1776
1795
|
*/
|
|
@@ -1884,7 +1903,18 @@ var WorkflowRegistryService = class {
|
|
|
1884
1903
|
const runningRecordPath = (0, node_path.resolve)(runningDir, RUN_RECORD_FILENAME$1);
|
|
1885
1904
|
return this.writeCoordinator.forKey(runningRecordPath)(async () => {
|
|
1886
1905
|
const currentRecord = await this.readRunRecord(runningRecordPath);
|
|
1887
|
-
if (expected !== void 0 && (currentRecord.runId !== expected.runId || currentRecord.env?.PI_SESSION_ID !== expected.ownerSessionId)
|
|
1906
|
+
if (expected !== void 0 && (currentRecord.runId !== expected.runId || currentRecord.env?.PI_SESSION_ID !== expected.ownerSessionId)) {
|
|
1907
|
+
if (expected.stop) throw new Error(`Workflow run ownership changed: ${workspace}/${runKey}`);
|
|
1908
|
+
return currentRecord;
|
|
1909
|
+
}
|
|
1910
|
+
if (!currentRecord.pid || this.isProcessAlive(currentRecord.pid)) {
|
|
1911
|
+
if (expected?.stop) throw new Error(`Workflow process is not known to be dead: ${workspace}/${runKey}`);
|
|
1912
|
+
return currentRecord;
|
|
1913
|
+
}
|
|
1914
|
+
if (expected?.stop) {
|
|
1915
|
+
if (currentRecord.stage !== RUNNING_STAGE) throw new Error(`Workflow run is no longer controllable: ${workspace}/${runKey}`);
|
|
1916
|
+
await this.writeAtomic((0, node_path.resolve)(runningDir, STOP_REQUEST_FILENAME), expected.stop);
|
|
1917
|
+
}
|
|
1888
1918
|
if (currentRecord.stage !== RUNNING_STAGE) {
|
|
1889
1919
|
const stageDir = this.getRunDirectory(workspace, currentRecord.stage, runKey);
|
|
1890
1920
|
await this.removeFileIfExists((0, node_path.resolve)(runningDir, FINALIZING_FILENAME));
|
|
@@ -1903,14 +1933,15 @@ var WorkflowRegistryService = class {
|
|
|
1903
1933
|
failedJob: currentRecord.executionCursor?.job ?? currentRecord.failedJob,
|
|
1904
1934
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1905
1935
|
outcome: "interrupted",
|
|
1906
|
-
stage: ERROR_STAGE$2
|
|
1936
|
+
stage: ERROR_STAGE$2,
|
|
1937
|
+
...expected?.stop ? { resume: void 0 } : {}
|
|
1907
1938
|
};
|
|
1939
|
+
await this.writeRunRecord(runningRecordPath, finalRecord);
|
|
1908
1940
|
(0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.remove(errorDir, {
|
|
1909
1941
|
recursive: true,
|
|
1910
1942
|
force: true
|
|
1911
1943
|
}, this.runtime.ioContext));
|
|
1912
1944
|
(0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.renameFile(runningDir, errorDir, this.runtime.ioContext));
|
|
1913
|
-
await this.writeRunRecord((0, node_path.resolve)(errorDir, RUN_RECORD_FILENAME$1), finalRecord);
|
|
1914
1945
|
return finalRecord;
|
|
1915
1946
|
});
|
|
1916
1947
|
}
|
|
@@ -2437,6 +2468,7 @@ var WorkflowParserService = class {
|
|
|
2437
2468
|
this.githubSha = "local";
|
|
2438
2469
|
}
|
|
2439
2470
|
const workflow = WorkflowSchema.parse((0, js_yaml.load)(content));
|
|
2471
|
+
this.resolveToolsPaths(workflow, resolved);
|
|
2440
2472
|
if (workflow.imports) await this.resolveImports(workflow, resolved);
|
|
2441
2473
|
if (!workflow.jobs || Object.keys(workflow.jobs).length === 0) throw new Error("No jobs found in workflow file");
|
|
2442
2474
|
this.resolveExtends(workflow);
|
|
@@ -2455,11 +2487,20 @@ var WorkflowParserService = class {
|
|
|
2455
2487
|
const parsed = (0, js_yaml.load)(content);
|
|
2456
2488
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`Workflow template must be a YAML object: ${resolved}`);
|
|
2457
2489
|
const workflow = parsed;
|
|
2490
|
+
this.resolveToolsPaths(workflow, resolved);
|
|
2458
2491
|
workflow.jobs ??= {};
|
|
2459
2492
|
if (workflow.imports) await this.resolveImports(workflow, resolved);
|
|
2460
2493
|
workflow.jobs ??= {};
|
|
2461
2494
|
return workflow;
|
|
2462
2495
|
}
|
|
2496
|
+
/** Resolve before merging so imported jobs and defaults keep their declaring file's origin. */
|
|
2497
|
+
resolveToolsPaths(workflow, workflowPath) {
|
|
2498
|
+
for (const declaration of [
|
|
2499
|
+
workflow,
|
|
2500
|
+
...Object.values(workflow.jobs ?? {}),
|
|
2501
|
+
workflow.repair
|
|
2502
|
+
]) if (declaration?.tools !== void 0) declaration.tools = (0, node_path.resolve)((0, node_path.dirname)(workflowPath), WorkflowToolsPathSchema.parse(declaration.tools));
|
|
2503
|
+
}
|
|
2463
2504
|
/**
|
|
2464
2505
|
* Render a command template into the shell command a step runs.
|
|
2465
2506
|
*
|
|
@@ -2601,7 +2642,8 @@ var WorkflowParserService = class {
|
|
|
2601
2642
|
* - system-prompt: concatenated (imported + main)
|
|
2602
2643
|
* - commands, choices: merged per name (main overrides imported)
|
|
2603
2644
|
* - runConfig, sessionConfig: merged per key (main overrides imported)
|
|
2604
|
-
*
|
|
2645
|
+
* - tools: scalar default (main wins, otherwise first imported selection)
|
|
2646
|
+
* Imports are processed in order; earlier merged values act as main overrides.
|
|
2605
2647
|
* Circular imports are detected and throw an error.
|
|
2606
2648
|
*/
|
|
2607
2649
|
async resolveImports(workflow, workflowPath, visited = /* @__PURE__ */ new Set()) {
|
|
@@ -2614,7 +2656,9 @@ var WorkflowParserService = class {
|
|
|
2614
2656
|
if (!await (0, _agimon_ai_po_core_platform.pathExists)(this.runtime.fs, resolvedImport, this.runtime.ioContext)) throw new Error(`Imported workflow file not found: ${resolvedImport} (from ${normalizedPath})`);
|
|
2615
2657
|
const importContent = (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.readTextFile(resolvedImport, this.runtime.ioContext));
|
|
2616
2658
|
const imported = (0, js_yaml.load)(importContent);
|
|
2659
|
+
this.resolveToolsPaths(imported, resolvedImport);
|
|
2617
2660
|
if (imported.imports) await this.resolveImports(imported, resolvedImport, visited);
|
|
2661
|
+
workflow.tools ??= imported.tools;
|
|
2618
2662
|
if (imported.env) workflow.env = {
|
|
2619
2663
|
...imported.env,
|
|
2620
2664
|
...workflow.env
|
|
@@ -2694,7 +2738,7 @@ var WorkflowParserService = class {
|
|
|
2694
2738
|
* - postJob.steps: template steps prepended before job postJob steps
|
|
2695
2739
|
* - env: merged (job overrides template)
|
|
2696
2740
|
* - system-prompt: concatenated (template + job)
|
|
2697
|
-
* -
|
|
2741
|
+
* - runs-on, strategy, context, tools: inherited when not set on the job
|
|
2698
2742
|
* Template jobs are removed from the jobs map after resolution.
|
|
2699
2743
|
*/
|
|
2700
2744
|
resolveExtends(workflow) {
|
|
@@ -2743,6 +2787,7 @@ var WorkflowParserService = class {
|
|
|
2743
2787
|
if (template["runs-on"] && !job["runs-on"]) job["runs-on"] = template["runs-on"];
|
|
2744
2788
|
if (template.strategy && !job.strategy) job.strategy = template.strategy;
|
|
2745
2789
|
if (template.context && !job.context) job.context = template.context;
|
|
2790
|
+
job.tools ??= template.tools;
|
|
2746
2791
|
}
|
|
2747
2792
|
mergeJobHookSteps(job, template, hookKey) {
|
|
2748
2793
|
const templateSteps = template[hookKey]?.steps ?? [];
|
|
@@ -5109,6 +5154,10 @@ function createWorkflowHost(options) {
|
|
|
5109
5154
|
input: process.stdin,
|
|
5110
5155
|
output: process.stdout
|
|
5111
5156
|
}),
|
|
5157
|
+
importJobTools: (path) => import(
|
|
5158
|
+
/* @vite-ignore */
|
|
5159
|
+
(0, node_url.pathToFileURL)(path).href
|
|
5160
|
+
),
|
|
5112
5161
|
openArtifact,
|
|
5113
5162
|
writeExclusive: (path, content) => (0, node_fs_promises.writeFile)(path, content, {
|
|
5114
5163
|
flag: "wx",
|
|
@@ -11359,6 +11408,12 @@ Object.defineProperty(exports, "WorkflowStageSchema", {
|
|
|
11359
11408
|
return WorkflowStageSchema;
|
|
11360
11409
|
}
|
|
11361
11410
|
});
|
|
11411
|
+
Object.defineProperty(exports, "WorkflowToolsPathSchema", {
|
|
11412
|
+
enumerable: true,
|
|
11413
|
+
get: function() {
|
|
11414
|
+
return WorkflowToolsPathSchema;
|
|
11415
|
+
}
|
|
11416
|
+
});
|
|
11362
11417
|
Object.defineProperty(exports, "connectNativeTerminal", {
|
|
11363
11418
|
enumerable: true,
|
|
11364
11419
|
get: function() {
|
|
@@ -11486,4 +11541,4 @@ Object.defineProperty(exports, "workflowShellOutput", {
|
|
|
11486
11541
|
}
|
|
11487
11542
|
});
|
|
11488
11543
|
|
|
11489
|
-
//# sourceMappingURL=RecoverWorkflowService-
|
|
11544
|
+
//# sourceMappingURL=RecoverWorkflowService-DL5itVWA.cjs.map
|