@bastani/atomic 0.5.0-3 → 0.5.0-4

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 (40) hide show
  1. package/.atomic/workflows/hello/claude/index.ts +22 -25
  2. package/.atomic/workflows/hello/copilot/index.ts +41 -31
  3. package/.atomic/workflows/hello/opencode/index.ts +40 -40
  4. package/.atomic/workflows/hello-parallel/claude/index.ts +54 -54
  5. package/.atomic/workflows/hello-parallel/copilot/index.ts +89 -70
  6. package/.atomic/workflows/hello-parallel/opencode/index.ts +77 -77
  7. package/.atomic/workflows/ralph/claude/index.ts +128 -93
  8. package/.atomic/workflows/ralph/copilot/index.ts +212 -112
  9. package/.atomic/workflows/ralph/helpers/prompts.ts +45 -2
  10. package/.atomic/workflows/ralph/opencode/index.ts +174 -111
  11. package/README.md +62 -53
  12. package/package.json +1 -1
  13. package/src/commands/cli/chat/index.ts +28 -8
  14. package/src/commands/cli/init/index.ts +6 -4
  15. package/src/commands/cli/init/scm.ts +27 -10
  16. package/src/sdk/components/connectors.test.ts +45 -0
  17. package/src/sdk/components/layout.test.ts +321 -0
  18. package/src/sdk/components/layout.ts +51 -15
  19. package/src/sdk/components/orchestrator-panel-store.test.ts +156 -0
  20. package/src/sdk/components/orchestrator-panel-store.ts +24 -0
  21. package/src/sdk/components/orchestrator-panel.tsx +21 -0
  22. package/src/sdk/components/session-graph-panel.tsx +3 -9
  23. package/src/sdk/components/statusline.tsx +4 -6
  24. package/src/sdk/define-workflow.test.ts +71 -0
  25. package/src/sdk/define-workflow.ts +42 -39
  26. package/src/sdk/errors.ts +1 -1
  27. package/src/sdk/index.ts +4 -1
  28. package/src/sdk/providers/claude.ts +1 -1
  29. package/src/sdk/providers/copilot.ts +5 -3
  30. package/src/sdk/providers/opencode.ts +5 -3
  31. package/src/sdk/runtime/executor.ts +512 -301
  32. package/src/sdk/runtime/loader.ts +2 -2
  33. package/src/sdk/runtime/tmux.ts +31 -2
  34. package/src/sdk/types.ts +93 -20
  35. package/src/sdk/workflows.ts +7 -4
  36. package/src/services/config/definitions.ts +39 -2
  37. package/src/services/config/settings.ts +0 -6
  38. package/src/services/system/skills.ts +3 -7
  39. package/.atomic/workflows/package-lock.json +0 -31
  40. package/.atomic/workflows/package.json +0 -8
@@ -297,7 +297,7 @@ export namespace WorkflowLoader {
297
297
  `Workflow at ${validated.path} was defined but not compiled.\n` +
298
298
  ` Add .compile() at the end of your defineWorkflow() chain:\n\n` +
299
299
  ` export default defineWorkflow({ ... })\n` +
300
- ` .session({ ... })\n` +
300
+ ` .run(async (ctx) => { ... })\n` +
301
301
  ` .compile();`,
302
302
  };
303
303
  }
@@ -308,7 +308,7 @@ export namespace WorkflowLoader {
308
308
  error: new Error("Invalid workflow export"),
309
309
  message:
310
310
  `${validated.path} does not export a valid WorkflowDefinition.\n` +
311
- ` Make sure it exports defineWorkflow(...).compile() as the default export.`,
311
+ ` Make sure it exports defineWorkflow(...).run(...).compile() as the default export.`,
312
312
  };
313
313
  }
314
314
 
