@bastani/atomic 0.8.21 → 0.8.22-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/CHANGELOG.md +40 -9
- package/dist/builtin/intercom/broker/broker.ts +3 -3
- package/dist/builtin/intercom/config.ts +3 -3
- package/dist/builtin/intercom/index.ts +1 -1
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/ui/compose.ts +2 -2
- package/dist/builtin/mcp/host-html-template.ts +0 -3
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +13 -4
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +9 -9
- package/dist/builtin/subagents/agents/debugger.md +6 -6
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/builtin/subagents/skills/browser-use/SKILL.md +234 -0
- package/dist/builtin/subagents/skills/browser-use/references/cdp-python.md +76 -0
- package/dist/builtin/subagents/skills/browser-use/references/multi-session.md +92 -0
- package/dist/builtin/subagents/skills/subagent/SKILL.md +4 -4
- package/dist/builtin/subagents/src/agents/skills.ts +19 -1
- package/dist/builtin/subagents/src/extension/index.ts +24 -22
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +7 -1
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +23 -7
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +98 -3
- package/dist/builtin/subagents/src/runs/background/async-status.ts +3 -1
- package/dist/builtin/subagents/src/runs/background/run-status.ts +1 -1
- package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +3 -0
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +37 -12
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify.ts +15 -15
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +26 -2
- package/dist/builtin/subagents/src/runs/shared/nested-render.ts +1 -1
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +7 -0
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +28 -1
- package/dist/builtin/subagents/src/shared/fast-mode.ts +80 -0
- package/dist/builtin/subagents/src/shared/formatters.ts +4 -2
- package/dist/builtin/subagents/src/shared/types.ts +4 -2
- package/dist/builtin/subagents/src/shared/utils.ts +3 -61
- package/dist/builtin/subagents/src/tui/render.ts +303 -157
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +95 -35
- package/dist/builtin/workflows/README.md +228 -41
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +535 -541
- package/dist/builtin/workflows/builtin/goal.ts +39 -25
- package/dist/builtin/workflows/builtin/open-claude-design.ts +66 -69
- package/dist/builtin/workflows/builtin/ralph.ts +21 -21
- package/dist/builtin/workflows/package.json +6 -5
- package/dist/builtin/workflows/skills/research-codebase/SKILL.md +1 -1
- package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +2 -2
- package/dist/builtin/workflows/src/extension/discovery.ts +25 -146
- package/dist/builtin/workflows/src/extension/dispatcher.ts +72 -24
- package/dist/builtin/workflows/src/extension/hil-answer-notifications.ts +363 -0
- package/dist/builtin/workflows/src/extension/index.ts +690 -352
- package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +99 -62
- package/dist/builtin/workflows/src/extension/render-call.ts +2 -1
- package/dist/builtin/workflows/src/extension/render-result.ts +9 -3
- package/dist/builtin/workflows/src/extension/renderers.ts +5 -3
- package/dist/builtin/workflows/src/extension/runtime.ts +68 -33
- package/dist/builtin/workflows/src/extension/status-writer.ts +1 -1
- package/dist/builtin/workflows/src/extension/wiring.ts +34 -13
- package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +142 -0
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +4 -4
- package/dist/builtin/workflows/src/index.ts +2 -0
- package/dist/builtin/workflows/src/intercom/result-intercom.ts +1 -1
- package/dist/builtin/workflows/src/runs/background/runner.ts +6 -4
- package/dist/builtin/workflows/src/runs/background/status.ts +45 -21
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +624 -52
- package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +80 -24
- package/dist/builtin/workflows/src/runs/shared/validate-inputs.ts +61 -24
- package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +32 -10
- package/dist/builtin/workflows/src/sdk-surface.ts +6 -0
- package/dist/builtin/workflows/src/shared/expanded-workflow-graph.ts +178 -0
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +92 -12
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +21 -3
- package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +1 -2
- package/dist/builtin/workflows/src/shared/run-visibility.ts +9 -0
- package/dist/builtin/workflows/src/shared/schema-introspection.ts +121 -0
- package/dist/builtin/workflows/src/shared/serializable.ts +132 -0
- package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +91 -9
- package/dist/builtin/workflows/src/shared/store-types.ts +31 -3
- package/dist/builtin/workflows/src/shared/store.ts +58 -14
- package/dist/builtin/workflows/src/shared/types.ts +105 -40
- package/dist/builtin/workflows/src/tui/chat-surface-message.ts +129 -13
- package/dist/builtin/workflows/src/tui/chat-surface.ts +6 -1
- package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +3 -2
- package/dist/builtin/workflows/src/tui/graph-canvas.ts +1 -1
- package/dist/builtin/workflows/src/tui/graph-view.ts +91 -65
- package/dist/builtin/workflows/src/tui/inline-form-card.ts +1 -1
- package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +3 -2
- package/dist/builtin/workflows/src/tui/inputs-overlay.ts +3 -2
- package/dist/builtin/workflows/src/tui/inputs-picker.ts +8 -7
- package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +2 -0
- package/dist/builtin/workflows/src/tui/node-card.ts +34 -8
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +4 -11
- package/dist/builtin/workflows/src/tui/prompt-card.ts +98 -50
- package/dist/builtin/workflows/src/tui/session-list.ts +7 -2
- package/dist/builtin/workflows/src/tui/session-picker.ts +2 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +226 -55
- package/dist/builtin/workflows/src/tui/status-helpers.ts +2 -0
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +37 -158
- package/dist/builtin/workflows/src/tui/toast.ts +2 -2
- package/dist/builtin/workflows/src/tui/widget.ts +53 -12
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +270 -19
- package/dist/builtin/workflows/src/tui/workflow-notice-card.ts +184 -0
- package/dist/builtin/workflows/src/workflows/define-workflow.ts +138 -43
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +45 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +27 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +196 -17
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +2 -2
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/codex-fast-mode.d.ts +36 -0
- package/dist/core/codex-fast-mode.d.ts.map +1 -0
- package/dist/core/codex-fast-mode.js +117 -0
- package/dist/core/codex-fast-mode.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +1 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/index.d.ts +4 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -0
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +7 -2
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +23 -8
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/reactive-widget.d.ts +58 -0
- package/dist/core/extensions/reactive-widget.d.ts.map +1 -0
- package/dist/core/extensions/reactive-widget.js +182 -0
- package/dist/core/extensions/reactive-widget.js.map +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +1 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +26 -12
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/messages.d.ts +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +8 -2
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts +4 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +11 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/resource-loader.d.ts +9 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +49 -21
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +22 -13
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +7 -5
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +5 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +16 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +64 -5
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +7 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/ask-user-question.js +2 -2
- package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +12 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -1
- package/dist/modes/interactive/chat-input-actions.js.map +1 -1
- package/dist/modes/interactive/components/diff.d.ts.map +1 -1
- package/dist/modes/interactive/components/diff.js +0 -1
- package/dist/modes/interactive/components/diff.js.map +1 -1
- package/dist/modes/interactive/components/fast-mode-selector.d.ts +27 -0
- package/dist/modes/interactive/components/fast-mode-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/fast-mode-selector.js +105 -0
- package/dist/modes/interactive/components/fast-mode-selector.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +7 -12
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +132 -30
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +53 -6
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +3 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/compaction.md +1 -1
- package/docs/custom-provider.md +2 -2
- package/docs/development.md +2 -2
- package/docs/docs.json +2 -2
- package/docs/extensions.md +18 -13
- package/docs/providers.md +5 -1
- package/docs/quickstart.md +5 -3
- package/docs/rpc.md +5 -5
- package/docs/sdk.md +12 -12
- package/docs/settings.md +18 -0
- package/docs/themes.md +6 -6
- package/docs/tui.md +20 -18
- package/docs/usage.md +2 -0
- package/docs/workflows.md +403 -39
- package/examples/extensions/qna.ts +2 -2
- package/package.json +4 -4
- package/dist/builtin/subagents/skills/playwright-cli/SKILL.md +0 -392
- package/dist/builtin/subagents/skills/playwright-cli/references/element-attributes.md +0 -23
- package/dist/builtin/subagents/skills/playwright-cli/references/playwright-tests.md +0 -39
- package/dist/builtin/subagents/skills/playwright-cli/references/request-mocking.md +0 -87
- package/dist/builtin/subagents/skills/playwright-cli/references/running-code.md +0 -241
- package/dist/builtin/subagents/skills/playwright-cli/references/session-management.md +0 -225
- package/dist/builtin/subagents/skills/playwright-cli/references/spec-driven-testing.md +0 -305
- package/dist/builtin/subagents/skills/playwright-cli/references/storage-state.md +0 -275
- package/dist/builtin/subagents/skills/playwright-cli/references/test-generation.md +0 -134
- package/dist/builtin/subagents/skills/playwright-cli/references/tracing.md +0 -139
- package/dist/builtin/subagents/skills/playwright-cli/references/video-recording.md +0 -143
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { Component, Focusable, TUI } from "@earendil-works/pi-tui";
|
|
3
|
-
import type { Store } from "./store.js";
|
|
3
|
+
import type { StagePromptAnswerSource, Store } from "./store.js";
|
|
4
4
|
import { store as defaultStore } from "./store.js";
|
|
5
5
|
import type { StageInputRequest } from "./store-types.js";
|
|
6
6
|
import type { StageInputAnswer, StagePromptAdapter } from "./stage-prompt.js";
|
|
@@ -22,6 +22,22 @@ export interface StageCustomUiHost {
|
|
|
22
22
|
hideCustomUi?(request: StageCustomUiRequest, reason: unknown): void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export interface StagePromptResolvedEvent {
|
|
26
|
+
readonly runId: string;
|
|
27
|
+
readonly stageId: string;
|
|
28
|
+
readonly prompt: StageInputRequest;
|
|
29
|
+
readonly answer: unknown;
|
|
30
|
+
readonly answeredAt: number;
|
|
31
|
+
readonly answerSource?: StagePromptAnswerSource;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface AnswerStagePromptOptions {
|
|
35
|
+
/** Identifies who answered the prompt so notification code can avoid echoing workflow-tool answers. */
|
|
36
|
+
readonly answerSource?: StagePromptAnswerSource;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type StagePromptResolvedListener = (event: StagePromptResolvedEvent) => void;
|
|
40
|
+
|
|
25
41
|
function key(runId: string, stageId: string): string {
|
|
26
42
|
return `${runId}\0${stageId}`;
|
|
27
43
|
}
|
|
@@ -39,6 +55,8 @@ export class StageUiBroker {
|
|
|
39
55
|
// custom-UI prompt can be answered programmatically (e.g. via `workflow
|
|
40
56
|
// send`) without a TUI host rendering the interactive component.
|
|
41
57
|
private readonly adapters = new Map<string, StagePromptAdapter>();
|
|
58
|
+
private readonly resolvedPromptIds = new Map<string, string>();
|
|
59
|
+
private readonly resolvedListeners = new Set<StagePromptResolvedListener>();
|
|
42
60
|
|
|
43
61
|
constructor(store: Store = defaultStore) {
|
|
44
62
|
this.store = store;
|
|
@@ -52,7 +70,13 @@ export class StageUiBroker {
|
|
|
52
70
|
* after the matching `requestCustomUi`; the (runId, stageId) key joins them.
|
|
53
71
|
*/
|
|
54
72
|
provideStagePrompt(runId: string, stageId: string, adapter: StagePromptAdapter): void {
|
|
55
|
-
|
|
73
|
+
const hostKey = key(runId, stageId);
|
|
74
|
+
this.adapters.set(hostKey, adapter);
|
|
75
|
+
// A newly provided adapter represents a fresh prompt instance, even when
|
|
76
|
+
// the caller reuses the same prompt id (readiness gates historically did).
|
|
77
|
+
// Clear the resolved marker unconditionally so a raced answer for this new
|
|
78
|
+
// request cannot be mistaken for a duplicate answer to the prior instance.
|
|
79
|
+
this.resolvedPromptIds.delete(hostKey);
|
|
56
80
|
this.store.recordStageInputRequest(runId, stageId, adapter.prompt);
|
|
57
81
|
}
|
|
58
82
|
|
|
@@ -63,6 +87,23 @@ export class StageUiBroker {
|
|
|
63
87
|
}
|
|
64
88
|
}
|
|
65
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Settle and hide any brokered prompt that belongs to a stage whose workflow
|
|
92
|
+
* lifecycle has ended. Detaching a host intentionally does not cancel human
|
|
93
|
+
* input, but terminal stage/run cleanup must not leave stale HIL UI mounted.
|
|
94
|
+
*/
|
|
95
|
+
cancelStagePrompt(runId: string, stageId: string, reason: unknown): void {
|
|
96
|
+
const hostKey = key(runId, stageId);
|
|
97
|
+
const request = this.pending.get(hostKey);
|
|
98
|
+
if (request) {
|
|
99
|
+
this.reject(request, reason);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (this.adapters.delete(hostKey)) {
|
|
103
|
+
this.store.clearStageInputRequest(runId, stageId);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
66
107
|
/**
|
|
67
108
|
* Return the structured descriptor for a stage's brokered prompt when BOTH a
|
|
68
109
|
* headless-answer adapter and a live pending request exist for it — i.e. when
|
|
@@ -80,15 +121,41 @@ export class StageUiBroker {
|
|
|
80
121
|
* `ctx.ui.custom` promise with the adapter-built result. Returns `false` when
|
|
81
122
|
* there is no adapter+request pair for the stage.
|
|
82
123
|
*/
|
|
83
|
-
answerStagePrompt(
|
|
124
|
+
answerStagePrompt(
|
|
125
|
+
runId: string,
|
|
126
|
+
stageId: string,
|
|
127
|
+
answer: StageInputAnswer,
|
|
128
|
+
options: AnswerStagePromptOptions = {},
|
|
129
|
+
): boolean {
|
|
84
130
|
const hostKey = key(runId, stageId);
|
|
85
131
|
const adapter = this.adapters.get(hostKey);
|
|
86
132
|
const request = this.pending.get(hostKey);
|
|
87
133
|
if (!adapter || !request) return false;
|
|
88
|
-
this.resolve(request, adapter.buildResult(answer));
|
|
134
|
+
this.resolve(request, adapter.buildResult(answer), options);
|
|
89
135
|
return true;
|
|
90
136
|
}
|
|
91
137
|
|
|
138
|
+
wasStagePromptResolved(runId: string, stageId: string, promptId: string): boolean {
|
|
139
|
+
return this.resolvedPromptIds.get(key(runId, stageId)) === promptId;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
onStagePromptResolved(listener: StagePromptResolvedListener): () => void {
|
|
143
|
+
this.resolvedListeners.add(listener);
|
|
144
|
+
return () => {
|
|
145
|
+
this.resolvedListeners.delete(listener);
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private notifyStagePromptAnswered(event: StagePromptResolvedEvent): void {
|
|
150
|
+
for (const listener of this.resolvedListeners) {
|
|
151
|
+
try {
|
|
152
|
+
listener(event);
|
|
153
|
+
} catch {
|
|
154
|
+
// Listener failures must not prevent the prompt from resolving.
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
92
159
|
private hideHost(host: StageCustomUiHost | undefined, request: StageCustomUiRequest, reason: unknown): void {
|
|
93
160
|
try {
|
|
94
161
|
host?.hideCustomUi?.(request, reason);
|
|
@@ -113,7 +180,7 @@ export class StageUiBroker {
|
|
|
113
180
|
this.hideHost(
|
|
114
181
|
previousHost,
|
|
115
182
|
request,
|
|
116
|
-
new Error(`
|
|
183
|
+
new Error(`atomic-workflows: stage ${stageId} custom UI host replaced`),
|
|
117
184
|
);
|
|
118
185
|
}
|
|
119
186
|
this.hosts.set(hostKey, host);
|
|
@@ -139,12 +206,12 @@ export class StageUiBroker {
|
|
|
139
206
|
signal?: AbortSignal,
|
|
140
207
|
): Promise<T> {
|
|
141
208
|
if (signal?.aborted) {
|
|
142
|
-
return Promise.reject(signal.reason ?? new Error("
|
|
209
|
+
return Promise.reject(signal.reason ?? new Error("atomic-workflows: stage UI request aborted"));
|
|
143
210
|
}
|
|
144
211
|
const hostKey = key(runId, stageId);
|
|
145
212
|
const existing = this.pending.get(hostKey);
|
|
146
213
|
if (existing) {
|
|
147
|
-
return Promise.reject(new Error(`
|
|
214
|
+
return Promise.reject(new Error(`atomic-workflows: stage ${stageId} already has a pending custom UI request`));
|
|
148
215
|
}
|
|
149
216
|
|
|
150
217
|
let request!: StageCustomUiRequest<T>;
|
|
@@ -162,7 +229,7 @@ export class StageUiBroker {
|
|
|
162
229
|
});
|
|
163
230
|
|
|
164
231
|
const onAbort = (): void => {
|
|
165
|
-
this.reject(request, signal?.reason ?? new Error("
|
|
232
|
+
this.reject(request, signal?.reason ?? new Error("atomic-workflows: stage UI request aborted"));
|
|
166
233
|
};
|
|
167
234
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
168
235
|
|
|
@@ -179,14 +246,26 @@ export class StageUiBroker {
|
|
|
179
246
|
});
|
|
180
247
|
}
|
|
181
248
|
|
|
182
|
-
resolve<T>(request: StageCustomUiRequest<T>, value: T): void {
|
|
249
|
+
resolve<T>(request: StageCustomUiRequest<T>, value: T, options: AnswerStagePromptOptions = {}): void {
|
|
183
250
|
const hostKey = key(request.runId, request.stageId);
|
|
184
251
|
if (this.pending.get(hostKey)?.id !== request.id) return;
|
|
252
|
+
const prompt = this.adapters.get(hostKey)?.prompt;
|
|
185
253
|
this.pending.delete(hostKey);
|
|
186
254
|
this.adapters.delete(hostKey);
|
|
255
|
+
if (prompt !== undefined) this.resolvedPromptIds.set(hostKey, prompt.id);
|
|
187
256
|
this.store.clearStageInputRequest(request.runId, request.stageId);
|
|
188
257
|
this.store.recordStageAwaitingInput(request.runId, request.stageId, false);
|
|
189
258
|
this.hideHost(this.hosts.get(hostKey), request, undefined);
|
|
259
|
+
if (prompt !== undefined) {
|
|
260
|
+
this.notifyStagePromptAnswered({
|
|
261
|
+
runId: request.runId,
|
|
262
|
+
stageId: request.stageId,
|
|
263
|
+
prompt,
|
|
264
|
+
answer: value,
|
|
265
|
+
answeredAt: Date.now(),
|
|
266
|
+
...(options.answerSource !== undefined ? { answerSource: options.answerSource } : {}),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
190
269
|
request.resolve(value);
|
|
191
270
|
}
|
|
192
271
|
|
|
@@ -195,6 +274,7 @@ export class StageUiBroker {
|
|
|
195
274
|
if (this.pending.get(hostKey)?.id !== request.id) return;
|
|
196
275
|
this.pending.delete(hostKey);
|
|
197
276
|
this.adapters.delete(hostKey);
|
|
277
|
+
this.resolvedPromptIds.delete(hostKey);
|
|
198
278
|
this.store.clearStageInputRequest(request.runId, request.stageId);
|
|
199
279
|
this.store.recordStageAwaitingInput(request.runId, request.stageId, false);
|
|
200
280
|
this.hideHost(this.hosts.get(hostKey), request, reason);
|
|
@@ -214,12 +294,14 @@ export async function mountStageCustomUi(
|
|
|
214
294
|
keybindings: PiKeybindings,
|
|
215
295
|
broker: StageUiBroker,
|
|
216
296
|
onDone?: () => void,
|
|
297
|
+
canResolve?: () => boolean,
|
|
217
298
|
): Promise<MountedStageCustomUi> {
|
|
218
299
|
const rawComponent = await request.factory(
|
|
219
300
|
tui as unknown as Parameters<StageCustomUiRequest["factory"]>[0],
|
|
220
301
|
theme,
|
|
221
302
|
keybindings,
|
|
222
303
|
(result: unknown) => {
|
|
304
|
+
if (canResolve?.() === false) return;
|
|
223
305
|
broker.resolve(request, result);
|
|
224
306
|
onDone?.();
|
|
225
307
|
},
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* cross-ref: spec §5.5
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { WorkflowInputValues, WorkflowOutputValues } from "./types.js";
|
|
7
|
+
|
|
6
8
|
export type RunStatus = "pending" | "running" | "paused" | "completed" | "failed" | "killed";
|
|
7
9
|
export type StageStatus =
|
|
8
10
|
| "pending"
|
|
@@ -94,6 +96,20 @@ export interface StageNotice {
|
|
|
94
96
|
readonly meta?: string;
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
export interface WorkflowChildRunRef {
|
|
100
|
+
readonly alias: string;
|
|
101
|
+
readonly workflow: string;
|
|
102
|
+
readonly runId: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface WorkflowChildReplaySnapshot {
|
|
106
|
+
readonly alias: string;
|
|
107
|
+
readonly workflow: string;
|
|
108
|
+
readonly runId: string;
|
|
109
|
+
readonly status: "completed";
|
|
110
|
+
readonly outputs: WorkflowOutputValues;
|
|
111
|
+
}
|
|
112
|
+
|
|
97
113
|
export interface StageSnapshot {
|
|
98
114
|
readonly id: string;
|
|
99
115
|
readonly name: string;
|
|
@@ -125,6 +141,10 @@ export interface StageSnapshot {
|
|
|
125
141
|
replayedFromStageId?: string;
|
|
126
142
|
/** True when provider work was skipped by continuation replay. */
|
|
127
143
|
replayed?: boolean;
|
|
144
|
+
/** Live child workflow run metadata used to expand nested workflow graphs while the child is running. */
|
|
145
|
+
workflowChildRun?: WorkflowChildRunRef;
|
|
146
|
+
/** Snapshot-safe child workflow result metadata for continuation replay of import boundaries. */
|
|
147
|
+
workflowChild?: WorkflowChildReplaySnapshot;
|
|
128
148
|
readonly toolEvents: ToolEvent[];
|
|
129
149
|
/** True while an in-stage ask_user_question tool is waiting on the user. */
|
|
130
150
|
awaitingInputSince?: number;
|
|
@@ -155,8 +175,10 @@ export interface StageSnapshot {
|
|
|
155
175
|
*/
|
|
156
176
|
sessionId?: string;
|
|
157
177
|
sessionFile?: string;
|
|
158
|
-
/** Effective model selected for this stage after fallback resolution. */
|
|
178
|
+
/** Effective model id selected for this stage after fallback resolution. */
|
|
159
179
|
model?: string;
|
|
180
|
+
/** True when Codex fast mode applied to this workflow stage. */
|
|
181
|
+
fastMode?: boolean;
|
|
160
182
|
/** Ordered model ids attempted by fallback orchestration. */
|
|
161
183
|
attemptedModels?: readonly string[];
|
|
162
184
|
/** Per-model fallback attempt outcomes. */
|
|
@@ -180,7 +202,7 @@ export interface StageSnapshot {
|
|
|
180
202
|
export interface RunSnapshot {
|
|
181
203
|
readonly id: string;
|
|
182
204
|
readonly name: string;
|
|
183
|
-
readonly inputs: Readonly<
|
|
205
|
+
readonly inputs: Readonly<WorkflowInputValues>;
|
|
184
206
|
status: RunStatus;
|
|
185
207
|
readonly stages: StageSnapshot[];
|
|
186
208
|
startedAt: number;
|
|
@@ -192,7 +214,7 @@ export interface RunSnapshot {
|
|
|
192
214
|
pausedAt?: number;
|
|
193
215
|
/** Timestamp recorded on the most recent resume from a paused state. */
|
|
194
216
|
resumedAt?: number;
|
|
195
|
-
result?:
|
|
217
|
+
result?: WorkflowOutputValues;
|
|
196
218
|
error?: string;
|
|
197
219
|
/** Structured workflow failure category for failed runs. */
|
|
198
220
|
failureKind?: WorkflowFailureKind;
|
|
@@ -200,6 +222,12 @@ export interface RunSnapshot {
|
|
|
200
222
|
failureMessage?: string;
|
|
201
223
|
failedStageId?: string;
|
|
202
224
|
resumable?: boolean;
|
|
225
|
+
/** Parent workflow run when this snapshot is an internal child workflow run. Hidden from top-level status lists. */
|
|
226
|
+
parentRunId?: string;
|
|
227
|
+
/** Parent workflow boundary stage that launched this internal child workflow run. */
|
|
228
|
+
parentStageId?: string;
|
|
229
|
+
/** Top-level workflow run that owns this nested run tree. */
|
|
230
|
+
rootRunId?: string;
|
|
203
231
|
/** Source failed run when this run is a continuation. */
|
|
204
232
|
resumedFromRunId?: string;
|
|
205
233
|
/** Source stage id where continuation resumes real execution. */
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* cross-ref: spec §5.5
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { WorkflowOutputValues } from "./types.js";
|
|
6
7
|
import type {
|
|
7
8
|
PendingPrompt,
|
|
8
9
|
PromptKind,
|
|
@@ -16,8 +17,10 @@ import type {
|
|
|
16
17
|
StageStatus,
|
|
17
18
|
WorkflowFailureKind,
|
|
18
19
|
WorkflowNotice,
|
|
20
|
+
WorkflowChildRunRef,
|
|
19
21
|
} from "./store-types.js";
|
|
20
22
|
import { accumulatePausedDurationMs, elapsedRunMs } from "./timing.js";
|
|
23
|
+
import { isTopLevelWorkflowRun } from "./run-visibility.js";
|
|
21
24
|
|
|
22
25
|
/** Statuses that represent a terminal run state — cannot be overwritten. */
|
|
23
26
|
const TERMINAL_STATUSES: ReadonlySet<RunStatus> = new Set(["completed", "failed", "killed"]);
|
|
@@ -45,6 +48,8 @@ export interface RunEndMetadata {
|
|
|
45
48
|
readonly resumable?: boolean;
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
export type StagePromptAnswerSource = "workflow_ui" | "workflow_tool";
|
|
52
|
+
|
|
48
53
|
export interface PromptAnswerRecord {
|
|
49
54
|
readonly runId: string;
|
|
50
55
|
readonly stageId: string;
|
|
@@ -52,6 +57,7 @@ export interface PromptAnswerRecord {
|
|
|
52
57
|
readonly kind: PromptKind;
|
|
53
58
|
readonly value: unknown;
|
|
54
59
|
readonly answeredAt: number;
|
|
60
|
+
readonly answerSource?: StagePromptAnswerSource;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
export interface ResolveStagePendingPromptOptions {
|
|
@@ -60,6 +66,8 @@ export interface ResolveStagePendingPromptOptions {
|
|
|
60
66
|
* continuation replay. Abort/default resolutions should set this to false.
|
|
61
67
|
*/
|
|
62
68
|
readonly recordAnswer?: boolean;
|
|
69
|
+
/** Identifies who answered the prompt so notification code can avoid echoing workflow-tool answers. */
|
|
70
|
+
readonly answerSource?: StagePromptAnswerSource;
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
export interface Store {
|
|
@@ -68,6 +76,8 @@ export interface Store {
|
|
|
68
76
|
activeRunId(): string | null;
|
|
69
77
|
recordRunStart(run: RunSnapshot): void;
|
|
70
78
|
recordStageStart(runId: string, stage: StageSnapshot): void;
|
|
79
|
+
/** Link a workflow boundary stage to its live child run before that child completes. */
|
|
80
|
+
recordStageWorkflowChildRun(runId: string, stageId: string, ref: WorkflowChildRunRef): boolean;
|
|
71
81
|
recordToolStart(runId: string, stageId: string, evt: ToolEvent): void;
|
|
72
82
|
recordToolEnd(runId: string, stageId: string, evt: ToolEvent): void;
|
|
73
83
|
recordStageEnd(runId: string, stage: StageSnapshot): void;
|
|
@@ -81,7 +91,7 @@ export interface Store {
|
|
|
81
91
|
recordRunEnd(
|
|
82
92
|
runId: string,
|
|
83
93
|
status: RunStatus,
|
|
84
|
-
result?:
|
|
94
|
+
result?: WorkflowOutputValues,
|
|
85
95
|
error?: string,
|
|
86
96
|
metadata?: RunEndMetadata,
|
|
87
97
|
): boolean;
|
|
@@ -298,7 +308,19 @@ export function createStore(): Store {
|
|
|
298
308
|
},
|
|
299
309
|
|
|
300
310
|
activeRunId(): string | null {
|
|
301
|
-
// Most recently started run that hasn't ended
|
|
311
|
+
// Most recently started top-level run that hasn't ended. Nested
|
|
312
|
+
// workflow runs stay in the store for live control/expanded graph
|
|
313
|
+
// rendering, but should not steal the active top-level workflow slot.
|
|
314
|
+
for (let i = _runs.length - 1; i >= 0; i--) {
|
|
315
|
+
const run = _runs[i];
|
|
316
|
+
if (run && isTopLevelWorkflowRun(run) && run.endedAt === undefined) {
|
|
317
|
+
return run.id;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Fallback for the degraded "orphaned-nested-only" state: a child run is in
|
|
321
|
+
// flight but no top-level run is. This normally cannot happen (a parent
|
|
322
|
+
// stays in flight while awaiting `ctx.workflow(...)`), so callers that rely
|
|
323
|
+
// on a top-level id should treat a returned nested id as best-effort.
|
|
302
324
|
for (let i = _runs.length - 1; i >= 0; i--) {
|
|
303
325
|
const run = _runs[i];
|
|
304
326
|
if (run && run.endedAt === undefined) {
|
|
@@ -325,6 +347,24 @@ export function createStore(): Store {
|
|
|
325
347
|
notify();
|
|
326
348
|
},
|
|
327
349
|
|
|
350
|
+
recordStageWorkflowChildRun(runId: string, stageId: string, ref: WorkflowChildRunRef): boolean {
|
|
351
|
+
const run = findRun(runId);
|
|
352
|
+
if (!run) return false;
|
|
353
|
+
const stage = findStage(run, stageId);
|
|
354
|
+
if (!stage) return false;
|
|
355
|
+
if (
|
|
356
|
+
stage.workflowChildRun?.runId === ref.runId &&
|
|
357
|
+
stage.workflowChildRun.alias === ref.alias &&
|
|
358
|
+
stage.workflowChildRun.workflow === ref.workflow
|
|
359
|
+
) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
stage.workflowChildRun = { ...ref };
|
|
363
|
+
_version++;
|
|
364
|
+
notify();
|
|
365
|
+
return true;
|
|
366
|
+
},
|
|
367
|
+
|
|
328
368
|
recordToolStart(runId: string, stageId: string, evt: ToolEvent): void {
|
|
329
369
|
const run = findRun(runId);
|
|
330
370
|
if (!run) return;
|
|
@@ -383,9 +423,11 @@ export function createStore(): Store {
|
|
|
383
423
|
if (stage.promptAnswerState !== undefined) existing.promptAnswerState = stage.promptAnswerState;
|
|
384
424
|
if (stage.replayedFromStageId !== undefined) existing.replayedFromStageId = stage.replayedFromStageId;
|
|
385
425
|
if (stage.replayed !== undefined) existing.replayed = stage.replayed;
|
|
426
|
+
if (stage.workflowChildRun !== undefined) existing.workflowChildRun = { ...stage.workflowChildRun };
|
|
427
|
+
if (stage.workflowChild !== undefined) existing.workflowChild = structuredClone(stage.workflowChild);
|
|
386
428
|
delete existing.awaitingInputSince;
|
|
387
429
|
delete existing.inputRequest;
|
|
388
|
-
rejectStagePrompt(existing, `
|
|
430
|
+
rejectStagePrompt(existing, `atomic-workflows: stage ${stage.id} ended before prompt resolved`);
|
|
389
431
|
_version++;
|
|
390
432
|
notify();
|
|
391
433
|
},
|
|
@@ -393,7 +435,7 @@ export function createStore(): Store {
|
|
|
393
435
|
recordRunEnd(
|
|
394
436
|
runId: string,
|
|
395
437
|
status: RunStatus,
|
|
396
|
-
result?:
|
|
438
|
+
result?: WorkflowOutputValues,
|
|
397
439
|
error?: string,
|
|
398
440
|
metadata?: RunEndMetadata,
|
|
399
441
|
): boolean {
|
|
@@ -430,9 +472,9 @@ export function createStore(): Store {
|
|
|
430
472
|
const pending = run.pendingPrompt;
|
|
431
473
|
if (pending) {
|
|
432
474
|
run.pendingPrompt = undefined;
|
|
433
|
-
rejectPrompt(pending.id, `
|
|
475
|
+
rejectPrompt(pending.id, `atomic-workflows: run ${runId} ended before prompt resolved`);
|
|
434
476
|
}
|
|
435
|
-
rejectAllStagePrompts(run, `
|
|
477
|
+
rejectAllStagePrompts(run, `atomic-workflows: run ${runId} ended before prompt resolved`);
|
|
436
478
|
_version++;
|
|
437
479
|
notify();
|
|
438
480
|
return true;
|
|
@@ -444,9 +486,9 @@ export function createStore(): Store {
|
|
|
444
486
|
const run = _runs[index]!;
|
|
445
487
|
const pending = run.pendingPrompt;
|
|
446
488
|
if (pending) {
|
|
447
|
-
rejectPrompt(pending.id, `
|
|
489
|
+
rejectPrompt(pending.id, `atomic-workflows: run ${runId} was removed before prompt resolved`);
|
|
448
490
|
}
|
|
449
|
-
rejectAllStagePrompts(run, `
|
|
491
|
+
rejectAllStagePrompts(run, `atomic-workflows: run ${runId} was removed before prompt resolved`);
|
|
450
492
|
for (const stage of run.stages) {
|
|
451
493
|
_stagePromptAnswers.delete(stagePromptAnswerKey(runId, stage.id));
|
|
452
494
|
}
|
|
@@ -511,14 +553,14 @@ export function createStore(): Store {
|
|
|
511
553
|
return new Promise<unknown>((resolve, reject) => {
|
|
512
554
|
const run = findRun(runId);
|
|
513
555
|
if (!run) {
|
|
514
|
-
reject(new Error(`
|
|
556
|
+
reject(new Error(`atomic-workflows: run "${runId}" not found`));
|
|
515
557
|
return;
|
|
516
558
|
}
|
|
517
559
|
const pending = run.pendingPrompt;
|
|
518
560
|
if (!pending || pending.id !== promptId) {
|
|
519
561
|
reject(
|
|
520
562
|
new Error(
|
|
521
|
-
`
|
|
563
|
+
`atomic-workflows: pending prompt "${promptId}" not registered on run "${runId}"`,
|
|
522
564
|
),
|
|
523
565
|
);
|
|
524
566
|
return;
|
|
@@ -565,6 +607,7 @@ export function createStore(): Store {
|
|
|
565
607
|
kind: pending.kind,
|
|
566
608
|
value: response,
|
|
567
609
|
answeredAt: Date.now(),
|
|
610
|
+
...(options.answerSource !== undefined ? { answerSource: options.answerSource } : {}),
|
|
568
611
|
});
|
|
569
612
|
stage.promptAnswerState = "available";
|
|
570
613
|
} else {
|
|
@@ -590,19 +633,19 @@ export function createStore(): Store {
|
|
|
590
633
|
return new Promise<unknown>((resolve, reject) => {
|
|
591
634
|
const run = findRun(runId);
|
|
592
635
|
if (!run) {
|
|
593
|
-
reject(new Error(`
|
|
636
|
+
reject(new Error(`atomic-workflows: run "${runId}" not found`));
|
|
594
637
|
return;
|
|
595
638
|
}
|
|
596
639
|
const stage = findStage(run, stageId);
|
|
597
640
|
if (!stage) {
|
|
598
|
-
reject(new Error(`
|
|
641
|
+
reject(new Error(`atomic-workflows: stage "${stageId}" not found on run "${runId}"`));
|
|
599
642
|
return;
|
|
600
643
|
}
|
|
601
644
|
const pending = stage.pendingPrompt;
|
|
602
645
|
if (!pending || pending.id !== promptId) {
|
|
603
646
|
reject(
|
|
604
647
|
new Error(
|
|
605
|
-
`
|
|
648
|
+
`atomic-workflows: pending prompt "${promptId}" not registered on stage "${stageId}" in run "${runId}"`,
|
|
606
649
|
),
|
|
607
650
|
);
|
|
608
651
|
return;
|
|
@@ -700,6 +743,7 @@ export function createStore(): Store {
|
|
|
700
743
|
stage.status = "awaiting_input";
|
|
701
744
|
stage.awaitingInputSince = ts ?? Date.now();
|
|
702
745
|
} else {
|
|
746
|
+
if (stage.pendingPrompt !== undefined) return false;
|
|
703
747
|
if (stage.status !== "awaiting_input") return false;
|
|
704
748
|
stage.status = "running";
|
|
705
749
|
delete stage.awaitingInputSince;
|
|
@@ -859,7 +903,7 @@ export function createStore(): Store {
|
|
|
859
903
|
// instead of leaking. The error message is intentionally generic — the
|
|
860
904
|
// caller already issued a session boundary, exact cause isn't needed.
|
|
861
905
|
for (const entry of _resolvers.values()) {
|
|
862
|
-
entry.reject(new Error("
|
|
906
|
+
entry.reject(new Error("atomic-workflows: store cleared"));
|
|
863
907
|
}
|
|
864
908
|
_resolvers.clear();
|
|
865
909
|
_stagePromptAnswers.clear();
|