@bastani/atomic 0.9.11-alpha.3 → 0.9.11-alpha.5
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 +19 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +7 -0
- package/dist/builtin/subagents/README.md +10 -1
- package/dist/builtin/subagents/agents/debugger.md +12 -10
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +6 -6
- package/dist/builtin/subagents/src/extension/prompt-guidance.ts +4 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +29 -0
- package/dist/builtin/workflows/README.md +3 -3
- package/dist/builtin/workflows/builtin/goal-artifacts.ts +1 -1
- package/dist/builtin/workflows/builtin/goal-models.ts +33 -29
- package/dist/builtin/workflows/builtin/goal-orchestrator-prompts.ts +133 -0
- package/dist/builtin/workflows/builtin/goal-prompts.ts +23 -58
- package/dist/builtin/workflows/builtin/goal-reducer.ts +1 -1
- package/dist/builtin/workflows/builtin/goal-runner.ts +40 -52
- package/dist/builtin/workflows/builtin/goal.ts +10 -10
- package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +1 -1
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +13 -8
- package/dist/builtin/workflows/builtin/ralph-runner.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph.ts +4 -4
- package/dist/builtin/workflows/builtin/shared-prompts.ts +37 -7
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/src/authoring/workflow.ts +1 -0
- package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +27 -1
- package/dist/builtin/workflows/src/durable/factory.ts +29 -3
- package/dist/builtin/workflows/src/durable/index.ts +2 -0
- package/dist/builtin/workflows/src/engine/run-durable-admission.ts +50 -0
- package/dist/builtin/workflows/src/engine/run.ts +12 -17
- package/dist/builtin/workflows/src/extension/extension-factory.ts +16 -1
- package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +16 -2
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +71826 -0
- package/dist/builtin/workflows/src/extension/runtime.ts +5 -3
- package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +4 -1
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +14 -5
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +1 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +1 -0
- package/dist/builtin/workflows/src/shared/types.ts +7 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +1 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +1 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +6 -8
- package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +2 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +4 -0
- package/dist/cli/args.d.ts +8 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +16 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/atomic-guide-command.js +3 -3
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/slash-commands.js +2 -2
- 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 +11 -0
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.js +5 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +7 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +12 -6
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive-engine/engine-monitor.d.ts +8 -1
- package/dist/modes/interactive-engine/engine-monitor.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-monitor.js +13 -15
- package/dist/modes/interactive-engine/engine-monitor.js.map +1 -1
- package/dist/modes/rpc/rpc-client-process.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-process.js +3 -0
- package/dist/modes/rpc/rpc-client-process.js.map +1 -1
- package/dist/modes/rpc/rpc-extension-ui.d.ts +3 -1
- package/dist/modes/rpc/rpc-extension-ui.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-extension-ui.js +7 -2
- package/dist/modes/rpc/rpc-extension-ui.js.map +1 -1
- package/dist/modes/rpc/rpc-session-binding.d.ts +1 -0
- package/dist/modes/rpc/rpc-session-binding.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-session-binding.js +66 -43
- package/dist/modes/rpc/rpc-session-binding.js.map +1 -1
- package/dist/rpc-entry.js +6 -2
- package/dist/rpc-entry.js.map +1 -1
- package/dist/utils/compile-cache.d.ts +8 -0
- package/dist/utils/compile-cache.d.ts.map +1 -0
- package/dist/utils/compile-cache.js +31 -0
- package/dist/utils/compile-cache.js.map +1 -0
- package/docs/quickstart.md +3 -3
- package/docs/subagents.md +10 -2
- package/docs/tui.md +2 -0
- package/docs/usage.md +16 -0
- package/docs/workflows.md +96 -45
- package/npm-shrinkwrap.json +23 -23
- package/package.json +2 -2
|
@@ -23,7 +23,7 @@ export function renderE2eQaVideoReviewGuidance(
|
|
|
23
23
|
knownVideoPath?: string,
|
|
24
24
|
): string {
|
|
25
25
|
const target = knownVideoPath === undefined || knownVideoPath.length === 0
|
|
26
|
-
? "Look for QA E2E video references in the goal ledger,
|
|
26
|
+
? "Look for QA E2E video references in the goal ledger, implementation receipt, implementation notes, orchestrator report, or other review context artifacts."
|
|
27
27
|
: `Known QA E2E video path for this run: ${knownVideoPath}`;
|
|
28
28
|
return [
|
|
29
29
|
target,
|
|
@@ -31,7 +31,7 @@ export function renderE2eQaVideoReviewGuidance(
|
|
|
31
31
|
"Use available video/file tooling such as `fetch_content` on the local video path with a prompt focused on whether the recording proves the required user scenario, or inspect representative frames/metadata when full video analysis is unavailable.",
|
|
32
32
|
"Check that the video reflects the current repository/application state, exercises the objective-relevant user path, shows the expected final behavior, and does not visibly hide errors, stale UI, broken loading states, or skipped steps.",
|
|
33
33
|
"For UI-applicable or full-stack changes, treat a missing, stale, unreadable, or inconclusive QA video as missing E2E evidence unless the receipt or implementation notes justify why no video applies and provide adequate alternate end-to-end proof.",
|
|
34
|
-
"Treat skipped E2E due to assumed-missing credentials, auth, or environment access as missing evidence unless the
|
|
34
|
+
"Treat skipped E2E due to assumed-missing credentials, auth, or environment access as missing evidence unless the implementation agent actually checked credential/auth state, attempted the launch/flow, and reported exact commands plus observed failure output.",
|
|
35
35
|
].join("\n");
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -55,7 +55,7 @@ export const REVIEWER_SPEC_VS_OBJECTIVE_GUARD =
|
|
|
55
55
|
"Do not use external spec/standard conformance alone to flag a wide trigger surface for an error condition the objective/acceptance criteria enumerate; the contract prefers loud errors over silent reinterpretation of ambiguous inputs, so classify such spec-vs-objective tension as beyond_objective rather than a blocking defect.";
|
|
56
56
|
|
|
57
57
|
export const REVIEWER_OVERIMPLEMENTATION_GUARD =
|
|
58
|
-
"Hunt over-implementation as seriously as gaps: any validation error, required field, uniqueness/format constraint, immutability wrapper, or normalization the contract does not require is a defect that rejects inputs or produces shapes the contract permits — classify it required_by_objective. Probe at least one contract-permitted input the
|
|
58
|
+
"Hunt over-implementation as seriously as gaps: any validation error, required field, uniqueness/format constraint, immutability wrapper, or normalization the contract does not require is a defect that rejects inputs or produces shapes the contract permits — classify it required_by_objective. Probe at least one contract-permitted input the implementation's own tests do not exercise before approving.";
|
|
59
59
|
|
|
60
60
|
export const ACCEPTANCE_MATRIX_CONTRACT = [
|
|
61
61
|
"Acceptance/contract matrix:",
|
|
@@ -87,10 +87,24 @@ export const REVIEWER_INTERCOM_COORDINATION_PROTOCOL = [
|
|
|
87
87
|
|
|
88
88
|
export const REVIEWER_INDEPENDENT_VERIFICATION_CONTRACT = [
|
|
89
89
|
"Independent verification derivation:",
|
|
90
|
-
"- Before relying on the
|
|
91
|
-
"-
|
|
92
|
-
"-
|
|
93
|
-
"-
|
|
90
|
+
"- Before relying on the implementation receipt, implementation-authored tests, or any prior reviewer output, derive your own adversarial check list from the literal objective and acceptance criteria alone: per-clause observable checks plus boundary, edge, negative, and invalid-input probes; contract-permitted-input probes; exact type/shape/text-identity probes; and state/transition/invariant probes.",
|
|
91
|
+
"- Apply this conditional contract-probe playbook when supported by the contract and repository:",
|
|
92
|
+
" - Exact public API/type contracts: create a minimal external-consumer compile or typecheck probe using the names, parameter types, return types, field types, pointer/value identity, and method shapes stated by the objective.",
|
|
93
|
+
" - Build tags/features/configuration variants: exercise every named positive and negative build-tag, feature, or configuration variant; prove required symbols compile and forbidden symbols are unavailable.",
|
|
94
|
+
" - Schemas and generated artifacts: regenerate or inspect the authoritative schema, probe omitted and zero-value fields, and verify required-versus-optional behavior and downstream representation match the literal contract.",
|
|
95
|
+
" - Stateful behavior: enumerate relevant states and mutation paths and exercise the transition matrix, not only happy-path end states; for boolean membership or predicate behavior this includes false→false, false→true, true→false, and true→true when applicable.",
|
|
96
|
+
" - Configurable paths and precedence: use temporary or injected paths, changed working directories, and relevant environment or configuration overrides; verify initialization and defaults do not overwrite caller-controlled state.",
|
|
97
|
+
" - Low-level APIs versus feature flags: exercise direct loaders, parsers, or validators with the surrounding feature both enabled and disabled unless the literal low-level API contract explicitly makes that flag authoritative.",
|
|
98
|
+
" - Permissive inputs and over-implementation: probe at least one contract-permitted omitted, empty, zero, duplicate, aliased, or unusual value that an implementation may have made unnecessarily invalid.",
|
|
99
|
+
"- Select only the risk classes supported by the literal objective and repository context. These are generic risk classes, not hidden test cases; do not manufacture requirements outside the literal contract.",
|
|
100
|
+
"- Execute or delegate every applicable material probe against the current repository state before mapping implementation evidence to requirements. Name each command or scenario and its observed result in the existing narrative and requirements_traceability fields.",
|
|
101
|
+
"- Implementation-authored tests, snapshots, and receipts corroborate your derived checks; they never substitute for them. Passing implementation-authored tests is circular evidence for the clauses those tests were written from. Repository-local or implementation-authored tests are not sufficient evidence for an exact API, build, or schema clause without the applicable independent compile, type, build-variant, or schema probe.",
|
|
102
|
+
"- A compile, type, build, or schema requirement without its applicable independent probe remains unverified: keep its requirements_traceability status missing, explain the gap, add an objective-aligned finding when the patch is materially deficient, and set stop_review_loop=false.",
|
|
103
|
+
"- When an applicable material probe is missing, blocked, or failed, record the command or scenario and its observed result or limitation in overall_explanation and requirements_traceability, use the workflow's existing remaining-verification or finding fields, and set stop_review_loop=false. When tools or dependencies prevent necessary verification after reasonable recovery, populate the existing reviewer_error field instead of approving around the limitation.",
|
|
104
|
+
"",
|
|
105
|
+
"Pre-verdict self-audit:",
|
|
106
|
+
"- Before returning stop_review_loop=true, confirm overall_correctness is patch is correct; every objective-relevant implementation and validation requirements_traceability entry is proven; no blocking objective-aligned finding remains; every applicable exact API, build, schema, state, configuration, and feature-flag risk has direct evidence or a clear explanation of why it does not apply; and reviewer_error is null or omitted.",
|
|
107
|
+
"- If any item in this self-audit is false or unverified, set stop_review_loop=false and report the gap through the existing fields; never make the structured verdict internally inconsistent.",
|
|
94
108
|
].join("\n");
|
|
95
109
|
|
|
96
110
|
export const REGRESSION_EVIDENCE_CONTRACT = [
|
|
@@ -114,3 +128,19 @@ export const EVIDENCE_CLOSURE_POLICY = [
|
|
|
114
128
|
"- Derive stop_review_loop=true when independent verification proves the implementation and validation requirements and everything left is non-blocking: consistent_with_objective P3 nice-to-haves, beyond_objective/contradicts_objective observations, an explicitly authorized post-approval final action such as PR/MR/review creation, or the multi-reviewer quorum process itself. Never hold the flag at false for those items — quorum is counted by the harness across reviewers and is not an implementation gap any single reviewer can prove.",
|
|
115
129
|
"- The loop is bounded: when the turn budget ends before convergence, the run stops with the unresolved findings and remaining work recorded for a human instead of relabeling them away.",
|
|
116
130
|
].join("\n");
|
|
131
|
+
|
|
132
|
+
export const WORKTREE_DISCIPLINE_CONTRACT = [
|
|
133
|
+
"Worktree discipline:",
|
|
134
|
+
"- Do all work in the working directory this stage was invoked in (the workflow-designated checkout/worktree).",
|
|
135
|
+
"- Never create additional git worktrees, clones, or repository copies unless the user's task explicitly requests them; a merge conflict, a locked file, a dirty tree, or a failed command is not such a request.",
|
|
136
|
+
"- If you discover required work stranded in another worktree, clone, or copy, bring it into the invoking checkout (apply, cherry-pick, or replay the changes) before continuing; work left outside the invoking checkout does not exist for review or delivery.",
|
|
137
|
+
].join("\n");
|
|
138
|
+
|
|
139
|
+
export const REVIEW_CODE_DELTA_CONTRACT = [
|
|
140
|
+
"Code delta presence and integrity:",
|
|
141
|
+
"- Review the actual code delta, and first prove that delta exists where the workflow delivers it: in the invoking working directory, or in the explicitly configured git worktree when the run was set up with one.",
|
|
142
|
+
"- Use the repository's version-control tooling to inspect state (for git: `git worktree list`, `git status --short`, and a diff against the baseline branch; use the equivalent commands for other systems). If receipts, implementation notes, or stage summaries claim implemented work but the review checkout shows no corresponding delta, that is a blocking [P0] required_by_objective finding: the work may be stranded in another worktree, clone, or unapplied state. Do not approve; require the work to be brought into the review checkout first.",
|
|
143
|
+
"- Never set stop_review_loop=true for an implementation objective when the review checkout's delta is empty or unrelated to that objective; an empty delta cannot satisfy an implementation objective regardless of what receipts claim.",
|
|
144
|
+
"- Unless the objective explicitly forbids committing, treat uncommitted work at claimed readiness as remaining work: require the implementation to be committed (or outstanding changes intentionally discarded) so the delivered state is durable.",
|
|
145
|
+
"- Treat any modification, rename, or deletion of pre-existing test files or test functions in the delta as a finding requiring explicit justification against the literal contract; validating against existing tests means running them, not editing them.",
|
|
146
|
+
].join("\n");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/workflows",
|
|
3
|
-
"version": "0.9.11-alpha.
|
|
3
|
+
"version": "0.9.11-alpha.5",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for multi-stage workflow authoring and execution.",
|
|
6
6
|
"contributors": [
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"pi": {
|
|
69
69
|
"extensions": [
|
|
70
|
-
"./src/extension/index.
|
|
70
|
+
"./src/extension/index.bundle.mjs"
|
|
71
71
|
],
|
|
72
72
|
"builtin": [
|
|
73
73
|
"./builtin"
|
|
@@ -169,6 +169,7 @@ export function workflow<
|
|
|
169
169
|
name,
|
|
170
170
|
normalizedName,
|
|
171
171
|
description: spec.description,
|
|
172
|
+
...(spec.autoAttach === true ? { autoAttach: true } : {}),
|
|
172
173
|
inputs: frozenInputs,
|
|
173
174
|
outputs: frozenOutputs,
|
|
174
175
|
...(inputBindings !== undefined ? { inputBindings } : {}),
|
|
@@ -15,7 +15,8 @@ export type DbosLifecycleState =
|
|
|
15
15
|
| "launching"
|
|
16
16
|
| "ready"
|
|
17
17
|
| "failed"
|
|
18
|
-
| "shutting_down"
|
|
18
|
+
| "shutting_down"
|
|
19
|
+
| "shut_down";
|
|
19
20
|
|
|
20
21
|
export class DbosDurabilityError extends Error {
|
|
21
22
|
constructor(message: string, options?: ErrorOptions) {
|
|
@@ -30,6 +31,16 @@ export class DbosNotReadyError extends DbosDurabilityError {
|
|
|
30
31
|
this.name = "DbosNotReadyError";
|
|
31
32
|
}
|
|
32
33
|
}
|
|
34
|
+
|
|
35
|
+
export class DbosShutdownError extends DbosDurabilityError {
|
|
36
|
+
constructor() {
|
|
37
|
+
super(
|
|
38
|
+
"DBOS workflow durability has been shut down in this process. "
|
|
39
|
+
+ "Durable workflows can no longer start; restart Atomic to restore durability.",
|
|
40
|
+
);
|
|
41
|
+
this.name = "DbosShutdownError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
33
44
|
type DbosConfigurator = () => Promise<ConfiguredDbosDurability>;
|
|
34
45
|
type LocalDbosProvisioner = () => Promise<void>;
|
|
35
46
|
|
|
@@ -78,6 +89,10 @@ export async function configureDbosOnce(): Promise<ConfiguredDbosDurability> {
|
|
|
78
89
|
|
|
79
90
|
export async function launchDbosOnce(): Promise<void> {
|
|
80
91
|
if (failure !== undefined) throw failure;
|
|
92
|
+
// The executor is process-scoped and stops exactly once, at process exit.
|
|
93
|
+
// Post-shutdown launches must fail loudly instead of returning a backend
|
|
94
|
+
// whose SDK launched marker has been cleared.
|
|
95
|
+
if (state === "shutting_down" || state === "shut_down") throw new DbosShutdownError();
|
|
81
96
|
const durability = await configureDbosOnce();
|
|
82
97
|
launchPromise ??= (async () => {
|
|
83
98
|
state = "launching";
|
|
@@ -133,6 +148,7 @@ export async function shutdownDbos(): Promise<void> {
|
|
|
133
148
|
state = "shutting_down";
|
|
134
149
|
await durability.backend.flush();
|
|
135
150
|
await durability.shutdown();
|
|
151
|
+
state = "shut_down";
|
|
136
152
|
})().catch((error: unknown) => {
|
|
137
153
|
failure = durabilityFailure("shutdown", error);
|
|
138
154
|
state = "failed";
|
|
@@ -141,6 +157,16 @@ export async function shutdownDbos(): Promise<void> {
|
|
|
141
157
|
await shutdownPromise;
|
|
142
158
|
}
|
|
143
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Flush queued durable writes without stopping the process-scoped executor.
|
|
162
|
+
* Used at process-preserving host-session boundaries (`/new`, `/resume`,
|
|
163
|
+
* `/fork`, `/reload`) where the DBOS executor must stay launched.
|
|
164
|
+
*/
|
|
165
|
+
export async function flushDbos(): Promise<void> {
|
|
166
|
+
if (state !== "ready" || active === undefined) return;
|
|
167
|
+
await active.backend.flush();
|
|
168
|
+
}
|
|
169
|
+
|
|
144
170
|
export function dbosLifecycleState(): DbosLifecycleState {
|
|
145
171
|
return state;
|
|
146
172
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { InMemoryDurableBackend, type DurableWorkflowBackend } from "./backend.js";
|
|
4
4
|
import {
|
|
5
5
|
DbosNotReadyError,
|
|
6
|
+
DbosShutdownError,
|
|
7
|
+
dbosLifecycleState,
|
|
6
8
|
getReadyDbosBackend,
|
|
7
9
|
getReadyDbosBackendSync,
|
|
8
10
|
} from "./dbos-lifecycle.js";
|
|
@@ -11,9 +13,22 @@ let injectedBackend: DurableWorkflowBackend | undefined;
|
|
|
11
13
|
let initializedBackend: DurableWorkflowBackend | undefined;
|
|
12
14
|
let initializing: Promise<DurableWorkflowBackend> | undefined;
|
|
13
15
|
|
|
16
|
+
/**
|
|
17
|
+
* A memoized backend is only reusable while its lifecycle generation is
|
|
18
|
+
* healthy. The in-memory degraded backend has no external lifecycle; a
|
|
19
|
+
* persistent (DBOS) backend is usable only while the process-scoped DBOS
|
|
20
|
+
* executor is still `ready` — never after shutdown.
|
|
21
|
+
*/
|
|
22
|
+
function isMemoizedBackendUsable(backend: DurableWorkflowBackend): boolean {
|
|
23
|
+
return !backend.persistent || dbosLifecycleState() === "ready";
|
|
24
|
+
}
|
|
25
|
+
|
|
14
26
|
/** Return the injected test backend or the process-wide initialized backend. */
|
|
15
27
|
export function getDurableBackend(): DurableWorkflowBackend {
|
|
16
|
-
const
|
|
28
|
+
const memoized = initializedBackend !== undefined && isMemoizedBackendUsable(initializedBackend)
|
|
29
|
+
? initializedBackend
|
|
30
|
+
: undefined;
|
|
31
|
+
const backend = injectedBackend ?? memoized ?? getReadyDbosBackendSync();
|
|
17
32
|
if (backend === undefined) throw new DbosNotReadyError();
|
|
18
33
|
return backend;
|
|
19
34
|
}
|
|
@@ -44,9 +59,20 @@ export function createInMemoryTestBackend(): InMemoryDurableBackend {
|
|
|
44
59
|
*/
|
|
45
60
|
export async function initializeDurableBackend(): Promise<DurableWorkflowBackend> {
|
|
46
61
|
if (injectedBackend !== undefined) return injectedBackend;
|
|
47
|
-
if (initializedBackend !== undefined)
|
|
62
|
+
if (initializedBackend !== undefined) {
|
|
63
|
+
if (isMemoizedBackendUsable(initializedBackend)) return initializedBackend;
|
|
64
|
+
// Never hand out a backend from a stopped lifecycle generation.
|
|
65
|
+
initializedBackend = undefined;
|
|
66
|
+
initializing = undefined;
|
|
67
|
+
}
|
|
48
68
|
initializing ??= getReadyDbosBackend()
|
|
49
|
-
.catch((error: unknown) =>
|
|
69
|
+
.catch((error: unknown) => {
|
|
70
|
+
// Post-shutdown initialization is a process-exit race, not a
|
|
71
|
+
// provisioning failure: fail loudly instead of silently degrading to a
|
|
72
|
+
// non-durable backend.
|
|
73
|
+
if (error instanceof DbosShutdownError) throw error;
|
|
74
|
+
return degradeToNonDurableBackend(error);
|
|
75
|
+
})
|
|
50
76
|
.then((backend) => {
|
|
51
77
|
initializedBackend = backend;
|
|
52
78
|
return backend;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable root-run registration and startup admission.
|
|
3
|
+
*
|
|
4
|
+
* Root registration/status must be durably persisted before startup admission
|
|
5
|
+
* or workflow code runs: a stopped or unhealthy backend fails here, before
|
|
6
|
+
* any side effects execute (issue #1957).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { DurableWorkflowBackend, WorkflowRegistrationInput } from "../durable/backend.js";
|
|
10
|
+
import type { WorkflowSerializableValue } from "../shared/types.js";
|
|
11
|
+
|
|
12
|
+
/** Build the root registration handle, or `undefined` when the run must not (re-)register. */
|
|
13
|
+
export function durableRootRegistrationForRun(args: {
|
|
14
|
+
readonly runId: string;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly inputs: Readonly<Record<string, unknown>>;
|
|
17
|
+
readonly createdAt: number;
|
|
18
|
+
readonly hasPersistence: boolean;
|
|
19
|
+
readonly isChildRun: boolean;
|
|
20
|
+
readonly continuationSourceId: string | undefined;
|
|
21
|
+
}): WorkflowRegistrationInput | undefined {
|
|
22
|
+
const shouldRegister = !args.isChildRun
|
|
23
|
+
&& (args.continuationSourceId === undefined || args.continuationSourceId !== args.runId);
|
|
24
|
+
if (!shouldRegister) return undefined;
|
|
25
|
+
return {
|
|
26
|
+
workflowId: args.runId,
|
|
27
|
+
name: args.name,
|
|
28
|
+
inputs: args.inputs as Record<string, WorkflowSerializableValue>,
|
|
29
|
+
createdAt: args.createdAt,
|
|
30
|
+
status: "running" as const,
|
|
31
|
+
rootWorkflowId: args.runId,
|
|
32
|
+
resumable: true,
|
|
33
|
+
...(args.hasPersistence ? { sessionFile: undefined } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Register/mark the root run and flush so admission requires healthy durable persistence. */
|
|
38
|
+
export async function admitDurableRootRun(args: {
|
|
39
|
+
readonly backend: DurableWorkflowBackend;
|
|
40
|
+
readonly runId: string;
|
|
41
|
+
readonly isChildRun: boolean;
|
|
42
|
+
readonly registration: WorkflowRegistrationInput | undefined;
|
|
43
|
+
}): Promise<void> {
|
|
44
|
+
if (args.registration !== undefined) {
|
|
45
|
+
args.backend.registerWorkflow(args.registration);
|
|
46
|
+
} else if (!args.isChildRun) {
|
|
47
|
+
args.backend.setWorkflowStatus(args.runId, "running");
|
|
48
|
+
}
|
|
49
|
+
if (!args.isChildRun) await args.backend.flush();
|
|
50
|
+
}
|
|
@@ -47,6 +47,7 @@ import { finalizeDurableTerminalStatus } from "./run-durable-finalize.js";
|
|
|
47
47
|
import { createDurableStageSessionRecorder } from "./run-durable-stage-session.js";
|
|
48
48
|
import type { DurableWorkflowBackend } from "../durable/backend.js";
|
|
49
49
|
import { createDurableCachedStageRecorder, createDurableStageDeps } from "./run-durable-topology.js";
|
|
50
|
+
import { admitDurableRootRun, durableRootRegistrationForRun } from "./run-durable-admission.js";
|
|
50
51
|
|
|
51
52
|
type WorkflowRunInputArgument = Parameters<typeof resolveAndValidateInputs>[1];
|
|
52
53
|
|
|
@@ -312,18 +313,11 @@ export async function run<
|
|
|
312
313
|
},
|
|
313
314
|
runWorkflow: run,
|
|
314
315
|
});
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
status: "running" as const,
|
|
321
|
-
rootWorkflowId: runId,
|
|
322
|
-
resumable: true,
|
|
323
|
-
...(opts.persistence !== undefined ? { sessionFile: undefined } : {}),
|
|
324
|
-
};
|
|
325
|
-
const shouldRegisterDurableRoot = opts.parentRun === undefined
|
|
326
|
-
&& (opts.continuation === undefined || opts.continuation.source.id !== runId);
|
|
316
|
+
const durableRootRegistration = durableRootRegistrationForRun({
|
|
317
|
+
runId, name: def.name, inputs: resolvedInputs, createdAt: runSnapshot.startedAt,
|
|
318
|
+
hasPersistence: opts.persistence !== undefined, isChildRun: opts.parentRun !== undefined,
|
|
319
|
+
continuationSourceId: opts.continuation?.source.id,
|
|
320
|
+
});
|
|
327
321
|
const tool = createToolPrimitive({
|
|
328
322
|
workflowId: runId,
|
|
329
323
|
backend: durableBackend,
|
|
@@ -394,6 +388,7 @@ export async function run<
|
|
|
394
388
|
parallel: createParallelPrimitive({ runtime, task: durableTask }),
|
|
395
389
|
workflow: durableWorkflow,
|
|
396
390
|
tool,
|
|
391
|
+
...(opts.models !== undefined ? { models: opts.models } : {}),
|
|
397
392
|
};
|
|
398
393
|
|
|
399
394
|
try {
|
|
@@ -408,11 +403,11 @@ export async function run<
|
|
|
408
403
|
}
|
|
409
404
|
}
|
|
410
405
|
|
|
411
|
-
|
|
412
|
-
durableBackend
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
406
|
+
await admitDurableRootRun({
|
|
407
|
+
backend: durableBackend, runId, isChildRun: opts.parentRun !== undefined,
|
|
408
|
+
registration: durableRootRegistration === undefined ? undefined
|
|
409
|
+
: { ...durableRootRegistration, ...workflowInvocationMetadata(inputRuntimeDefaults, workflowInvocationCwd, gitWorktreeSetupCache) },
|
|
410
|
+
});
|
|
416
411
|
if (opts.deferWorkflowStart === true) opts.onWorkflowStartReady?.();
|
|
417
412
|
const rawResult = await runWorkflowDefinitionCallback(def.name, runId, () => def.run(ctx));
|
|
418
413
|
if (ownController.signal.aborted) {
|
|
@@ -20,6 +20,7 @@ import type { PostMortemHandleResolution } from "../tui/workflow-attach-pane-typ
|
|
|
20
20
|
import { registerWorkflowTool } from "./workflow-tool-registration.js";
|
|
21
21
|
import { registerWorkflowSlashCommand } from "./workflow-command-registration.js";
|
|
22
22
|
import { installInputInterceptor, type WorkflowCommandHandler } from "./workflow-command-utils.js";
|
|
23
|
+
import { workflowPolicyFromContext } from "./workflow-policy.js";
|
|
23
24
|
import { overlaySurfaceFromContext } from "./workflow-targets.js";
|
|
24
25
|
|
|
25
26
|
function registerWorkflowMessageRenderers(pi: ExtensionAPI): void {
|
|
@@ -91,8 +92,22 @@ function factory(pi: ExtensionAPI): void {
|
|
|
91
92
|
runtimeState.ensureWorkflowResourcesLoaded,
|
|
92
93
|
{ resolvePostMortemDeps: (runId) => postMortemDepsForRun(runId, postMortemResolverDeps) },
|
|
93
94
|
);
|
|
95
|
+
const executeWorkflowToolWithAutoAttach: typeof executeWorkflowTool = async (args, ctx) => {
|
|
96
|
+
const result = await executeWorkflowTool(args, ctx);
|
|
97
|
+
if (
|
|
98
|
+
workflowPolicyFromContext(ctx).mode === "interactive" &&
|
|
99
|
+
typeof args.workflow === "string" &&
|
|
100
|
+
result.action === "run" &&
|
|
101
|
+
result.status === "running" &&
|
|
102
|
+
result.runId.length > 0 &&
|
|
103
|
+
runtimeState.runtimeProxy.registry.get(args.workflow)?.autoAttach === true
|
|
104
|
+
) {
|
|
105
|
+
overlay.open(result.runId, overlaySurfaceFromContext(ctx));
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
};
|
|
94
109
|
|
|
95
|
-
registerWorkflowTool(pi,
|
|
110
|
+
registerWorkflowTool(pi, executeWorkflowToolWithAutoAttach, runtimeState.runWithLifecycleSuppressedForPolicy);
|
|
96
111
|
registerWorkflowSlashCommand(pi, workflowCommands, {
|
|
97
112
|
runtimeProxy: runtimeState.runtimeProxy,
|
|
98
113
|
runtimeForContext: runtimeState.runtimeForContext,
|
|
@@ -12,7 +12,7 @@ import type { ExtensionAPI } from "./public-types.js";
|
|
|
12
12
|
import type { WorkflowExtensionRuntimeState } from "./extension-runtime-state.js";
|
|
13
13
|
import { deAdvertiseAskUserQuestionWhenHeadless, formatStartupDiagnostics } from "./workflow-command-surfaces.js";
|
|
14
14
|
import { inFlightRunCount } from "./workflow-targets.js";
|
|
15
|
-
import { shutdownDbos } from "../durable/dbos-lifecycle.js";
|
|
15
|
+
import { flushDbos, shutdownDbos } from "../durable/dbos-lifecycle.js";
|
|
16
16
|
|
|
17
17
|
let processShutdownInstalled = false;
|
|
18
18
|
|
|
@@ -28,6 +28,19 @@ function shutdownDbosQuietly(): Promise<void> {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Process-preserving host-session boundaries (`/new`, `/resume`, `/fork`,
|
|
33
|
+
* `/reload`) must NOT stop the process-scoped DBOS executor: the replacement
|
|
34
|
+
* session reuses it for subsequent workflow runs. Flush pending durable
|
|
35
|
+
* writes instead, and reserve SDK shutdown for actual process exit.
|
|
36
|
+
*/
|
|
37
|
+
function flushDbosQuietly(): Promise<void> {
|
|
38
|
+
return flushDbos().catch((error: unknown) => {
|
|
39
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
40
|
+
console.error(`atomic-workflows: DBOS durability flush failed: ${detail}`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
function installDbosProcessShutdown(): void {
|
|
32
45
|
if (processShutdownInstalled) return;
|
|
33
46
|
processShutdownInstalled = true;
|
|
@@ -135,6 +148,7 @@ export function registerWorkflowLifecycleHandlers(
|
|
|
135
148
|
deps.storeWidgetRef.current = null;
|
|
136
149
|
runtimeState.resetWorkflowDiscoveryForSession();
|
|
137
150
|
runtimeState.setNotificationsActive(false);
|
|
138
|
-
await shutdownDbosQuietly();
|
|
151
|
+
if (reason === "quit") await shutdownDbosQuietly();
|
|
152
|
+
else await flushDbosQuietly();
|
|
139
153
|
});
|
|
140
154
|
}
|