@bastani/atomic 0.9.14-alpha.4 → 0.9.14-alpha.6
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 +13 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/extension/schemas.ts +5 -0
- package/dist/builtin/subagents/src/runs/shared/long-running-guard.ts +3 -1
- package/dist/builtin/subagents/src/runs/shared/progress-trend.ts +69 -0
- package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +12 -1
- package/dist/builtin/subagents/src/shared/types-results.ts +2 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +47 -1
- package/dist/builtin/workflows/README.md +6 -5
- package/dist/builtin/workflows/builtin/adversarial-verification-prompts.ts +13 -5
- package/dist/builtin/workflows/builtin/adversarial-verification-runner.ts +376 -89
- package/dist/builtin/workflows/builtin/adversarial-verification.d.ts +30 -6
- package/dist/builtin/workflows/builtin/adversarial-verification.ts +14 -9
- package/dist/builtin/workflows/builtin/generate-and-filter-prompts.ts +26 -3
- package/dist/builtin/workflows/builtin/generate-and-filter-runner.ts +18 -14
- package/dist/builtin/workflows/builtin/goal-artifacts.ts +9 -8
- package/dist/builtin/workflows/builtin/goal-convergence.ts +87 -0
- package/dist/builtin/workflows/builtin/goal-ledger.ts +4 -0
- package/dist/builtin/workflows/builtin/goal-prompts.ts +2 -0
- package/dist/builtin/workflows/builtin/goal-reducer.ts +6 -1
- package/dist/builtin/workflows/builtin/goal-reverify.ts +305 -0
- package/dist/builtin/workflows/builtin/goal-runner.ts +75 -10
- package/dist/builtin/workflows/builtin/goal-schemas.ts +7 -0
- package/dist/builtin/workflows/builtin/goal-types.ts +6 -0
- package/dist/builtin/workflows/builtin/loop-until-done-runner.ts +94 -6
- package/dist/builtin/workflows/builtin/loop-until-done.d.ts +8 -0
- package/dist/builtin/workflows/builtin/loop-until-done.ts +15 -0
- package/dist/builtin/workflows/builtin/progress-scoring.ts +230 -0
- package/dist/builtin/workflows/builtin/ralph-core.ts +11 -0
- package/dist/builtin/workflows/builtin/ralph-review-gate.ts +1 -0
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph-runner.ts +60 -10
- package/dist/builtin/workflows/builtin/selection-math.ts +156 -0
- package/dist/builtin/workflows/builtin/shared-prompts.ts +5 -0
- package/dist/builtin/workflows/builtin/tournament-prompts.ts +57 -75
- package/dist/builtin/workflows/builtin/tournament-runner.ts +384 -178
- package/dist/builtin/workflows/builtin/tournament.d.ts +46 -17
- package/dist/builtin/workflows/builtin/tournament.ts +66 -32
- package/dist/builtin/workflows/builtin/verification-criteria.ts +330 -0
- package/dist/builtin/workflows/builtin/verification-prompts.ts +206 -0
- package/dist/builtin/workflows/builtin/verification-usage.ts +44 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +90 -30
- package/dist/builtin/workflows/skills/show-me/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/show-me/SKILL.md +143 -0
- package/dist/builtin/workflows/src/authoring/workflow.ts +8 -0
- package/dist/builtin/workflows/src/authoring.d.ts +1 -1
- package/dist/builtin/workflows/src/durable/completed-catalog.ts +5 -2
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +1 -1
- package/dist/builtin/workflows/src/durable/resume-eligibility.ts +5 -3
- package/dist/builtin/workflows/src/durable/run-timing.ts +41 -10
- package/dist/builtin/workflows/src/durable/tool-primitive.ts +24 -2
- package/dist/builtin/workflows/src/engine/options.ts +1 -0
- package/dist/builtin/workflows/src/engine/primitives/workflow.ts +12 -3
- package/dist/builtin/workflows/src/engine/run-budget.ts +308 -0
- package/dist/builtin/workflows/src/engine/run-returned-status.ts +8 -0
- package/dist/builtin/workflows/src/engine/run-tool-node-lifecycle.ts +6 -0
- package/dist/builtin/workflows/src/engine/run.ts +124 -2
- package/dist/builtin/workflows/src/engine/runtime.ts +9 -0
- package/dist/builtin/workflows/src/extension/config-file-loader.ts +6 -0
- package/dist/builtin/workflows/src/extension/config-loader.ts +24 -1
- package/dist/builtin/workflows/src/extension/dispatcher.ts +6 -5
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +2 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +2975 -843
- package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +51 -4
- package/dist/builtin/workflows/src/extension/public-types.ts +3 -1
- package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +7 -1
- package/dist/builtin/workflows/src/extension/runtime.ts +22 -10
- package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +5 -0
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +1 -0
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +16 -0
- package/dist/builtin/workflows/src/extension/workflow-status-summary.ts +44 -1
- package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +10 -1
- package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +21 -9
- package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +14 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-lifecycle.ts +15 -4
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +62 -5
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +4 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +2 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +3 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +10 -1
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +1 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +1 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +7 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +7 -0
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +1 -0
- package/dist/builtin/workflows/src/shared/budget-meter.ts +34 -0
- package/dist/builtin/workflows/src/shared/budget.d.ts +67 -0
- package/dist/builtin/workflows/src/shared/budget.ts +127 -0
- package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +92 -8
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +11 -1
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +15 -3
- package/dist/builtin/workflows/src/shared/returned-run-status.ts +35 -2
- package/dist/builtin/workflows/src/shared/store-public-types.ts +4 -1
- package/dist/builtin/workflows/src/shared/store-run-methods.ts +8 -1
- package/dist/builtin/workflows/src/shared/store-stage-methods.ts +1 -0
- package/dist/builtin/workflows/src/shared/store-types.ts +24 -0
- package/dist/builtin/workflows/src/shared/types.ts +3 -0
- package/dist/builtin/workflows/src/shared/workflow-artifacts.ts +1 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +3 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +3 -0
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +1 -0
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/extensions/ui-types.d.ts +13 -3
- package/dist/core/extensions/ui-types.d.ts.map +1 -1
- package/dist/core/extensions/ui-types.js +15 -3
- package/dist/core/extensions/ui-types.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +33 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/main-deferred-startup.d.ts.map +1 -1
- package/dist/main-deferred-startup.js +6 -2
- package/dist/main-deferred-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +4 -0
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-tui.js +19 -1
- package/dist/modes/interactive/interactive-tui.js.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.d.ts +7 -0
- package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +94 -37
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +1 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +15 -2
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.d.ts +3 -2
- package/dist/modes/rpc/rpc-input-scheduler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.js +5 -2
- package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
- package/docs/extensions.md +1 -1
- package/docs/quickstart.md +1 -0
- package/docs/skills.md +4 -0
- package/docs/workflows.md +74 -10
- package/npm-shrinkwrap.json +29 -29
- package/package.json +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BudgetReport } from "../shared/budget.js";
|
|
1
2
|
import {
|
|
2
3
|
actionableReturnedStatusText,
|
|
3
4
|
effectiveRunStatus,
|
|
@@ -33,6 +34,7 @@ export type WorkflowLifecycleNoticeKind =
|
|
|
33
34
|
| "completed"
|
|
34
35
|
| "failed"
|
|
35
36
|
| "blocked"
|
|
37
|
+
| "budget_warning"
|
|
36
38
|
| "awaiting_input"
|
|
37
39
|
| "paused"
|
|
38
40
|
| "quit"
|
|
@@ -45,12 +47,12 @@ export type WorkflowLifecycleNoticeKind =
|
|
|
45
47
|
* action to a user.
|
|
46
48
|
*/
|
|
47
49
|
type WorkflowControlNoticeKind = "started" | "paused" | "quit" | "resumed";
|
|
48
|
-
|
|
49
50
|
export const WORKFLOW_LIFECYCLE_NOTICE_KINDS = [
|
|
50
51
|
"started",
|
|
51
52
|
"completed",
|
|
52
53
|
"failed",
|
|
53
54
|
"blocked",
|
|
55
|
+
"budget_warning",
|
|
54
56
|
"awaiting_input",
|
|
55
57
|
"paused",
|
|
56
58
|
"quit",
|
|
@@ -80,6 +82,7 @@ export interface WorkflowLifecycleNoticeDetails {
|
|
|
80
82
|
readonly toolNodeId?: string;
|
|
81
83
|
readonly toolName?: string;
|
|
82
84
|
readonly durationMs?: number;
|
|
85
|
+
readonly budgetDimension?: "duration" | "tokens" | "cost";
|
|
83
86
|
readonly active?: boolean;
|
|
84
87
|
/** Whether a failed author exit or quit run advertises itself as resumable. */
|
|
85
88
|
readonly resumable?: boolean;
|
|
@@ -156,6 +159,8 @@ export function seedWorkflowLifecycleNotificationState(
|
|
|
156
159
|
state.deliveredTerminalRuns.add(controlKey);
|
|
157
160
|
}
|
|
158
161
|
}
|
|
162
|
+
for (const warning of budgetWarningReports(run))
|
|
163
|
+
state.deliveredTerminalRuns.add(budgetWarningKey(run, warning.dimension));
|
|
159
164
|
if (run.pendingPrompt !== undefined) {
|
|
160
165
|
state.deliveredInputPrompts.add(runAwaitingInputKey(run.id, run.pendingPrompt));
|
|
161
166
|
}
|
|
@@ -308,6 +313,15 @@ export function installWorkflowLifecycleNotifications(options: WorkflowLifecycle
|
|
|
308
313
|
// through workflow status/connect surfaces instead of the main chat context.
|
|
309
314
|
};
|
|
310
315
|
|
|
316
|
+
const emitBudgetWarningOnce = (run: RunSnapshot): void => {
|
|
317
|
+
for (const warning of budgetWarningReports(run)) {
|
|
318
|
+
const key = budgetWarningKey(run, warning.dimension);
|
|
319
|
+
if (state.deliveredTerminalRuns.has(key) || state.pendingTerminalRuns.has(key)) continue;
|
|
320
|
+
if (state.suppressionDepth > 0) state.deliveredTerminalRuns.add(key);
|
|
321
|
+
else delivery.deliver(key, makeBudgetWarningNotice(run, warning));
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
311
325
|
const inspect = (snapshot: StoreSnapshot): void => {
|
|
312
326
|
for (const run of snapshot.runs) {
|
|
313
327
|
if (!isTopLevelWorkflowRun(run)) continue;
|
|
@@ -316,6 +330,7 @@ export function installWorkflowLifecycleNotifications(options: WorkflowLifecycle
|
|
|
316
330
|
emitTerminalNoticeOnce(run, "blocked");
|
|
317
331
|
emitControlNoticesOnce(run);
|
|
318
332
|
|
|
333
|
+
if (notifyOn.has("budget_warning")) emitBudgetWarningOnce(run);
|
|
319
334
|
if (!notifyOn.has("awaiting_input")) continue;
|
|
320
335
|
emitRunAwaitingInputNoticeOnce(run);
|
|
321
336
|
for (const stage of run.stages) {
|
|
@@ -386,6 +401,11 @@ export function formatWorkflowLifecycleNoticeText(details: WorkflowLifecycleNoti
|
|
|
386
401
|
const stateText = details.active === true ? "is blocked" : "ended blocked";
|
|
387
402
|
return `! Workflow "${workflowName}" ${stateText} (run ${details.runId})${origin}${errorText}. Inspect: /workflow status ${details.runId}`;
|
|
388
403
|
}
|
|
404
|
+
if (details.kind === "budget_warning") {
|
|
405
|
+
const warningText = details.error ? `: ${details.error}` : "";
|
|
406
|
+
const dimension = details.budgetDimension ?? "duration";
|
|
407
|
+
return `! Workflow "${workflowName}" is approaching its ${dimension} budget (run ${details.runId})${origin}${warningText}. Inspect: /workflow status ${details.runId}`;
|
|
408
|
+
}
|
|
389
409
|
if (details.kind === "paused" || details.kind === "quit") {
|
|
390
410
|
const stopStage = details.stageName ?? details.stageId;
|
|
391
411
|
// "(run 8c31), which you started, at stage review" — the stage clause takes a
|
|
@@ -448,13 +468,31 @@ function makeTerminalNotice(
|
|
|
448
468
|
...(failedToolNodeId !== undefined ? { toolNodeId: failedToolNodeId } : {}),
|
|
449
469
|
...(failedTool !== undefined ? { toolName: failedTool.name } : {}),
|
|
450
470
|
...(run.durationMs !== undefined ? { durationMs: run.durationMs } : {}),
|
|
451
|
-
// Attribution renders on every kind, and is omitted for a run that never
|
|
452
|
-
// recorded an origin rather than guessed at.
|
|
453
471
|
...(run.origin !== undefined ? { origin: run.origin } : {}),
|
|
454
472
|
createdAt: lifecycleOccurrenceAt(run, kind) ?? Date.now(),
|
|
455
473
|
};
|
|
456
474
|
}
|
|
457
475
|
|
|
476
|
+
function budgetWarningReports(run: RunSnapshot): readonly BudgetReport[] {
|
|
477
|
+
const warnings = Object.values(run.budgetState?.warnings ?? {}).filter(
|
|
478
|
+
(warning): warning is BudgetReport => warning !== undefined,
|
|
479
|
+
);
|
|
480
|
+
return warnings.length > 0 ? warnings : run.budgetState?.warning === undefined ? [] : [run.budgetState.warning];
|
|
481
|
+
}
|
|
482
|
+
function makeBudgetWarningNotice(run: RunSnapshot, warning: BudgetReport): WorkflowLifecycleNoticeDetails {
|
|
483
|
+
return {
|
|
484
|
+
kind: "budget_warning",
|
|
485
|
+
scope: "run",
|
|
486
|
+
runId: run.id,
|
|
487
|
+
workflowName: run.name,
|
|
488
|
+
status: run.status,
|
|
489
|
+
budgetDimension: warning.dimension,
|
|
490
|
+
error: `At ${warning.percent.toFixed(1)}% of the ${warning.dimension} budget (${warning.reading} / ${warning.ceiling}).`,
|
|
491
|
+
...(warning.dimension === "duration" ? { durationMs: warning.reading } : {}),
|
|
492
|
+
...(run.origin !== undefined ? { origin: run.origin } : {}),
|
|
493
|
+
createdAt: run.startedAt,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
458
496
|
function makeControlNotice(run: RunSnapshot, occurrence: ControlOccurrence): WorkflowLifecycleNoticeDetails {
|
|
459
497
|
const stage = occurrence.stage ?? restingStage(run);
|
|
460
498
|
const elapsedMs = run.durationMs ?? (occurrence.at >= run.startedAt ? occurrence.at - run.startedAt : undefined);
|
|
@@ -531,6 +569,9 @@ function terminalRunKey(kind: "completed" | "failed" | "blocked", run: RunSnapsh
|
|
|
531
569
|
const occurrence = kind === "blocked" ? (run.blockedAt ?? lifecycleOccurrenceAt(run, kind)) : "";
|
|
532
570
|
return occurrence === undefined ? `${kind}:${run.id}` : `${kind}:${run.id}:${occurrence}`;
|
|
533
571
|
}
|
|
572
|
+
function budgetWarningKey(run: RunSnapshot, dimension = run.budgetState?.warning?.dimension ?? "duration"): string {
|
|
573
|
+
return `budget_warning:${run.id}:${dimension}`;
|
|
574
|
+
}
|
|
534
575
|
|
|
535
576
|
/** One deliberate control action a run currently carries. */
|
|
536
577
|
interface ControlOccurrence {
|
|
@@ -697,12 +738,12 @@ function renderLifecycleNoticeCard(
|
|
|
697
738
|
...(opts.theme ? { theme: opts.theme } : {}),
|
|
698
739
|
});
|
|
699
740
|
}
|
|
700
|
-
|
|
701
741
|
function lifecycleNoticeTone(kind: WorkflowLifecycleNoticeKind): WorkflowNoticeTone {
|
|
702
742
|
switch (kind) {
|
|
703
743
|
case "failed":
|
|
704
744
|
return "error";
|
|
705
745
|
case "blocked":
|
|
746
|
+
case "budget_warning":
|
|
706
747
|
case "awaiting_input":
|
|
707
748
|
case "paused":
|
|
708
749
|
case "quit":
|
|
@@ -718,6 +759,8 @@ function lifecycleNoticeTitle(kind: WorkflowLifecycleNoticeKind): string {
|
|
|
718
759
|
return "WORKFLOW STARTED";
|
|
719
760
|
case "failed":
|
|
720
761
|
return "WORKFLOW FAILED";
|
|
762
|
+
case "budget_warning":
|
|
763
|
+
return "WORKFLOW BUDGET WARNING";
|
|
721
764
|
case "awaiting_input":
|
|
722
765
|
return "WORKFLOW INPUT";
|
|
723
766
|
case "blocked":
|
|
@@ -737,6 +780,8 @@ function lifecycleNoticeGlyph(kind: WorkflowLifecycleNoticeKind): string {
|
|
|
737
780
|
switch (kind) {
|
|
738
781
|
case "failed":
|
|
739
782
|
return "✗";
|
|
783
|
+
case "budget_warning":
|
|
784
|
+
return "!";
|
|
740
785
|
case "awaiting_input":
|
|
741
786
|
return "?";
|
|
742
787
|
case "blocked":
|
|
@@ -761,6 +806,8 @@ function lifecycleNoticeHeadline(details: WorkflowLifecycleNoticeDetails): strin
|
|
|
761
806
|
return `Workflow "${name}" started`;
|
|
762
807
|
case "failed":
|
|
763
808
|
return `Workflow "${name}" failed`;
|
|
809
|
+
case "budget_warning":
|
|
810
|
+
return `Workflow "${name}" is approaching its duration budget`;
|
|
764
811
|
case "awaiting_input":
|
|
765
812
|
return `Workflow "${name}" needs input`;
|
|
766
813
|
case "blocked":
|
|
@@ -3,7 +3,7 @@ import type { Api, Model } from "@earendil-works/pi-ai/compat";
|
|
|
3
3
|
import type { StageSessionRuntime } from "../runs/foreground/stage-runner.js";
|
|
4
4
|
import type { SessionManager } from "../shared/persistence-restore.js";
|
|
5
5
|
import type { RunStatus, StageStatus } from "../shared/store-types.js";
|
|
6
|
-
import type { WorkflowInputValues } from "../shared/types.js";
|
|
6
|
+
import type { WorkflowBudget, WorkflowInputValues } from "../shared/types.js";
|
|
7
7
|
import type { WidgetFactory } from "../tui/store-widget-installer.js";
|
|
8
8
|
import type { RenderResultOpts, WorkflowToolResult } from "./render-result.js";
|
|
9
9
|
import type { PiUISurface } from "./wiring.js";
|
|
@@ -197,6 +197,8 @@ export interface ExtensionAPI {
|
|
|
197
197
|
export interface WorkflowToolArgs {
|
|
198
198
|
workflow?: string;
|
|
199
199
|
inputs?: WorkflowInputValues;
|
|
200
|
+
/** Per-run budget override for action "run". */
|
|
201
|
+
budget?: WorkflowBudget;
|
|
200
202
|
action?:
|
|
201
203
|
| "models"
|
|
202
204
|
| "run"
|
|
@@ -18,6 +18,7 @@ import type { ResumableWorkflowEntry } from "../durable/types.js";
|
|
|
18
18
|
import type { JobTracker } from "../runs/background/job-tracker.js";
|
|
19
19
|
import type { RunOpts } from "../runs/foreground/executor.js";
|
|
20
20
|
import type { StageAdapters } from "../runs/foreground/stage-runner.js";
|
|
21
|
+
import type { WorkflowBudget } from "../shared/budget.js";
|
|
21
22
|
import type { Store } from "../shared/store.js";
|
|
22
23
|
import type { RunSnapshot, WorkflowActor } from "../shared/store-types.js";
|
|
23
24
|
import type { WorkflowExecutionPolicy } from "../shared/types.js";
|
|
@@ -27,7 +28,11 @@ import { discoverWorkflows } from "./discovery.js";
|
|
|
27
28
|
export interface DurableResumeRuntime {
|
|
28
29
|
resumeDurableWorkflow(
|
|
29
30
|
workflowId: string,
|
|
30
|
-
options?: {
|
|
31
|
+
options?: {
|
|
32
|
+
readonly policy?: WorkflowExecutionPolicy;
|
|
33
|
+
readonly actor?: WorkflowActor;
|
|
34
|
+
readonly budget?: WorkflowBudget;
|
|
35
|
+
},
|
|
31
36
|
): Promise<ResumeDurableResult>;
|
|
32
37
|
inspectDurableWorkflow(workflowId: string): Promise<TargetedDurableInspection>;
|
|
33
38
|
listDurableResumable(): readonly ResumableWorkflowEntry[];
|
|
@@ -83,6 +88,7 @@ export function createDurableResumeRuntime(deps: DurableResumeRuntimeDeps): Dura
|
|
|
83
88
|
registry: deps.registry,
|
|
84
89
|
baseRunOpts: {
|
|
85
90
|
...deps.baseRunOpts(options?.policy),
|
|
91
|
+
...(options?.budget === undefined ? {} : { budget: options.budget }),
|
|
86
92
|
...(options?.actor === undefined ? {} : { resumeActor: options.actor }),
|
|
87
93
|
},
|
|
88
94
|
durableBackend: backend,
|
|
@@ -20,6 +20,7 @@ import type { Store } from "../shared/store.js";
|
|
|
20
20
|
import { store as defaultStore } from "../shared/store.js";
|
|
21
21
|
import type { RunSnapshot, WorkflowActor } from "../shared/store-types.js";
|
|
22
22
|
import type {
|
|
23
|
+
WorkflowBudget,
|
|
23
24
|
WorkflowDefinition,
|
|
24
25
|
WorkflowExecutionPolicy,
|
|
25
26
|
WorkflowMcpPort,
|
|
@@ -86,7 +87,7 @@ export interface ExtensionRuntimeOpts {
|
|
|
86
87
|
// Public interface
|
|
87
88
|
// ---------------------------------------------------------------------------
|
|
88
89
|
export type ResumeFailedRunResult =
|
|
89
|
-
| { ok: true; runId: string; sourceRunId: string; resumeFromStageId
|
|
90
|
+
| { ok: true; runId: string; sourceRunId: string; resumeFromStageId?: string; message: string }
|
|
90
91
|
| {
|
|
91
92
|
ok: false;
|
|
92
93
|
reason: "run_not_found" | "not_resumable" | "workflow_not_found" | "insufficient_state";
|
|
@@ -119,6 +120,8 @@ export interface RuntimeDispatchOptions {
|
|
|
119
120
|
readonly origin?: WorkflowActor;
|
|
120
121
|
/** Who requested this resume. Only an attributable requester supplies it. */
|
|
121
122
|
readonly actor?: WorkflowActor;
|
|
123
|
+
/** Run-level budget override used when a continuation is launched. */
|
|
124
|
+
readonly budget?: WorkflowBudget;
|
|
122
125
|
}
|
|
123
126
|
// ---------------------------------------------------------------------------
|
|
124
127
|
// Factory
|
|
@@ -214,20 +217,21 @@ export function createExtensionRuntime(opts: ExtensionRuntimeOpts = {}): Extensi
|
|
|
214
217
|
function resolveResumeStage(
|
|
215
218
|
source: RunSnapshot,
|
|
216
219
|
stageId?: string,
|
|
217
|
-
): { ok: true; stageId
|
|
220
|
+
): { ok: true; stageId?: string } | { ok: false; message: string } {
|
|
221
|
+
const budgetExceededSource =
|
|
222
|
+
source.result?.status === "budget_exceeded" && source.budgetState?.systemOwnedStop === true;
|
|
218
223
|
if (stageId !== undefined) {
|
|
219
224
|
const resolved = resolveUniqueResumeStage(source, stageId);
|
|
220
225
|
if (!resolved.ok) return { ok: false, message: resolved.message };
|
|
221
226
|
const stage = resolved.stage;
|
|
222
|
-
if (stage.status !== "failed")
|
|
227
|
+
if (stage.status !== "failed" && !(budgetExceededSource && stage.id === source.failedStageId))
|
|
223
228
|
return { ok: false, message: `insufficient_state: stage ${stage.name} is ${stage.status}, not failed` };
|
|
224
229
|
return { ok: true, stageId: stage.id };
|
|
225
230
|
}
|
|
226
231
|
const failedStageId = source.failedStageId ?? source.stages.find((stage) => stage.status === "failed")?.id;
|
|
227
|
-
if (failedStageId
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
return { ok: true, stageId: failedStageId };
|
|
232
|
+
if (failedStageId !== undefined) return { ok: true, stageId: failedStageId };
|
|
233
|
+
if (budgetExceededSource && source.stages.length === 0) return { ok: true };
|
|
234
|
+
return { ok: false, message: `insufficient_state: failed run ${source.id} does not identify a failed stage` };
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
async function resumeFailedRun(
|
|
@@ -241,8 +245,12 @@ export function createExtensionRuntime(opts: ExtensionRuntimeOpts = {}): Extensi
|
|
|
241
245
|
}
|
|
242
246
|
const isTerminalFailedResumable =
|
|
243
247
|
source.status === "failed" && source.endedAt !== undefined && source.resumable !== false;
|
|
248
|
+
const isBudgetResumable =
|
|
249
|
+
source.result?.status === "budget_exceeded" && source.budgetState?.systemOwnedStop === true;
|
|
244
250
|
const isActiveBlockedResumable =
|
|
245
|
-
source.endedAt === undefined
|
|
251
|
+
(source.endedAt === undefined || isBudgetResumable) &&
|
|
252
|
+
source.resumable === true &&
|
|
253
|
+
source.failureRecoverability === "recoverable";
|
|
246
254
|
if (!isTerminalFailedResumable && !isActiveBlockedResumable) {
|
|
247
255
|
return { ok: false, reason: "not_resumable", message: `run ${sourceRunId} is not a resumable workflow run` };
|
|
248
256
|
}
|
|
@@ -265,13 +273,17 @@ export function createExtensionRuntime(opts: ExtensionRuntimeOpts = {}): Extensi
|
|
|
265
273
|
};
|
|
266
274
|
}
|
|
267
275
|
const stageMessage = (verb: string, runId: string): string =>
|
|
268
|
-
`${verb} workflow "${def.name}" from run ${source.id} at stage ${resolvedStage.stageId} (run ${runId}).`;
|
|
276
|
+
`${verb} workflow "${def.name}" from run ${source.id}${resolvedStage.stageId === undefined ? " at workflow start" : ` at stage ${resolvedStage.stageId}`} (run ${runId}).`;
|
|
269
277
|
const launchContinuation = () =>
|
|
270
278
|
launchDetachedUntilStartup(def, sourceInputs, {
|
|
271
279
|
...runOptions(options?.policy),
|
|
272
|
-
continuation: {
|
|
280
|
+
continuation: {
|
|
281
|
+
source,
|
|
282
|
+
...(resolvedStage.stageId !== undefined ? { resumeFromStageId: resolvedStage.stageId } : {}),
|
|
283
|
+
},
|
|
273
284
|
...(options?.actor === undefined ? {} : { resumeActor: options.actor }),
|
|
274
285
|
...(jobs !== undefined ? { jobs } : {}),
|
|
286
|
+
...(options?.budget === undefined ? {} : { budget: options.budget }),
|
|
275
287
|
});
|
|
276
288
|
if (isActiveBlockedResumable) {
|
|
277
289
|
// Keep the durable blocked source recoverable until fresh-ID startup admission succeeds.
|
|
@@ -19,6 +19,7 @@ import ralph from "../../builtin/ralph.js";
|
|
|
19
19
|
import tournament from "../../builtin/tournament.js";
|
|
20
20
|
import { isBrandedWorkflowDefinition } from "../authoring/workflow.js";
|
|
21
21
|
import * as workflowsSdkSurface from "../sdk-surface.js";
|
|
22
|
+
import { validateWorkflowBudget } from "../shared/budget.js";
|
|
22
23
|
|
|
23
24
|
const WORKFLOWS_MODULE_SPECIFIER = "@bastani/workflows";
|
|
24
25
|
const WORKFLOWS_BUILTIN_MODULE_SPECIFIER = `${WORKFLOWS_MODULE_SPECIFIER}/builtin`;
|
|
@@ -130,6 +131,10 @@ export function validateWorkflowDefinitionShape(value: unknown): string | null {
|
|
|
130
131
|
if (typeof d.run !== "function") {
|
|
131
132
|
return "run must be a function";
|
|
132
133
|
}
|
|
134
|
+
if (d.budget !== undefined) {
|
|
135
|
+
const reason = validateWorkflowBudget(d.budget, "budget");
|
|
136
|
+
if (reason !== null) return reason;
|
|
137
|
+
}
|
|
133
138
|
return null;
|
|
134
139
|
}
|
|
135
140
|
|
|
@@ -3,6 +3,7 @@ export const WORKFLOW_TOOL_DESCRIPTION =
|
|
|
3
3
|
"when workflow execution fits but another shape would better achieve the task, author a custom TypeScript workflow({...}) inline with normal coding tools, reload it, and run it; after successfully creating and reloading a newly authored custom workflow, report the folder containing its generated code as 'Custom workflow created. You can inspect its code at: <workflow-folder-path>'; do this only for newly created custom workflows, never builtin or pre-existing workflows; " +
|
|
4
4
|
"discover with list/get/inputs/models, list session runs with status (no runId; statusFilter narrows the list), inspect status/stages/stage details, " +
|
|
5
5
|
"send prompt answers or steering only while the root workflow is nonterminal, pause/resume/interrupt/quit runs, and reload workflow resources. " +
|
|
6
|
+
"For action 'run' and 'resume', budget accepts per-field duration, token, cost, and warning overrides; fields resolve over the workflow declaration and config, and 0 disables a field. " +
|
|
6
7
|
"For primitive prompt answers, use booleans or the documented confirm labels, exact case-insensitive select labels or 1-based indexes, and text strings for input/editor; an invalid answer remains pending and returns guidance instead of choosing a default. " +
|
|
7
8
|
"For large stage handoffs, write context to files/artifacts, pass paths via reads, and prompt downstream agents to 'Read the file at <path>...' instead of injecting large previous text. " +
|
|
8
9
|
"Wrap critical parts of run inputs and steering messages in <keepContext>...</keepContext> so compaction preserves them verbatim in the stages that inherit them; tag role constraints, prohibitions, must-hold criteria, and identifiers, not background or bulk reference material. " +
|
|
@@ -28,6 +28,21 @@ const WorkflowResponseSchema = Type.Union(
|
|
|
28
28
|
},
|
|
29
29
|
);
|
|
30
30
|
|
|
31
|
+
/** Run-budget declaration accepted by config, authored workflows, and tool runs. */
|
|
32
|
+
export const WorkflowBudgetSchema = Type.Object(
|
|
33
|
+
{
|
|
34
|
+
maxDurationMs: Type.Optional(
|
|
35
|
+
Type.Integer({ minimum: 0, description: "Maximum run duration in milliseconds; 0 disables it." }),
|
|
36
|
+
),
|
|
37
|
+
maxTokens: Type.Optional(Type.Integer({ minimum: 0, description: "Maximum charged tokens; 0 disables it." })),
|
|
38
|
+
maxCost: Type.Optional(Type.Number({ minimum: 0, description: "Maximum cost in USD; 0 disables it." })),
|
|
39
|
+
warnAtPercent: Type.Optional(
|
|
40
|
+
Type.Number({ minimum: 0, description: "Usage percentage at which to warn; 0 disables it." }),
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
{ additionalProperties: false, description: "Optional workflow run budget." },
|
|
44
|
+
);
|
|
45
|
+
|
|
31
46
|
export const WorkflowParametersSchema = Type.Object(
|
|
32
47
|
{
|
|
33
48
|
workflow: Type.Optional(
|
|
@@ -41,6 +56,7 @@ export const WorkflowParametersSchema = Type.Object(
|
|
|
41
56
|
description: "Key/value inputs passed to a named workflow run.",
|
|
42
57
|
}),
|
|
43
58
|
),
|
|
59
|
+
budget: Type.Optional(WorkflowBudgetSchema),
|
|
44
60
|
action: Type.Optional(
|
|
45
61
|
Type.Union(
|
|
46
62
|
[
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import { effectiveRunStatus } from "../shared/returned-run-status.js";
|
|
17
17
|
import type {
|
|
18
18
|
PendingPrompt,
|
|
19
|
+
RunBudgetSnapshot,
|
|
20
|
+
RunBudgetState,
|
|
19
21
|
RunSnapshot,
|
|
20
22
|
RunStatus,
|
|
21
23
|
StageInputRequest,
|
|
@@ -91,6 +93,8 @@ export interface WorkflowRunStatusSummary {
|
|
|
91
93
|
readonly tools?: readonly WorkflowStatusToolNode[];
|
|
92
94
|
readonly awaitingInputCount: number;
|
|
93
95
|
readonly awaitingInput: readonly WorkflowStatusAwaitingInput[];
|
|
96
|
+
readonly budget?: RunBudgetSnapshot;
|
|
97
|
+
readonly budgetState?: RunBudgetState;
|
|
94
98
|
readonly exitReason?: string;
|
|
95
99
|
readonly error?: string;
|
|
96
100
|
}
|
|
@@ -154,16 +158,55 @@ function awaitingInputEntries(run: RunSnapshot): WorkflowStatusAwaitingInput[] {
|
|
|
154
158
|
return entries;
|
|
155
159
|
}
|
|
156
160
|
|
|
161
|
+
function budgetReport(dimension: "duration" | "tokens" | "cost", reading: number, ceiling: number) {
|
|
162
|
+
return { dimension, reading, ceiling, percent: ceiling === 0 ? 0 : (reading / ceiling) * 100 };
|
|
163
|
+
}
|
|
157
164
|
/** Reduce one run snapshot to its concise status summary. */
|
|
158
165
|
export function summarizeRunSnapshot(run: RunSnapshot, now = Date.now()): WorkflowRunStatusSummary {
|
|
159
166
|
const awaitingInput = awaitingInputEntries(run);
|
|
167
|
+
const elapsedMs = elapsedRunMs(run, now);
|
|
168
|
+
const duration =
|
|
169
|
+
run.budget === undefined
|
|
170
|
+
? undefined
|
|
171
|
+
: run.endedAt === undefined
|
|
172
|
+
? {
|
|
173
|
+
dimension: "duration" as const,
|
|
174
|
+
reading: elapsedMs,
|
|
175
|
+
ceiling: run.budget.maxDurationMs,
|
|
176
|
+
percent: run.budget.maxDurationMs === 0 ? 0 : (elapsedMs / run.budget.maxDurationMs) * 100,
|
|
177
|
+
}
|
|
178
|
+
: (run.budgetState?.duration ?? {
|
|
179
|
+
dimension: "duration" as const,
|
|
180
|
+
reading: elapsedMs,
|
|
181
|
+
ceiling: run.budget.maxDurationMs,
|
|
182
|
+
percent: run.budget.maxDurationMs === 0 ? 0 : (elapsedMs / run.budget.maxDurationMs) * 100,
|
|
183
|
+
});
|
|
184
|
+
const usageBudgetReport = (dimension: "tokens" | "cost", ceiling: number | undefined) => {
|
|
185
|
+
if (ceiling === undefined || ceiling <= 0) return undefined;
|
|
186
|
+
return (
|
|
187
|
+
run.budgetState?.[dimension] ?? budgetReport(dimension, run.budgetState?.accounting?.[dimension] ?? 0, ceiling)
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
const tokens = usageBudgetReport("tokens", run.budget?.maxTokens) as RunBudgetState["tokens"];
|
|
191
|
+
const cost = usageBudgetReport("cost", run.budget?.maxCost) as RunBudgetState["cost"];
|
|
192
|
+
const budgetState =
|
|
193
|
+
run.budget === undefined
|
|
194
|
+
? undefined
|
|
195
|
+
: {
|
|
196
|
+
...(run.budgetState ?? {}),
|
|
197
|
+
duration,
|
|
198
|
+
...(tokens !== undefined ? { tokens } : {}),
|
|
199
|
+
...(cost !== undefined ? { cost } : {}),
|
|
200
|
+
};
|
|
160
201
|
return {
|
|
161
202
|
runId: run.id,
|
|
162
203
|
name: run.name,
|
|
163
204
|
status: effectiveRunStatus(run),
|
|
164
205
|
startedAt: run.startedAt,
|
|
165
206
|
endedAt: run.endedAt,
|
|
166
|
-
elapsedMs
|
|
207
|
+
elapsedMs,
|
|
208
|
+
...(run.budget !== undefined ? { budget: run.budget } : {}),
|
|
209
|
+
...(budgetState !== undefined ? { budgetState } : {}),
|
|
167
210
|
activeStages: run.stages.filter(stageIsActive).map((stage) => ({
|
|
168
211
|
stageId: stage.id,
|
|
169
212
|
name: stage.name,
|
|
@@ -43,6 +43,14 @@ function statusRunHint(run: WorkflowRunStatusSummary): string | undefined {
|
|
|
43
43
|
const tools = run.tools ?? [];
|
|
44
44
|
const toolHint =
|
|
45
45
|
tools.length > 0 ? `tools: ${tools.map((tool) => `${tool.name} (${tool.status})`).join(", ")}` : undefined;
|
|
46
|
+
const budgetHint =
|
|
47
|
+
[run.budgetState?.duration, run.budgetState?.tokens, run.budgetState?.cost]
|
|
48
|
+
.filter((budget): budget is NonNullable<typeof budget> => budget !== undefined)
|
|
49
|
+
.map(
|
|
50
|
+
(budget) =>
|
|
51
|
+
`${budget.dimension === "duration" ? "budget" : budget.dimension}: ${budget.reading}/${budget.ceiling}${budget.dimension === "duration" ? "ms" : ""} (${budget.percent.toFixed(1)}%)`,
|
|
52
|
+
)
|
|
53
|
+
.join(", ") || undefined;
|
|
46
54
|
let primary: string | undefined;
|
|
47
55
|
if (run.awaitingInputCount > 0) {
|
|
48
56
|
const stages = run.awaitingInput
|
|
@@ -55,7 +63,8 @@ function statusRunHint(run: WorkflowRunStatusSummary): string | undefined {
|
|
|
55
63
|
} else if (run.activeStages.length > 0) {
|
|
56
64
|
primary = `stage: ${run.activeStages.map((stage) => stage.name).join(", ")}`;
|
|
57
65
|
}
|
|
58
|
-
if (primary !== undefined) return
|
|
66
|
+
if (primary !== undefined) return [primary, budgetHint, toolHint].filter((part) => part !== undefined).join(" · ");
|
|
67
|
+
if (budgetHint !== undefined) return toolHint === undefined ? budgetHint : `${budgetHint} · ${toolHint}`;
|
|
59
68
|
if (toolHint !== undefined) return toolHint;
|
|
60
69
|
if (run.error !== undefined && run.error.length > 0) return truncateStatusText(run.error);
|
|
61
70
|
if (run.exitReason !== undefined && run.exitReason.length > 0) {
|
|
@@ -343,6 +343,7 @@ export async function workflowInterruptAction(args: WorkflowToolArgs): Promise<W
|
|
|
343
343
|
async function resumeDurableShadow(
|
|
344
344
|
runId: string,
|
|
345
345
|
deps: Pick<WorkflowControlActionDeps, "getRuntime" | "policy" | "ensureWorkflowResourcesLoaded">,
|
|
346
|
+
budget?: WorkflowToolArgs["budget"],
|
|
346
347
|
): Promise<WorkflowToolResult> {
|
|
347
348
|
const runtime = deps.getRuntime();
|
|
348
349
|
let warning: string | undefined;
|
|
@@ -354,7 +355,11 @@ async function resumeDurableShadow(
|
|
|
354
355
|
} catch (error) {
|
|
355
356
|
warning = formatWorkflowResourceLoadWarning(error);
|
|
356
357
|
}
|
|
357
|
-
const resumed = await runtime.resumeDurableWorkflow(runId, {
|
|
358
|
+
const resumed = await runtime.resumeDurableWorkflow(runId, {
|
|
359
|
+
policy: deps.policy,
|
|
360
|
+
actor: "agent",
|
|
361
|
+
...(budget === undefined ? {} : { budget }),
|
|
362
|
+
});
|
|
358
363
|
const message = warning === undefined ? resumed.message : `${warning}\n\n${resumed.message}`;
|
|
359
364
|
return {
|
|
360
365
|
action: "resume",
|
|
@@ -367,9 +372,14 @@ async function resumeDurableShadow(
|
|
|
367
372
|
async function resumePreparedDurableTarget(
|
|
368
373
|
runId: string,
|
|
369
374
|
deps: Pick<WorkflowControlActionDeps, "getRuntime" | "policy">,
|
|
375
|
+
budget?: WorkflowToolArgs["budget"],
|
|
370
376
|
): Promise<WorkflowToolResult> {
|
|
371
377
|
try {
|
|
372
|
-
const resumed = await deps.getRuntime().resumeDurableWorkflow(runId, {
|
|
378
|
+
const resumed = await deps.getRuntime().resumeDurableWorkflow(runId, {
|
|
379
|
+
policy: deps.policy,
|
|
380
|
+
actor: "agent",
|
|
381
|
+
...(budget === undefined ? {} : { budget }),
|
|
382
|
+
});
|
|
373
383
|
return {
|
|
374
384
|
action: "resume",
|
|
375
385
|
runId: resumed.ok ? resumed.runId : runId,
|
|
@@ -398,7 +408,7 @@ async function resolveExplicitDurableTarget(
|
|
|
398
408
|
if (resolved.kind === "malformed") {
|
|
399
409
|
return { action: "resume", runId: target, status: "noop", message: resolved.message };
|
|
400
410
|
}
|
|
401
|
-
if (resolved.kind === "durable") return resumePreparedDurableTarget(resolved.workflowId, deps);
|
|
411
|
+
if (resolved.kind === "durable") return resumePreparedDurableTarget(resolved.workflowId, deps, args.budget);
|
|
402
412
|
if (resolved.kind === "live") {
|
|
403
413
|
return workflowResumeAction({ ...args, runId: resolved.workflowId }, deps);
|
|
404
414
|
}
|
|
@@ -424,7 +434,7 @@ async function resolveExplicitDurableTarget(
|
|
|
424
434
|
message: `Workflow ${target} has no durable checkpoint or pending prompt progress and is not resumable.`,
|
|
425
435
|
};
|
|
426
436
|
}
|
|
427
|
-
if (durableHandle !== undefined) return resumePreparedDurableTarget(target, deps);
|
|
437
|
+
if (durableHandle !== undefined) return resumePreparedDurableTarget(target, deps, args.budget);
|
|
428
438
|
return { action: "resume", runId: target, status: "noop", message: `Run not found: ${target}` };
|
|
429
439
|
}
|
|
430
440
|
|
|
@@ -450,7 +460,7 @@ export async function workflowResumeAction(
|
|
|
450
460
|
const backend = getDurableBackend();
|
|
451
461
|
const exact = store.runs().find((run) => run.id === target.runId);
|
|
452
462
|
const shadow = exact === undefined ? "not_shadow" : classifyDurableResumeShadow(exact, store, { backend });
|
|
453
|
-
if (shadow === "eligible") return resumeDurableShadow(target.runId, deps);
|
|
463
|
+
if (shadow === "eligible") return resumeDurableShadow(target.runId, deps, args.budget);
|
|
454
464
|
if (shadow === "ineligible") {
|
|
455
465
|
return {
|
|
456
466
|
action: "resume",
|
|
@@ -488,16 +498,18 @@ export async function workflowResumeAction(
|
|
|
488
498
|
!isPaused &&
|
|
489
499
|
run.exitReason !== "quit" &&
|
|
490
500
|
isWorkflowRunResumable(workflowRunResumeCandidate(run));
|
|
491
|
-
if (isDurableAuthorExit) return resumeDurableShadow(stageRunId, deps);
|
|
501
|
+
if (isDurableAuthorExit) return resumeDurableShadow(stageRunId, deps, args.budget);
|
|
492
502
|
if (isResumableContinuation) {
|
|
493
503
|
try {
|
|
494
504
|
await deps.ensureWorkflowResourcesLoaded();
|
|
495
505
|
} catch (error) {
|
|
496
506
|
warning = formatWorkflowResourceLoadWarning(error);
|
|
497
507
|
}
|
|
498
|
-
const continuation = await deps
|
|
499
|
-
.
|
|
500
|
-
|
|
508
|
+
const continuation = await deps.getRuntime().resumeFailedRun(stageRunId, stage.stageId, {
|
|
509
|
+
policy: deps.policy,
|
|
510
|
+
actor: "agent",
|
|
511
|
+
...(args.budget === undefined ? {} : { budget: args.budget }),
|
|
512
|
+
});
|
|
501
513
|
const message = warning === undefined ? continuation.message : `${warning}\n\n${continuation.message}`;
|
|
502
514
|
return {
|
|
503
515
|
action: "resume",
|
|
@@ -62,6 +62,20 @@ export function createContinuationReplayIndex(continuation: RunContinuationOpts
|
|
|
62
62
|
markPromptAnswerReplayed: () => {},
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
+
if (continuation.resumeFromStageId === undefined) {
|
|
66
|
+
if (continuation.source.stages.length > 0)
|
|
67
|
+
throw new Error(
|
|
68
|
+
`atomic-workflows: insufficient_state: continuation has no resume stage in source run ${continuation.source.id}`,
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
decide: (input) => ({
|
|
72
|
+
kind: "execute",
|
|
73
|
+
parentIds: input.parentIds,
|
|
74
|
+
answerReplay: "unavailable",
|
|
75
|
+
}),
|
|
76
|
+
markPromptAnswerReplayed: () => {},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
65
79
|
const resumeStage = continuation.source.stages.find((stage) => stage.id === continuation.resumeFromStageId);
|
|
66
80
|
if (resumeStage === undefined) {
|
|
67
81
|
throw new Error(
|
|
@@ -2,6 +2,7 @@ import { appendRunBlocked, appendRunEnd } from "../../shared/persistence-session
|
|
|
2
2
|
import type { Store } from "../../shared/store.js";
|
|
3
3
|
import { isTerminalRunStatus } from "../../shared/store-internal.js";
|
|
4
4
|
import type {
|
|
5
|
+
RunBudgetState,
|
|
5
6
|
RunSnapshot,
|
|
6
7
|
RunStatus,
|
|
7
8
|
StageSnapshot,
|
|
@@ -101,6 +102,9 @@ export interface RunFailureMetadata {
|
|
|
101
102
|
readonly failedToolNodeId?: string;
|
|
102
103
|
readonly resumable: boolean;
|
|
103
104
|
readonly retryAfterMs?: number;
|
|
105
|
+
readonly endedAt?: number;
|
|
106
|
+
readonly result?: WorkflowOutputValues;
|
|
107
|
+
readonly budgetState?: RunBudgetState;
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
/**
|
|
@@ -435,30 +439,33 @@ export function finalizeKilledByFailure(
|
|
|
435
439
|
onRunEnd,
|
|
436
440
|
);
|
|
437
441
|
}
|
|
438
|
-
|
|
439
442
|
export function recordActiveBlockedFailure(
|
|
440
443
|
runId: string,
|
|
441
444
|
runSnapshot: RunSnapshot,
|
|
442
445
|
activeStore: Store,
|
|
443
446
|
persistence: WorkflowPersistencePort | undefined,
|
|
444
|
-
metadata: RunFailureMetadata & { readonly failureRecoverability: "recoverable"
|
|
447
|
+
metadata: RunFailureMetadata & { readonly failureRecoverability: "recoverable" },
|
|
445
448
|
): RunResult {
|
|
446
449
|
const blockedAt = Date.now();
|
|
450
|
+
const endedAt = metadata.budgetState?.systemOwnedStop === true ? blockedAt : undefined;
|
|
447
451
|
const recorded = activeStore.recordRunBlocked(runId, metadata.errorMessage, {
|
|
448
452
|
failureKind: metadata.failureKind,
|
|
449
453
|
...(metadata.failureCode !== undefined ? { failureCode: metadata.failureCode } : {}),
|
|
450
454
|
failureRecoverability: "recoverable",
|
|
451
455
|
...(metadata.failureDisposition !== undefined ? { failureDisposition: metadata.failureDisposition } : {}),
|
|
452
456
|
failureMessage: metadata.failureMessage,
|
|
453
|
-
failedStageId: metadata.failedStageId,
|
|
457
|
+
...(metadata.failedStageId !== undefined ? { failedStageId: metadata.failedStageId } : {}),
|
|
454
458
|
resumable: true,
|
|
455
459
|
...(metadata.retryAfterMs !== undefined ? { retryAfterMs: metadata.retryAfterMs } : {}),
|
|
460
|
+
...(endedAt !== undefined ? { endedAt } : {}),
|
|
461
|
+
...(metadata.result !== undefined ? { result: metadata.result } : {}),
|
|
462
|
+
...(metadata.budgetState !== undefined ? { budgetState: metadata.budgetState } : {}),
|
|
456
463
|
blockedAt,
|
|
457
464
|
});
|
|
458
465
|
if (recorded && persistence !== undefined) {
|
|
459
466
|
appendRunBlocked(persistence, {
|
|
460
467
|
runId,
|
|
461
|
-
failedStageId: metadata.failedStageId,
|
|
468
|
+
...(metadata.failedStageId !== undefined ? { failedStageId: metadata.failedStageId } : {}),
|
|
462
469
|
error: metadata.errorMessage,
|
|
463
470
|
failureKind: metadata.failureKind,
|
|
464
471
|
...(metadata.failureCode !== undefined ? { failureCode: metadata.failureCode } : {}),
|
|
@@ -467,12 +474,16 @@ export function recordActiveBlockedFailure(
|
|
|
467
474
|
...(metadata.failureDisposition !== undefined ? { failureDisposition: metadata.failureDisposition } : {}),
|
|
468
475
|
...(metadata.retryAfterMs !== undefined ? { retryAfterMs: metadata.retryAfterMs } : {}),
|
|
469
476
|
resumable: true,
|
|
477
|
+
...(endedAt !== undefined ? { endedAt } : {}),
|
|
478
|
+
...(metadata.result !== undefined ? { result: metadata.result } : {}),
|
|
479
|
+
...(metadata.budgetState !== undefined ? { budgetState: metadata.budgetState } : {}),
|
|
470
480
|
ts: blockedAt,
|
|
471
481
|
});
|
|
472
482
|
}
|
|
473
483
|
return {
|
|
474
484
|
runId,
|
|
475
485
|
status: "running",
|
|
486
|
+
...(metadata.result !== undefined ? { result: metadata.result } : {}),
|
|
476
487
|
error: metadata.errorMessage,
|
|
477
488
|
stages: [...runSnapshot.stages],
|
|
478
489
|
toolNodes: [...(runSnapshot.toolNodes ?? [])],
|