@workflow-manager/runner 0.7.0 → 0.9.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/dist/manPage.js CHANGED
@@ -21,25 +21,36 @@ List the agent skills bundled with the npm package.
21
21
  .B skill install [name ...] [--agent claude|opencode] [--global] [--dir path] [--all] [--force]
22
22
  Install bundled agent skills into an agent skill directory. Defaults to the workflow-manager-cli skill and the project-level Claude Code directory (./.claude/skills). Existing skills are not overwritten unless --force is passed.
23
23
  .TP
24
- .B scaffold [path] [--format markdown|json]
24
+ .B scaffold [path] [--format markdown|json] [--template default|agent-validated]
25
25
  Create a starter workflow file. Format defaults to markdown unless the output
26
- path ends in .json.
26
+ path ends in .json. Template defaults to default (the general multi-step example);
27
+ agent-validated scaffolds a compact pipeline demonstrating first-class agent
28
+ validation (validation.mode: agent with a validator agent and criteria).
27
29
  .TP
28
30
  .B validate <workflow.md|workflow.json>
29
31
  Validate workflow structure and report schema errors.
30
32
  .TP
31
- .B run <workflow.md|workflow.json> [--input input.json] [--objective text] [--confirm list] [--auto-confirm-all] [--port number] [--verbose] [--json] [--ui]
33
+ .B run <workflow.md|workflow.json> [--input input.json] [--objective text] [--confirm list] [--auto-confirm-all] [--port number] [--session-file path] [--verbose] [--json] [--ui]
32
34
  Run the workflow with live CLI progress and optional JSON output.
33
35
  .TP
34
- .B approve [--url value] [--token value] [--run-id value] [--step value] [--actor value] [--note text]
36
+ .B approve [--url value] [--token value] [--session-file path] [--run-id value] [--step value] [--actor value] [--note text]
35
37
  Approve the current waiting runner step through the local attach API.
36
38
  .TP
37
- .B resume [--url value] [--token value] [--run-id value] [--step value] [--actor value] [--note text]
39
+ .B resume [--url value] [--token value] [--session-file path] [--run-id value] [--step value] [--actor value] [--note text]
38
40
  Alias for approve, intended for external resume flows.
39
41
  .TP
40
- .B cancel [--url value] [--token value] [--run-id value] [--step value] [--actor value] [--note text]
42
+ .B cancel [--url value] [--token value] [--session-file path] [--run-id value] [--step value] [--actor value] [--note text]
41
43
  Cancel the current waiting runner step through the local attach API.
42
44
  .TP
45
+ .B status [--url value] [--token value] [--session-file path] [--run-id value] [--step key]
46
+ Print the current run snapshot (or one step detail with --step) as JSON on stdout.
47
+ .TP
48
+ .B logs [--url value] [--token value] [--session-file path] [--run-id value] [--step key] [--limit number] [--cursor value]
49
+ Print buffered agent stdout/stderr chunks as JSON on stdout.
50
+ .TP
51
+ .B events [--url value] [--token value] [--session-file path] [--run-id value] [--since sequence] [--include-logs]
52
+ Print run events as JSON on stdout in a single poll (no streaming). Log events are excluded unless --include-logs is passed.
53
+ .TP
43
54
  .B auth <login|whoami|logout> [--token value]
44
55
  Manage remote registry authentication for CLI publish and pull flows.
45
56
  .TP
@@ -77,6 +88,9 @@ Bypass confirmation gating for all steps.
77
88
  .B --port <number>
78
89
  Bind the local attach API to a specific port. If omitted, the OS assigns a free port on 127.0.0.1.
79
90
  .TP
91
+ .B --session-file <path>
92
+ Write attach connection details (base URL, bearer token, run id, pid, timestamps) to a JSON file with mode 0600 when the run starts, and rewrite it with endedAt and the final status when the run finishes. Attach commands (approve, resume, cancel, status, logs, events) accept the same flag to read those details back.
93
+ .TP
80
94
  .B --verbose
81
95
  Stream per-step agent output and execution updates to stderr while the workflow runs.
82
96
  .TP
