@bastani/atomic 0.5.1 → 0.5.2
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 +79 -183
- package/package.json +6 -4
- package/src/commands/cli/chat/index.ts +2 -2
- package/src/commands/cli/workflow.ts +6 -6
- package/src/scripts/constants.ts +2 -2
- package/src/sdk/components/session-graph-panel.tsx +1 -3
- package/src/sdk/runtime/discovery.ts +51 -3
- package/src/sdk/runtime/executor-entry.ts +16 -0
- package/src/sdk/runtime/executor.ts +31 -17
- package/src/sdk/runtime/loader.ts +0 -127
- package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/claude/index.ts +1 -1
- package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/copilot/index.ts +1 -1
- package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/opencode/index.ts +1 -1
- package/src/sdk/{workflows.ts → workflows/index.ts} +14 -14
- package/src/services/system/auto-sync.ts +0 -2
- package/.atomic/workflows/hello/claude/index.ts +0 -40
- package/.atomic/workflows/hello/copilot/index.ts +0 -53
- package/.atomic/workflows/hello/opencode/index.ts +0 -52
- package/.atomic/workflows/hello-parallel/claude/index.ts +0 -73
- package/.atomic/workflows/hello-parallel/copilot/index.ts +0 -103
- package/.atomic/workflows/hello-parallel/opencode/index.ts +0 -105
- package/.atomic/workflows/tsconfig.json +0 -22
- package/src/services/system/workflows.ts +0 -240
- /package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/helpers/git.ts +0 -0
- /package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/helpers/prompts.ts +0 -0
- /package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/helpers/review.ts +0 -0
package/README.md
CHANGED
|
@@ -123,57 +123,6 @@ Windows PowerShell 7+:
|
|
|
123
123
|
irm https://raw.githubusercontent.com/flora131/atomic/main/install.ps1 | iex
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
<details>
|
|
127
|
-
<summary>Migrating from v0.4.x (Binary) to v0.5.x (npm)?</summary>
|
|
128
|
-
|
|
129
|
-
Atomic has moved from a standalone binary distribution to an **npm package**. The new version gives you the Workflow SDK, 58 skills, and 12 sub-agents as a single installable package.
|
|
130
|
-
|
|
131
|
-
#### Migration Steps
|
|
132
|
-
|
|
133
|
-
**1. Uninstall the old binary:**
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
atomic uninstall
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**2. Remove the old Workflow SDK global package:**
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
bun uninstall -g @bastani/atomic-workflows
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
**3. Delete the old configuration directory:**
|
|
146
|
-
|
|
147
|
-
```bash
|
|
148
|
-
rm -rf ~/.atomic
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
**4. Install the new version:**
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
bun install -g @bastani/atomic
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**5. Re-initialize your project:**
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
cd your-project
|
|
161
|
-
atomic init
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
> On first run after install, Atomic automatically syncs all agent configurations, skills, workflows, and tooling. This replaces the old `atomic update` command — updates now happen lazily on CLI startup when a version mismatch is detected.
|
|
165
|
-
|
|
166
|
-
#### What Changed
|
|
167
|
-
|
|
168
|
-
| Aspect | v0.4.x (Binary) | v0.5.x (npm) |
|
|
169
|
-
| --- | --- | --- |
|
|
170
|
-
| **Distribution** | Pre-compiled binary via `install.sh` | npm package via `bun install -g` |
|
|
171
|
-
| **Updates** | `atomic update` command | Reinstall via `bun install -g @bastani/atomic` + auto-sync on first run |
|
|
172
|
-
| **Uninstall** | `atomic uninstall` | `bun uninstall -g @bastani/atomic` |
|
|
173
|
-
| **Workflow SDK** | Separate `@bastani/atomic-workflows` global package | Bundled with CLI as workspace package |
|
|
174
|
-
| **Config sync** | Manual via install scripts | Automatic on first run after upgrade |
|
|
175
|
-
|
|
176
|
-
</details>
|
|
177
126
|
|
|
178
127
|
### 2. Initialize Your Project
|
|
179
128
|
|
|
@@ -202,56 +151,50 @@ This explores your codebase using sub-agents and generates documentation that gi
|
|
|
202
151
|
|
|
203
152
|
Every team has a process. Atomic lets you encode it as TypeScript — chain agent sessions together, pass transcripts between them, and run the whole thing from the CLI.
|
|
204
153
|
|
|
205
|
-
|
|
154
|
+
Create a workflow project, install the SDK, and add your workflow file:
|
|
206
155
|
|
|
207
156
|
```bash
|
|
208
|
-
|
|
157
|
+
bun init && bun add @bastani/atomic
|
|
158
|
+
mkdir -p .atomic/workflows/my-workflow/claude
|
|
209
159
|
```
|
|
210
160
|
|
|
211
|
-
Here's a workflow that researches a codebase, implements a feature, and reviews the result — three sessions, each in its own context window:
|
|
212
|
-
|
|
213
161
|
```ts
|
|
214
162
|
// .atomic/workflows/my-workflow/claude/index.ts
|
|
215
|
-
import { defineWorkflow
|
|
163
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
216
164
|
|
|
217
|
-
export default defineWorkflow({
|
|
165
|
+
export default defineWorkflow<"claude">({
|
|
218
166
|
name: "my-workflow",
|
|
219
167
|
description: "Research -> Implement -> Review",
|
|
220
168
|
})
|
|
221
169
|
.run(async (ctx) => {
|
|
222
|
-
const research = await ctx.
|
|
223
|
-
{ name: "research", description: "Analyze the codebase
|
|
170
|
+
const research = await ctx.stage(
|
|
171
|
+
{ name: "research", description: "Analyze the codebase" },
|
|
172
|
+
{}, {},
|
|
224
173
|
async (s) => {
|
|
225
|
-
await
|
|
226
|
-
await claudeQuery({
|
|
227
|
-
paneId: s.paneId,
|
|
228
|
-
prompt: `/research-codebase ${s.userPrompt}`,
|
|
229
|
-
});
|
|
174
|
+
await s.session.query(`/research-codebase ${s.userPrompt}`);
|
|
230
175
|
s.save(s.sessionId);
|
|
231
176
|
},
|
|
232
177
|
);
|
|
233
178
|
|
|
234
|
-
|
|
235
|
-
{ name: "implement", description: "Implement
|
|
179
|
+
await ctx.stage(
|
|
180
|
+
{ name: "implement", description: "Implement based on research" },
|
|
181
|
+
{}, {},
|
|
236
182
|
async (s) => {
|
|
237
183
|
const transcript = await s.transcript(research);
|
|
238
|
-
await
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
prompt: `Read ${transcript.path} and implement the changes described. Run tests to verify.`,
|
|
242
|
-
});
|
|
184
|
+
await s.session.query(
|
|
185
|
+
`Read ${transcript.path} and implement the changes. Run tests to verify.`,
|
|
186
|
+
);
|
|
243
187
|
s.save(s.sessionId);
|
|
244
188
|
},
|
|
245
189
|
);
|
|
246
190
|
|
|
247
|
-
await ctx.
|
|
248
|
-
{ name: "review", description: "Review the implementation
|
|
191
|
+
await ctx.stage(
|
|
192
|
+
{ name: "review", description: "Review the implementation" },
|
|
193
|
+
{}, {},
|
|
249
194
|
async (s) => {
|
|
250
|
-
await
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
prompt: "Review all uncommitted changes. Flag any issues with correctness, tests, or style.",
|
|
254
|
-
});
|
|
195
|
+
await s.session.query(
|
|
196
|
+
"Review all uncommitted changes. Flag any issues with correctness, tests, or style.",
|
|
197
|
+
);
|
|
255
198
|
s.save(s.sessionId);
|
|
256
199
|
},
|
|
257
200
|
);
|
|
@@ -259,7 +202,13 @@ export default defineWorkflow({
|
|
|
259
202
|
.compile();
|
|
260
203
|
```
|
|
261
204
|
|
|
262
|
-
|
|
205
|
+
Run it:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
atomic workflow -n my-workflow -a claude "add user avatars to the profile page"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Add a spec phase, parallelize independent sessions, swap in a different agent — the workflow is yours to define. See [Workflow SDK — Build Your Own Harness](#workflow-sdk--build-your-own-harness) for the full API and more examples.
|
|
263
212
|
|
|
264
213
|
> **Want something that works out of the box?** Atomic ships with `ralph`, a built-in workflow that plans, implements, reviews, and debugs autonomously — see [Autonomous Execution (Ralph)](#autonomous-execution-ralph).
|
|
265
214
|
|
|
@@ -283,42 +232,41 @@ Each agent gets its own configuration directory (`.claude/`, `.opencode/`, `.git
|
|
|
283
232
|
|
|
284
233
|
Every team has a process — triage bugs this way, ship features that way, review PRs with these checks. Most of it lives in a wiki nobody reads or in one senior engineer's head. The **Workflow SDK** (`@bastani/atomic/workflows`) lets you encode that process as TypeScript — spawn agent sessions dynamically with native control flow (`for`, `if`, `Promise.all()`), and watch them appear in a live graph as they execute.
|
|
285
234
|
|
|
286
|
-
|
|
235
|
+
Set up a workflow project (`bun init && bun add @bastani/atomic`), create a `.ts` file in `.atomic/workflows/<name>/<agent>/index.ts`, and run it:
|
|
287
236
|
|
|
288
237
|
```bash
|
|
289
|
-
atomic workflow -n
|
|
238
|
+
atomic workflow -n my-workflow -a claude "describe this project"
|
|
290
239
|
```
|
|
291
240
|
|
|
292
241
|
<details>
|
|
293
242
|
<summary>Example: Sequential workflow (describe -> summarize)</summary>
|
|
294
243
|
|
|
295
244
|
```ts
|
|
296
|
-
// .atomic/workflows/
|
|
297
|
-
import { defineWorkflow
|
|
245
|
+
// .atomic/workflows/my-workflow/claude/index.ts
|
|
246
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
298
247
|
|
|
299
|
-
export default defineWorkflow({
|
|
300
|
-
name: "
|
|
301
|
-
description: "Two-session
|
|
248
|
+
export default defineWorkflow<"claude">({
|
|
249
|
+
name: "my-workflow",
|
|
250
|
+
description: "Two-session pipeline: describe -> summarize",
|
|
302
251
|
})
|
|
303
252
|
.run(async (ctx) => {
|
|
304
253
|
const describe = await ctx.stage(
|
|
305
254
|
{ name: "describe", description: "Ask Claude to describe the project" },
|
|
255
|
+
{}, {},
|
|
306
256
|
async (s) => {
|
|
307
|
-
await
|
|
308
|
-
await claudeQuery({ paneId: s.paneId, prompt: s.userPrompt });
|
|
257
|
+
await s.session.query(s.userPrompt);
|
|
309
258
|
s.save(s.sessionId);
|
|
310
259
|
},
|
|
311
260
|
);
|
|
312
261
|
|
|
313
262
|
await ctx.stage(
|
|
314
263
|
{ name: "summarize", description: "Summarize the previous session's output" },
|
|
264
|
+
{}, {},
|
|
315
265
|
async (s) => {
|
|
316
266
|
const research = await s.transcript(describe);
|
|
317
|
-
await
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
prompt: `Read ${research.path} and summarize it in 2-3 bullet points.`,
|
|
321
|
-
});
|
|
267
|
+
await s.session.query(
|
|
268
|
+
`Read ${research.path} and summarize it in 2-3 bullet points.`,
|
|
269
|
+
);
|
|
322
270
|
s.save(s.sessionId);
|
|
323
271
|
},
|
|
324
272
|
);
|
|
@@ -332,71 +280,42 @@ export default defineWorkflow({
|
|
|
332
280
|
<summary>Example: Parallel workflow (describe -> [summarize-a, summarize-b] -> merge)</summary>
|
|
333
281
|
|
|
334
282
|
```ts
|
|
335
|
-
|
|
336
|
-
import { defineWorkflow, createClaudeSession, claudeQuery } from "@bastani/atomic/workflows";
|
|
283
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
337
284
|
|
|
338
|
-
export default defineWorkflow({
|
|
339
|
-
name: "
|
|
340
|
-
description: "
|
|
285
|
+
export default defineWorkflow<"claude">({
|
|
286
|
+
name: "parallel-demo",
|
|
287
|
+
description: "describe -> [summarize-a, summarize-b] -> merge",
|
|
341
288
|
})
|
|
342
289
|
.run(async (ctx) => {
|
|
343
|
-
const describe = await ctx.
|
|
344
|
-
{ name: "describe",
|
|
290
|
+
const describe = await ctx.stage(
|
|
291
|
+
{ name: "describe" }, {}, {},
|
|
345
292
|
async (s) => {
|
|
346
|
-
await
|
|
347
|
-
await claudeQuery({ paneId: s.paneId, prompt: s.userPrompt });
|
|
293
|
+
await s.session.query(s.userPrompt);
|
|
348
294
|
s.save(s.sessionId);
|
|
349
295
|
},
|
|
350
296
|
);
|
|
351
297
|
|
|
352
298
|
const [summarizeA, summarizeB] = await Promise.all([
|
|
353
|
-
ctx.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
},
|
|
364
|
-
),
|
|
365
|
-
ctx.session(
|
|
366
|
-
{ name: "summarize-b", description: "Summarize the description as a one-liner" },
|
|
367
|
-
async (s) => {
|
|
368
|
-
const research = await s.transcript(describe);
|
|
369
|
-
await createClaudeSession({ paneId: s.paneId });
|
|
370
|
-
await claudeQuery({
|
|
371
|
-
paneId: s.paneId,
|
|
372
|
-
prompt: `Read ${research.path} and summarize it in a single sentence.`,
|
|
373
|
-
});
|
|
374
|
-
s.save(s.sessionId);
|
|
375
|
-
},
|
|
376
|
-
),
|
|
299
|
+
ctx.stage({ name: "summarize-a" }, {}, {}, async (s) => {
|
|
300
|
+
const research = await s.transcript(describe);
|
|
301
|
+
await s.session.query(`Read ${research.path} and summarize in 2-3 bullet points.`);
|
|
302
|
+
s.save(s.sessionId);
|
|
303
|
+
}),
|
|
304
|
+
ctx.stage({ name: "summarize-b" }, {}, {}, async (s) => {
|
|
305
|
+
const research = await s.transcript(describe);
|
|
306
|
+
await s.session.query(`Read ${research.path} and summarize in a single sentence.`);
|
|
307
|
+
s.save(s.sessionId);
|
|
308
|
+
}),
|
|
377
309
|
]);
|
|
378
310
|
|
|
379
|
-
await ctx.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
prompt: [
|
|
388
|
-
"Combine the following two summaries into one concise paragraph:",
|
|
389
|
-
"",
|
|
390
|
-
"## Bullet points",
|
|
391
|
-
bullets.content,
|
|
392
|
-
"",
|
|
393
|
-
"## One-liner",
|
|
394
|
-
oneliner.content,
|
|
395
|
-
].join("\n"),
|
|
396
|
-
});
|
|
397
|
-
s.save(s.sessionId);
|
|
398
|
-
},
|
|
399
|
-
);
|
|
311
|
+
await ctx.stage({ name: "merge" }, {}, {}, async (s) => {
|
|
312
|
+
const bullets = await s.transcript(summarizeA);
|
|
313
|
+
const oneliner = await s.transcript(summarizeB);
|
|
314
|
+
await s.session.query(
|
|
315
|
+
`Combine:\n\n## Bullets\n${bullets.content}\n\n## One-liner\n${oneliner.content}`,
|
|
316
|
+
);
|
|
317
|
+
s.save(s.sessionId);
|
|
318
|
+
});
|
|
400
319
|
})
|
|
401
320
|
.compile();
|
|
402
321
|
```
|
|
@@ -428,7 +347,7 @@ Workflows are deterministic by design — the same definition always produces th
|
|
|
428
347
|
|
|
429
348
|
This means you can run the same workflow on different machines, different agents, or at different times and get structurally identical execution — same steps, same data flow, same ordering. The only variance comes from the LLM's responses, not from the harness.
|
|
430
349
|
|
|
431
|
-
|
|
350
|
+
Set up a project (`bun init && bun add @bastani/atomic`), drop a `.ts` file in `.atomic/workflows/<name>/<agent>/index.ts`, and run it. You can also ask Atomic to create workflows for you:
|
|
432
351
|
|
|
433
352
|
```
|
|
434
353
|
Use your workflow-creator skill to create a workflow that plans, implements, and reviews a feature.
|
|
@@ -451,7 +370,7 @@ Use your workflow-creator skill to create a workflow that plans, implements, and
|
|
|
451
370
|
| ----------------------- | ------------------------- | -------------------------------------------------------------- |
|
|
452
371
|
| `ctx.userPrompt` | `string` | Original user prompt from the CLI invocation |
|
|
453
372
|
| `ctx.agent` | `AgentType` | Which agent is running (`"claude"`, `"copilot"`, `"opencode"`) |
|
|
454
|
-
| `ctx.stage(opts, fn)` | `Promise<SessionHandle<T>>` | Spawn a session — returns handle with `name`, `id`, `result` |
|
|
373
|
+
| `ctx.stage(opts, clientOpts, sessionOpts, fn)` | `Promise<SessionHandle<T>>` | Spawn a session — returns handle with `name`, `id`, `result` |
|
|
455
374
|
| `ctx.transcript(ref)` | `Promise<Transcript>` | Get a completed session's transcript (`{ path, content }`) |
|
|
456
375
|
| `ctx.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages |
|
|
457
376
|
|
|
@@ -459,7 +378,8 @@ Use your workflow-creator skill to create a workflow that plans, implements, and
|
|
|
459
378
|
|
|
460
379
|
| Property | Type | Description |
|
|
461
380
|
| ----------------------- | ------------------------- | -------------------------------------------------------------- |
|
|
462
|
-
| `s.
|
|
381
|
+
| `s.client` | `ProviderClient<A>` | Pre-created SDK client (auto-managed by runtime) |
|
|
382
|
+
| `s.session` | `ProviderSession<A>` | Pre-created provider session (auto-managed by runtime) |
|
|
463
383
|
| `s.userPrompt` | `string` | Original user prompt from the CLI invocation |
|
|
464
384
|
| `s.agent` | `AgentType` | Which agent is running |
|
|
465
385
|
| `s.paneId` | `string` | tmux pane ID for this session |
|
|
@@ -468,7 +388,7 @@ Use your workflow-creator skill to create a workflow that plans, implements, and
|
|
|
468
388
|
| `s.save(messages)` | `SaveTranscript` | Save this session's output for subsequent sessions |
|
|
469
389
|
| `s.transcript(ref)` | `Promise<Transcript>` | Get a completed session's transcript |
|
|
470
390
|
| `s.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages |
|
|
471
|
-
| `s.stage(opts, fn)`
|
|
391
|
+
| `s.stage(opts, clientOpts, sessionOpts, fn)` | `Promise<SessionHandle<T>>` | Spawn a nested sub-session (child in the graph) |
|
|
472
392
|
|
|
473
393
|
#### Session Options (`SessionRunOptions`)
|
|
474
394
|
|
|
@@ -489,38 +409,15 @@ Each provider saves transcripts differently:
|
|
|
489
409
|
| **Copilot** | `s.save(await session.getMessages())` — pass `SessionEvent[]` |
|
|
490
410
|
| **OpenCode** | `s.save(result.data!)` — pass the full `{ info, parts }` response |
|
|
491
411
|
|
|
492
|
-
####
|
|
493
|
-
|
|
494
|
-
| Export | Purpose |
|
|
495
|
-
| --------------------------------- | --------------------------------------------------- |
|
|
496
|
-
| `createClaudeSession(options)` | Start a Claude TUI in a tmux pane |
|
|
497
|
-
| `claudeQuery(options)` | Send a prompt to Claude and wait for the response |
|
|
498
|
-
| `clearClaudeSession(paneId)` | Free memory for a killed/finished Claude session |
|
|
499
|
-
| `validateClaudeWorkflow()` | Validate a Claude workflow source before run |
|
|
500
|
-
| `validateCopilotWorkflow()` | Validate a Copilot workflow source before run |
|
|
501
|
-
| `validateOpenCodeWorkflow()` | Validate an OpenCode workflow source before run |
|
|
502
|
-
|
|
503
|
-
`createClaudeSession` accepts:
|
|
412
|
+
#### Per-Agent Session APIs
|
|
504
413
|
|
|
505
|
-
|
|
506
|
-
| ----------------- | ---------- | ----------------------------------------------------- | ---------------------------------- |
|
|
507
|
-
| `paneId` | `string` | — | tmux pane ID (required) |
|
|
508
|
-
| `chatFlags` | `string[]` | `["--dangerously-skip-permissions"]` | CLI flags passed to `claude` |
|
|
509
|
-
| `readyTimeoutMs` | `number` | `30000` | Timeout waiting for TUI readiness |
|
|
414
|
+
The runtime auto-creates `s.client` and `s.session` — use them directly inside the callback:
|
|
510
415
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
|
514
|
-
|
|
|
515
|
-
|
|
|
516
|
-
| `prompt` | `string` | — | The prompt to send (required) |
|
|
517
|
-
| `timeoutMs` | `number` | `300000` | Response timeout (5 min) |
|
|
518
|
-
| `pollIntervalMs` | `number` | `2000` | Polling interval for output stabilization |
|
|
519
|
-
| `submitPresses` | `number` | `1` | C-m presses per submit round |
|
|
520
|
-
| `maxSubmitRounds` | `number` | `6` | Max retry rounds for delivery confirmation |
|
|
521
|
-
| `readyTimeoutMs` | `number` | `30000` | Pane readiness timeout before sending |
|
|
522
|
-
|
|
523
|
-
Returns `{ output: string; delivered: boolean }` — `delivered` confirms the prompt was accepted by the agent.
|
|
416
|
+
| Agent | How to send a prompt |
|
|
417
|
+
| ----- | -------------------- |
|
|
418
|
+
| **Claude** | `await s.session.query(prompt)` |
|
|
419
|
+
| **Copilot** | `await s.session.sendAndWait({ prompt }, TIMEOUT_MS)` — explicit timeout required (default 60s throws) |
|
|
420
|
+
| **OpenCode** | `await s.client.session.prompt({ sessionID: s.session.id, parts: [{ type: "text", text: prompt }] })` |
|
|
524
421
|
|
|
525
422
|
#### Key Rules
|
|
526
423
|
|
|
@@ -529,8 +426,7 @@ Returns `{ output: string; delivered: boolean }` — `delivered` confirms the pr
|
|
|
529
426
|
3. `transcript()` / `getMessages()` only access completed sessions (callback returned + saves flushed)
|
|
530
427
|
4. Each session runs in its own tmux window with the chosen agent
|
|
531
428
|
5. Workflows are organized per-workflow: `.atomic/workflows/<name>/<agent>/index.ts`
|
|
532
|
-
|
|
533
|
-
Workflow files need no `package.json` or `node_modules` of their own — the Atomic loader rewrites `@bastani/atomic/*` and atomic's transitive deps (`@github/copilot-sdk`, `@opencode-ai/sdk`, `@anthropic-ai/claude-agent-sdk`, `zod`, etc.) to absolute paths inside the installed atomic package at load time. Drop a `.ts` file and it runs.
|
|
429
|
+
6. Set up your workflow project with `bun init && bun add @bastani/atomic` — standard module resolution handles imports
|
|
534
430
|
|
|
535
431
|
For the authoring walkthrough with worked examples, ask Atomic to use the `workflow-creator` skill or read the skill reference at `.agents/skills/workflow-creator/`.
|
|
536
432
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Configuration management CLI and SDK for coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
},
|
|
28
28
|
"exports": {
|
|
29
29
|
".": "./src/sdk/index.ts",
|
|
30
|
-
"./workflows": "./src/sdk/workflows.ts"
|
|
30
|
+
"./workflows": "./src/sdk/workflows/index.ts",
|
|
31
|
+
"./workflows/builtin/ralph/claude": "./src/sdk/workflows/builtin/ralph/claude/index.ts",
|
|
32
|
+
"./workflows/builtin/ralph/copilot": "./src/sdk/workflows/builtin/ralph/copilot/index.ts",
|
|
33
|
+
"./workflows/builtin/ralph/opencode": "./src/sdk/workflows/builtin/ralph/opencode/index.ts"
|
|
31
34
|
},
|
|
32
35
|
"files": [
|
|
33
36
|
"src",
|
|
@@ -36,8 +39,7 @@
|
|
|
36
39
|
".claude/agents",
|
|
37
40
|
".opencode/agents",
|
|
38
41
|
".github/agents",
|
|
39
|
-
".github/lsp.json"
|
|
40
|
-
".atomic/workflows"
|
|
42
|
+
".github/lsp.json"
|
|
41
43
|
],
|
|
42
44
|
"scripts": {
|
|
43
45
|
"dev": "bun run src/cli.ts",
|
|
@@ -27,12 +27,12 @@ import {
|
|
|
27
27
|
isInsideTmux,
|
|
28
28
|
isTmuxInstalled,
|
|
29
29
|
resetMuxBinaryCache,
|
|
30
|
-
} from "@/sdk/workflows.ts";
|
|
30
|
+
} from "@/sdk/workflows/index.ts";
|
|
31
31
|
import {
|
|
32
32
|
createSession,
|
|
33
33
|
killSession,
|
|
34
34
|
getMuxBinary,
|
|
35
|
-
} from "@/sdk/workflows.ts";
|
|
35
|
+
} from "@/sdk/workflows/index.ts";
|
|
36
36
|
import { ensureTmuxInstalled } from "@/lib/spawn.ts";
|
|
37
37
|
|
|
38
38
|
// ============================================================================
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
executeWorkflow,
|
|
18
18
|
WorkflowLoader,
|
|
19
19
|
resetMuxBinaryCache,
|
|
20
|
-
} from "@/sdk/workflows.ts";
|
|
21
|
-
import type { AgentType, DiscoveredWorkflow } from "@/sdk/workflows.ts";
|
|
20
|
+
} from "@/sdk/workflows/index.ts";
|
|
21
|
+
import type { AgentType, DiscoveredWorkflow } from "@/sdk/workflows/index.ts";
|
|
22
22
|
|
|
23
23
|
export async function workflowCommand(options: {
|
|
24
24
|
name?: string;
|
|
@@ -117,9 +117,7 @@ export async function workflowCommand(options: {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
// Load workflow through the pipeline: resolve → validate → load.
|
|
120
|
-
//
|
|
121
|
-
// atomic's installed deps onto the running CLI's own module graph, so
|
|
122
|
-
// workflow files don't need their own `package.json` / `node_modules`.
|
|
120
|
+
// External workflows must have `@bastani/atomic` installed as a dependency.
|
|
123
121
|
const result = await WorkflowLoader.loadWorkflow(discovered, {
|
|
124
122
|
warn(warnings) {
|
|
125
123
|
for (const w of warnings) {
|
|
@@ -185,16 +183,18 @@ const AGENT_DISPLAY_NAMES: Record<AgentType, string> = {
|
|
|
185
183
|
copilot: "Copilot CLI",
|
|
186
184
|
};
|
|
187
185
|
/** Local first — project-scoped workflows are the most immediately relevant. */
|
|
188
|
-
const SOURCE_ORDER: readonly DiscoveredWorkflow["source"][] = ["local", "global"];
|
|
186
|
+
const SOURCE_ORDER: readonly DiscoveredWorkflow["source"][] = ["local", "global", "builtin"];
|
|
189
187
|
/** Friendly directory labels shown inline with each section heading. */
|
|
190
188
|
const SOURCE_DIRS: Record<DiscoveredWorkflow["source"], string> = {
|
|
191
189
|
local: ".atomic/workflows",
|
|
192
190
|
global: "~/.atomic/workflows",
|
|
191
|
+
builtin: "built-in",
|
|
193
192
|
};
|
|
194
193
|
/** Section heading colour per source — preserves the source-type semantic. */
|
|
195
194
|
const SOURCE_COLORS: Record<DiscoveredWorkflow["source"], PaletteKey> = {
|
|
196
195
|
local: "success",
|
|
197
196
|
global: "mauve",
|
|
197
|
+
builtin: "accent",
|
|
198
198
|
};
|
|
199
199
|
|
|
200
200
|
/**
|
package/src/scripts/constants.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* change propagates everywhere.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { AGENTS } from "@/sdk/workflows.ts";
|
|
9
|
-
import type { AgentType } from "@/sdk/workflows.ts";
|
|
8
|
+
import { AGENTS } from "@/sdk/workflows/index.ts";
|
|
9
|
+
import type { AgentType } from "@/sdk/workflows/index.ts";
|
|
10
10
|
|
|
11
11
|
export {
|
|
12
12
|
SDK_PACKAGE_NAME,
|
|
@@ -127,9 +127,7 @@ export function SessionGraphPanel() {
|
|
|
127
127
|
setAttachMsg(`\u2192 ${n.name}`);
|
|
128
128
|
attachTimerRef.current = setTimeout(() => setAttachMsg(""), ATTACH_MSG_DISPLAY_MS);
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
tmuxRun(["select-window", "-t", `${tmuxSession}:${n.name}`]);
|
|
132
|
-
} catch {}
|
|
130
|
+
tmuxRun(["switch-client", "-t", `${tmuxSession}:${n.name}`]);
|
|
133
131
|
},
|
|
134
132
|
[layout.map, tmuxSession, store.sessions],
|
|
135
133
|
);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { join } from "path";
|
|
12
12
|
import { readdir, writeFile } from "fs/promises";
|
|
13
|
+
import { existsSync, readdirSync } from "fs";
|
|
13
14
|
import { homedir } from "os";
|
|
14
15
|
import ignore from "ignore";
|
|
15
16
|
import type { AgentType } from "../types.ts";
|
|
@@ -18,7 +19,7 @@ export interface DiscoveredWorkflow {
|
|
|
18
19
|
name: string;
|
|
19
20
|
agent: AgentType;
|
|
20
21
|
path: string;
|
|
21
|
-
source: "local" | "global";
|
|
22
|
+
source: "local" | "global" | "builtin";
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
function getLocalWorkflowsDir(projectRoot: string): string {
|
|
@@ -131,8 +132,50 @@ async function discoverFromBaseDir(
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
135
|
+
* Absolute path to the `builtin/` directory inside the SDK source tree.
|
|
136
|
+
* Computed from this file's URL so it always resolves correctly regardless
|
|
137
|
+
* of how the package was installed (dev checkout, global, bunx, etc.).
|
|
138
|
+
*/
|
|
139
|
+
const BUILTIN_WORKFLOWS_DIR = join(
|
|
140
|
+
Bun.fileURLToPath(new URL("../workflows/builtin", import.meta.url)),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Discover built-in workflows shipped as SDK modules.
|
|
145
|
+
*
|
|
146
|
+
* Scans `src/sdk/workflows/builtin/<name>/<agent>/index.ts` for known
|
|
147
|
+
* workflow directories. Returns entries with `source: "builtin"`.
|
|
148
|
+
*/
|
|
149
|
+
function discoverBuiltinWorkflows(
|
|
150
|
+
agentFilter?: AgentType,
|
|
151
|
+
): DiscoveredWorkflow[] {
|
|
152
|
+
const results: DiscoveredWorkflow[] = [];
|
|
153
|
+
const agents = agentFilter ? [agentFilter] : AGENTS;
|
|
154
|
+
|
|
155
|
+
let workflowNames: string[];
|
|
156
|
+
try {
|
|
157
|
+
workflowNames = readdirSync(BUILTIN_WORKFLOWS_DIR, { withFileTypes: true })
|
|
158
|
+
.filter((d) => d.isDirectory())
|
|
159
|
+
.map((d) => d.name);
|
|
160
|
+
} catch {
|
|
161
|
+
return results;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const name of workflowNames) {
|
|
165
|
+
for (const agent of agents) {
|
|
166
|
+
const indexPath = join(BUILTIN_WORKFLOWS_DIR, name, agent, "index.ts");
|
|
167
|
+
if (existsSync(indexPath)) {
|
|
168
|
+
results.push({ name, agent, path: indexPath, source: "builtin" });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return results;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Discover all available workflows from built-in, global, and local sources.
|
|
178
|
+
* Optionally filter by agent. Precedence: local > global > builtin.
|
|
136
179
|
*/
|
|
137
180
|
export async function discoverWorkflows(
|
|
138
181
|
projectRoot: string = process.cwd(),
|
|
@@ -141,12 +184,17 @@ export async function discoverWorkflows(
|
|
|
141
184
|
const localDir = getLocalWorkflowsDir(projectRoot);
|
|
142
185
|
const globalDir = getGlobalWorkflowsDir();
|
|
143
186
|
|
|
187
|
+
const builtinResults = discoverBuiltinWorkflows(agentFilter);
|
|
144
188
|
const [globalResults, localResults] = await Promise.all([
|
|
145
189
|
discoverFromBaseDir(globalDir, "global", agentFilter),
|
|
146
190
|
discoverFromBaseDir(localDir, "local", agentFilter),
|
|
147
191
|
]);
|
|
148
192
|
|
|
193
|
+
// Merge with precedence: builtin (lowest) → global → local (highest)
|
|
149
194
|
const byKey = new Map<string, DiscoveredWorkflow>();
|
|
195
|
+
for (const wf of builtinResults) {
|
|
196
|
+
byKey.set(`${wf.agent}/${wf.name}`, wf);
|
|
197
|
+
}
|
|
150
198
|
for (const wf of globalResults) {
|
|
151
199
|
byKey.set(`${wf.agent}/${wf.name}`, wf);
|
|
152
200
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator entry point — invoked inside a tmux pane by the launcher script.
|
|
3
|
+
*
|
|
4
|
+
* Separated from executor.ts to avoid the dual-module-identity problem:
|
|
5
|
+
* Bun evaluates a file twice when it is both the entry point (`bun run`)
|
|
6
|
+
* and reached through package.json `exports` self-referencing. Keeping
|
|
7
|
+
* the side-effectful `--run` guard here ensures executor.ts stays a pure
|
|
8
|
+
* library module that can be safely re-exported from the SDK barrel.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { runOrchestrator } from "./executor.ts";
|
|
12
|
+
|
|
13
|
+
runOrchestrator().catch((err) => {
|
|
14
|
+
console.error("Fatal:", err);
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
});
|