@bastani/atomic 0.5.1 → 0.5.2-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.
Files changed (26) hide show
  1. package/README.md +79 -183
  2. package/package.json +6 -4
  3. package/src/commands/cli/chat/index.ts +2 -2
  4. package/src/commands/cli/workflow.ts +6 -6
  5. package/src/scripts/constants.ts +2 -2
  6. package/src/sdk/components/session-graph-panel.tsx +1 -3
  7. package/src/sdk/runtime/discovery.ts +51 -3
  8. package/src/sdk/runtime/executor-entry.ts +16 -0
  9. package/src/sdk/runtime/executor.ts +31 -17
  10. package/src/sdk/runtime/loader.ts +0 -127
  11. package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/claude/index.ts +1 -1
  12. package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/copilot/index.ts +1 -1
  13. package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/opencode/index.ts +1 -1
  14. package/src/sdk/{workflows.ts → workflows/index.ts} +14 -14
  15. package/src/services/system/auto-sync.ts +0 -2
  16. package/.atomic/workflows/hello/claude/index.ts +0 -40
  17. package/.atomic/workflows/hello/copilot/index.ts +0 -53
  18. package/.atomic/workflows/hello/opencode/index.ts +0 -52
  19. package/.atomic/workflows/hello-parallel/claude/index.ts +0 -73
  20. package/.atomic/workflows/hello-parallel/copilot/index.ts +0 -103
  21. package/.atomic/workflows/hello-parallel/opencode/index.ts +0 -105
  22. package/.atomic/workflows/tsconfig.json +0 -22
  23. package/src/services/system/workflows.ts +0 -240
  24. /package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/helpers/git.ts +0 -0
  25. /package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/helpers/prompts.ts +0 -0
  26. /package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/helpers/review.ts +0 -0
@@ -4,10 +4,14 @@
4
4
  * Architecture:
5
5
  * 1. `executeWorkflow()` is called by the CLI command
6
6
  * 2. It creates a tmux session with an orchestrator pane that runs
7
- * `bun run executor.ts --run <args>`
7
+ * `bun run executor-entry.ts` (a thin wrapper that calls `runOrchestrator()`)
8
8
  * 3. The CLI then attaches to the tmux session (user sees it live)
9
9
  * 4. The orchestrator pane calls `definition.run(workflowCtx)` — the
10
10
  * user's callback uses `ctx.stage()` to spawn agent sessions
11
+ *
12
+ * The entry point is in executor-entry.ts (not this file) to avoid Bun's
13
+ * dual-module-identity issue: Bun evaluates a file twice when it is both
14
+ * the entry point and reached through package.json `exports` self-referencing.
11
15
  */
12
16
 
13
17
  import { join, resolve } from "path";
