@bastani/atomic 0.5.0 → 0.5.1
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
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `bun run executor.ts --run <args>`
|
|
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
|
-
* user's callback uses `ctx.
|
|
10
|
+
* user's callback uses `ctx.stage()` to spawn agent sessions
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { join, resolve } from "path";
|
|
@@ -24,6 +24,8 @@ import type {
|
|
|
24
24
|
Transcript,
|
|
25
25
|
SavedMessage,
|
|
26
26
|
SaveTranscript,
|
|
27
|
+
StageClientOptions,
|
|
28
|
+
StageSessionOptions,
|
|
27
29
|
} from "../types.ts";
|
|
28
30
|
import type { SessionEvent } from "@github/copilot-sdk";
|
|
29
31
|
import type { SessionPromptResponse } from "@opencode-ai/sdk/v2";
|
|
@@ -31,8 +33,13 @@ import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
|
31
33
|
import * as tmux from "./tmux.ts";
|
|
32
34
|
import { getMuxBinary } from "./tmux.ts";
|
|
33
35
|
import { WorkflowLoader } from "./loader.ts";
|
|
34
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
clearClaudeSession,
|
|
38
|
+
ClaudeClientWrapper,
|
|
39
|
+
ClaudeSessionWrapper,
|
|
40
|
+
} from "../providers/claude.ts";
|
|
35
41
|
import { OrchestratorPanel } from "./panel.tsx";
|
|
42
|
+
import { GraphFrontierTracker } from "./graph-inference.ts";
|
|
36
43
|
|
|
37
44
|
/** Maximum time (ms) to wait for an agent's server to become reachable. */
|
|
38
45
|
const SERVER_WAIT_TIMEOUT_MS = 60_000;
|
|
@@ -98,11 +105,7 @@ interface SessionResult {
|
|
|
98
105
|
interface ActiveSession {
|
|
99
106
|
name: string;
|
|
100
107
|
paneId: string;
|
|
101
|
-
/**
|
|
102
|
-
* Settles when the session finishes. Resolves on success, rejects with the
|
|
103
|
-
* callback's error on failure. Dependent sessions awaiting via `dependsOn`
|
|
104
|
-
* block on this promise.
|
|
105
|
-
*/
|
|
108
|
+
/** Settles when the session finishes. Resolves on success, rejects on failure. */
|
|
106
109
|
done: Promise<void>;
|
|
107
110
|
}
|
|
108
111
|
|
|
@@ -410,7 +413,7 @@ function resolveRef(ref: SessionRef): string {
|
|
|
410
413
|
}
|
|
411
414
|
|
|
412
415
|
// ============================================================================
|
|
413
|
-
// Session runner — implements ctx.
|
|
416
|
+
// Session runner — implements ctx.stage() lifecycle
|
|
414
417
|
// ============================================================================
|
|
415
418
|
|
|
416
419
|
/** Shared state passed to session runners by the orchestrator. */
|
|
@@ -427,23 +430,118 @@ interface SharedRunnerState {
|
|
|
427
430
|
}
|
|
428
431
|
|
|
429
432
|
/**
|
|
430
|
-
* Create
|
|
433
|
+
* Create the provider-specific client and session for a stage.
|
|
434
|
+
* Called by the session runner after server readiness is confirmed.
|
|
435
|
+
*/
|
|
436
|
+
async function initProviderClientAndSession(
|
|
437
|
+
agent: AgentType,
|
|
438
|
+
serverUrl: string,
|
|
439
|
+
paneId: string,
|
|
440
|
+
sessionId: string,
|
|
441
|
+
clientOpts: StageClientOptions<AgentType>,
|
|
442
|
+
sessionOpts: StageSessionOptions<AgentType>,
|
|
443
|
+
): Promise<{ client: unknown; session: unknown }> {
|
|
444
|
+
switch (agent) {
|
|
445
|
+
case "copilot": {
|
|
446
|
+
const { CopilotClient, approveAll } = await import("@github/copilot-sdk");
|
|
447
|
+
const copilotClientOpts = clientOpts as StageClientOptions<"copilot">;
|
|
448
|
+
const copilotSessionOpts = sessionOpts as StageSessionOptions<"copilot">;
|
|
449
|
+
const client = new CopilotClient({ cliUrl: serverUrl, ...copilotClientOpts });
|
|
450
|
+
await client.start();
|
|
451
|
+
const session = await client.createSession({
|
|
452
|
+
onPermissionRequest: approveAll,
|
|
453
|
+
...copilotSessionOpts,
|
|
454
|
+
});
|
|
455
|
+
await client.setForegroundSessionId(session.sessionId);
|
|
456
|
+
return { client, session };
|
|
457
|
+
}
|
|
458
|
+
case "opencode": {
|
|
459
|
+
const { createOpencodeClient } = await import("@opencode-ai/sdk/v2");
|
|
460
|
+
const ocClientOpts = clientOpts as StageClientOptions<"opencode">;
|
|
461
|
+
const ocSessionOpts = sessionOpts as StageSessionOptions<"opencode">;
|
|
462
|
+
const client = createOpencodeClient({ baseUrl: serverUrl, ...ocClientOpts });
|
|
463
|
+
const sessionResult = await client.session.create(ocSessionOpts);
|
|
464
|
+
await client.tui.selectSession({ sessionID: sessionResult.data!.id });
|
|
465
|
+
return { client, session: sessionResult.data! };
|
|
466
|
+
}
|
|
467
|
+
case "claude": {
|
|
468
|
+
const claudeClientOpts = clientOpts as StageClientOptions<"claude">;
|
|
469
|
+
const claudeSessionOpts = sessionOpts as StageSessionOptions<"claude">;
|
|
470
|
+
const client = new ClaudeClientWrapper(paneId, claudeClientOpts);
|
|
471
|
+
await client.start();
|
|
472
|
+
const session = new ClaudeSessionWrapper(paneId, sessionId, claudeSessionOpts);
|
|
473
|
+
return { client, session };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Clean up provider-specific resources after a stage callback completes.
|
|
480
|
+
* Errors are silently caught — cleanup must not mask callback errors.
|
|
481
|
+
*/
|
|
482
|
+
async function cleanupProvider(
|
|
483
|
+
agent: AgentType,
|
|
484
|
+
providerClient: unknown,
|
|
485
|
+
providerSession: unknown,
|
|
486
|
+
paneId: string,
|
|
487
|
+
): Promise<void> {
|
|
488
|
+
switch (agent) {
|
|
489
|
+
case "copilot": {
|
|
490
|
+
const { CopilotSession: CopilotSessionClass } = await import("@github/copilot-sdk");
|
|
491
|
+
try {
|
|
492
|
+
if (providerSession instanceof CopilotSessionClass) {
|
|
493
|
+
await providerSession.disconnect();
|
|
494
|
+
}
|
|
495
|
+
} catch {}
|
|
496
|
+
try {
|
|
497
|
+
const { CopilotClient: CopilotClientClass } = await import("@github/copilot-sdk");
|
|
498
|
+
if (providerClient instanceof CopilotClientClass) {
|
|
499
|
+
await providerClient.stop();
|
|
500
|
+
}
|
|
501
|
+
} catch {}
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
case "opencode":
|
|
505
|
+
// Stateless HTTP client — no cleanup needed
|
|
506
|
+
break;
|
|
507
|
+
case "claude":
|
|
508
|
+
clearClaudeSession(paneId);
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Create a `ctx.stage()` function bound to a parent name for graph edges.
|
|
515
|
+
*
|
|
516
|
+
* Graph topology is auto-inferred from JavaScript's execution order:
|
|
517
|
+
* - **Sequential** (`await`): the completed stage is in the frontier when the
|
|
518
|
+
* next stage spawns → parent-child edge.
|
|
519
|
+
* - **Parallel** (`Promise.all`): both calls fire in the same synchronous
|
|
520
|
+
* frame → frontier is empty for the second call → sibling edges.
|
|
521
|
+
* - **Fan-in**: after `Promise.all` resolves, all parallel stages are in the
|
|
522
|
+
* frontier → the next stage depends on all of them.
|
|
523
|
+
*
|
|
431
524
|
* The returned function manages the full session lifecycle:
|
|
432
|
-
* spawn → run callback → flush saves → complete/error
|
|
525
|
+
* spawn → init client/session → run callback → flush saves → cleanup → complete/error.
|
|
433
526
|
*/
|
|
434
527
|
function createSessionRunner(
|
|
435
528
|
shared: SharedRunnerState,
|
|
436
529
|
parentName: string,
|
|
437
530
|
): <T = void>(
|
|
438
531
|
options: SessionRunOptions,
|
|
532
|
+
clientOpts: StageClientOptions<AgentType>,
|
|
533
|
+
sessionOpts: StageSessionOptions<AgentType>,
|
|
439
534
|
run: (ctx: SessionContext) => Promise<T>,
|
|
440
535
|
) => Promise<SessionHandle<T>> {
|
|
536
|
+
const graphTracker = new GraphFrontierTracker(parentName);
|
|
537
|
+
|
|
441
538
|
return async <T = void>(
|
|
442
539
|
options: SessionRunOptions,
|
|
540
|
+
clientOpts: StageClientOptions<AgentType>,
|
|
541
|
+
sessionOpts: StageSessionOptions<AgentType>,
|
|
443
542
|
run: (ctx: SessionContext) => Promise<T>,
|
|
444
543
|
): Promise<SessionHandle<T>> => {
|
|
445
544
|
const { name } = options;
|
|
446
|
-
const deps = options.dependsOn ?? [];
|
|
447
545
|
|
|
448
546
|
// ── 1. Validate name uniqueness (synchronous, before any await) ──
|
|
449
547
|
if (!name || name.trim() === "") {
|
|
@@ -453,23 +551,8 @@ function createSessionRunner(
|
|
|
453
551
|
throw new Error(`Duplicate session name: "${name}"`);
|
|
454
552
|
}
|
|
455
553
|
|
|
456
|
-
// ── 2.
|
|
457
|
-
|
|
458
|
-
if (deps.includes(name)) {
|
|
459
|
-
throw new Error(`Session "${name}" cannot depend on itself.`);
|
|
460
|
-
}
|
|
461
|
-
const unknown = deps.filter(
|
|
462
|
-
(d) =>
|
|
463
|
-
!shared.activeRegistry.has(d) && !shared.completedRegistry.has(d),
|
|
464
|
-
);
|
|
465
|
-
if (unknown.length > 0) {
|
|
466
|
-
throw new Error(
|
|
467
|
-
`Session "${name}" dependsOn unknown session(s): ${unknown.join(
|
|
468
|
-
", ",
|
|
469
|
-
)}. Dependencies must be spawned before the dependent session.`,
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
554
|
+
// ── 2. Auto-infer graph parents from frontier (synchronous) ──
|
|
555
|
+
const graphParents = graphTracker.onSpawn();
|
|
473
556
|
|
|
474
557
|
// ── 3. Create done promise so dependent sessions can await this one ──
|
|
475
558
|
let resolveDone!: () => void;
|
|
@@ -487,22 +570,8 @@ function createSessionRunner(
|
|
|
487
570
|
|
|
488
571
|
const sessionId = generateId();
|
|
489
572
|
let paneId = "";
|
|
490
|
-
// Graph parents: explicit deps if provided, otherwise the enclosing scope.
|
|
491
|
-
const graphParents = deps.length > 0 ? [...deps] : [parentName];
|
|
492
573
|
|
|
493
574
|
try {
|
|
494
|
-
// ── 5. Wait for dependsOn sessions to finish ──
|
|
495
|
-
// Active deps block; completed deps resolve immediately. If a dep
|
|
496
|
-
// rejects (its session failed), this throws and aborts the dependent.
|
|
497
|
-
if (deps.length > 0) {
|
|
498
|
-
await Promise.all(
|
|
499
|
-
deps.map((d) => {
|
|
500
|
-
const active = shared.activeRegistry.get(d);
|
|
501
|
-
if (active) return active.done;
|
|
502
|
-
return Promise.resolve();
|
|
503
|
-
}),
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
575
|
|
|
507
576
|
// ── 6. Allocate port ──
|
|
508
577
|
const port = await getRandomPort();
|
|
@@ -652,9 +721,21 @@ function createSessionRunner(
|
|
|
652
721
|
return JSON.parse(raw) as SavedMessage[];
|
|
653
722
|
};
|
|
654
723
|
|
|
655
|
-
// ── 12.
|
|
724
|
+
// ── 12. Auto-create provider client and session ──
|
|
725
|
+
const { client: providerClient, session: providerSession } =
|
|
726
|
+
await initProviderClientAndSession(
|
|
727
|
+
shared.agent,
|
|
728
|
+
serverUrl,
|
|
729
|
+
paneId,
|
|
730
|
+
sessionId,
|
|
731
|
+
clientOpts,
|
|
732
|
+
sessionOpts,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
// ── 13. Construct SessionContext ──
|
|
656
736
|
const ctx: SessionContext = {
|
|
657
|
-
|
|
737
|
+
client: providerClient as SessionContext["client"],
|
|
738
|
+
session: providerSession as SessionContext["session"],
|
|
658
739
|
userPrompt: shared.prompt,
|
|
659
740
|
agent: shared.agent,
|
|
660
741
|
sessionDir,
|
|
@@ -663,7 +744,7 @@ function createSessionRunner(
|
|
|
663
744
|
save,
|
|
664
745
|
transcript: transcriptFn,
|
|
665
746
|
getMessages: getMessagesFn,
|
|
666
|
-
|
|
747
|
+
stage: createSessionRunner(shared, name) as SessionContext["stage"],
|
|
667
748
|
};
|
|
668
749
|
|
|
669
750
|
// ── Write session metadata ──
|
|
@@ -684,7 +765,7 @@ function createSessionRunner(
|
|
|
684
765
|
),
|
|
685
766
|
);
|
|
686
767
|
|
|
687
|
-
// ──
|
|
768
|
+
// ── 14. Run user callback ──
|
|
688
769
|
let callbackResult: T;
|
|
689
770
|
try {
|
|
690
771
|
callbackResult = await run(ctx);
|
|
@@ -697,27 +778,29 @@ function createSessionRunner(
|
|
|
697
778
|
);
|
|
698
779
|
shared.panel.sessionError(name, message);
|
|
699
780
|
throw error;
|
|
781
|
+
} finally {
|
|
782
|
+
// ── 14a. Auto-cleanup provider resources ──
|
|
783
|
+
await cleanupProvider(shared.agent, providerClient, providerSession, paneId);
|
|
700
784
|
}
|
|
701
785
|
|
|
702
|
-
// ──
|
|
786
|
+
// ── 15. Mark session complete ──
|
|
703
787
|
shared.panel.sessionSuccess(name);
|
|
704
788
|
const result: SessionResult = { name, sessionId, sessionDir, paneId };
|
|
705
789
|
shared.completedRegistry.set(name, result);
|
|
706
790
|
shared.activeRegistry.delete(name);
|
|
707
791
|
resolveDone();
|
|
708
792
|
|
|
793
|
+
// Update frontier so the next stage in this scope chains from us.
|
|
794
|
+
graphTracker.onSettle(name);
|
|
709
795
|
return { name, id: sessionId, result: callbackResult! };
|
|
710
796
|
} catch (error) {
|
|
711
|
-
// Ensure the done promise settles and the active entry is cleared
|
|
712
|
-
// dependents fail fast instead of hanging forever on a ghost dep.
|
|
797
|
+
// Ensure the done promise settles and the active entry is cleared.
|
|
713
798
|
shared.activeRegistry.delete(name);
|
|
714
799
|
rejectDone(error);
|
|
800
|
+
// Update frontier even on failure — if the caller catches and
|
|
801
|
+
// continues, the next stage should still chain from this one.
|
|
802
|
+
graphTracker.onSettle(name);
|
|
715
803
|
throw error;
|
|
716
|
-
} finally {
|
|
717
|
-
// ── 15. Cleanup (Claude session state) ──
|
|
718
|
-
if (shared.agent === "claude" && paneId) {
|
|
719
|
-
clearClaudeSession(paneId);
|
|
720
|
-
}
|
|
721
804
|
}
|
|
722
805
|
};
|
|
723
806
|
}
|
|
@@ -831,7 +914,7 @@ async function runOrchestrator(): Promise<void> {
|
|
|
831
914
|
const workflowCtx: WorkflowContext = {
|
|
832
915
|
userPrompt: prompt,
|
|
833
916
|
agent,
|
|
834
|
-
|
|
917
|
+
stage: sessionRunner as WorkflowContext["stage"],
|
|
835
918
|
transcript: async (ref: SessionRef): Promise<Transcript> => {
|
|
836
919
|
const refName = resolveRef(ref);
|
|
837
920
|
const prev = shared.completedRegistry.get(refName);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontier-based graph inference for workflow stage topology.
|
|
3
|
+
*
|
|
4
|
+
* Automatically infers parent-child edges from JavaScript's execution order:
|
|
5
|
+
* - **Sequential** (`await`): completed stages are in the frontier when the
|
|
6
|
+
* next stage spawns → parent-child edge.
|
|
7
|
+
* - **Parallel** (`Promise.all`): both calls fire in the same synchronous
|
|
8
|
+
* frame → frontier is empty for the second call → sibling edges.
|
|
9
|
+
* - **Fan-in**: after `Promise.all` resolves, all parallel stages are in the
|
|
10
|
+
* frontier → the next stage depends on all of them.
|
|
11
|
+
*/
|
|
12
|
+
export class GraphFrontierTracker {
|
|
13
|
+
/**
|
|
14
|
+
* Stages that completed since the last stage was spawned in this scope.
|
|
15
|
+
* When non-empty at spawn time, the new stage is sequential (depends on frontier).
|
|
16
|
+
*/
|
|
17
|
+
private frontier: string[] = [];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The parent set for the current parallel batch — a snapshot of the frontier
|
|
21
|
+
* at the point the first sibling consumed it.
|
|
22
|
+
*/
|
|
23
|
+
private parallelAncestors: string[];
|
|
24
|
+
|
|
25
|
+
constructor(parentName: string) {
|
|
26
|
+
this.parallelAncestors = [parentName];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Called synchronously when a new stage is spawned.
|
|
31
|
+
* Returns the inferred graph parents for this stage.
|
|
32
|
+
*/
|
|
33
|
+
onSpawn(): string[] {
|
|
34
|
+
if (this.frontier.length > 0) {
|
|
35
|
+
// Sequential: previous stage(s) completed → new wave
|
|
36
|
+
this.parallelAncestors = [...this.frontier];
|
|
37
|
+
this.frontier = [];
|
|
38
|
+
}
|
|
39
|
+
// Parallel sibling, first stage, or sequential → same ancestors
|
|
40
|
+
return [...this.parallelAncestors];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Called when a stage settles (completes or fails).
|
|
45
|
+
* Adds the stage to the frontier so the next spawn can chain from it.
|
|
46
|
+
*/
|
|
47
|
+
onSettle(name: string): void {
|
|
48
|
+
this.frontier.push(name);
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/sdk/types.ts
CHANGED
|
@@ -8,9 +8,95 @@ import type { SessionEvent } from "@github/copilot-sdk";
|
|
|
8
8
|
import type { SessionPromptResponse } from "@opencode-ai/sdk/v2";
|
|
9
9
|
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
10
10
|
|
|
11
|
+
// Provider SDK types for the type maps
|
|
12
|
+
import type {
|
|
13
|
+
CopilotClient,
|
|
14
|
+
CopilotClientOptions,
|
|
15
|
+
CopilotSession,
|
|
16
|
+
SessionConfig as CopilotSessionConfig,
|
|
17
|
+
} from "@github/copilot-sdk";
|
|
18
|
+
import type {
|
|
19
|
+
OpencodeClient,
|
|
20
|
+
Session as OpencodeSession,
|
|
21
|
+
} from "@opencode-ai/sdk/v2";
|
|
22
|
+
import type {
|
|
23
|
+
ClaudeClientWrapper,
|
|
24
|
+
ClaudeSessionWrapper,
|
|
25
|
+
ClaudeQueryDefaults,
|
|
26
|
+
} from "./providers/claude.ts";
|
|
27
|
+
|
|
11
28
|
/** Supported agent types */
|
|
12
29
|
export type AgentType = "copilot" | "opencode" | "claude";
|
|
13
30
|
|
|
31
|
+
// ─── Provider type maps ─────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Maps each agent to the client init options the user passes to `ctx.stage()`.
|
|
35
|
+
* Auto-injected fields (`cliUrl`, `baseUrl`, `paneId`) are omitted.
|
|
36
|
+
*/
|
|
37
|
+
type ClientOptionsMap = {
|
|
38
|
+
opencode: { directory?: string; experimental_workspaceID?: string };
|
|
39
|
+
copilot: Omit<CopilotClientOptions, "cliUrl">;
|
|
40
|
+
claude: { chatFlags?: string[]; readyTimeoutMs?: number };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Maps each agent to the session create options the user passes to `ctx.stage()`.
|
|
45
|
+
* - OpenCode: `client.session.create()` body params
|
|
46
|
+
* - Copilot: `client.createSession()` config (onPermissionRequest defaults to approveAll)
|
|
47
|
+
* - Claude: `claudeQuery()` defaults for subsequent queries
|
|
48
|
+
*/
|
|
49
|
+
type SessionOptionsMap = {
|
|
50
|
+
opencode: {
|
|
51
|
+
parentID?: string;
|
|
52
|
+
title?: string;
|
|
53
|
+
workspaceID?: string;
|
|
54
|
+
};
|
|
55
|
+
copilot: Partial<CopilotSessionConfig>;
|
|
56
|
+
claude: ClaudeQueryDefaults;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Maps each agent to the `s.client` type provided in the stage callback. */
|
|
60
|
+
type ClientMap = {
|
|
61
|
+
opencode: OpencodeClient;
|
|
62
|
+
copilot: CopilotClient;
|
|
63
|
+
claude: ClaudeClientWrapper;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Maps each agent to the `s.session` type provided in the stage callback. */
|
|
67
|
+
type SessionMap = {
|
|
68
|
+
opencode: OpencodeSession;
|
|
69
|
+
copilot: CopilotSession;
|
|
70
|
+
claude: ClaudeSessionWrapper;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Client init options for `ctx.stage()`, resolved by agent type. */
|
|
74
|
+
export type StageClientOptions<A extends AgentType> = ClientOptionsMap[A];
|
|
75
|
+
|
|
76
|
+
/** Session create options for `ctx.stage()`, resolved by agent type. */
|
|
77
|
+
export type StageSessionOptions<A extends AgentType> = SessionOptionsMap[A];
|
|
78
|
+
|
|
79
|
+
/** The `s.client` type in a stage callback, resolved by agent type. */
|
|
80
|
+
export type ProviderClient<A extends AgentType> = ClientMap[A];
|
|
81
|
+
|
|
82
|
+
/** The `s.session` type in a stage callback, resolved by agent type. */
|
|
83
|
+
export type ProviderSession<A extends AgentType> = SessionMap[A];
|
|
84
|
+
|
|
85
|
+
// Re-export provider types for convenience
|
|
86
|
+
export type {
|
|
87
|
+
CopilotClient,
|
|
88
|
+
CopilotClientOptions,
|
|
89
|
+
CopilotSession,
|
|
90
|
+
CopilotSessionConfig,
|
|
91
|
+
OpencodeClient,
|
|
92
|
+
OpencodeSession,
|
|
93
|
+
ClaudeClientWrapper,
|
|
94
|
+
ClaudeSessionWrapper,
|
|
95
|
+
ClaudeQueryDefaults,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// ─── Core types ─────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
14
100
|
/**
|
|
15
101
|
* A transcript from a completed session.
|
|
16
102
|
* Provides both the file path and rendered text content.
|
|
@@ -34,7 +120,7 @@ export type SavedMessage =
|
|
|
34
120
|
/**
|
|
35
121
|
* Save native message objects from the provider SDK.
|
|
36
122
|
*
|
|
37
|
-
* - **Copilot**: `s.save(await session.getMessages())`
|
|
123
|
+
* - **Copilot**: `s.save(await s.session.getMessages())`
|
|
38
124
|
* - **OpenCode**: `s.save(result.data)` — the full `{ info, parts }` response
|
|
39
125
|
* - **Claude**: `s.save(sessionId)` — auto-reads via `getSessionMessages()`
|
|
40
126
|
*/
|
|
@@ -51,7 +137,7 @@ export interface SaveTranscript {
|
|
|
51
137
|
export type SessionRef = string | SessionHandle<unknown>;
|
|
52
138
|
|
|
53
139
|
/**
|
|
54
|
-
* Handle returned by `ctx.
|
|
140
|
+
* Handle returned by `ctx.stage()`. Used for type-safe transcript references
|
|
55
141
|
* and carries the callback's return value.
|
|
56
142
|
*/
|
|
57
143
|
export interface SessionHandle<T = void> {
|
|
@@ -64,45 +150,29 @@ export interface SessionHandle<T = void> {
|
|
|
64
150
|
}
|
|
65
151
|
|
|
66
152
|
/**
|
|
67
|
-
* Options for spawning a session via `ctx.
|
|
153
|
+
* Options for spawning a session via `ctx.stage()`.
|
|
68
154
|
*/
|
|
69
155
|
export interface SessionRunOptions {
|
|
70
156
|
/** Unique name for this session (used for transcript references and graph display) */
|
|
71
157
|
name: string;
|
|
72
158
|
/** Human-readable description */
|
|
73
159
|
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
160
|
}
|
|
94
161
|
|
|
95
162
|
/**
|
|
96
163
|
* Context provided to each session's callback.
|
|
97
|
-
* Created by `ctx.
|
|
164
|
+
* Created by `ctx.stage(opts, clientOpts, sessionOpts, fn)` — the callback
|
|
165
|
+
* receives this as its argument with pre-initialized `client` and `session`.
|
|
98
166
|
*/
|
|
99
|
-
export interface SessionContext {
|
|
100
|
-
/**
|
|
101
|
-
|
|
167
|
+
export interface SessionContext<A extends AgentType = AgentType> {
|
|
168
|
+
/** Provider-specific SDK client (auto-created by runtime) */
|
|
169
|
+
client: ProviderClient<A>;
|
|
170
|
+
/** Provider-specific session (auto-created by runtime) */
|
|
171
|
+
session: ProviderSession<A>;
|
|
102
172
|
/** The original user prompt from the CLI invocation */
|
|
103
173
|
userPrompt: string;
|
|
104
174
|
/** Which agent is running */
|
|
105
|
-
agent:
|
|
175
|
+
agent: A;
|
|
106
176
|
/**
|
|
107
177
|
* Get a completed session's transcript as rendered text.
|
|
108
178
|
* Accepts a SessionHandle (recommended) or session name string.
|
|
@@ -129,29 +199,34 @@ export interface SessionContext {
|
|
|
129
199
|
* The sub-session is a child of this session in the graph.
|
|
130
200
|
* The callback's return value is available as `handle.result`.
|
|
131
201
|
*/
|
|
132
|
-
|
|
202
|
+
stage<T = void>(
|
|
133
203
|
options: SessionRunOptions,
|
|
134
|
-
|
|
204
|
+
clientOpts: StageClientOptions<A>,
|
|
205
|
+
sessionOpts: StageSessionOptions<A>,
|
|
206
|
+
run: (ctx: SessionContext<A>) => Promise<T>,
|
|
135
207
|
): Promise<SessionHandle<T>>;
|
|
136
208
|
}
|
|
137
209
|
|
|
138
210
|
/**
|
|
139
211
|
* Top-level context provided to the workflow's `.run()` callback.
|
|
140
|
-
* Does not have session-specific fields (
|
|
212
|
+
* Does not have session-specific fields (paneId, save, etc.).
|
|
141
213
|
*/
|
|
142
|
-
export interface WorkflowContext {
|
|
214
|
+
export interface WorkflowContext<A extends AgentType = AgentType> {
|
|
143
215
|
/** The original user prompt from the CLI invocation */
|
|
144
216
|
userPrompt: string;
|
|
145
217
|
/** Which agent is running */
|
|
146
|
-
agent:
|
|
218
|
+
agent: A;
|
|
147
219
|
/**
|
|
148
220
|
* Spawn a session with its own tmux window and graph node.
|
|
149
|
-
* The runtime manages the full lifecycle:
|
|
150
|
-
* The callback's return value is available as
|
|
221
|
+
* The runtime manages the full lifecycle: create client → create session →
|
|
222
|
+
* run callback → cleanup. The callback's return value is available as
|
|
223
|
+
* `handle.result`.
|
|
151
224
|
*/
|
|
152
|
-
|
|
225
|
+
stage<T = void>(
|
|
153
226
|
options: SessionRunOptions,
|
|
154
|
-
|
|
227
|
+
clientOpts: StageClientOptions<A>,
|
|
228
|
+
sessionOpts: StageSessionOptions<A>,
|
|
229
|
+
run: (ctx: SessionContext<A>) => Promise<T>,
|
|
155
230
|
): Promise<SessionHandle<T>>;
|
|
156
231
|
/**
|
|
157
232
|
* Get a completed session's transcript as rendered text.
|
|
@@ -178,10 +253,10 @@ export interface WorkflowOptions {
|
|
|
178
253
|
/**
|
|
179
254
|
* A compiled workflow definition — the sealed output of defineWorkflow().compile().
|
|
180
255
|
*/
|
|
181
|
-
export interface WorkflowDefinition {
|
|
256
|
+
export interface WorkflowDefinition<A extends AgentType = AgentType> {
|
|
182
257
|
readonly __brand: "WorkflowDefinition";
|
|
183
258
|
readonly name: string;
|
|
184
259
|
readonly description: string;
|
|
185
260
|
/** The workflow's entry point. Called by the executor with a WorkflowContext. */
|
|
186
|
-
readonly run: (ctx: WorkflowContext) => Promise<void>;
|
|
261
|
+
readonly run: (ctx: WorkflowContext<A>) => Promise<void>;
|
|
187
262
|
}
|
package/src/sdk/workflows.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* atomic/workflows
|
|
3
3
|
*
|
|
4
4
|
* Workflow SDK for defining dynamic agent workflows.
|
|
5
|
-
* Workflows use defineWorkflow().run().compile() with ctx.
|
|
5
|
+
* Workflows use defineWorkflow().run().compile() with ctx.stage()
|
|
6
6
|
* for spawning agent sessions using native TypeScript control flow.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -20,6 +20,19 @@ export type {
|
|
|
20
20
|
WorkflowContext,
|
|
21
21
|
WorkflowOptions,
|
|
22
22
|
WorkflowDefinition,
|
|
23
|
+
StageClientOptions,
|
|
24
|
+
StageSessionOptions,
|
|
25
|
+
ProviderClient,
|
|
26
|
+
ProviderSession,
|
|
27
|
+
CopilotClient,
|
|
28
|
+
CopilotClientOptions,
|
|
29
|
+
CopilotSession,
|
|
30
|
+
CopilotSessionConfig,
|
|
31
|
+
OpencodeClient,
|
|
32
|
+
OpencodeSession,
|
|
33
|
+
ClaudeClientWrapper,
|
|
34
|
+
ClaudeSessionWrapper,
|
|
35
|
+
ClaudeQueryDefaults,
|
|
23
36
|
} from "./types.ts";
|
|
24
37
|
|
|
25
38
|
// Re-export native SDK types for convenience
|