@@ -89,16 +103,28 @@ Full-screen terminal UI (requires a TTY; falls back to standard output).
89
103
  Human approval steps in an interactive terminal show an inline review prompt so they can be approved or cancelled without a separate HTTP client.
90
104
  .TP
91
105
  .B --url <value>
92
- Runner attach API base URL for approve, resume, or cancel commands.
106
+ Runner attach API base URL for approve, resume, cancel, status, logs, or events commands.
93
107
  .TP
94
108
  .B --token <value>
95
- Runner attach API bearer token for approve, resume, or cancel commands.
109
+ Runner attach API bearer token for approve, resume, cancel, status, logs, or events commands.
96
110
  .TP
97
111
  .B --run-id <value>
98
112
  Runner id to control. If omitted, the CLI reads it from /session.
99
113
  .TP
100
114
  .B --step <value>
101
- Optional step key when controlling a specific waiting step.
115
+ Optional step key when controlling a specific waiting step, or when scoping status and logs output.
116
+ .TP
117
+ .B --limit <number>
118
+ Maximum number of log chunks returned by the logs command. Defaults to 200.
119
+ .TP
120
+ .B --cursor <value>
121
+ Pagination cursor for the logs command, taken from a previous nextCursor value.
122
+ .TP
123
+ .B --since <sequence>
124
+ Only return events with a sequence greater than this value in the events command.
125
+ .TP
126
+ .B --include-logs
127
+ Include agent.stdout and agent.stderr events in the events command output.
102
128
  .TP
103
129
  .B --actor <value>
104
130
  Actor name recorded in approval audit events.
@@ -116,6 +142,9 @@ Validate json workflow:
116
142
  Scaffold json workflow file:
117
143
  .B wfm scaffold ./new-workflow.json --format json
118
144
  .TP
145
+ Scaffold an agent-validated pipeline example:
146
+ .B wfm scaffold ./agent-validated.md --template agent-validated
147
+ .TP
119
148
  Authenticate with a CLI token:
120
149
  .B wfm auth login --token wm_exampletoken
121
150
  .TP
@@ -131,6 +160,12 @@ Search the remote registry:
131
160
  Run with explicit confirmations:
132
161
  .B wfm run ./example-workflow.json --confirm discover:human,qa_gate:human
133
162
  .TP
163
+ Run with a session file for attach clients:
164
+ .B wfm run ./example-workflow.json --session-file ./run-session.json
165
+ .TP
166
+ Observe and control the run through the session file:
167
+ .B wfm status --session-file ./run-session.json && wfm approve --session-file ./run-session.json --step qa_gate
168
+ .TP
134
169
  Inspect host setup:
135
170
  .B wfm doctor
136
171
  .TP
package/dist/parser.js CHANGED
@@ -185,9 +185,18 @@ export function validateWorkflow(def) {
185
185
  errors.push(`Unsupported adapter for ${step.key}: ${adapter}`);
186
186
  }
187
187
  const mode = step.validation?.mode ?? step.approvalSpec?.validation?.mode;
188
- if (mode && !["none", "human", "external"].includes(mode)) {
188
+ if (mode && !["none", "human", "external", "agent"].includes(mode)) {
189
189
  errors.push(`Invalid validation mode for ${step.key}: ${mode}`);
190
190
  }
191
+ if (step.approvalSpec?.validation?.mode === "agent") {
192
+ errors.push(`Approval step ${step.key} cannot use agent validation`);
193
+ }
194
+ if (step.validation?.mode === "agent" && step.validation.agent?.adapterKey) {
195
+ const validatorAdapter = step.validation.agent.adapterKey;
196
+ if (!SUPPORTED_ADAPTERS.includes(validatorAdapter)) {
197
+ errors.push(`Unsupported validator adapter for ${step.key}: ${validatorAdapter}`);
198
+ }
199
+ }
191
200
  }
192
201
  const cycle = findDependencyCycle(def);
193
202
  if (cycle) {
package/dist/runnerApi.js CHANGED
@@ -136,6 +136,20 @@ export async function startRunnerApiServer(store, requestedPort) {
136
136
  });
137
137
  return;
138
138
  }