@@ -275,8 +279,11 @@ export async function executeWorkflow(
275
279
  const sessionsBaseDir = join(getSessionsBaseDir(), workflowRunId);
276
280
  await ensureDir(sessionsBaseDir);
277
281
 
278
- // Write a launcher script for the orchestrator pane
279
- const thisFile = resolve(import.meta.dir, "executor.ts");
282
+ // Write a launcher script for the orchestrator pane.
283
+ // Points to executor-entry.ts (not executor.ts) to avoid Bun's
284
+ // dual-module-identity issue: entry points and package self-references
285
+ // are evaluated as separate module instances in Bun.
286
+ const thisFile = resolve(import.meta.dir, "executor-entry.ts");
280
287
  const isWin = process.platform === "win32";
281
288
  const launcherExt = isWin ? "ps1" : "sh";
282
289
  const launcherPath = join(sessionsBaseDir, `orchestrator.${launcherExt}`);
@@ -291,7 +298,7 @@ export async function executeWorkflow(
291
298
  `$env:ATOMIC_WF_PROMPT = "${escPwsh(Buffer.from(prompt).toString("base64"))}"`,
292
299
  `$env:ATOMIC_WF_FILE = "${escPwsh(workflowFile)}"`,
293
300
  `$env:ATOMIC_WF_CWD = "${escPwsh(projectRoot)}"`,
294
- `bun run "${escPwsh(thisFile)}" --run 2>"${escPwsh(logPath)}"`,
301
+ `bun run "${escPwsh(thisFile)}" 2>"${escPwsh(logPath)}"`,
295
302
  ].join("\n")
296
303
  : [
297
304
  "#!/bin/bash",
@@ -302,7 +309,7 @@ export async function executeWorkflow(
302
309
  `export ATOMIC_WF_PROMPT="${escBash(Buffer.from(prompt).toString("base64"))}"`,
303
310
  `export ATOMIC_WF_FILE="${escBash(workflowFile)}"`,
304
311
  `export ATOMIC_WF_CWD="${escBash(projectRoot)}"`,
305
- `bun run "${escBash(thisFile)}" --run 2>"${escBash(logPath)}"`,
312
+ `bun run "${escBash(thisFile)}" 2>"${escBash(logPath)}"`,
306
313
  ].join("\n");
307
314
 
308
315
  await writeFile(launcherPath, launcherScript, { mode: 0o755 });
@@ -427,6 +434,8 @@ interface SharedRunnerState {
427
434
  activeRegistry: Map<string, ActiveSession>;
428
435
  /** Sessions that completed successfully (for transcript reads). */
429
436
  completedRegistry: Map<string, SessionResult>;
437
+ /** Sessions that already failed before completing successfully. */
438
+ failedRegistry: Set<string>;
430
439
  }
431
440
 
432
441
  /**
@@ -547,7 +556,11 @@ function createSessionRunner(
547
556
  if (!name || name.trim() === "") {
548
557
  throw new Error("Session name is required.");
549
558
  }
550
- if (shared.activeRegistry.has(name) || shared.completedRegistry.has(name)) {
559
+ if (
560
+ shared.activeRegistry.has(name) ||
561
+ shared.completedRegistry.has(name) ||
562
+ shared.failedRegistry.has(name)
563
+ ) {
551
564
  throw new Error(`Duplicate session name: "${name}"`);
552
565
  }
553
566
 
@@ -570,6 +583,7 @@ function createSessionRunner(
570
583
 
571
584
  const sessionId = generateId();
572
585
  let paneId = "";
586
+ let panelSessionAdded = false;
573
587
 
574
588
  try {
575
589
 
@@ -603,6 +617,7 @@ function createSessionRunner(
603
617
 
604
618
  // ── 10. Add node to graph panel ──
605
619
  shared.panel.addSession(name, graphParents);
620
+ panelSessionAdded = true;
606
621
 
607
622
  // ── 11. Claude session snapshot (for identifying new sessions later) ──
608
623
  let knownClaudeSessionIds: Set<string> | undefined;
@@ -794,8 +809,16 @@ function createSessionRunner(
794
809
  graphTracker.onSettle(name);
795
810
  return { name, id: sessionId, result: callbackResult! };
796
811
  } catch (error) {
812
+ const message = error instanceof Error ? error.message : String(error);
813
+ if (panelSessionAdded) {
814
+ shared.panel.sessionError(name, message);
815
+ }
816
+ if (paneId) {
817
+ tmux.killWindow(shared.tmuxSessionName, name);
818
+ }
797
819
  // Ensure the done promise settles and the active entry is cleared.
798
820
  shared.activeRegistry.delete(name);
821
+ shared.failedRegistry.add(name);
799
822
  rejectDone(error);
800
823
  // Update frontier even on failure — if the caller catches and
801
824
  // continues, the next stage should still chain from this one.
@@ -809,7 +832,7 @@ function createSessionRunner(
809
832
  // Orchestrator logic — runs inside a tmux pane
810
833
  // ============================================================================
811
834
 
812
- async function runOrchestrator(): Promise<void> {
835
+ export async function runOrchestrator(): Promise<void> {
813
836
  const requiredEnvVars = [
814
837
  "ATOMIC_WF_ID",
815
838
  "ATOMIC_WF_TMUX",
@@ -868,6 +891,7 @@ async function runOrchestrator(): Promise<void> {
868
891
  panel,
869
892
  activeRegistry: new Map(),
870
893
  completedRegistry: new Map(),
894
+ failedRegistry: new Set(),
871
895
  };
872
896
 
873
897
  try {
@@ -977,13 +1001,3 @@ async function runOrchestrator(): Promise<void> {
977
1001
  }
978
1002
  }
979
1003
 
980
- // ============================================================================
981
- // Direct invocation: `bun run executor.ts --run`
982
- // ============================================================================
983
-
984
- if (process.argv.includes("--run")) {
985
- runOrchestrator().catch((err) => {
986
- console.error("Fatal:", err);
987
- process.exitCode = 1;
988
- });
989
- }
@@ -10,136 +10,12 @@
10
10
  * This module handles everything after a workflow is discovered.
11
11
  */
12
12
 
13
- import { join, dirname } from "path";
14
- import { existsSync } from "fs";
15
13
  import type { WorkflowDefinition, AgentType } from "../types.ts";
16
14
  import type { DiscoveredWorkflow } from "./discovery.ts";
17
15
  import { validateCopilotWorkflow } from "../providers/copilot.ts";
18
16
  import { validateOpenCodeWorkflow } from "../providers/opencode.ts";
19
17
  import { validateClaudeWorkflow } from "../providers/claude.ts";
20
18
 
21
- // ---------------------------------------------------------------------------
22
- // SDK module resolver
23
- // ---------------------------------------------------------------------------
24
-
25
- // Absolute path to the currently-running atomic CLI's own SDK source tree
26
- // (i.e. `<install_root>/src/sdk`). Computed from this file's URL so it always
27
- // points at the actual installed atomic, regardless of how it was launched
28
- // (dev checkout, global `bun install -g @bastani/atomic`, `bunx atomic`, etc).
29
- const ATOMIC_SDK_DIR = Bun.fileURLToPath(new URL("..", import.meta.url));
30
-
31
- // Directory of this loader file. Used as the parent for `Bun.resolveSync`
32
- // when delegating non-`atomic/*` bare specifiers, so workflows resolve them
33
- // against atomic's own `node_modules` tree.
34
- const LOADER_DIR = Bun.fileURLToPath(new URL(".", import.meta.url));
35
-
36
- // Common TypeScript/JavaScript extensions tried for `atomic/<subpath>`
37
- // imports when the specifier doesn't already include one.
38
- const ATOMIC_SUBPATH_EXTS = [".ts", ".tsx", ".js", ".jsx"];
39
-
40
- // Workflow roots for which a Bun.plugin onLoad hook has already been
41
- // registered. Deduped so repeated `load()` calls don't stack plugins.
42
- const registeredRoots = new Set<string>();
43
-
44
- function escapeRegex(s: string): string {
45
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46
- }
47
-
48
- /**
49
- * Resolve `atomic/<subpath>` (or bare `atomic`) to an absolute file path in
50
- * the running CLI's `src/sdk/` tree. Returns `null` if no matching file
51
- * exists so the caller can fall back to the original specifier.
52
- */
53
- function resolveAtomicSubpath(subpath: string): string | null {
54
- const direct = join(ATOMIC_SDK_DIR, subpath);
55
- if (existsSync(direct)) return direct;
56
- for (const ext of ATOMIC_SUBPATH_EXTS) {
57
- const candidate = join(ATOMIC_SDK_DIR, subpath + ext);
58
- if (existsSync(candidate)) return candidate;
59
- }
60
- return null;
61
- }
62
-
63
- /**
64
- * Resolve a bare import specifier to an absolute file path. `atomic/<x>`
65
- * maps onto the SDK source tree; everything else delegates to atomic's own
66
- * module resolution context (so `@github/copilot-sdk`, `zod`, etc. resolve
67
- * from atomic's installed `node_modules`).
68
- */
69
- const ATOMIC_PKG = "@bastani/atomic";
70
- const ATOMIC_PKG_PREFIX = `${ATOMIC_PKG}/`;
71
-
72
- function resolveBareSpecifier(spec: string): string | null {
73
- if (spec === ATOMIC_PKG) return resolveAtomicSubpath("index");
74
- if (spec.startsWith(ATOMIC_PKG_PREFIX)) {
75
- return resolveAtomicSubpath(spec.slice(ATOMIC_PKG_PREFIX.length));
76
- }
77
- try {
78
- return Bun.resolveSync(spec, LOADER_DIR);
79
- } catch {
80
- return null;
81
- }
82
- }
83
-
84
- /**
85
- * Rewrite bare `import`/`from` specifiers in workflow source to absolute
86
- * file paths resolved from atomic's own context. Relative and absolute
87
- * paths are left untouched. Unresolvable specifiers are also left alone so
88
- * Bun produces its standard "module not found" error instead of a silent
89
- * miss.
90
- */
91
- function rewriteBareImports(source: string): string {
92
- // Matches: from "spec" | from 'spec' | import "spec" | import 'spec'
93
- // The backreference (\2) ensures matched quotes are balanced.
94
- const importRe = /(\bfrom\s*|\bimport\s*)(["'])([^"']+)\2/g;
95
- return source.replace(importRe, (match, kw: string, quote: string, spec: string) => {
96
- if (spec.startsWith(".") || spec.startsWith("/")) return match;
97
- const resolved = resolveBareSpecifier(spec);
98
- return resolved ? `${kw}${quote}${resolved}${quote}` : match;
99
- });
100
- }
101
-
102
- /**
103
- * Register a Bun `onLoad` plugin that rewrites bare imports inside any TS
104
- * file under the given workflow root. This lets workflow authors write
105
- * `import { defineWorkflow } from "@bastani/atomic/workflows"` (and import
106
- * atomic's transitive deps like `@github/copilot-sdk`) without maintaining
107
- * their own `package.json` / `node_modules`.
108
- *
109
- * Why source rewriting via `onLoad` instead of `onResolve`?
110
- * Bun's runtime plugin API honors `onLoad` but silently ignores `onResolve`
111
- * hooks for dynamic `await import()` calls — `onResolve` only fires during
112
- * `Bun.build`. Source rewriting is the only mechanism that actually changes
113
- * how the runtime loader resolves imports inside workflow files today.
114
- *
115
- * Each distinct workflow root installs its own plugin (deduped via
116
- * `registeredRoots`). The plugin's filter is a path-prefix regex so we
117
- * don't incur the cost of reading every `.ts` file in the process — only
118
- * files under active workflow roots are intercepted.
119
- */
120
- function installAtomicLoader(workflowRoot: string): void {
121
- if (registeredRoots.has(workflowRoot)) return;
122
- registeredRoots.add(workflowRoot);
123
-
124
- // Match `<workflowRoot>/<...>/*.ts(x)`. Uses a character class for the
125
- // separator so the same filter works on both POSIX and Windows paths.
126
- const filter = new RegExp(
127
- "^" + escapeRegex(workflowRoot) + "[/\\\\].*\\.tsx?$",
128
- );
129
-
130
- Bun.plugin({
131
- name: `atomic-sdk-rewriter:${workflowRoot}`,
132
- setup(build) {
133
- build.onLoad({ filter }, async (args) => {
134
- const source = await Bun.file(args.path).text();
135
- const contents = rewriteBareImports(source);
136
- const loader = args.path.endsWith("x") ? "tsx" : "ts";
137
- return { contents, loader };
138
- });
139
- },
140
- });
141
- }
142
-
143
19
  export namespace WorkflowLoader {
144
20
  // ---------------------------------------------------------------------------
145
21
  // Result types
@@ -281,9 +157,6 @@ export namespace WorkflowLoader {
281
157
  validated: Validated,
282
158
  ): Promise<StageResult<Loaded, "load">> {
283
159
  try {
284
- // The workflow root is two levels up from <root>/<agent>/index.ts,
285
- // which covers agent dirs and any sibling `helpers/` folders.
286
- installAtomicLoader(dirname(dirname(validated.path)));
287
160
  const mod = await import(validated.path);
288
161
  const definition = mod.default ?? mod;
289
162
 
@@ -10,7 +10,7 @@
10
10
  * Run: atomic workflow -n ralph -a claude "<your spec>"
11
11
  */
12
12
 
13
- import { defineWorkflow } from "@bastani/atomic/workflows";
13
+ import { defineWorkflow } from "../../../index.ts";
14
14
 
15
15
  import {
16
16
  buildPlannerPrompt,
@@ -10,7 +10,7 @@
10
10
  * Run: atomic workflow -n ralph -a copilot "<your spec>"
11
11
  */
12
12
 
13
- import { defineWorkflow } from "@bastani/atomic/workflows";
13
+ import { defineWorkflow } from "../../../index.ts";
14
14
  import type { SessionEvent } from "@github/copilot-sdk";
15
15
 
16
16
  import {
@@ -10,7 +10,7 @@
10
10
  * Run: atomic workflow -n ralph -a opencode "<your spec>"
11
11
  */
12
12
 
13
- import { defineWorkflow } from "@bastani/atomic/workflows";
13
+ import { defineWorkflow } from "../../../index.ts";
14
14
 
15
15
  import {
16
16
  buildPlannerPrompt,
@@ -6,7 +6,7 @@
6
6
  * for spawning agent sessions using native TypeScript control flow.
7
7
  */
8
8
 
9
- export { defineWorkflow, WorkflowBuilder } from "./define-workflow.ts";
9
+ export { defineWorkflow, WorkflowBuilder } from "../define-workflow.ts";
10
10
 
11
11
  export type {
12
12
  AgentType,
@@ -33,7 +33,7 @@ export type {
33
33
  ClaudeClientWrapper,
34
34
  ClaudeSessionWrapper,
35
35
  ClaudeQueryDefaults,
36
- } from "./types.ts";
36
+ } from "../types.ts";
37
37
 
38
38
  // Re-export native SDK types for convenience
39
39
  export type { SessionEvent as CopilotSessionEvent } from "@github/copilot-sdk";
@@ -41,14 +41,14 @@ export type { SessionPromptResponse as OpenCodePromptResponse } from "@opencode-
41
41
  export type { SessionMessage as ClaudeSessionMessage } from "@anthropic-ai/claude-agent-sdk";
42
42
 
43
43
  // Providers
44
- export { createClaudeSession, claudeQuery, clearClaudeSession, validateClaudeWorkflow } from "./providers/claude.ts";
45
- export type { ClaudeSessionOptions, ClaudeQueryOptions, ClaudeQueryResult, ClaudeValidationWarning } from "./providers/claude.ts";
44
+ export { createClaudeSession, claudeQuery, clearClaudeSession, validateClaudeWorkflow } from "../providers/claude.ts";
45
+ export type { ClaudeSessionOptions, ClaudeQueryOptions, ClaudeQueryResult, ClaudeValidationWarning } from "../providers/claude.ts";
46
46
 
47
- export { validateCopilotWorkflow } from "./providers/copilot.ts";
48
- export type { CopilotValidationWarning } from "./providers/copilot.ts";
47
+ export { validateCopilotWorkflow } from "../providers/copilot.ts";
48
+ export type { CopilotValidationWarning } from "../providers/copilot.ts";
49
49
 
50
- export { validateOpenCodeWorkflow } from "./providers/opencode.ts";
51
- export type { OpenCodeValidationWarning } from "./providers/opencode.ts";
50
+ export { validateOpenCodeWorkflow } from "../providers/opencode.ts";
51
+ export type { OpenCodeValidationWarning } from "../providers/opencode.ts";
52
52
 
53
53
  // Runtime — tmux utilities
54
54
  export {
@@ -82,7 +82,7 @@ export {
82
82
  paneIsIdle,
83
83
  waitForPaneReady,
84
84
  attemptSubmitRounds,
85
- } from "./runtime/tmux.ts";
85
+ } from "../runtime/tmux.ts";
86
86
 
87
87
  // Runtime — workflow discovery
88
88
  export {
@@ -90,12 +90,12 @@ export {
90
90
  discoverWorkflows,
91
91
  findWorkflow,
92
92
  WORKFLOWS_GITIGNORE,
93
- } from "./runtime/discovery.ts";
94
- export type { DiscoveredWorkflow } from "./runtime/discovery.ts";
93
+ } from "../runtime/discovery.ts";
94
+ export type { DiscoveredWorkflow } from "../runtime/discovery.ts";
95
95
 
96
96
  // Runtime — workflow loader pipeline
97
- export { WorkflowLoader } from "./runtime/loader.ts";
97
+ export { WorkflowLoader } from "../runtime/loader.ts";
98
98
 
99
99
  // Runtime — workflow executor
100
- export { executeWorkflow } from "./runtime/executor.ts";
101
- export type { WorkflowRunOptions } from "./runtime/executor.ts";
100
+ export { executeWorkflow } from "../runtime/executor.ts";
101
+ export type { WorkflowRunOptions } from "../runtime/executor.ts";
@@ -38,7 +38,6 @@ import {
38
38
  upgradeLiteparse,
39
39
  } from "@/lib/spawn.ts";
40
40
  import { installGlobalAgents } from "@/services/system/agents.ts";
41
- import { installGlobalWorkflows } from "@/services/system/workflows.ts";
42
41
  import { installGlobalSkills } from "@/services/system/skills.ts";
43
42
  import { runSteps, printSummary } from "@/services/system/install-ui.ts";
44
43
 
@@ -108,7 +107,6 @@ export async function autoSyncIfStale(): Promise<void> {
108
107
  { label: "@playwright/cli", fn: upgradePlaywrightCli },
109
108
  { label: "@llamaindex/liteparse", fn: upgradeLiteparse },
110
109
  { label: "global agent configs", fn: installGlobalAgents },
111
- { label: "global workflows", fn: installGlobalWorkflows },
112
110
  { label: "global skills", fn: installGlobalSkills },
113
111
  ]);
114
112
 
@@ -1,40 +0,0 @@
1
- /**
2
- * Hello workflow for Claude Code — two-session example.
3
- *
4
- * Claude runs as a full interactive TUI in a tmux pane.
5
- * We automate it via tmux send-keys using the claudeQuery() helper.
6
- * Transcript is extracted via the Claude Agent SDK's getSessionMessages().
7
- *
8
- * Run: atomic workflow -n hello -a claude "describe this project"
9
- */
10
-
11
- import { defineWorkflow } from "@bastani/atomic/workflows";
12
-
13
- export default defineWorkflow<"claude">({
14
- name: "hello",
15
- description: "Two-session Claude demo: describe → summarize",
16
- })
17
- .run(async (ctx) => {
18
- const describe = await ctx.stage(
19
- { name: "describe", description: "Ask Claude to describe the project" },
20
- {},
21
- {},
22
- async (s) => {
23
- await s.session.query(s.userPrompt);
24
- s.save(s.sessionId);
25
- },
26
- );
27
-
28
- await ctx.stage(
29
- { name: "summarize", description: "Summarize the previous session's output" },
30
- {},
31
- {},
32
- async (s) => {
33
- const research = await s.transcript(describe);
34
-
35
- await s.session.query(`Read ${research.path} and summarize it in 2-3 bullet points.`);
36
- s.save(s.sessionId);
37
- },
38
- );
39
- })
40
- .compile();
@@ -1,53 +0,0 @@
1
- /**
2
- * Hello workflow for Copilot — two-session example.
3
- *
4
- * Session 1: Ask the agent to describe the project.
5
- * Session 2: Read session 1's transcript and summarize it.
6
- *
7
- * Run: atomic workflow -n hello -a copilot "describe this project"
8
- */
9
-
10
- import { defineWorkflow } from "@bastani/atomic/workflows";
11
-
12
- /**
13
- * `CopilotSession.sendAndWait` defaults to a 60s timeout and THROWS on
14
- * expiry, which crashes the workflow mid-stage. Override with a generous
15
- * 30-minute budget so legitimate long-running agent work completes.
16
- */
17
- const SEND_TIMEOUT_MS = 30 * 60 * 1000;
18
-
19
- export default defineWorkflow<"copilot">({
20
- name: "hello",
21
- description: "Two-session Copilot demo: describe → summarize",
22
- })
23
- .run(async (ctx) => {
24
- const describe = await ctx.stage(
25
- { name: "describe", description: "Ask the agent to describe the project" },
26
- {},
27
- {},
28
- async (s) => {
29
- await s.session.sendAndWait({ prompt: s.userPrompt }, SEND_TIMEOUT_MS);
30
-
31
- s.save(await s.session.getMessages());
32
- },
33
- );
34
-
35
- await ctx.stage(
36
- { name: "summarize", description: "Summarize the previous session's output" },
37
- {},
38
- {},
39
- async (s) => {
40
- const research = await s.transcript(describe);
41
-
42
- await s.session.sendAndWait(
43
- {
44
- prompt: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
45
- },
46
- SEND_TIMEOUT_MS,
47
- );
48
-
49
- s.save(await s.session.getMessages());
50
- },
51
- );
52
- })
53
- .compile();
@@ -1,52 +0,0 @@
1
- /**
2
- * Hello workflow for OpenCode — two-session example.
3
- *
4
- * Session 1: Ask the agent to describe the project.
5
- * Session 2: Read session 1's transcript and summarize it.
6
- *
7
- * Run: atomic workflow -n hello -a opencode "describe this project"
8
- */
9
-
10
- import { defineWorkflow } from "@bastani/atomic/workflows";
11
-
12
- export default defineWorkflow<"opencode">({
13
- name: "hello",
14
- description: "Two-session OpenCode demo: describe → summarize",
15
- })
16
- .run(async (ctx) => {
17
- const describe = await ctx.stage(
18
- { name: "describe", description: "Ask the agent to describe the project" },
19
- {},
20
- { title: "describe" },
21
- async (s) => {
22
- const result = await s.client.session.prompt({
23
- sessionID: s.session.id,
24
- parts: [{ type: "text", text: s.userPrompt }],
25
- });
26
-
27
- s.save(result.data!);
28
- },
29
- );
30
-
31
- await ctx.stage(
32
- { name: "summarize", description: "Summarize the previous session's output" },
33
- {},
34
- { title: "summarize" },
35
- async (s) => {
36
- const research = await s.transcript(describe);
37
-
38
- const result = await s.client.session.prompt({
39
- sessionID: s.session.id,
40
- parts: [
41
- {
42
- type: "text",
43
- text: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
44
- },
45
- ],
46
- });
47
-
48
- s.save(result.data!);
49
- },
50
- );
51
- })
52
- .compile();
@@ -1,73 +0,0 @@
1
- /**
2
- * Hello-parallel workflow for Claude Code — parallel session example.
3
- *
4
- * Session 1 (sequential): Ask Claude to describe the project.
5
- * Sessions 2+3 (parallel): Two agents summarize session 1 concurrently.
6
- * Session 4 (sequential): Merge both summaries into a final output.
7
- *
8
- * Run: atomic workflow -n hello-parallel -a claude "describe this project"
9
- */
10
-
11
- import { defineWorkflow } from "@bastani/atomic/workflows";
12
-
13
- export default defineWorkflow<"claude">({
14
- name: "hello-parallel",
15
- description: "Parallel Claude demo: describe → [summarize-a, summarize-b] → merge",
16
- })
17
- .run(async (ctx) => {
18
- const describe = await ctx.stage(
19
- { name: "describe", description: "Ask Claude to describe the project" },
20
- {},
21
- {},
22
- async (s) => {
23
- await s.session.query(s.userPrompt);
24
- s.save(s.sessionId);
25
- },
26
- );
27
-
28
- const [summarizeA, summarizeB] = await Promise.all([
29
- ctx.stage(
30
- { name: "summarize-a", description: "Summarize the description as bullet points" },
31
- {},
32
- {},
33
- async (s) => {
34
- const research = await s.transcript(describe);
35
- await s.session.query(`Read ${research.path} and summarize it in 2-3 bullet points.`);
36
- s.save(s.sessionId);
37
- },
38
- ),
39
- ctx.stage(
40
- { name: "summarize-b", description: "Summarize the description as a one-liner" },
41
- {},
42
- {},
43
- async (s) => {
44
- const research = await s.transcript(describe);
45
- await s.session.query(`Read ${research.path} and summarize it in a single sentence.`);
46
- s.save(s.sessionId);
47
- },
48
- ),
49
- ]);
50
-
51
- await ctx.stage(
52
- { name: "merge", description: "Merge both summaries into a final output" },
53
- {},
54
- {},
55
- async (s) => {
56
- const bullets = await s.transcript(summarizeA);
57
- const oneliner = await s.transcript(summarizeB);
58
- await s.session.query(
59
- [
60
- "Combine the following two summaries into one concise paragraph:",
61
- "",
62
- "## Bullet points",
63
- bullets.content,
64
- "",
65
- "## One-liner",
66
- oneliner.content,
67
- ].join("\n"),
68
- );
69
- s.save(s.sessionId);
70
- },
71
- );
72
- })
73
- .compile();