@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.
- package/.atomic/workflows/hello/claude/index.ts +10 -11
- package/.atomic/workflows/hello/copilot/index.ts +11 -26
- package/.atomic/workflows/hello/opencode/index.ts +11 -17
- package/.atomic/workflows/hello-parallel/claude/index.ts +20 -23
- package/.atomic/workflows/hello-parallel/copilot/index.ts +21 -42
- package/.atomic/workflows/hello-parallel/opencode/index.ts +21 -31
- package/.atomic/workflows/ralph/claude/index.ts +42 -53
- package/.atomic/workflows/ralph/copilot/index.ts +36 -79
- package/.atomic/workflows/ralph/opencode/index.ts +36 -59
- package/README.md +391 -166
- package/package.json +1 -1
- package/src/sdk/define-workflow.ts +35 -19
- package/src/sdk/index.ts +4 -0
- package/src/sdk/providers/claude.ts +103 -10
- package/src/sdk/providers/copilot.ts +16 -23
- package/src/sdk/providers/opencode.ts +15 -22
- package/src/sdk/runtime/executor.ts +138 -55
- package/src/sdk/runtime/graph-inference.ts +50 -0
- package/src/sdk/types.ts +113 -38
- package/src/sdk/workflows.ts +14 -1
- package/src/services/system/workflows.ts +136 -1
package/package.json
CHANGED
|
@@ -2,25 +2,25 @@
|
|
|
2
2
|
* Workflow Builder — defines a workflow with a single `.run()` entry point.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* defineWorkflow({ name: "my-workflow", description: "..." })
|
|
5
|
+
* defineWorkflow<"copilot">({ name: "my-workflow", description: "..." })
|
|
6
6
|
* .run(async (ctx) => {
|
|
7
|
-
* await ctx.
|
|
8
|
-
* await ctx.
|
|
7
|
+
* await ctx.stage({ name: "research" }, {}, {}, async (s) => { ... });
|
|
8
|
+
* await ctx.stage({ name: "plan" }, {}, {}, async (s) => { ... });
|
|
9
9
|
* })
|
|
10
10
|
* .compile()
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { WorkflowOptions, WorkflowContext, WorkflowDefinition } from "./types.ts";
|
|
13
|
+
import type { AgentType, WorkflowOptions, WorkflowContext, WorkflowDefinition } from "./types.ts";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Chainable workflow builder. Records the run callback,
|
|
17
17
|
* then .compile() seals it into a WorkflowDefinition.
|
|
18
18
|
*/
|
|
19
|
-
export class WorkflowBuilder {
|
|
19
|
+
export class WorkflowBuilder<A extends AgentType = AgentType> {
|
|
20
20
|
/** @internal Brand for detection across package boundaries */
|
|
21
21
|
readonly __brand = "WorkflowBuilder" as const;
|
|
22
22
|
private readonly options: WorkflowOptions;
|
|
23
|
-
private runFn: ((ctx: WorkflowContext) => Promise<void>) | null = null;
|
|
23
|
+
private runFn: ((ctx: WorkflowContext<A>) => Promise<void>) | null = null;
|
|
24
24
|
|
|
25
25
|
constructor(options: WorkflowOptions) {
|
|
26
26
|
this.options = options;
|
|
@@ -29,12 +29,12 @@ export class WorkflowBuilder {
|
|
|
29
29
|
/**
|
|
30
30
|
* Set the workflow's entry point.
|
|
31
31
|
*
|
|
32
|
-
* The callback receives a {@link WorkflowContext} with `
|
|
32
|
+
* The callback receives a {@link WorkflowContext} with `stage()` for
|
|
33
33
|
* spawning agent sessions, and `transcript()` / `getMessages()` for
|
|
34
34
|
* reading completed session outputs. Use native TypeScript control flow
|
|
35
35
|
* (loops, conditionals, `Promise.all()`) for orchestration.
|
|
36
36
|
*/
|
|
37
|
-
run(fn: (ctx: WorkflowContext) => Promise<void>): this {
|
|
37
|
+
run(fn: (ctx: WorkflowContext<A>) => Promise<void>): this {
|
|
38
38
|
if (this.runFn) {
|
|
39
39
|
throw new Error("run() can only be called once per workflow.");
|
|
40
40
|
}
|
|
@@ -51,7 +51,7 @@ export class WorkflowBuilder {
|
|
|
51
51
|
* After calling compile(), the returned object is consumed by the
|
|
52
52
|
* Atomic CLI runtime.
|
|
53
53
|
*/
|
|
54
|
-
compile(): WorkflowDefinition {
|
|
54
|
+
compile(): WorkflowDefinition<A> {
|
|
55
55
|
if (!this.runFn) {
|
|
56
56
|
throw new Error(
|
|
57
57
|
`Workflow "${this.options.name}" has no run callback. ` +
|
|
@@ -73,29 +73,45 @@ export class WorkflowBuilder {
|
|
|
73
73
|
/**
|
|
74
74
|
* Entry point for defining a workflow.
|
|
75
75
|
*
|
|
76
|
+
* Pass a type parameter to narrow all context types to a specific agent:
|
|
77
|
+
*
|
|
76
78
|
* @example
|
|
77
79
|
* ```typescript
|
|
78
80
|
* import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
79
81
|
*
|
|
80
|
-
* export default defineWorkflow({
|
|
82
|
+
* export default defineWorkflow<"copilot">({
|
|
81
83
|
* name: "hello",
|
|
82
84
|
* description: "Two-session demo",
|
|
83
85
|
* })
|
|
84
86
|
* .run(async (ctx) => {
|
|
85
|
-
* const describe = await ctx.
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
87
|
+
* const describe = await ctx.stage(
|
|
88
|
+
* { name: "describe" },
|
|
89
|
+
* {},
|
|
90
|
+
* {},
|
|
91
|
+
* async (s) => {
|
|
92
|
+
* // s.client: CopilotClient, s.session: CopilotSession
|
|
93
|
+
* await s.session.sendAndWait({ prompt: s.userPrompt });
|
|
94
|
+
* s.save(await s.session.getMessages());
|
|
95
|
+
* },
|
|
96
|
+
* );
|
|
97
|
+
* await ctx.stage(
|
|
98
|
+
* { name: "summarize" },
|
|
99
|
+
* {},
|
|
100
|
+
* {},
|
|
101
|
+
* async (s) => {
|
|
102
|
+
* const research = await s.transcript(describe);
|
|
103
|
+
* // ...
|
|
104
|
+
* },
|
|
105
|
+
* );
|
|
92
106
|
* })
|
|
93
107
|
* .compile();
|
|
94
108
|
* ```
|
|
95
109
|
*/
|
|
96
|
-
export function defineWorkflow
|
|
110
|
+
export function defineWorkflow<A extends AgentType = AgentType>(
|
|
111
|
+
options: WorkflowOptions,
|
|
112
|
+
): WorkflowBuilder<A> {
|
|
97
113
|
if (!options.name || options.name.trim() === "") {
|
|
98
114
|
throw new Error("Workflow name is required.");
|
|
99
115
|
}
|
|
100
|
-
return new WorkflowBuilder(options);
|
|
116
|
+
return new WorkflowBuilder<A>(options);
|
|
101
117
|
}
|
package/src/sdk/index.ts
CHANGED
|
@@ -283,6 +283,92 @@ export async function claudeQuery(options: ClaudeQueryOptions): Promise<ClaudeQu
|
|
|
283
283
|
return { output: lastContent || capturePaneScrollback(paneId), delivered };
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Synthetic wrappers — uniform s.client / s.session API for Claude stages
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Default query options the user can set per-stage via the `sessionOpts` arg.
|
|
292
|
+
* These become defaults for every `s.session.query()` call within that stage.
|
|
293
|
+
*/
|
|
294
|
+
export interface ClaudeQueryDefaults {
|
|
295
|
+
/** Timeout in ms waiting for Claude to finish responding (default: 300s) */
|
|
296
|
+
timeoutMs?: number;
|
|
297
|
+
/** Polling interval in ms (default: 2000) */
|
|
298
|
+
pollIntervalMs?: number;
|
|
299
|
+
/** Number of C-m presses per submit round (default: 1) */
|
|
300
|
+
submitPresses?: number;
|
|
301
|
+
/** Max submit rounds if text isn't consumed (default: 6) */
|
|
302
|
+
maxSubmitRounds?: number;
|
|
303
|
+
/** Timeout in ms waiting for pane to be ready before sending (default: 30s) */
|
|
304
|
+
readyTimeoutMs?: number;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Synthetic client wrapper for Claude stages.
|
|
309
|
+
* Auto-starts the Claude CLI in the tmux pane during `start()`.
|
|
310
|
+
*/
|
|
311
|
+
export class ClaudeClientWrapper {
|
|
312
|
+
readonly paneId: string;
|
|
313
|
+
private readonly opts: { chatFlags?: string[]; readyTimeoutMs?: number };
|
|
314
|
+
|
|
315
|
+
constructor(
|
|
316
|
+
paneId: string,
|
|
317
|
+
opts: { chatFlags?: string[]; readyTimeoutMs?: number } = {},
|
|
318
|
+
) {
|
|
319
|
+
this.paneId = paneId;
|
|
320
|
+
this.opts = opts;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Start the Claude CLI in the tmux pane. Called by the runtime during init. */
|
|
324
|
+
async start(): Promise<void> {
|
|
325
|
+
await createClaudeSession({
|
|
326
|
+
paneId: this.paneId,
|
|
327
|
+
chatFlags: this.opts.chatFlags,
|
|
328
|
+
readyTimeoutMs: this.opts.readyTimeoutMs,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Noop — cleanup is handled by the runtime via `clearClaudeSession`. */
|
|
333
|
+
async stop(): Promise<void> {}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Synthetic session wrapper for Claude stages.
|
|
338
|
+
* Wraps `claudeQuery()` so users call `s.session.query(prompt)`.
|
|
339
|
+
*/
|
|
340
|
+
export class ClaudeSessionWrapper {
|
|
341
|
+
readonly paneId: string;
|
|
342
|
+
readonly sessionId: string;
|
|
343
|
+
private readonly defaults: ClaudeQueryDefaults;
|
|
344
|
+
|
|
345
|
+
constructor(
|
|
346
|
+
paneId: string,
|
|
347
|
+
sessionId: string,
|
|
348
|
+
defaults: ClaudeQueryDefaults = {},
|
|
349
|
+
) {
|
|
350
|
+
this.paneId = paneId;
|
|
351
|
+
this.sessionId = sessionId;
|
|
352
|
+
this.defaults = defaults;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Send a prompt to Claude and wait for the response. */
|
|
356
|
+
async query(
|
|
357
|
+
prompt: string,
|
|
358
|
+
opts?: Partial<ClaudeQueryDefaults>,
|
|
359
|
+
): Promise<ClaudeQueryResult> {
|
|
360
|
+
return claudeQuery({
|
|
361
|
+
paneId: this.paneId,
|
|
362
|
+
prompt,
|
|
363
|
+
...this.defaults,
|
|
364
|
+
...opts,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Noop — for API symmetry with CopilotSession.disconnect(). */
|
|
369
|
+
async disconnect(): Promise<void> {}
|
|
370
|
+
}
|
|
371
|
+
|
|
286
372
|
// ---------------------------------------------------------------------------
|
|
287
373
|
// Static source validation
|
|
288
374
|
// ---------------------------------------------------------------------------
|
|
@@ -295,21 +381,28 @@ export interface ClaudeValidationWarning {
|
|
|
295
381
|
/**
|
|
296
382
|
* Validate a Claude workflow source file for common mistakes.
|
|
297
383
|
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
384
|
+
* Warns on direct usage of createClaudeSession/claudeQuery — the runtime
|
|
385
|
+
* now handles init/cleanup automatically via s.client and s.session.
|
|
300
386
|
*/
|
|
301
387
|
export function validateClaudeWorkflow(source: string): ClaudeValidationWarning[] {
|
|
302
388
|
const warnings: ClaudeValidationWarning[] = [];
|
|
303
389
|
|
|
390
|
+
if (/\bcreateClaudeSession\b/.test(source)) {
|
|
391
|
+
warnings.push({
|
|
392
|
+
rule: "claude/manual-session",
|
|
393
|
+
message:
|
|
394
|
+
"Manual createClaudeSession() call detected. The runtime auto-starts the Claude CLI — " +
|
|
395
|
+
"use s.session.query() instead of claudeQuery(). Pass chatFlags via the second arg to ctx.stage().",
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
304
399
|
if (/\bclaudeQuery\b/.test(source)) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
});
|
|
312
|
-
}
|
|
400
|
+
warnings.push({
|
|
401
|
+
rule: "claude/manual-query",
|
|
402
|
+
message:
|
|
403
|
+
"Direct claudeQuery() call detected. Use s.session.query(prompt) instead — " +
|
|
404
|
+
"it wraps claudeQuery with the correct paneId.",
|
|
405
|
+
});
|
|
313
406
|
}
|
|
314
407
|
|
|
315
408
|
return warnings;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Copilot workflow source validation.
|
|
3
3
|
*
|
|
4
|
-
* Checks that Copilot workflow source files
|
|
5
|
-
*
|
|
6
|
-
* - `setForegroundSessionId` is called after creating a session
|
|
4
|
+
* Checks that Copilot workflow source files use the runtime-managed
|
|
5
|
+
* `s.client` and `s.session` instead of manual SDK client creation.
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
export interface CopilotValidationWarning {
|
|
@@ -17,28 +16,22 @@ export interface CopilotValidationWarning {
|
|
|
17
16
|
export function validateCopilotWorkflow(source: string): CopilotValidationWarning[] {
|
|
18
17
|
const warnings: CopilotValidationWarning[] = [];
|
|
19
18
|
|
|
20
|
-
if (/\
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"Could not verify that CopilotClient is created with { cliUrl: s.serverUrl }. " +
|
|
28
|
-
"This is required to connect to the workflow's agent pane.",
|
|
29
|
-
});
|
|
30
|
-
}
|
|
19
|
+
if (/\bnew\s+CopilotClient\b/.test(source)) {
|
|
20
|
+
warnings.push({
|
|
21
|
+
rule: "copilot/manual-client",
|
|
22
|
+
message:
|
|
23
|
+
"Manual CopilotClient creation detected. Use s.client instead — " +
|
|
24
|
+
"the runtime auto-creates and cleans up the client.",
|
|
25
|
+
});
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
if (/\
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
}
|
|
28
|
+
if (/\bclient\.createSession\b/.test(source)) {
|
|
29
|
+
warnings.push({
|
|
30
|
+
rule: "copilot/manual-session",
|
|
31
|
+
message:
|
|
32
|
+
"Manual createSession() call detected. Use s.session instead — " +
|
|
33
|
+
"the runtime auto-creates the session. Pass session config as the third arg to ctx.stage().",
|
|
34
|
+
});
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
return warnings;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode workflow source validation.
|
|
3
3
|
*
|
|
4
|
-
* Checks that OpenCode workflow source files
|
|
5
|
-
*
|
|
6
|
-
* - `tui.selectSession` is called after creating a session
|
|
4
|
+
* Checks that OpenCode workflow source files use the runtime-managed
|
|
5
|
+
* `s.client` and `s.session` instead of manual SDK client creation.
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
export interface OpenCodeValidationWarning {
|
|
@@ -18,27 +17,21 @@ export function validateOpenCodeWorkflow(source: string): OpenCodeValidationWarn
|
|
|
18
17
|
const warnings: OpenCodeValidationWarning[] = [];
|
|
19
18
|
|
|
20
19
|
if (/\bcreateOpencodeClient\b/.test(source)) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"Could not verify that createOpencodeClient is called with { baseUrl: s.serverUrl }. " +
|
|
28
|
-
"This is required to connect to the workflow's agent pane.",
|
|
29
|
-
});
|
|
30
|
-
}
|
|
20
|
+
warnings.push({
|
|
21
|
+
rule: "opencode/manual-client",
|
|
22
|
+
message:
|
|
23
|
+
"Manual createOpencodeClient() call detected. Use s.client instead — " +
|
|
24
|
+
"the runtime auto-creates the client. Pass client config as the second arg to ctx.stage().",
|
|
25
|
+
});
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
if (/\
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
}
|
|
28
|
+
if (/\bclient\.session\.create\b/.test(source)) {
|
|
29
|
+
warnings.push({
|
|
30
|
+
rule: "opencode/manual-session",
|
|
31
|
+
message:
|
|
32
|
+
"Manual client.session.create() call detected. Use s.session instead — " +
|
|
33
|
+
"the runtime auto-creates the session. Pass session config as the third arg to ctx.stage().",
|
|
34
|
+
});
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
return warnings;
|