@bastani/atomic 0.5.20-0 → 0.5.21-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/.agents/skills/workflow-creator/SKILL.md +56 -8
- package/dist/sdk/components/orchestrator-panel.d.ts +23 -1
- package/dist/sdk/components/orchestrator-panel.d.ts.map +1 -1
- package/dist/sdk/runtime/executor.d.ts.map +1 -1
- package/dist/sdk/runtime/status-writer.d.ts +101 -0
- package/dist/sdk/runtime/status-writer.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +57 -3
- package/src/commands/cli/session.test.ts +43 -0
- package/src/commands/cli/session.ts +18 -8
- package/src/commands/cli/workflow-inputs.test.ts +321 -0
- package/src/commands/cli/workflow-inputs.ts +219 -0
- package/src/commands/cli/workflow-status.test.ts +451 -0
- package/src/commands/cli/workflow-status.ts +330 -0
- package/src/sdk/components/orchestrator-panel.tsx +36 -1
- package/src/sdk/runtime/executor.ts +37 -0
- package/src/sdk/runtime/status-writer.test.ts +245 -0
- package/src/sdk/runtime/status-writer.ts +201 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: workflow-creator
|
|
3
|
-
description: Create multi-agent workflows for Atomic CLI using defineWorkflow().run().compile() with ctx.stage() for session orchestration across Claude, Copilot, and OpenCode SDKs, AND invoke existing workflows on behalf of the user. Use whenever the user wants to create, edit, debug, or RUN workflows ("run the ralph workflow", "kick off deep-research-codebase", "start the gen-spec workflow"), build agent pipelines, define multi-stage automations, set up review loops, declare workflow inputs, run background/headless stages, or mentions .atomic/workflows/, defineWorkflow, ctx.stage, ctx.inputs, headless, background stages, the atomic workflow picker,
|
|
3
|
+
description: Create multi-agent workflows for Atomic CLI using defineWorkflow().run().compile() with ctx.stage() for session orchestration across Claude, Copilot, and OpenCode SDKs, AND invoke, monitor, and tear down existing workflows on behalf of the user. Use whenever the user wants to create, edit, debug, or RUN workflows ("run the ralph workflow", "kick off deep-research-codebase", "start the gen-spec workflow"), check on a running workflow ("is it done yet?", "what's the status?", "did it error out?"), kill a workflow or session, build agent pipelines, define multi-stage automations, set up review loops, declare workflow inputs, run background/headless stages, or mentions .atomic/workflows/, defineWorkflow, ctx.stage, ctx.inputs, headless, background stages, the atomic workflow picker, `atomic workflow -n`, `atomic workflow inputs`, `atomic workflow status`, or `atomic session kill`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Workflow Creator
|
|
@@ -208,7 +208,10 @@ Workflows that accept a free-form prompt should declare it explicitly: `{ name:
|
|
|
208
208
|
| Named, with prompt | `atomic workflow -n hello -a claude "fix the bug"` | Scripted runs; requires the workflow to declare a `prompt` input |
|
|
209
209
|
| Named, structured | `atomic workflow -n gen-spec -a claude --research_doc=notes.md` | Scripted structured runs |
|
|
210
210
|
| Interactive picker | `atomic workflow -a claude` | Discovery; shows fuzzy list + form |
|
|
211
|
-
| List | `atomic workflow
|
|
211
|
+
| List | `atomic workflow list` | Browse everything by source |
|
|
212
|
+
| Inspect inputs | `atomic workflow inputs <name> -a claude` | Print a workflow's input schema as JSON — agents use this instead of reading source |
|
|
213
|
+
| Status (one or all) | `atomic workflow status [<session-id>]` | Query state — `in_progress`, `error`, `completed`, `needs_review` (HIL pause). JSON by default |
|
|
214
|
+
| Kill non-interactively | `atomic session kill <id> -y` | Tear down a workflow/chat session without the confirmation prompt — the form agents use |
|
|
212
215
|
| Detached (background) | `atomic workflow -n ralph -a claude -d "..."` | Scripted/CI runs where the caller shouldn't block on the TUI — the orchestrator keeps running on the atomic tmux socket; attach later with `atomic workflow session connect <name>` |
|
|
213
216
|
|
|
214
217
|
Any of the named shapes above (positional or structured) accepts `-d` / `--detach` to run without attaching. Use it when you're automating from a script and want the CLI to return as soon as the session is spawned.
|
|
@@ -398,9 +401,18 @@ Once you've confirmed the workflow exists, you need to know two things about its
|
|
|
398
401
|
1. **Does it declare a `prompt` input?** If so, it's free-form — you pass a positional string.
|
|
399
402
|
2. **Does it declare structured inputs?** If so, you pass `--<field>=<value>` flags, one per required field.
|
|
400
403
|
|
|
401
|
-
|
|
404
|
+
**Use `atomic workflow inputs <name> -a <agent>` to get the schema.** This prints a JSON envelope with every field's `name`, `type`, `required`, `default`, `description`, and (for enums) `values` — exactly what AskUserQuestion needs. The `freeform: true` flag tells you whether the workflow takes a positional prompt vs. structured flags, with a synthetic `prompt` field included so the JSON shape is uniform either way.
|
|
402
405
|
|
|
403
|
-
|
|
406
|
+
```bash
|
|
407
|
+
atomic workflow inputs gen-spec -a claude
|
|
408
|
+
# {"workflow":"gen-spec","agent":"claude","freeform":false,
|
|
409
|
+
# "inputs":[{"name":"research_doc","type":"string","required":true,...},
|
|
410
|
+
# {"name":"focus","type":"enum","values":["minimal","standard","exhaustive"],"default":"standard"}]}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Why this command instead of reading the source file: `inputs` is the contract the CLI actually validates against. It survives refactors, handles built-in workflows that aren't in the project tree, and never falls out of sync with the runtime. Reading TypeScript source is a fallback for the rare case where the command can't resolve the workflow.
|
|
414
|
+
|
|
415
|
+
Once you have the schema, use the **AskUserQuestion tool** to collect any values the user hasn't already provided in their message. One question per missing input field. For enum fields, pass the declared `values` as multiple-choice options so the user sees exactly what's allowed. Keep questions tight and purposeful — if the user's message already answers a question, don't ask it again.
|
|
404
416
|
|
|
405
417
|
Skip AskUserQuestion entirely when:
|
|
406
418
|
- The user already supplied every required value in their message ("run ralph on 'add OAuth to the API'" — the prompt is right there).
|
|
@@ -413,13 +425,46 @@ Skip AskUserQuestion entirely when:
|
|
|
413
425
|
- Exact match in the list → continue.
|
|
414
426
|
- Close match → confirm via AskUserQuestion before proceeding.
|
|
415
427
|
- No match → tell the user what's available and offer to author it (see previous section). If they decline, stop.
|
|
416
|
-
3. **Discover the inputs schema** —
|
|
428
|
+
3. **Discover the inputs schema** — run `atomic workflow inputs <name> -a <agent>` and parse the JSON.
|
|
417
429
|
4. **Ask for missing inputs** — use AskUserQuestion, one question per unanswered required field. Enums become multiple-choice.
|
|
418
430
|
5. **Invoke** — build one of these commands:
|
|
419
431
|
- Free-form: `atomic workflow -n <name> "<prompt>"`
|
|
420
432
|
- Structured: `atomic workflow -n <name> --<field1>=<value1> --<field2>=<value2>`
|
|
421
433
|
6. **Report the session name** the CLI printed and tell the user: "attach any time with `atomic workflow session connect <session>` — or `atomic workflow session list` to see what's running."
|
|
422
434
|
|
|
435
|
+
### Monitoring a running workflow
|
|
436
|
+
|
|
437
|
+
Detached workflows return immediately with a session name; the actual work runs in the background on the atomic tmux socket. Use `atomic workflow status` to check whether the workflow is still running, has completed, errored out, or paused for human input — without attaching to its TUI.
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
atomic workflow status atomic-wf-claude-gen-spec-a1b2c3d4
|
|
441
|
+
# {"id":"atomic-wf-claude-gen-spec-a1b2c3d4","overall":"in_progress","alive":true,
|
|
442
|
+
# "sessions":[{"name":"orchestrator","status":"running",...}],...}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Four overall states the agent must handle distinctly:
|
|
446
|
+
|
|
447
|
+
| Status | Meaning | What you should do |
|
|
448
|
+
|---|---|---|
|
|
449
|
+
| `in_progress` | The orchestrator is running and no stage is paused | Wait, or report progress to the user |
|
|
450
|
+
| `needs_review` | At least one stage is paused for human input (HIL) — Copilot `ask_user`, OpenCode `question.asked`, Copilot/MCP elicitation | **Surface this to the user immediately** — they need to attach with `atomic workflow session connect <id>` to respond, otherwise the workflow stalls indefinitely |
|
|
451
|
+
| `completed` | Workflow finished successfully | Report success and summarize the output |
|
|
452
|
+
| `error` | Fatal error or a stage failed | Report the `fatalError` field and offer to investigate logs |
|
|
453
|
+
|
|
454
|
+
`needs_review` outranks `completed` so a HIL pause near the end is never reported as done while still waiting on a human. A dead orchestrator with a stale snapshot is automatically downgraded to `error`.
|
|
455
|
+
|
|
456
|
+
Omit the id to list every running workflow at once: `atomic workflow status`. Useful when checking on multiple parallel runs, or when the user just asks "what's running?".
|
|
457
|
+
|
|
458
|
+
### Cleaning up sessions
|
|
459
|
+
|
|
460
|
+
When the user is done with a workflow — or you launched one detached and it's no longer needed — tear it down with `-y` so no confirmation prompt blocks you:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
atomic session kill atomic-wf-claude-gen-spec-a1b2c3d4 -y
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
The `-y` flag is mandatory for agent use. Without it, the CLI calls `@clack/prompts confirm`, which expects a TTY and will hang indefinitely in a non-interactive context. Same flag works for `atomic workflow session kill` and `atomic chat session kill`. Without an id, `kill -y` tears down every in-scope session — only do that when the user has asked to stop everything.
|
|
467
|
+
|
|
423
468
|
### Worked examples
|
|
424
469
|
|
|
425
470
|
**Example A — workflow exists, structured inputs**
|
|
@@ -428,10 +473,10 @@ Skip AskUserQuestion entirely when:
|
|
|
428
473
|
|
|
429
474
|
1. Run `atomic workflow list`. Output includes `gen-spec` under local. Good.
|
|
430
475
|
2. Target resolved exactly: `gen-spec`.
|
|
431
|
-
3.
|
|
476
|
+
3. Run `atomic workflow inputs gen-spec -a claude`. Parse the JSON: `research_doc` (required string — already given), `focus` (required enum of `minimal|standard|exhaustive`, default `standard`), `notes` (optional text).
|
|
432
477
|
4. Ask via AskUserQuestion once: "What focus level for the spec?" with choices `minimal`, `standard`, `exhaustive`. User picks `standard`. Skip `notes` since it's optional.
|
|
433
478
|
5. Run: `atomic workflow -n gen-spec --research_doc=research/docs/2026-04-11-auth.md --focus=standard`
|
|
434
|
-
6. The CLI prints a session name like `atomic-wf-claude-gen-spec-a1b2c3d4`. Tell the user: "Started in the background. Attach with `atomic workflow session connect atomic-wf-claude-gen-spec-a1b2c3d4
|
|
479
|
+
6. The CLI prints a session name like `atomic-wf-claude-gen-spec-a1b2c3d4`. Tell the user: "Started in the background. Attach with `atomic workflow session connect atomic-wf-claude-gen-spec-a1b2c3d4`, check progress with `atomic workflow status atomic-wf-claude-gen-spec-a1b2c3d4`, or stop it with `atomic session kill atomic-wf-claude-gen-spec-a1b2c3d4 -y`."
|
|
435
480
|
|
|
436
481
|
**Example B — workflow does not exist**
|
|
437
482
|
|
|
@@ -448,6 +493,9 @@ Skip AskUserQuestion entirely when:
|
|
|
448
493
|
|
|
449
494
|
- **Skipping `atomic workflow list`** — leads to guessing and `workflow not found` errors. It's a one-line command; always run it.
|
|
450
495
|
- **Inventing a workflow name** — if it's not in the list, it doesn't exist. Say so and offer to author it.
|
|
496
|
+
- **Reading the workflow source file to discover inputs** — use `atomic workflow inputs <name> -a <agent>` instead. JSON, no TS parsing required, always in sync with the runtime. Source-file reads are a fallback, not a default.
|
|
451
497
|
- **Asking everything at once** — let AskUserQuestion drive one question per field. Enum fields are multiple-choice, not free text.
|
|
452
498
|
- **Re-asking what the user already said** — read their message first.
|
|
453
|
-
- **Forgetting to report the session name** — the user needs it to reattach.
|
|
499
|
+
- **Forgetting to report the session name** — the user needs it to reattach and to query status later.
|
|
500
|
+
- **Leaving `needs_review` unreported** — when `atomic workflow status` returns `needs_review`, surface it to the user right away. The workflow is blocked on human input and will sit forever otherwise.
|
|
501
|
+
- **Calling `session kill` without `-y`** — the prompt hangs in a non-interactive context. Always pass `-y` from an agent.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* executor interface with the React-based session graph TUI.
|
|
5
5
|
*/
|
|
6
6
|
import { type CliRenderer } from "@opentui/core";
|
|
7
|
-
import type { PanelSession, PanelOptions } from "./orchestrator-panel-types.ts";
|
|
7
|
+
import type { PanelSession, PanelOptions, SessionData } from "./orchestrator-panel-types.ts";
|
|
8
8
|
export declare class OrchestratorPanel {
|
|
9
9
|
private store;
|
|
10
10
|
private renderer;
|
|
@@ -54,5 +54,27 @@ export declare class OrchestratorPanel {
|
|
|
54
54
|
waitForAbort(): Promise<void>;
|
|
55
55
|
/** Tear down the terminal renderer and release resources. Idempotent. */
|
|
56
56
|
destroy(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to store mutations. Returned function unsubscribes.
|
|
59
|
+
*
|
|
60
|
+
* Used by the orchestrator process to mirror the in-memory panel
|
|
61
|
+
* state to a `status.json` file on disk so out-of-process consumers
|
|
62
|
+
* (e.g. `atomic workflow status`) can read the live workflow state.
|
|
63
|
+
*/
|
|
64
|
+
subscribe(fn: () => void): () => void;
|
|
65
|
+
/**
|
|
66
|
+
* Read-only snapshot of the fields needed by the on-disk status
|
|
67
|
+
* writer. Defined here (not in PanelStore) because the store keeps
|
|
68
|
+
* full mutable references; this projection drops the renderer-only
|
|
69
|
+
* promise resolvers and version counter.
|
|
70
|
+
*/
|
|
71
|
+
getSnapshot(): {
|
|
72
|
+
workflowName: string;
|
|
73
|
+
agent: string;
|
|
74
|
+
prompt: string;
|
|
75
|
+
fatalError: string | null;
|
|
76
|
+
completionReached: boolean;
|
|
77
|
+
sessions: readonly SessionData[];
|
|
78
|
+
};
|
|
57
79
|
}
|
|
58
80
|
//# sourceMappingURL=orchestrator-panel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator-panel.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/orchestrator-panel.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC;;;GAGG;AAEH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAOpE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"orchestrator-panel.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/orchestrator-panel.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC;;;GAGG;AAEH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAOpE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAI7F,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO;IAsCP;;;;;OAKG;WACU,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAQtE,0EAA0E;IAC1E,MAAM,CAAC,kBAAkB,CACvB,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,YAAY,GACpB,iBAAiB;IAOpB;;;OAGG;IACH,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,YAAY,EAAE,EACxB,MAAM,EAAE,MAAM,GACb,IAAI;IAIP,iDAAiD;IACjD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIhC,gEAAgE;IAChE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIlC,8EAA8E;IAC9E,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAIjD,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIlC,0DAA0D;IAC1D,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAUjD,8EAA8E;IAC9E,qBAAqB,IAAI,IAAI;IAI7B,8EAA8E;IAC9E,sBAAsB,IAAI,IAAI;IAI9B,0EAA0E;IAC1E,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI;IAInE,+CAA+C;IAC/C,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIrC;;;OAGG;IACH,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;;OAGG;IACH,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAM7B,yEAAyE;IACzE,OAAO,IAAI,IAAI;IAQf;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAIrC;;;;;OAKG;IACH,WAAW,IAAI;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,iBAAiB,EAAE,OAAO,CAAC;QAC3B,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;KAClC;CAUF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EACV,kBAAkB,EAMlB,SAAS,EAET,YAAY,EAMb,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow status snapshot — bridges the in-process panel state with
|
|
3
|
+
* out-of-process consumers (e.g. `atomic workflow status`).
|
|
4
|
+
*
|
|
5
|
+
* The orchestrator subscribes to its `PanelStore` and writes a fresh
|
|
6
|
+
* snapshot to `~/.atomic/sessions/<workflowRunId>/status.json` every
|
|
7
|
+
* time the store mutates. Consumers read that file to derive the
|
|
8
|
+
* overall workflow state without needing IPC into the orchestrator.
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionData, SessionStatus } from "../components/orchestrator-panel-types.ts";
|
|
11
|
+
/** File name used for the status snapshot inside each workflow's session directory. */
|
|
12
|
+
export declare const STATUS_FILE_NAME = "status.json";
|
|
13
|
+
/** High-level workflow state surfaced to the agent / CLI consumer. */
|
|
14
|
+
export type WorkflowOverallStatus = "in_progress" | "error" | "completed" | "needs_review";
|
|
15
|
+
/** Per-session entry mirrored from the orchestrator's panel store. */
|
|
16
|
+
export interface WorkflowStatusSession {
|
|
17
|
+
name: string;
|
|
18
|
+
status: SessionStatus;
|
|
19
|
+
parents: string[];
|
|
20
|
+
error?: string;
|
|
21
|
+
startedAt: number | null;
|
|
22
|
+
endedAt: number | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Snapshot persisted to disk for `atomic workflow status` to read.
|
|
26
|
+
* Schema is versioned so future readers can stay backwards-compatible.
|
|
27
|
+
*/
|
|
28
|
+
export interface WorkflowStatusSnapshot {
|
|
29
|
+
schemaVersion: 1;
|
|
30
|
+
workflowRunId: string;
|
|
31
|
+
tmuxSession: string;
|
|
32
|
+
workflowName: string;
|
|
33
|
+
agent: string;
|
|
34
|
+
prompt: string;
|
|
35
|
+
/** Overall state derived from per-session status + completion flags. */
|
|
36
|
+
overall: WorkflowOverallStatus;
|
|
37
|
+
/** True when the orchestrator has shown its completion banner. */
|
|
38
|
+
completionReached: boolean;
|
|
39
|
+
/** Fatal-error message set via `panel.showFatalError`, if any. */
|
|
40
|
+
fatalError: string | null;
|
|
41
|
+
/** Wall-clock time of the snapshot in ISO-8601 format. */
|
|
42
|
+
updatedAt: string;
|
|
43
|
+
sessions: WorkflowStatusSession[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Inputs the writer needs to render a snapshot — a strict subset of
|
|
47
|
+
* `PanelStore` so the writer doesn't depend on the renderer module.
|
|
48
|
+
*/
|
|
49
|
+
export interface StatusWriterInputs {
|
|
50
|
+
workflowRunId: string;
|
|
51
|
+
tmuxSession: string;
|
|
52
|
+
workflowName: string;
|
|
53
|
+
agent: string;
|
|
54
|
+
prompt: string;
|
|
55
|
+
fatalError: string | null;
|
|
56
|
+
completionReached: boolean;
|
|
57
|
+
sessions: readonly SessionData[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Derive the overall workflow state from per-session statuses + the
|
|
61
|
+
* orchestrator-level completion / fatal-error flags.
|
|
62
|
+
*
|
|
63
|
+
* Precedence (highest first):
|
|
64
|
+
* 1. `error` — fatal error or any session ended in error
|
|
65
|
+
* 2. `needs_review` — at least one session is awaiting human input (HIL)
|
|
66
|
+
* 3. `completed` — completion banner reached and nothing errored
|
|
67
|
+
* 4. `in_progress` — default
|
|
68
|
+
*
|
|
69
|
+
* `needs_review` outranks `completed` so an agent that pauses for HIL
|
|
70
|
+
* right at the end is never reported as done while still waiting.
|
|
71
|
+
*/
|
|
72
|
+
export declare function deriveOverallStatus(input: {
|
|
73
|
+
sessions: readonly SessionData[];
|
|
74
|
+
completionReached: boolean;
|
|
75
|
+
fatalError: string | null;
|
|
76
|
+
}): WorkflowOverallStatus;
|
|
77
|
+
/** Build a snapshot from the writer inputs (pure — exported for tests). */
|
|
78
|
+
export declare function buildSnapshot(input: StatusWriterInputs, now?: () => Date): WorkflowStatusSnapshot;
|
|
79
|
+
/** Absolute path of the status file for a given workflow run directory. */
|
|
80
|
+
export declare function statusFilePath(sessionDir: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* Write a snapshot to `<sessionDir>/status.json`. Uses an atomic
|
|
83
|
+
* write-then-rename so concurrent readers never see partial JSON.
|
|
84
|
+
* Errors are swallowed — the orchestrator must keep running even if
|
|
85
|
+
* the status file can't be persisted.
|
|
86
|
+
*/
|
|
87
|
+
export declare function writeSnapshot(sessionDir: string, snapshot: WorkflowStatusSnapshot): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Read a snapshot from disk. Returns `null` when the file doesn't
|
|
90
|
+
* exist or fails to parse — callers fall back to deriving status from
|
|
91
|
+
* the live tmux session list.
|
|
92
|
+
*/
|
|
93
|
+
export declare function readSnapshot(sessionDir: string): Promise<WorkflowStatusSnapshot | null>;
|
|
94
|
+
/**
|
|
95
|
+
* Extract the `workflowRunId` (the trailing 8-hex segment) from a
|
|
96
|
+
* tmux session name shaped `atomic-wf-<agent>-<name>-<id>`. Returns
|
|
97
|
+
* `null` for non-workflow sessions or names that don't end in a
|
|
98
|
+
* UUID-style suffix.
|
|
99
|
+
*/
|
|
100
|
+
export declare function workflowRunIdFromTmuxName(name: string): string | null;
|
|
101
|
+
//# sourceMappingURL=status-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-writer.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/status-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAE5F,uFAAuF;AACvF,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAE9C,sEAAsE;AACtE,MAAM,MAAM,qBAAqB,GAC7B,aAAa,GACb,OAAO,GACP,WAAW,GACX,cAAc,CAAC;AAEnB,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,CAAC,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,OAAO,EAAE,qBAAqB,CAAC;IAC/B,kEAAkE;IAClE,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kEAAkE;IAClE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;CAClC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,qBAAqB,CAQxB;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAC3B,KAAK,EAAE,kBAAkB,EACzB,GAAG,GAAE,MAAM,IAAuB,GACjC,sBAAsB,CAyBxB;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,sBAAsB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAUxC;AAeD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQrE"}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
* atomic chat session list List running chat/workflow sessions
|
|
10
10
|
* atomic chat session connect <id> Attach to a session
|
|
11
11
|
* atomic workflow list List available workflows
|
|
12
|
+
* atomic workflow inputs <name> -a <agent> Print a workflow's input schema (JSON)
|
|
13
|
+
* atomic workflow status [<id>] Query workflow status (JSON)
|
|
12
14
|
* atomic workflow session list List running sessions
|
|
13
15
|
* atomic workflow session connect <id> Attach to a session
|
|
14
16
|
* atomic session list List all running sessions
|
|
15
17
|
* atomic session connect [id] Interactive session picker
|
|
16
|
-
* atomic session kill [id]
|
|
18
|
+
* atomic session kill [id] [-y] Kill a session (or all when no id); -y skips prompt
|
|
17
19
|
* atomic config set <key> <value> Set configuration value
|
|
18
20
|
* atomic --version Show version
|
|
19
21
|
* atomic --help Show help
|
|
@@ -88,9 +90,16 @@ function addSessionSubcommand(parent: Command, scope: "chat" | "workflow" | "all
|
|
|
88
90
|
collectAgent,
|
|
89
91
|
[] as string[],
|
|
90
92
|
)
|
|
93
|
+
.option("-y, --yes", "Skip the confirmation prompt (for non-interactive callers like agents)")
|
|
91
94
|
.action(async (sessionId, localOpts) => {
|
|
92
95
|
const { sessionKillCommand } = await import("./commands/cli/session.ts");
|
|
93
|
-
const exitCode = await sessionKillCommand(
|
|
96
|
+
const exitCode = await sessionKillCommand(
|
|
97
|
+
sessionId,
|
|
98
|
+
localOpts.agent,
|
|
99
|
+
scope,
|
|
100
|
+
undefined,
|
|
101
|
+
{ yes: localOpts.yes === true },
|
|
102
|
+
);
|
|
94
103
|
process.exit(exitCode);
|
|
95
104
|
});
|
|
96
105
|
|
|
@@ -220,9 +229,12 @@ Examples:
|
|
|
220
229
|
$ atomic workflow -n gen-spec -a claude --research_doc=notes.md --focus=standard
|
|
221
230
|
Run a structured-input workflow
|
|
222
231
|
$ atomic workflow -n ralph -a claude -d "fix bug" Run detached in the background
|
|
232
|
+
$ atomic workflow inputs <name> -a claude Print a workflow's input schema (JSON)
|
|
233
|
+
$ atomic workflow status List status for all running workflows
|
|
234
|
+
$ atomic workflow status <id> Query a single workflow's status
|
|
223
235
|
$ atomic workflow session list List running sessions
|
|
224
236
|
$ atomic workflow session connect <id> Attach to a session
|
|
225
|
-
$ atomic workflow session kill [id]
|
|
237
|
+
$ atomic workflow session kill [id] -y Kill a workflow session (or all), no prompt`,
|
|
226
238
|
)
|
|
227
239
|
.action(async (localOpts, cmd) => {
|
|
228
240
|
const { workflowCommand } = await import("./commands/cli/workflow.ts");
|
|
@@ -249,6 +261,48 @@ Examples:
|
|
|
249
261
|
process.exit(exitCode);
|
|
250
262
|
});
|
|
251
263
|
|
|
264
|
+
// Workflow inputs subcommand: atomic workflow inputs <name> -a <agent>
|
|
265
|
+
// Exposes the declared input schema so an orchestrating agent can build
|
|
266
|
+
// a valid `atomic workflow -n ...` invocation without reading source.
|
|
267
|
+
workflowCmd
|
|
268
|
+
.command("inputs")
|
|
269
|
+
.description("Print a workflow's declared input schema (JSON by default)")
|
|
270
|
+
.argument("<name>", "Workflow name")
|
|
271
|
+
.requiredOption("-a, --agent <name>", `Agent backend (${agentChoices})`)
|
|
272
|
+
.option("--format <format>", "Output format: json | text", "json")
|
|
273
|
+
.action(async (name, localOpts) => {
|
|
274
|
+
const { workflowInputsCommand } = await import(
|
|
275
|
+
"./commands/cli/workflow-inputs.ts"
|
|
276
|
+
);
|
|
277
|
+
const exitCode = await workflowInputsCommand({
|
|
278
|
+
name,
|
|
279
|
+
agent: localOpts.agent,
|
|
280
|
+
format: localOpts.format === "text" ? "text" : "json",
|
|
281
|
+
});
|
|
282
|
+
process.exit(exitCode);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Workflow status subcommand: atomic workflow status [<id>]
|
|
286
|
+
// Returns one of in_progress | error | completed | needs_review.
|
|
287
|
+
// Defaults to JSON so agents can parse it without screen-scraping.
|
|
288
|
+
workflowCmd
|
|
289
|
+
.command("status")
|
|
290
|
+
.description(
|
|
291
|
+
"Query workflow status (in_progress, error, completed, needs_review)",
|
|
292
|
+
)
|
|
293
|
+
.argument("[session_id]", "Workflow tmux session id (omit to list all)")
|
|
294
|
+
.option("--format <format>", "Output format: json | text", "json")
|
|
295
|
+
.action(async (sessionId, localOpts) => {
|
|
296
|
+
const { workflowStatusCommand } = await import(
|
|
297
|
+
"./commands/cli/workflow-status.ts"
|
|
298
|
+
);
|
|
299
|
+
const exitCode = await workflowStatusCommand({
|
|
300
|
+
id: sessionId,
|
|
301
|
+
format: localOpts.format === "text" ? "text" : "json",
|
|
302
|
+
});
|
|
303
|
+
process.exit(exitCode);
|
|
304
|
+
});
|
|
305
|
+
|
|
252
306
|
// Workflow session subcommands: atomic workflow session list / connect
|
|
253
307
|
addSessionSubcommand(workflowCmd, "workflow");
|
|
254
308
|
|
|
@@ -711,4 +711,47 @@ describe("sessionKillCommand", () => {
|
|
|
711
711
|
process.stdout.write = origWrite;
|
|
712
712
|
}
|
|
713
713
|
});
|
|
714
|
+
|
|
715
|
+
// (l) -y on named kill: skip prompt and kill immediately
|
|
716
|
+
test("yes flag skips the prompt for a named kill and calls killSession", async () => {
|
|
717
|
+
const now = new Date().toISOString();
|
|
718
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
719
|
+
{ name: "target-session", windows: 1, created: now, attached: false, type: "chat" as const },
|
|
720
|
+
]);
|
|
721
|
+
const origWrite = process.stdout.write;
|
|
722
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
723
|
+
try {
|
|
724
|
+
const code = await sessionKillCommand(
|
|
725
|
+
"target-session",
|
|
726
|
+
[],
|
|
727
|
+
"all",
|
|
728
|
+
makeDeps(),
|
|
729
|
+
{ yes: true },
|
|
730
|
+
);
|
|
731
|
+
expect(code).toBe(0);
|
|
732
|
+
expect(tmuxMocks.confirm).not.toHaveBeenCalled();
|
|
733
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledWith("target-session");
|
|
734
|
+
} finally {
|
|
735
|
+
process.stdout.write = origWrite;
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// (m) -y on kill-all: skip prompt and kill every in-scope session
|
|
740
|
+
test("yes flag skips the prompt for kill-all and kills every in-scope session", async () => {
|
|
741
|
+
const now = new Date().toISOString();
|
|
742
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
743
|
+
{ name: "session-a", windows: 1, created: now, attached: false, type: "chat" as const, agent: "claude" },
|
|
744
|
+
{ name: "session-b", windows: 1, created: now, attached: false, type: "workflow" as const, agent: "opencode" },
|
|
745
|
+
]);
|
|
746
|
+
const origWrite = process.stdout.write;
|
|
747
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
748
|
+
try {
|
|
749
|
+
const code = await sessionKillCommand(undefined, [], "all", makeDeps(), { yes: true });
|
|
750
|
+
expect(code).toBe(0);
|
|
751
|
+
expect(tmuxMocks.confirm).not.toHaveBeenCalled();
|
|
752
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledTimes(2);
|
|
753
|
+
} finally {
|
|
754
|
+
process.stdout.write = origWrite;
|
|
755
|
+
}
|
|
756
|
+
});
|
|
714
757
|
});
|
|
@@ -277,13 +277,19 @@ export async function sessionPickerCommand(agents: string[] = [], scope: Session
|
|
|
277
277
|
*
|
|
278
278
|
* - If `sessionId` is provided: confirm and kill that one session.
|
|
279
279
|
* - If `sessionId` is omitted: confirm and kill all sessions in scope.
|
|
280
|
+
*
|
|
281
|
+
* Pass `yes: true` (the `-y/--yes` flag on the CLI) to skip the
|
|
282
|
+
* confirmation prompt — useful for orchestrating agents that need to
|
|
283
|
+
* tear down a workflow session non-interactively.
|
|
280
284
|
*/
|
|
281
285
|
export async function sessionKillCommand(
|
|
282
286
|
sessionId: string | undefined,
|
|
283
287
|
agents: string[] = [],
|
|
284
288
|
scope: SessionScope = "all",
|
|
285
289
|
deps: SessionDeps = defaultDeps,
|
|
290
|
+
options: { yes?: boolean } = {},
|
|
286
291
|
): Promise<number> {
|
|
292
|
+
const skipConfirm = options.yes === true;
|
|
287
293
|
const paint = createPainter();
|
|
288
294
|
|
|
289
295
|
if (!deps.isTmuxInstalled()) {
|
|
@@ -318,10 +324,12 @@ export async function sessionKillCommand(
|
|
|
318
324
|
return 1;
|
|
319
325
|
}
|
|
320
326
|
|
|
321
|
-
const answer =
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
327
|
+
const answer = skipConfirm
|
|
328
|
+
? true
|
|
329
|
+
: await deps.confirm({
|
|
330
|
+
message: `Kill session '${sessionId}'?`,
|
|
331
|
+
initialValue: false,
|
|
332
|
+
});
|
|
325
333
|
|
|
326
334
|
if (deps.isCancel(answer)) {
|
|
327
335
|
cancel("Cancelled.");
|
|
@@ -353,10 +361,12 @@ export async function sessionKillCommand(
|
|
|
353
361
|
|
|
354
362
|
const noun = targets.length === 1 ? "session" : "sessions";
|
|
355
363
|
const scopePrefix = scope === "all" ? "" : `${scope} `;
|
|
356
|
-
const answer =
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
364
|
+
const answer = skipConfirm
|
|
365
|
+
? true
|
|
366
|
+
: await deps.confirm({
|
|
367
|
+
message: `Kill all ${targets.length} ${scopePrefix}${noun}?`,
|
|
368
|
+
initialValue: false,
|
|
369
|
+
});
|
|
360
370
|
|
|
361
371
|
if (deps.isCancel(answer)) {
|
|
362
372
|
cancel("Cancelled.");
|