@automatalabs/workflows 0.41.0 → 0.43.0
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 +52 -22
- package/dist/cli.js +100 -2
- package/dist/mcp-server.js +33711 -0
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -11,14 +11,15 @@ or a registered custom ACP agent — driving the actual subprocess to completion
|
|
|
11
11
|
|
|
12
12
|
This package is the **canonical SDK** that the stdio MCP server
|
|
13
13
|
[`@automatalabs/mcp-server`](https://www.npmjs.com/package/@automatalabs/mcp-server) is built on.
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Its CLI can also delegate to a build-time embedded copy of that server with the `mcp` subcommand,
|
|
15
|
+
so an MCP host can expose the `workflow` tool without a separate package install. The standalone
|
|
16
|
+
MCP server package remains independently published, while programs embedding the runner continue
|
|
17
|
+
to use this package's workflow/runner APIs.
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
ACP
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
through this SDK's workflow/runner APIs rather than MCP server schemas.
|
|
19
|
+
The SDK itself remains a thin programmatic facade over the engine + ACP packages, with
|
|
20
|
+
ACP-defaulted helpers for ordinary runs (`runDynamicWorkflow`) and substitution tests
|
|
21
|
+
(`runIsolation`). The ACP layer also uses `@modelcontextprotocol/sdk` internally when it hosts the
|
|
22
|
+
optional StructuredOutput tool for eligible agents.
|
|
22
23
|
|
|
23
24
|
---
|
|
24
25
|
|
|
@@ -117,21 +118,21 @@ Every script **must** begin with `export const meta = { name, description, phase
|
|
|
117
118
|
statement, and must be **deterministic** — `Date.now()`, `Math.random()`, and `new Date()` are
|
|
118
119
|
unavailable inside the realm (they would break journal replay on resume).
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
Completed reader/worktree calls both use ordinary journal replay:
|
|
121
122
|
|
|
122
123
|
```js
|
|
123
124
|
const [audit, experiment] = await parallel([
|
|
124
125
|
() => agent("Audit src/api without changing files.", {
|
|
125
|
-
label: "audit:api",
|
|
126
|
+
label: "audit:api",
|
|
126
127
|
}),
|
|
127
128
|
() => agent("Try the worker fix in isolation; return a unified diff.", {
|
|
128
|
-
label: "try:worker", isolation: "worktree",
|
|
129
|
+
label: "try:worker", isolation: "worktree",
|
|
129
130
|
}),
|
|
130
131
|
]);
|
|
131
132
|
```
|
|
132
133
|
|
|
133
|
-
The worktree and edits are discarded; return the diff as data.
|
|
134
|
-
|
|
134
|
+
The worktree and edits are discarded; return the diff as data. Replay requires matching journal
|
|
135
|
+
identity/input facts, not a filesystem-safety declaration. See the
|
|
135
136
|
[incremental resume API](../../docs/api.md#content-addressed-incremental-resume).
|
|
136
137
|
|
|
137
138
|
### Substitution testing (isolation mode)
|
|
@@ -303,7 +304,7 @@ boolean).
|
|
|
303
304
|
|
|
304
305
|
Resume guarantees journal/script replay integrity and checkpoint-reply targeting only. It never
|
|
305
306
|
assumes or guarantees that the filesystem, external systems, agent output semantics, or any other
|
|
306
|
-
part of the world stayed fresh between runs. If
|
|
307
|
+
part of the world stayed fresh between runs. If a prior correspondence miss runs live, a supplied reply is
|
|
307
308
|
injected only when execution reaches the exact recorded checkpoint call site with the same
|
|
308
309
|
checkpoint identity and inputs; content-only matching is insufficient after that live prefix. A
|
|
309
310
|
reply that is not applied is reported in `resumeReport` and terminal summaries. Authors who want a
|
|
@@ -352,15 +353,15 @@ Run results expose `effectiveLimits`; inspect status exposes the same values as
|
|
|
352
353
|
agent rows carry their resolved `timeoutMs` plus `errorCode`.
|
|
353
354
|
|
|
354
355
|
`exec.resumeFromRunId` asks the manager to admit a terminal source, persist a self-contained seed
|
|
355
|
-
under a new run ID, and match
|
|
356
|
+
under a new run ID, and match completed calls by exact path/hash or unique hash+input fingerprint.
|
|
356
357
|
Every allocated call receives a terminal manifest row even when the run halts around it. A
|
|
357
358
|
non-result occurrence remains live on resume and stays in the identity seed as an ambiguity
|
|
358
|
-
blocker until reached, so completed siblings on either side can replay
|
|
359
|
-
ambiguous, or
|
|
359
|
+
blocker until reached, so completed siblings on either side can replay. Uncertain,
|
|
360
|
+
ambiguous, or mismatched calls run live. Filesystem/current-environment and Node/V8 differences are
|
|
360
361
|
reported as provenance without changing admission or matching. Identity hits preserve
|
|
361
362
|
script-visible logical budget debit while adding zero current provider usage; replayed session
|
|
362
363
|
records are rebound to the current call index/label/phase. `resumePolicy: "positional"` requests
|
|
363
|
-
index/prefix matching but cannot bypass new-format format/metadata/manifest/input
|
|
364
|
+
index/prefix matching but cannot bypass new-format format/metadata/manifest/input checks. The distinct
|
|
364
365
|
same-ID `resume()` and low-level `resumeJournal` paths remain permanently legacy positional and
|
|
365
366
|
emit no `resumeReport`. See the [full contract](../../docs/api.md#content-addressed-incremental-resume).
|
|
366
367
|
Operational limits are resolved from the new execution's `exec` options and manager defaults, not
|
|
@@ -369,9 +370,8 @@ copied from the source run; pass the desired timeout/retry/concurrency values ag
|
|
|
369
370
|
neither replay identity nor the execution-input fingerprint and may change without invalidating
|
|
370
371
|
completed calls or interrupted-turn continuation.
|
|
371
372
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
sources with an input-fingerprint format below 2 use the
|
|
373
|
+
Current-format crash snapshots reconciled to `paused` / `interrupted` use identity correspondence
|
|
374
|
+
even without terminal-environment capture. Sources with an input-fingerprint format below 2 use the
|
|
375
375
|
`inputs-format-legacy` positional bridge. That bridge also accepts ancestor-scoped rows carried by a
|
|
376
376
|
≤0.23 resume hop when the ancestor run still exists in the same persistence directory; nested and deleted-run scopes stay live. Engine
|
|
377
377
|
package versions are persisted and surfaced as diagnostics but never gate replay. Every new-run
|
|
@@ -544,7 +544,7 @@ Semantics worth knowing:
|
|
|
544
544
|
is always treated as a verbatim script, never a name.
|
|
545
545
|
- **Versioning is git's job.** Same-ID `resume()` reloads the exact persisted script. A new
|
|
546
546
|
`resumeFromRunId` execution can use an edited script: changed calls run live, while uniquely
|
|
547
|
-
matching
|
|
547
|
+
matching completed calls may move and replay after admission.
|
|
548
548
|
- **`resolve()` validates name shape strictly** (one flat path segment) — inline nested scripts
|
|
549
549
|
fall through to verbatim parsing, and path traversal out of the configured dirs is impossible.
|
|
550
550
|
|
|
@@ -621,7 +621,7 @@ ships no runtime code).
|
|
|
621
621
|
|
|
622
622
|
| global | what it does |
|
|
623
623
|
|--------|--------------|
|
|
624
|
-
| `agent(prompt, options?)` | Run ONE subagent to completion; returns its result (text, or the validated object with `options.schema`). `options.resume
|
|
624
|
+
| `agent(prompt, options?)` | Run ONE subagent to completion; returns its result (text, or the validated object with `options.schema`). The legacy `options.resume` annotation is accepted but replay-neutral. |
|
|
625
625
|
| `parallel(thunks)` | Run an array of **thunks** (`() => Promise`) concurrently; resolves in input order. |
|
|
626
626
|
| `pipeline(items, ...stages)` | Map `items` through sequential async stages, concurrently across items. |
|
|
627
627
|
| `workflow(nameOrScript, args?)` | Run a saved (or inline) workflow nested in this run, sharing its limiter/budget. |
|
|
@@ -779,6 +779,36 @@ formatHarnessConfigReport(report); // the CLI's human table
|
|
|
779
779
|
|
|
780
780
|
---
|
|
781
781
|
|
|
782
|
+
## Launching the MCP server — `agentprism-workflows mcp`
|
|
783
|
+
|
|
784
|
+
Register the bundled stdio server directly from the workflows package; no separate
|
|
785
|
+
`@automatalabs/mcp-server` installation is required:
|
|
786
|
+
|
|
787
|
+
```json
|
|
788
|
+
{
|
|
789
|
+
"mcpServers": {
|
|
790
|
+
"agentprism-workflow": {
|
|
791
|
+
"command": "npx",
|
|
792
|
+
"args": ["-y", "@automatalabs/workflows", "mcp"]
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
For the source inner loop, build workflows before launching its compiled CLI:
|
|
799
|
+
|
|
800
|
+
```bash
|
|
801
|
+
pnpm --filter @automatalabs/workflows build
|
|
802
|
+
node packages/workflows/dist/cli.js mcp
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
If the embedded bundle is absent in a monorepo checkout, the command falls back to the built
|
|
806
|
+
`packages/mcp-server/dist/cli.js`. A root `pnpm build` therefore also supports development before
|
|
807
|
+
running `node packages/workflows/dist/cli.js mcp`. The independently published
|
|
808
|
+
`@automatalabs/mcp-server` package and `agentprism-workflow` bin remain available.
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
782
812
|
## Structured output
|
|
783
813
|
|
|
784
814
|
Pass a JSON Schema to `agent({ schema })` (in a script) or `runner.run(prompt, { schema })` (direct)
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// The @automatalabs/workflows bin (`agentprism-workflows`).
|
|
2
|
+
// The @automatalabs/workflows bin (`agentprism-workflows`). Three subcommands:
|
|
3
3
|
//
|
|
4
4
|
// agentprism-workflows validate <workflow-file> [options]
|
|
5
5
|
// agentprism-workflows config [harness ...] [options]
|
|
6
|
+
// agentprism-workflows mcp
|
|
6
7
|
//
|
|
7
8
|
// validate checks a workflow script without spending tokens: static parse (meta literal,
|
|
8
9
|
// syntax, direct nondeterministic call expressions), then a dry run over an in-process
|
|
@@ -13,7 +14,12 @@
|
|
|
13
14
|
// config runs that same no-prompt probe standalone — no script needed — and prints each
|
|
14
15
|
// requested harness's advertised config-option catalog (model ids, effort levels, modes,
|
|
15
16
|
// …). See ./config.ts for the programmatic API (`probeHarnessConfig`).
|
|
17
|
+
//
|
|
18
|
+
// mcp delegates stdio unchanged to the MCP server embedded at build time. In a source
|
|
19
|
+
// checkout without that bundle, it falls back to the separately built mcp-server entry.
|
|
20
|
+
import { spawn } from "node:child_process";
|
|
16
21
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { constants as osConstants } from "node:os";
|
|
17
23
|
import { resolve } from "node:path";
|
|
18
24
|
import { openWorkflowDir } from "@automatalabs/workflow-engine";
|
|
19
25
|
import { validateWorkflowScript, formatValidateReport } from "./validate.js";
|
|
@@ -27,6 +33,7 @@ Commands:
|
|
|
27
33
|
catalog (model ids, effort levels, modes, …) so
|
|
28
34
|
model/configOptions values come from the live
|
|
29
35
|
catalog, not guesswork
|
|
36
|
+
mcp launch the embedded AgentPrism stdio MCP server
|
|
30
37
|
|
|
31
38
|
Run \`agentprism-workflows <command> --help\` for that command's options.`;
|
|
32
39
|
const USAGE = `Usage: agentprism-workflows validate <workflow-file-or-name> [options]
|
|
@@ -90,6 +97,13 @@ Options:
|
|
|
90
97
|
-h, --help show this help
|
|
91
98
|
|
|
92
99
|
Exit codes: 0 all probed · 1 at least one probe failed · 3 usage error`;
|
|
100
|
+
const MCP_USAGE = `Usage: agentprism-workflows mcp
|
|
101
|
+
|
|
102
|
+
Launches the embedded AgentPrism MCP server over stdio. stdin and stdout are reserved
|
|
103
|
+
for JSON-RPC framing and are inherited unchanged by the server process.
|
|
104
|
+
|
|
105
|
+
Options:
|
|
106
|
+
-h, --help show this help`;
|
|
93
107
|
let activeCommand = "";
|
|
94
108
|
function fail(message) {
|
|
95
109
|
const hint = activeCommand === "" ? "agentprism-workflows --help" : `agentprism-workflows ${activeCommand} --help`;
|
|
@@ -143,6 +157,86 @@ async function mainConfig(rest) {
|
|
|
143
157
|
writeFileSync(process.stdout.fd, json ? `${JSON.stringify(report, null, 2)}\n` : `${formatHarnessConfigReport(report)}\n`);
|
|
144
158
|
process.exitCode = report.exitCode;
|
|
145
159
|
}
|
|
160
|
+
async function mainMcp(rest) {
|
|
161
|
+
if (rest.length === 1 && (rest[0] === "-h" || rest[0] === "--help")) {
|
|
162
|
+
process.stdout.write(`${MCP_USAGE}\n`);
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
if (rest.length > 0)
|
|
166
|
+
fail(`mcp does not accept arguments (received: ${rest.join(" ")})`);
|
|
167
|
+
const bundlePath = resolve(import.meta.dirname, "mcp-server.js");
|
|
168
|
+
const monorepoFallbackPath = resolve(import.meta.dirname, "../../mcp-server/dist/cli.js");
|
|
169
|
+
const serverPath = existsSync(bundlePath)
|
|
170
|
+
? bundlePath
|
|
171
|
+
: existsSync(monorepoFallbackPath)
|
|
172
|
+
? monorepoFallbackPath
|
|
173
|
+
: undefined;
|
|
174
|
+
if (serverPath === undefined) {
|
|
175
|
+
fail(`MCP server bundle not found at ${bundlePath}, and the monorepo fallback is not built. ` +
|
|
176
|
+
"Run `pnpm --filter @automatalabs/workflows build` to create the bundle, or run `pnpm build` at the repository root.");
|
|
177
|
+
}
|
|
178
|
+
await new Promise((resolvePromise) => {
|
|
179
|
+
const child = spawn(process.execPath, [serverPath], { stdio: "inherit" });
|
|
180
|
+
const forwardedSignals = ["SIGINT", "SIGTERM"];
|
|
181
|
+
let settled = false;
|
|
182
|
+
let cleaned = false;
|
|
183
|
+
const forwarders = new Map();
|
|
184
|
+
const cleanup = () => {
|
|
185
|
+
if (cleaned)
|
|
186
|
+
return;
|
|
187
|
+
cleaned = true;
|
|
188
|
+
for (const [signal, forward] of forwarders)
|
|
189
|
+
process.off(signal, forward);
|
|
190
|
+
child.off("error", onError);
|
|
191
|
+
child.off("exit", onExit);
|
|
192
|
+
};
|
|
193
|
+
const finish = (action) => {
|
|
194
|
+
if (settled)
|
|
195
|
+
return;
|
|
196
|
+
settled = true;
|
|
197
|
+
cleanup();
|
|
198
|
+
action();
|
|
199
|
+
resolvePromise();
|
|
200
|
+
};
|
|
201
|
+
const onError = (error) => {
|
|
202
|
+
finish(() => {
|
|
203
|
+
process.stderr.write(`mcp server failed to start: ${error.message}\n`);
|
|
204
|
+
process.exitCode = 1;
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
const onExit = (code, signal) => {
|
|
208
|
+
finish(() => {
|
|
209
|
+
if (signal === null) {
|
|
210
|
+
process.exitCode = code ?? 1;
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// Preserve signal termination for callers (shells, npx, and MCP hosts). Set a
|
|
214
|
+
// conventional nonzero fallback first in case re-raising is unsupported here.
|
|
215
|
+
process.exitCode = 128 + (osConstants.signals[signal] ?? 1);
|
|
216
|
+
try {
|
|
217
|
+
process.kill(process.pid, signal);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
process.stderr.write(`mcp server exited on ${signal}, but the parent could not re-raise it: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
for (const signal of forwardedSignals) {
|
|
225
|
+
const forward = () => {
|
|
226
|
+
try {
|
|
227
|
+
child.kill(signal);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
process.stderr.write(`could not forward ${signal} to the mcp server: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
forwarders.set(signal, forward);
|
|
234
|
+
process.on(signal, forward);
|
|
235
|
+
}
|
|
236
|
+
child.once("error", onError);
|
|
237
|
+
child.once("exit", onExit);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
146
240
|
async function main(argv) {
|
|
147
241
|
const [command, ...rest] = argv;
|
|
148
242
|
if (command === undefined || command === "-h" || command === "--help") {
|
|
@@ -153,8 +247,12 @@ async function main(argv) {
|
|
|
153
247
|
activeCommand = "config";
|
|
154
248
|
return mainConfig(rest);
|
|
155
249
|
}
|
|
250
|
+
if (command === "mcp") {
|
|
251
|
+
activeCommand = "mcp";
|
|
252
|
+
return mainMcp(rest);
|
|
253
|
+
}
|
|
156
254
|
if (command !== "validate")
|
|
157
|
-
fail(`unknown command "${command}" — the commands are: validate, config`);
|
|
255
|
+
fail(`unknown command "${command}" — the commands are: validate, config, mcp`);
|
|
158
256
|
activeCommand = "validate";
|
|
159
257
|
let file;
|
|
160
258
|
let json = false;
|