@@ -110,6 +110,19 @@ function tmuxExec(args: string[]): void {
110
110
  // Session and pane management
111
111
  // ---------------------------------------------------------------------------
112
112
 
113
+ /**
114
+ * Build `-e KEY=VALUE` argument pairs for tmux environment flags.
115
+ * Supported by tmux new-session/new-window since tmux 3.2.
116
+ */
117
+ function buildEnvArgs(envVars?: Record<string, string>): string[] {
118
+ if (!envVars) return [];
119
+ const args: string[] = [];
120
+ for (const [key, value] of Object.entries(envVars)) {
121
+ args.push("-e", `${key}=${value}`);
122
+ }
123
+ return args;
124
+ }
125
+
113
126
  /**
114
127
  * Create a new tmux session with the given name.
115
128
  * The session starts detached with an initial command in the first pane.
@@ -118,14 +131,22 @@ function tmuxExec(args: string[]): void {
118
131
  * @param initialCommand - Shell command to run in the initial pane
119
132
  * @param windowName - Optional name for the initial window
120
133
  * @param cwd - Optional working directory for the initial pane
134
+ * @param envVars - Optional environment variables for the initial pane
121
135
  * @returns The pane ID of the initial pane (e.g., "%0")
122
136
  */
123
- export function createSession(sessionName: string, initialCommand: string, windowName?: string, cwd?: string): string {
137
+ export function createSession(
138
+ sessionName: string,
139
+ initialCommand: string,
140
+ windowName?: string,
141
+ cwd?: string,
142
+ envVars?: Record<string, string>,
143
+ ): string {
124
144
  const args = [
125
145
  "new-session",
126
146
  "-d",
127
147
  "-s", sessionName,
128
148
  "-P", "-F", "#{pane_id}",
149
+ ...buildEnvArgs(envVars),
129
150
  ];
130
151
  if (windowName) {
131
152
  args.push("-n", windowName);
@@ -145,15 +166,23 @@ export function createSession(sessionName: string, initialCommand: string, windo
145
166
  * @param windowName - Name for the new window
146
167
  * @param command - Shell command to run in the new window
147
168
  * @param cwd - Optional working directory for the new window
169
+ * @param envVars - Optional environment variables for the new window
148
170
  * @returns The pane ID of the new window's pane
149
171
  */
150
- export function createWindow(sessionName: string, windowName: string, command: string, cwd?: string): string {
172
+ export function createWindow(
173
+ sessionName: string,
174
+ windowName: string,
175
+ command: string,
176
+ cwd?: string,
177
+ envVars?: Record<string, string>,
178
+ ): string {
151
179
  const args = [
152
180
  "new-window",
153
181
  "-d",
154
182
  "-t", sessionName,
155
183
  "-n", windowName,
156
184
  "-P", "-F", "#{pane_id}",
185
+ ...buildEnvArgs(envVars),
157
186
  ];
158
187
  if (cwd) {
159
188
  args.push("-c", cwd);
package/src/sdk/types.ts CHANGED
@@ -34,9 +34,9 @@ export type SavedMessage =
34
34
  /**
35
35
  * Save native message objects from the provider SDK.
36
36
  *
37
- * - **Copilot**: `ctx.save(await session.getMessages())`
38
- * - **OpenCode**: `ctx.save(result.data)` — the full `{ info, parts }` response
39
- * - **Claude**: `ctx.save(sessionId)` — auto-reads via `getSessionMessages()`
37
+ * - **Copilot**: `s.save(await session.getMessages())`
38
+ * - **OpenCode**: `s.save(result.data)` — the full `{ info, parts }` response
39
+ * - **Claude**: `s.save(sessionId)` — auto-reads via `getSessionMessages()`
40
40
  */
41
41
  export interface SaveTranscript {
42
42
  /** Save Copilot SessionEvent[] from session.getMessages() */
@@ -47,8 +47,54 @@ export interface SaveTranscript {
47
47
  (claudeSessionId: string): Promise<void>;
48
48
  }
49
49
 
50
+ /** A reference to a completed session — either a handle or a session name string. */
51
+ export type SessionRef = string | SessionHandle<unknown>;
52
+
53
+ /**
54
+ * Handle returned by `ctx.session()`. Used for type-safe transcript references
55
+ * and carries the callback's return value.
56
+ */
57
+ export interface SessionHandle<T = void> {
58
+ /** The session's unique name */
59
+ readonly name: string;
60
+ /** The session's generated UUID */
61
+ readonly id: string;
62
+ /** The value returned by the session callback */
63
+ readonly result: T;
64
+ }
65
+
66
+ /**
67
+ * Options for spawning a session via `ctx.session()`.
68
+ */
69
+ export interface SessionRunOptions {
70
+ /** Unique name for this session (used for transcript references and graph display) */
71
+ name: string;
72
+ /** Human-readable description */
73
+ description?: string;
74
+ /**
75
+ * Names of sessions this one depends on. Serves two purposes:
76
+ *
77
+ * 1. **Graph rendering** — each named session becomes a parent edge in the
78
+ * graph, so chains and fan-ins show up as real topology instead of
79
+ * sibling-under-root.
80
+ * 2. **Runtime ordering** — at spawn time, the runtime waits for every
81
+ * named dep to finish before starting. This makes dependency-driven
82
+ * `Promise.all([...])` patterns safe: you can kick off many sessions
83
+ * concurrently and let `dependsOn` serialize only the edges that matter.
84
+ *
85
+ * Each name must refer to a session that has already been spawned (either
86
+ * active or completed) at the time the dependent session is created.
87
+ * Unknown names throw a clear error.
88
+ *
89
+ * When omitted, the session falls back to the default parent (the
90
+ * enclosing `ctx.session()` scope, or `orchestrator` at the top level).
91
+ */
92
+ dependsOn?: string[];
93
+ }
94
+
50
95
  /**
51
- * Session context provided to each session's run() callback at execution time.
96
+ * Context provided to each session's callback.
97
+ * Created by `ctx.session(opts, fn)` — the callback receives this as its argument.
52
98
  */
53
99
  export interface SessionContext {
54
100
  /** The agent's server URL (Copilot --ui-server / OpenCode built-in server) */
@@ -58,15 +104,15 @@ export interface SessionContext {
58
104
  /** Which agent is running */
59
105
  agent: AgentType;
60
106
  /**
61
- * Get a previous session's transcript as rendered text.
62
- * Returns `{ path, content }` path for file triggers, content for embedding.
107
+ * Get a completed session's transcript as rendered text.
108
+ * Accepts a SessionHandle (recommended) or session name string.
63
109
  */
64
- transcript(sessionName: string): Promise<Transcript>;
110
+ transcript(ref: SessionRef): Promise<Transcript>;
65
111
  /**
66
- * Get a previous session's raw native messages.
67
- * Returns SavedMessage[] exactly as stored by ctx.save().
112
+ * Get a completed session's raw native messages.
113
+ * Accepts a SessionHandle (recommended) or session name string.
68
114
  */
69
- getMessages(sessionName: string): Promise<SavedMessage[]>;
115
+ getMessages(ref: SessionRef): Promise<SavedMessage[]>;
70
116
  /**
71
117
  * Save this session's output for subsequent sessions.
72
118
  * Accepts native SDK message objects only.
@@ -78,18 +124,45 @@ export interface SessionContext {
78
124
  paneId: string;
79
125
  /** Session UUID */
80
126
  sessionId: string;
127
+ /**
128
+ * Spawn a nested sub-session with its own tmux window and graph node.
129
+ * The sub-session is a child of this session in the graph.
130
+ * The callback's return value is available as `handle.result`.
131
+ */
132
+ session<T = void>(
133
+ options: SessionRunOptions,
134
+ run: (ctx: SessionContext) => Promise<T>,
135
+ ): Promise<SessionHandle<T>>;
81
136
  }
82
137
 
83
138
  /**
84
- * Options for defining a session in a workflow.
139
+ * Top-level context provided to the workflow's `.run()` callback.
140
+ * Does not have session-specific fields (serverUrl, paneId, save, etc.).
85
141
  */
86
- export interface SessionOptions {
87
- /** Unique name for this session (used for transcript references) */
88
- name: string;
89
- /** Human-readable description */
90
- description?: string;
91
- /** The session callback. User writes raw provider-specific SDK code here. */
92
- run: (ctx: SessionContext) => Promise<void>;
142
+ export interface WorkflowContext {
143
+ /** The original user prompt from the CLI invocation */
144
+ userPrompt: string;
145
+ /** Which agent is running */
146
+ agent: AgentType;
147
+ /**
148
+ * Spawn a session with its own tmux window and graph node.
149
+ * The runtime manages the full lifecycle: start → run callback → complete/error.
150
+ * The callback's return value is available as `handle.result`.
151
+ */
152
+ session<T = void>(
153
+ options: SessionRunOptions,
154
+ run: (ctx: SessionContext) => Promise<T>,
155
+ ): Promise<SessionHandle<T>>;
156
+ /**
157
+ * Get a completed session's transcript as rendered text.
158
+ * Accepts a SessionHandle (recommended) or session name string.
159
+ */
160
+ transcript(ref: SessionRef): Promise<Transcript>;
161
+ /**
162
+ * Get a completed session's raw native messages.
163
+ * Accepts a SessionHandle (recommended) or session name string.
164
+ */
165
+ getMessages(ref: SessionRef): Promise<SavedMessage[]>;
93
166
  }
94
167
 
95
168
  /**
@@ -109,6 +182,6 @@ export interface WorkflowDefinition {
109
182
  readonly __brand: "WorkflowDefinition";
110
183
  readonly name: string;
111
184
  readonly description: string;
112
- /** Ordered execution steps. Each step is an array of sessions — length 1 is sequential, length > 1 is parallel. */
113
- readonly steps: ReadonlyArray<ReadonlyArray<SessionOptions>>;
185
+ /** The workflow's entry point. Called by the executor with a WorkflowContext. */
186
+ readonly run: (ctx: WorkflowContext) => Promise<void>;
114
187
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * atomic/workflows
3
3
  *
4
- * Workflow SDK for defining multi-session agent workflows.
5
- * Workflows are defined as a chain of .session() calls and compiled
6
- * into a WorkflowDefinition consumed by the Atomic CLI runtime.
4
+ * Workflow SDK for defining dynamic agent workflows.
5
+ * Workflows use defineWorkflow().run().compile() with ctx.session()
6
+ * for spawning agent sessions using native TypeScript control flow.
7
7
  */
8
8
 
9
9
  export { defineWorkflow, WorkflowBuilder } from "./define-workflow.ts";
@@ -14,7 +14,10 @@ export type {
14
14
  SavedMessage,
15
15
  SaveTranscript,
16
16
  SessionContext,
17
- SessionOptions,
17
+ SessionRef,
18
+ SessionHandle,
19
+ SessionRunOptions,
20
+ WorkflowContext,
18
21
  WorkflowOptions,
19
22
  WorkflowDefinition,
20
23
  } from "./types.ts";
@@ -9,6 +9,8 @@ export interface AgentConfig {
9
9
  cmd: string;
10
10
  /** Flags used when spawning the agent in interactive chat mode */
11
11
  chat_flags: string[];
12
+ /** Environment variables to set when spawning the agent (merged with process env) */
13
+ env_vars: Record<string, string>;
12
14
  /** Config folder relative to repo root */
13
15
  folder: string;
14
16
  /** URL for installation instructions */
@@ -30,7 +32,11 @@ export const AGENT_CONFIG: Record<AgentKey, AgentConfig> = {
30
32
  claude: {
31
33
  name: "Claude Code",
32
34
  cmd: "claude",
33
- chat_flags: ["--allow-dangerously-skip-permissions", "--dangerously-skip-permissions"],
35
+ chat_flags: [
36
+ "--allow-dangerously-skip-permissions",
37
+ "--dangerously-skip-permissions",
38
+ ],
39
+ env_vars: {},
34
40
  folder: ".claude",
35
41
  install_url: "https://code.claude.com/docs/en/setup",
36
42
  exclude: [".DS_Store", "settings.json"],
@@ -51,6 +57,7 @@ export const AGENT_CONFIG: Record<AgentKey, AgentConfig> = {
51
57
  name: "OpenCode",
52
58
  cmd: "opencode",
53
59
  chat_flags: [],
60
+ env_vars: { OPENCODE_EXPERIMENTAL_LSP_TOOL: "true" },
54
61
  folder: ".opencode",
55
62
  install_url: "https://opencode.ai",
56
63
  exclude: [
@@ -72,7 +79,16 @@ export const AGENT_CONFIG: Record<AgentKey, AgentConfig> = {
72
79
  copilot: {
73
80
  name: "GitHub Copilot CLI",
74
81
  cmd: "copilot",
75
- chat_flags: ["--add-dir", ".", "--yolo", "--experimental"],
82
+ chat_flags: [
83
+ "--add-dir",
84
+ ".",
85
+ "--yolo",
86
+ "--experimental",
87
+ "--no-auto-update",
88
+ ],
89
+ env_vars: {
90
+ COPILOT_ALLOW_ALL: "true",
91
+ },
76
92
  folder: ".github",
77
93
  install_url:
78
94
  "https://github.com/github/copilot-cli?tab=readme-ov-file#installation",
@@ -154,6 +170,27 @@ export const SCM_CONFIG: Record<SourceControlType, ScmConfig> = {
154
170
  /** Commands that have SCM-specific variants */
155
171
  export const SCM_SPECIFIC_COMMANDS = ["commit"];
156
172
 
173
+ /**
174
+ * SCM-variant skill names, grouped by source control type.
175
+ *
176
+ * These are the skills that `installGlobalSkills` removes from the global
177
+ * scope after the initial install, and that `installLocalScmSkills`
178
+ * re-installs per-project based on the user's selected SCM. Passed to
179
+ * `npx skills add --skill <name>` as explicit names (the skills CLI does
180
+ * not support glob patterns like `gh-*`).
181
+ */
182
+ export const SCM_SKILLS_BY_TYPE: Record<SourceControlType, readonly string[]> =
183
+ {
184
+ github: ["gh-commit", "gh-create-pr"],
185
+ sapling: ["sl-commit", "sl-submit-diff"],
186
+ };
187
+
188
+ /** Flat list of every SCM-variant skill across all source control types. */
189
+ export const ALL_SCM_SKILLS: readonly string[] = [
190
+ ...SCM_SKILLS_BY_TYPE.github,
191
+ ...SCM_SKILLS_BY_TYPE.sapling,
192
+ ];
193
+
157
194
  /**
158
195
  * Get all SCM keys for iteration
159
196
  */
@@ -35,12 +35,6 @@ function globalSettingsPath(): string {
35
35
  return join(home, ".atomic", "settings.json");
36
36
  }
37
37
 
38
- /** Local settings path: {cwd}/.atomic/settings.json (CWD-scoped by design) */
39
- function localSettingsPath(): string {
40
- const cwd = process.env.ATOMIC_SETTINGS_CWD ?? process.cwd();
41
- return join(cwd, ".atomic", "settings.json");
42
- }
43
-
44
38
  function loadSettingsFileSync(path: string): AtomicSettings {
45
39
  try {
46
40
  if (existsSync(path)) {
@@ -6,14 +6,10 @@
6
6
  * locally per-project based on the user's selected SCM + active agent.
7
7
  */
8
8
 
9
+ import { ALL_SCM_SKILLS } from "@/services/config/index.ts";
10
+
9
11
  const SKILLS_REPO = "https://github.com/flora131/atomic.git";
10
12
  const SKILLS_AGENTS = ["claude-code", "opencode", "github-copilot"] as const;
11
- const SCM_SKILLS_TO_REMOVE_GLOBALLY = [
12
- "gh-commit",
13
- "gh-create-pr",
14
- "sl-commit",
15
- "sl-submit-diff",
16
- ] as const;
17
13
 
18
14
  interface NpxSkillsResult {
19
15
  ok: boolean;
@@ -59,7 +55,7 @@ export async function installGlobalSkills(): Promise<void> {
59
55
  throw new Error(`npx skills add failed: ${addResult.details}`);
60
56
  }
61
57
 
62
- const removeSkillFlags = SCM_SKILLS_TO_REMOVE_GLOBALLY.flatMap((skill) => [
58
+ const removeSkillFlags = ALL_SCM_SKILLS.flatMap((skill) => [
63
59
  "--skill",
64
60
  skill,
65
61
  ]);
@@ -1,31 +0,0 @@
1
- {
2
- "name": "atomic-workflows",
3
- "lockfileVersion": 3,
4
- "requires": true,
5
- "packages": {
6
- "": {
7
- "name": "atomic-workflows",
8
- "dependencies": {
9
- "@bastani/atomic-workflows": "^0.4.56-0"
10
- }
11
- },
12
- "node_modules/@bastani/atomic-workflows": {
13
- "version": "0.4.56-0",
14
- "resolved": "https://registry.npmjs.org/@bastani/atomic-workflows/-/atomic-workflows-0.4.56-0.tgz",
15
- "integrity": "sha512-6JtZTcZLI5c/DnQFHiM5i4Ymm4T+iYBAtS7J/RbOBwge3kv9EEwzxYijfLhqoBt7VHnUSkWvYlcNFjNFdDwIvw==",
16
- "license": "MIT",
17
- "dependencies": {
18
- "zod": "^4.3.6"
19
- }
20
- },
21
- "node_modules/zod": {
22
- "version": "4.3.6",
23
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
24
- "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
25
- "license": "MIT",
26
- "funding": {
27
- "url": "https://github.com/sponsors/colinhacks"
28
- }
29
- }
30
- }
31
- }
@@ -1,8 +0,0 @@
1
- {
2
- "name": "atomic-workflows",
3
- "private": true,
4
- "type": "module",
5
- "dependencies": {
6
- "@bastani/atomic-workflows": "^0.4.56-0"
7
- }
8
- }