139
+ const eventsListMatch = pathname.match(/^\/runs\/([^/]+)\/events\/list$/);
140
+ if (method === "GET" && eventsListMatch) {
141
+ const runId = decodeURIComponent(eventsListMatch[1] ?? "");
142
+ if (!store.isKnownRun(runId)) {
143
+ errorResponse(res, 404, "not_found", `Unknown run: ${runId}`);
144
+ return;
145
+ }
146
+ const sinceSequence = Number.parseInt(url.searchParams.get("sinceSequence") ?? "0", 10) || undefined;
147
+ const includeLogs = parseBoolean(url.searchParams.get("includeLogs"), true);
148
+ const items = store.events(sinceSequence, includeLogs);
149
+ const nextSequence = items.reduce((max, event) => Math.max(max, event.sequence), sinceSequence ?? 0);
150
+ jsonResponse(res, 200, { items, nextSequence });
151
+ return;
152
+ }
139
153
  const eventsMatch = pathname.match(/^\/runs\/([^/]+)\/events$/);
140
154
  if (method === "GET" && eventsMatch) {
141
155
  const runId = decodeURIComponent(eventsMatch[1] ?? "");
@@ -178,6 +178,7 @@ export function runtimeDoctorChecks(env = process.env) {
178
178
  return [
179
179
  commandCheck("pi-agent", "Pi command", piAgentCommand(piAgentStep, env), true, env),
180
180
  acpCheck,
181
+ commandCheck("codex-acp", "Codex ACP bridge", "codex-acp", false, env),
181
182
  commandCheck("opencode", "OpenCode command (legacy)", "opencode", false, env),
182
183
  commandCheck("claude", "Claude Code command (legacy)", "claude", false, env),
183
184
  envCheck("openrouter-key", "OpenRouter API key", "OPENROUTER_API_KEY", env),
@@ -220,7 +221,7 @@ export function adapterMockFallbackReason(step) {
220
221
  // command) but useRealAdapter is off, so the step still mocks.
221
222
  return `adapterKey '${adapter}' is set, but useRealAdapter is not enabled, so the step runs as a mock. Set taskSpec.payload.useRealAdapter: true to run it through ACP.`;
222
223
  }
223
- // Bare acp/codex with no agent configured is treated as an intentional mock.
224
+ // Bare acp with no agent configured is treated as an intentional mock.
224
225
  return null;
225
226
  }
226
227
  export function adapterMockFallbackWarnings(definition) {
@@ -258,7 +259,7 @@ export function adapterImplementationStatuses() {
258
259
  {
259
260
  adapter: "codex",
260
261
  status: "partial",
261
- detail: "routed through ACP when useRealAdapter and an acpCommand/acpAgent are set; otherwise mock",
262
+ detail: "routed through ACP via the codex-acp bridge when useRealAdapter is true; otherwise mock",
262
263
  },
263
264
  {
264
265
  adapter: "claude-code",
@@ -0,0 +1,11 @@
1
+ export interface RunnerSessionFile {
2
+ baseUrl: string;
3
+ attachToken: string;
4
+ runId: string;
5
+ pid: number;
6
+ startedAt: string;
7
+ endedAt?: string;
8
+ status?: string;
9
+ }
10
+ export declare function writeSessionFile(filePath: string, session: RunnerSessionFile): void;
11
+ export declare function readSessionFile(filePath: string): RunnerSessionFile | string;
@@ -0,0 +1,61 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export function writeSessionFile(filePath, session) {
4
+ const resolvedPath = path.resolve(filePath);
5
+ fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
6
+ fs.writeFileSync(resolvedPath, `${JSON.stringify(session, null, 2)}\n`, {
7
+ encoding: "utf-8",
8
+ mode: 0o600,
9
+ });
10
+ // writeFileSync only applies the mode on creation; enforce it on rewrites too.
11
+ fs.chmodSync(resolvedPath, 0o600);
12
+ }
13
+ export function readSessionFile(filePath) {
14
+ const resolvedPath = path.resolve(filePath);
15
+ let raw;
16
+ try {
17
+ raw = fs.readFileSync(resolvedPath, "utf-8");
18
+ }
19
+ catch (error) {
20
+ return `Could not read session file ${resolvedPath}: ${error.message}`;
21
+ }
22
+ let parsed;
23
+ try {
24
+ parsed = JSON.parse(raw);
25
+ }
26
+ catch {
27
+ return `Session file ${resolvedPath} is not valid JSON`;
28
+ }
29
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
30
+ return `Session file ${resolvedPath} must contain a JSON object`;
31
+ }
32
+ const record = parsed;
33
+ for (const field of ["baseUrl", "attachToken", "runId", "startedAt"]) {
34
+ if (typeof record[field] !== "string" || record[field] === "") {
35
+ return `Session file ${resolvedPath} is missing required field: ${field}`;
36
+ }
37
+ }
38
+ if (typeof record.pid !== "number" || !Number.isInteger(record.pid)) {
39
+ return `Session file ${resolvedPath} is missing required field: pid`;
40
+ }
41
+ if (record.endedAt !== undefined && typeof record.endedAt !== "string") {
42
+ return `Session file ${resolvedPath} has an invalid endedAt field`;
43
+ }
44
+ if (record.status !== undefined && typeof record.status !== "string") {
45
+ return `Session file ${resolvedPath} has an invalid status field`;
46
+ }
47
+ const session = {
48
+ baseUrl: record.baseUrl,
49
+ attachToken: record.attachToken,
50
+ runId: record.runId,
51
+ pid: record.pid,
52
+ startedAt: record.startedAt,
53
+ };
54
+ if (typeof record.endedAt === "string") {
55
+ session.endedAt = record.endedAt;
56
+ }
57
+ if (typeof record.status === "string") {
58
+ session.status = record.status;
59
+ }
60
+ return session;
61
+ }
@@ -38,6 +38,17 @@ function buildEventMetaText(event) {
38
38
  const feedbackReason = readString(payload, "feedbackReason");
39
39
  return `execution finished: ${status}${action ? ` action=${action}` : ""}${feedbackReason ? ` reason=${feedbackReason}` : ""}`;
40
40
  }
41
+ if (event.type === "step.validation_started") {
42
+ const adapter = readString(payload, "adapter");
43
+ const criteria = readString(payload, "criteria");
44
+ return `agent validation started${adapter ? ` (${adapter})` : ""}${criteria ? ` criteria=${criteria}` : ""}`;
45
+ }
46
+ if (event.type === "step.validation_finished") {
47
+ const status = readString(payload, "status") ?? "unknown";
48
+ const action = readString(payload, "action");
49
+ const feedbackReason = readString(payload, "feedbackReason");
50
+ return `agent validation: ${status}${action ? ` action=${action}` : ""}${feedbackReason ? ` reason=${feedbackReason}` : ""}`;
51
+ }
41
52
  if (event.type === "step.retried") {
42
53
  const attempt = typeof payload.attempt === "number" ? payload.attempt : undefined;
43
54
  return attempt !== undefined ? `retry scheduled (attempt ${attempt})` : "retry scheduled";
package/dist/types.d.ts CHANGED
@@ -4,17 +4,11 @@ export type StepKind = "task" | "approval" | "system";
4
4
  export type NodeType = "AGENT" | "HUMAN" | "SYSTEM";
5
5
  export type ExecutionStatus = "SUCCESS" | "QA_REJECTED" | "YIELD_EXTERNAL" | "FAILED";
6
6
  export type QaAction = "PROCEED" | "RETRY_CURRENT" | "ROLLBACK_PREVIOUS" | "RESTART_ALL";
7
- export type ValidationMode = "none" | "human" | "external";
7
+ export type ValidationMode = "none" | "human" | "external" | "agent";
8
8
  export type AdapterKey = "pi-agent" | "mock" | "opencode" | "codex" | "claude-code" | "acp";
9
9
  export interface RetryPolicy {
10
10
  maxAttempts?: number;
11
11
  }
12
- export interface ValidationSpec {
13
- mode?: ValidationMode;
14
- required?: boolean;
15
- autoConfirm?: boolean;
16
- confirmerPolicy?: string;
17
- }
18
12
  export interface TaskInitConfig {
19
13
  context?: Record<string, unknown> | string;
20
14
  skills?: string[];
@@ -22,6 +16,19 @@ export interface TaskInitConfig {
22
16
  systemPrompts?: string[];
23
17
  model?: string;
24
18
  }
19
+ export interface AgentValidationSpec {
20
+ adapterKey?: AdapterKey;
21
+ init?: TaskInitConfig;
22
+ criteria?: string;
23
+ payload?: Record<string, unknown>;
24
+ }
25
+ export interface ValidationSpec {
26
+ mode?: ValidationMode;
27
+ required?: boolean;
28
+ autoConfirm?: boolean;
29
+ confirmerPolicy?: string;
30
+ agent?: AgentValidationSpec;
31
+ }
25
32
  export interface StepDefinition {
26
33
  key: string;
27
34
  kind: StepKind;
@@ -260,7 +267,7 @@ export interface RunEvent {
260
267
  id: string;
261
268
  runId: string;
262
269
  stepRunId?: string;
263
- type: "run.created" | "run.started" | "run.waiting_for_approval" | "step.runnable" | "step.claimed" | "step.execution_started" | "step.execution_finished" | "step.waiting_for_approval" | "approval.resolved" | "step.retried" | "step.confirmed" | "agent.started" | "agent.stdout" | "agent.stderr" | "agent.finished" | "run.completed" | "run.failed" | "run.cancelled";
270
+ type: "run.created" | "run.started" | "run.waiting_for_approval" | "step.runnable" | "step.claimed" | "step.execution_started" | "step.execution_finished" | "step.validation_started" | "step.validation_finished" | "step.waiting_for_approval" | "approval.resolved" | "step.retried" | "step.confirmed" | "agent.started" | "agent.stdout" | "agent.stderr" | "agent.finished" | "run.completed" | "run.failed" | "run.cancelled";
264
271
  sequenceNumber: number;
265
272
  occurredAt: string;
266
273
  actor: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow-manager/runner",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "CLI runner for in-memory and markdown workflow orchestration using ATEP-like envelopes",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,6 +23,7 @@
23
23
  ],
24
24
  "scripts": {
25
25
  "build": "tsc -p tsconfig.json",
26
+ "generate:skills": "node ./scripts/generate-bundled-skills.mjs",
26
27
  "build:bin": "node ./scripts/build-binary.mjs --outfile ./dist/wfm",
27
28
  "build:bin:macos": "node ./scripts/build-binary.mjs --target bun-darwin-arm64 --outfile ./dist/wfm-macos-arm64",
28
29
  "build:bin:linux": "node ./scripts/build-binary.mjs --target bun-linux-x64 --outfile ./dist/wfm-linux-x64",
@@ -72,7 +73,7 @@
72
73
  ],
73
74
  "license": "MIT",
74
75
  "dependencies": {
75
- "@zed-industries/agent-client-protocol": "^0.4.5",
76
+ "@agentclientprotocol/sdk": "^1.2.1",
76
77
  "gray-matter": "^4.0.3",
77
78
  "picocolors": "^1.1.1"
78
79
  },
@@ -0,0 +1,18 @@
1
+ # workflow-author skill
2
+
3
+ Teaches a host coding agent (Claude Code, opencode, pi) how to turn a natural-language
4
+ task description into a repeatable `wfm` workflow, validate it, run it, and narrate
5
+ progress — including approval gates — back to the user.
6
+
7
+ This skill ships inside the root `@workflow-manager/runner` npm package alongside
8
+ `workflow-manager-cli`. Install it into an agent's skill directory with:
9
+
10
+ ```bash
11
+ wfm skill install workflow-author # -> ./.claude/skills/
12
+ wfm skill install workflow-author --agent opencode # -> ./.opencode/skill/
13
+ wfm skill install workflow-author --global # -> ~/.claude/skills/
14
+ ```
15
+
16
+ See `SKILL.md` for the full operating manual, and `doc/guide/workflow-schema.md` /
17
+ `doc/guide/runner-api.md` for the underlying schema and attach-API contract this
18
+ skill is built on.