@bastani/atomic 0.5.0 → 0.5.1-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.
@@ -11,7 +11,6 @@
11
11
  */
12
12
 
13
13
  import { defineWorkflow } from "@bastani/atomic/workflows";
14
- import { CopilotClient, approveAll } from "@github/copilot-sdk";
15
14
  import type { SessionEvent } from "@github/copilot-sdk";
16
15
 
17
16
  import {
@@ -68,7 +67,7 @@ function getAssistantText(messages: SessionEvent[]): string {
68
67
  .join("\n\n");
69
68
  }
70
69
 
71
- export default defineWorkflow({
70
+ export default defineWorkflow<"copilot">({
72
71
  name: "ralph",
73
72
  description:
74
73
  "Plan → orchestrate → review → debug loop with bounded iteration",
@@ -76,28 +75,16 @@ export default defineWorkflow({
76
75
  .run(async (ctx) => {
77
76
  let consecutiveClean = 0;
78
77
  let debuggerReport = "";
79
- // Track the most recent session so the next stage can declare it as a
80
- // dependency — this chains planner → orchestrator → reviewer → [confirm]
81
- // → [debugger] → next planner in the graph instead of showing every
82
- // stage as an independent sibling under the root.
83
- let prevStage: string | undefined;
84
- const depsOn = (): string[] | undefined =>
85
- prevStage ? [prevStage] : undefined;
86
78
 
87
79
  for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
88
80
  // ── Plan ──────────────────────────────────────────────────────────
89
81
  const plannerName = `planner-${iteration}`;
90
- const planner = await ctx.session(
91
- { name: plannerName, dependsOn: depsOn() },
82
+ const planner = await ctx.stage(
83
+ { name: plannerName },
84
+ {},
85
+ { agent: "planner" },
92
86
  async (s) => {
93
- const client = new CopilotClient({ cliUrl: s.serverUrl });
94
- await client.start();
95
- const session = await client.createSession({
96
- agent: "planner",
97
- onPermissionRequest: approveAll,
98
- });
99
- await client.setForegroundSessionId(session.sessionId);
100
- await session.sendAndWait(
87
+ await s.session.sendAndWait(
101
88
  {
102
89
  prompt: buildPlannerPrompt(s.userPrompt, {
103
90
  iteration,
@@ -106,28 +93,21 @@ export default defineWorkflow({
106
93
  },
107
94
  AGENT_SEND_TIMEOUT_MS,
108
95
  );
109
- const messages = await session.getMessages();
96
+ const messages = await s.session.getMessages();
110
97
  s.save(messages);
111
- await session.disconnect();
112
- await client.stop();
113
98
  return getAssistantText(messages);
114
99
  },
115
100
  );
116
- prevStage = plannerName;
101
+
117
102
 
118
103
  // ── Orchestrate ───────────────────────────────────────────────────
119
104
  const orchName = `orchestrator-${iteration}`;
120
- await ctx.session(
121
- { name: orchName, dependsOn: depsOn() },
105
+ await ctx.stage(
106
+ { name: orchName },
107
+ {},
108
+ { agent: "orchestrator" },
122
109
  async (s) => {
123
- const client = new CopilotClient({ cliUrl: s.serverUrl });
124
- await client.start();
125
- const session = await client.createSession({
126
- agent: "orchestrator",
127
- onPermissionRequest: approveAll,
128
- });
129
- await client.setForegroundSessionId(session.sessionId);
130
- await session.sendAndWait(
110
+ await s.session.sendAndWait(
131
111
  {
132
112
  prompt: buildOrchestratorPrompt(s.userPrompt, {
133
113
  plannerNotes: planner.result,
@@ -135,27 +115,20 @@ export default defineWorkflow({
135
115
  },
136
116
  AGENT_SEND_TIMEOUT_MS,
137
117
  );
138
- s.save(await session.getMessages());
139
- await session.disconnect();
140
- await client.stop();
118
+ s.save(await s.session.getMessages());
141
119
  },
142
120
  );
143
- prevStage = orchName;
121
+
144
122
 
145
123
  // ── Review (first pass) ───────────────────────────────────────────
146
124
  let gitStatus = await safeGitStatusS();
147
125
  const reviewerName = `reviewer-${iteration}`;
148
- const review = await ctx.session(
149
- { name: reviewerName, dependsOn: depsOn() },
126
+ const review = await ctx.stage(
127
+ { name: reviewerName },
128
+ {},
129
+ { agent: "reviewer" },
150
130
  async (s) => {
151
- const client = new CopilotClient({ cliUrl: s.serverUrl });
152
- await client.start();
153
- const session = await client.createSession({
154
- agent: "reviewer",
155
- onPermissionRequest: approveAll,
156
- });
157
- await client.setForegroundSessionId(session.sessionId);
158
- await session.sendAndWait(
131
+ await s.session.sendAndWait(
159
132
  {
160
133
  prompt: buildReviewPrompt(s.userPrompt, {
161
134
  gitStatus,
@@ -164,14 +137,12 @@ export default defineWorkflow({
164
137
  },
165
138
  AGENT_SEND_TIMEOUT_MS,
166
139
  );
167
- const messages = await session.getMessages();
140
+ const messages = await s.session.getMessages();
168
141
  s.save(messages);
169
- await session.disconnect();
170
- await client.stop();
171
142
  return getAssistantText(messages);
172
143
  },
173
144
  );
174
- prevStage = reviewerName;
145
+
175
146
 
176
147
  let reviewRaw = review.result;
177
148
  let parsed = parseReviewResult(reviewRaw);
@@ -183,17 +154,12 @@ export default defineWorkflow({
183
154
  // Confirmation pass — re-run reviewer only
184
155
  gitStatus = await safeGitStatusS();
185
156
  const confirmName = `reviewer-${iteration}-confirm`;
186
- const confirm = await ctx.session(
187
- { name: confirmName, dependsOn: depsOn() },
157
+ const confirm = await ctx.stage(
158
+ { name: confirmName },
159
+ {},
160
+ { agent: "reviewer" },
188
161
  async (s) => {
189
- const client = new CopilotClient({ cliUrl: s.serverUrl });
190
- await client.start();
191
- const session = await client.createSession({
192
- agent: "reviewer",
193
- onPermissionRequest: approveAll,
194
- });
195
- await client.setForegroundSessionId(session.sessionId);
196
- await session.sendAndWait(
162
+ await s.session.sendAndWait(
197
163
  {
198
164
  prompt: buildReviewPrompt(s.userPrompt, {
199
165
  gitStatus,
@@ -203,14 +169,12 @@ export default defineWorkflow({
203
169
  },
204
170
  AGENT_SEND_TIMEOUT_MS,
205
171
  );
206
- const messages = await session.getMessages();
172
+ const messages = await s.session.getMessages();
207
173
  s.save(messages);
208
- await session.disconnect();
209
- await client.stop();
210
174
  return getAssistantText(messages);
211
175
  },
212
176
  );
213
- prevStage = confirmName;
177
+
214
178
 
215
179
  reviewRaw = confirm.result;
216
180
  parsed = parseReviewResult(reviewRaw);
@@ -228,17 +192,12 @@ export default defineWorkflow({
228
192
  // ── Debug (only if findings remain AND another iteration is allowed) ─
229
193
  if (hasActionableFindings(parsed, reviewRaw) && iteration < MAX_LOOPS) {
230
194
  const debuggerName = `debugger-${iteration}`;
231
- const debugger_ = await ctx.session(
232
- { name: debuggerName, dependsOn: depsOn() },
195
+ const debugger_ = await ctx.stage(
196
+ { name: debuggerName },
197
+ {},
198
+ { agent: "debugger" },
233
199
  async (s) => {
234
- const client = new CopilotClient({ cliUrl: s.serverUrl });
235
- await client.start();
236
- const session = await client.createSession({
237
- agent: "debugger",
238
- onPermissionRequest: approveAll,
239
- });
240
- await client.setForegroundSessionId(session.sessionId);
241
- await session.sendAndWait(
200
+ await s.session.sendAndWait(
242
201
  {
243
202
  prompt: buildDebuggerReportPrompt(parsed, reviewRaw, {
244
203
  iteration,
@@ -247,14 +206,12 @@ export default defineWorkflow({
247
206
  },
248
207
  AGENT_SEND_TIMEOUT_MS,
249
208
  );
250
- const messages = await session.getMessages();
209
+ const messages = await s.session.getMessages();
251
210
  s.save(messages);
252
- await session.disconnect();
253
- await client.stop();
254
211
  return getAssistantText(messages);
255
212
  },
256
213
  );
257
- prevStage = debuggerName;
214
+
258
215
  debuggerReport = extractMarkdownBlock(debugger_.result);
259
216
  }
260
217
  }
@@ -11,7 +11,6 @@
11
11
  */
12
12
 
13
13
  import { defineWorkflow } from "@bastani/atomic/workflows";
14
- import { createOpencodeClient } from "@opencode-ai/sdk/v2";
15
14
 
16
15
  import {
17
16
  buildPlannerPrompt,
@@ -37,7 +36,7 @@ function extractResponseText(
37
36
  .join("\n");
38
37
  }
39
38
 
40
- export default defineWorkflow({
39
+ export default defineWorkflow<"opencode">({
41
40
  name: "ralph",
42
41
  description:
43
42
  "Plan → orchestrate → review → debug loop with bounded iteration",
@@ -45,27 +44,17 @@ export default defineWorkflow({
45
44
  .run(async (ctx) => {
46
45
  let consecutiveClean = 0;
47
46
  let debuggerReport = "";
48
- // Track the most recent session so the next stage can declare it as a
49
- // dependency — this chains planner → orchestrator → reviewer → [confirm]
50
- // → [debugger] → next planner in the graph instead of showing every
51
- // stage as an independent sibling under the root.
52
- let prevStage: string | undefined;
53
- const depsOn = (): string[] | undefined =>
54
- prevStage ? [prevStage] : undefined;
55
47
 
56
48
  for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
57
49
  // ── Plan ────────────────────────────────────────────────────────────
58
50
  const plannerName = `planner-${iteration}`;
59
- const planner = await ctx.session(
60
- { name: plannerName, dependsOn: depsOn() },
51
+ const planner = await ctx.stage(
52
+ { name: plannerName },
53
+ {},
54
+ { title: `planner-${iteration}` },
61
55
  async (s) => {
62
- const client = createOpencodeClient({ baseUrl: s.serverUrl });
63
- const session = await client.session.create({
64
- title: `planner-${iteration}`,
65
- });
66
- await client.tui.selectSession({ sessionID: session.data!.id });
67
- const result = await client.session.prompt({
68
- sessionID: session.data!.id,
56
+ const result = await s.client.session.prompt({
57
+ sessionID: s.session.id,
69
58
  parts: [
70
59
  {
71
60
  type: "text",
@@ -81,20 +70,17 @@ export default defineWorkflow({
81
70
  return extractResponseText(result.data!.parts);
82
71
  },
83
72
  );
84
- prevStage = plannerName;
73
+
85
74
 
86
75
  // ── Orchestrate ─────────────────────────────────────────────────────
87
76
  const orchName = `orchestrator-${iteration}`;
88
- await ctx.session(
89
- { name: orchName, dependsOn: depsOn() },
77
+ await ctx.stage(
78
+ { name: orchName },
79
+ {},
80
+ { title: `orchestrator-${iteration}` },
90
81
  async (s) => {
91
- const client = createOpencodeClient({ baseUrl: s.serverUrl });
92
- const session = await client.session.create({
93
- title: `orchestrator-${iteration}`,
94
- });
95
- await client.tui.selectSession({ sessionID: session.data!.id });
96
- const result = await client.session.prompt({
97
- sessionID: session.data!.id,
82
+ const result = await s.client.session.prompt({
83
+ sessionID: s.session.id,
98
84
  parts: [
99
85
  {
100
86
  type: "text",
@@ -108,21 +94,18 @@ export default defineWorkflow({
108
94
  s.save(result.data!);
109
95
  },
110
96
  );
111
- prevStage = orchName;
97
+
112
98
 
113
99
  // ── Review (first pass) ─────────────────────────────────────────────
114
100
  let gitStatus = await safeGitStatusS();
115
101
  const reviewerName = `reviewer-${iteration}`;
116
- const review = await ctx.session(
117
- { name: reviewerName, dependsOn: depsOn() },
102
+ const review = await ctx.stage(
103
+ { name: reviewerName },
104
+ {},
105
+ { title: `reviewer-${iteration}` },
118
106
  async (s) => {
119
- const client = createOpencodeClient({ baseUrl: s.serverUrl });
120
- const session = await client.session.create({
121
- title: `reviewer-${iteration}`,
122
- });
123
- await client.tui.selectSession({ sessionID: session.data!.id });
124
- const result = await client.session.prompt({
125
- sessionID: session.data!.id,
107
+ const result = await s.client.session.prompt({
108
+ sessionID: s.session.id,
126
109
  parts: [
127
110
  {
128
111
  type: "text",
@@ -138,7 +121,7 @@ export default defineWorkflow({
138
121
  return extractResponseText(result.data!.parts);
139
122
  },
140
123
  );
141
- prevStage = reviewerName;
124
+
142
125
 
143
126
  let reviewRaw = review.result;
144
127
  let parsed = parseReviewResult(reviewRaw);
@@ -150,16 +133,13 @@ export default defineWorkflow({
150
133
  // Confirmation pass — re-run reviewer only
151
134
  gitStatus = await safeGitStatusS();
152
135
  const confirmName = `reviewer-${iteration}-confirm`;
153
- const confirm = await ctx.session(
154
- { name: confirmName, dependsOn: depsOn() },
136
+ const confirm = await ctx.stage(
137
+ { name: confirmName },
138
+ {},
139
+ { title: `reviewer-${iteration}-confirm` },
155
140
  async (s) => {
156
- const client = createOpencodeClient({ baseUrl: s.serverUrl });
157
- const session = await client.session.create({
158
- title: `reviewer-${iteration}-confirm`,
159
- });
160
- await client.tui.selectSession({ sessionID: session.data!.id });
161
- const result = await client.session.prompt({
162
- sessionID: session.data!.id,
141
+ const result = await s.client.session.prompt({
142
+ sessionID: s.session.id,
163
143
  parts: [
164
144
  {
165
145
  type: "text",
@@ -176,7 +156,7 @@ export default defineWorkflow({
176
156
  return extractResponseText(result.data!.parts);
177
157
  },
178
158
  );
179
- prevStage = confirmName;
159
+
180
160
 
181
161
  reviewRaw = confirm.result;
182
162
  parsed = parseReviewResult(reviewRaw);
@@ -194,16 +174,13 @@ export default defineWorkflow({
194
174
  // ── Debug (only if findings remain AND another iteration is allowed) ─
195
175
  if (hasActionableFindings(parsed, reviewRaw) && iteration < MAX_LOOPS) {
196
176
  const debuggerName = `debugger-${iteration}`;
197
- const debugger_ = await ctx.session(
198
- { name: debuggerName, dependsOn: depsOn() },
177
+ const debugger_ = await ctx.stage(
178
+ { name: debuggerName },
179
+ {},
180
+ { title: `debugger-${iteration}` },
199
181
  async (s) => {
200
- const client = createOpencodeClient({ baseUrl: s.serverUrl });
201
- const session = await client.session.create({
202
- title: `debugger-${iteration}`,
203
- });
204
- await client.tui.selectSession({ sessionID: session.data!.id });
205
- const result = await client.session.prompt({
206
- sessionID: session.data!.id,
182
+ const result = await s.client.session.prompt({
183
+ sessionID: s.session.id,
207
184
  parts: [
208
185
  {
209
186
  type: "text",
@@ -219,7 +196,7 @@ export default defineWorkflow({
219
196
  return extractResponseText(result.data!.parts);
220
197
  },
221
198
  );
222
- prevStage = debuggerName;
199
+
223
200
  debuggerReport = extractMarkdownBlock(debugger_.result);
224
201
  }
225
202
  }