@lostgradient/weft 0.2.0 → 0.3.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/README.md +47 -22
- package/dist/cli/generated/operation-client.generated.d.ts +28 -1
- package/dist/cli/generated/operation-client.generated.js +2 -0
- package/dist/cli-main.js +79 -79
- package/dist/client/handle-delegation.d.ts +4 -0
- package/dist/client/handle-delegation.js +6 -0
- package/dist/client/http-client-requests.d.ts +2 -0
- package/dist/client/http-client-requests.js +3 -0
- package/dist/client/http-client.d.ts +4 -1
- package/dist/client/http-client.js +9 -1
- package/dist/client/interface.d.ts +57 -2
- package/dist/client/local.d.ts +4 -1
- package/dist/client/local.js +7 -0
- package/dist/client/start-body.d.ts +7 -1
- package/dist/client/start-body.js +13 -4
- package/dist/core/codec/extension-codec.js +4 -2
- package/dist/core/codec/index.d.ts +1 -0
- package/dist/core/codec/index.js +1 -0
- package/dist/core/codec/serializer-registry.d.ts +122 -0
- package/dist/core/codec/serializer-registry.js +51 -0
- package/dist/core/context/index.d.ts +10 -0
- package/dist/core/context/index.js +3 -0
- package/dist/core/context/internals.d.ts +10 -0
- package/dist/core/context/internals.js +5 -1
- package/dist/core/context/run-operation.d.ts +16 -3
- package/dist/core/context/run-operation.js +16 -7
- package/dist/core/context/speculative-child.js +2 -0
- package/dist/core/context/types.d.ts +6 -0
- package/dist/core/engine/bulk-operations-purge.js +1 -0
- package/dist/core/engine/bulk-operations.js +1 -1
- package/dist/core/engine/callback-creators-bundles.js +2 -1
- package/dist/core/engine/callback-creators-core.js +2 -1
- package/dist/core/engine/construction.d.ts +1 -1
- package/dist/core/engine/construction.js +15 -3
- package/dist/core/engine/disposal.js +12 -0
- package/dist/core/engine/engine-create-types.d.ts +0 -14
- package/dist/core/engine/engine-internal-types.d.ts +17 -0
- package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
- package/dist/core/engine/engine-leak-warnings.js +4 -0
- package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
- package/dist/core/engine/engine-runtime-helpers.js +26 -5
- package/dist/core/engine/errors.d.ts +74 -0
- package/dist/core/engine/errors.js +25 -1
- package/dist/core/engine/handle-result.js +1 -1
- package/dist/core/engine/handles.d.ts +89 -40
- package/dist/core/engine/handles.js +25 -27
- package/dist/core/engine/index.d.ts +122 -4
- package/dist/core/engine/index.js +82 -5
- package/dist/core/engine/inline-launch-queue.d.ts +14 -0
- package/dist/core/engine/inline-launch-queue.js +32 -7
- package/dist/core/engine/internals.d.ts +26 -10
- package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
- package/dist/core/engine/lifecycle/persist.js +5 -20
- package/dist/core/engine/lifecycle/recovered-services.d.ts +45 -0
- package/dist/core/engine/lifecycle/recovered-services.js +34 -0
- package/dist/core/engine/lifecycle/resume.js +33 -5
- package/dist/core/engine/lifecycle/shared.d.ts +8 -0
- package/dist/core/engine/lifecycle/start-batch.js +23 -12
- package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
- package/dist/core/engine/lifecycle/start-commit.js +27 -0
- package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
- package/dist/core/engine/lifecycle/start-exec.js +38 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
- package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
- package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
- package/dist/core/engine/lifecycle/start.d.ts +3 -3
- package/dist/core/engine/lifecycle/start.js +42 -37
- package/dist/core/engine/lifecycle.d.ts +3 -2
- package/dist/core/engine/lifecycle.js +9 -2
- package/dist/core/engine/listing.js +1 -1
- package/dist/core/engine/operations-data.d.ts +16 -0
- package/dist/core/engine/operations-data.js +6 -0
- package/dist/core/engine/operations-time.d.ts +3 -2
- package/dist/core/engine/operations-time.js +6 -1
- package/dist/core/engine/persisted-data-version.d.ts +5 -9
- package/dist/core/engine/persisted-data-version.js +4 -5
- package/dist/core/engine/schedule-handle.d.ts +45 -0
- package/dist/core/engine/schedule-handle.js +26 -0
- package/dist/core/engine/schedules.d.ts +1 -1
- package/dist/core/engine/schedules.js +7 -3
- package/dist/core/engine/second-instance-detector.d.ts +96 -0
- package/dist/core/engine/second-instance-detector.js +108 -0
- package/dist/core/engine/signals.d.ts +22 -0
- package/dist/core/engine/signals.js +15 -0
- package/dist/core/engine/termination/cleanup.d.ts +25 -0
- package/dist/core/engine/termination/cleanup.js +21 -1
- package/dist/core/engine/termination/complete.js +4 -3
- package/dist/core/engine/termination/suspend.d.ts +68 -0
- package/dist/core/engine/termination/suspend.js +41 -0
- package/dist/core/engine/termination.d.ts +4 -2
- package/dist/core/engine/termination.js +2 -0
- package/dist/core/engine/validation.js +25 -1
- package/dist/core/engine/workflow-feed.d.ts +5 -3
- package/dist/core/events/event-map.d.ts +2 -1
- package/dist/core/events/workflow-events.d.ts +23 -0
- package/dist/core/events/workflow-events.js +9 -0
- package/dist/core/inline-execution-strategy.d.ts +5 -0
- package/dist/core/inline-execution-strategy.js +2 -1
- package/dist/core/list-filter-validation.js +2 -1
- package/dist/core/start-workflow-validation.d.ts +22 -0
- package/dist/core/start-workflow-validation.js +11 -1
- package/dist/core/step-context.d.ts +10 -6
- package/dist/core/step-context.js +7 -15
- package/dist/core/types/activity.d.ts +6 -3
- package/dist/core/types/identity.d.ts +8 -1
- package/dist/core/types/launch-metadata.d.ts +33 -0
- package/dist/core/types/launch-metadata.js +0 -0
- package/dist/core/types/message-handles.d.ts +25 -0
- package/dist/core/types/options.d.ts +90 -7
- package/dist/core/types/reviews.d.ts +2 -1
- package/dist/core/types/services-resolution.d.ts +47 -0
- package/dist/core/types/services-resolution.js +0 -0
- package/dist/core/types/state.d.ts +11 -11
- package/dist/core/types/workflow-builder.d.ts +5 -4
- package/dist/core/types/workflow-context.d.ts +25 -0
- package/dist/core/types/workflow-function.d.ts +17 -0
- package/dist/core/types/workflow-snapshot.d.ts +29 -0
- package/dist/core/types/workflow-snapshot.js +0 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.js +3 -0
- package/dist/core/weft-error.d.ts +46 -14
- package/dist/core/weft-error.js +12 -1
- package/dist/diagnostics/doctor.js +6 -3
- package/dist/diagnostics/format.js +2 -2
- package/dist/diagnostics/types.d.ts +1 -0
- package/dist/diagnostics/version-check.js +6 -4
- package/dist/index.d.ts +10 -5
- package/dist/index.js +11 -2
- package/dist/json-schema.js +3 -3
- package/dist/mcp/cli.js +35 -35
- package/dist/mcp/list-filter.js +2 -1
- package/dist/mcp/session.js +1 -0
- package/dist/observability/index.js +2 -2
- package/dist/server/handler.js +30 -30
- package/dist/server/index.js +33 -33
- package/dist/server/interactive-operations.js +1 -0
- package/dist/server/operations/resume-workflow.js +2 -2
- package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
- package/dist/server/operations/start-or-signal-workflow.js +140 -0
- package/dist/server/operations/start-workflow-options.d.ts +32 -0
- package/dist/server/operations/start-workflow-options.js +63 -0
- package/dist/server/operations/start-workflow.js +7 -69
- package/dist/server/operations/suspend-workflow.d.ts +13 -0
- package/dist/server/operations/suspend-workflow.js +36 -0
- package/dist/server/rest-binding.d.ts +18 -7
- package/dist/server/rest-bindings.js +12 -0
- package/dist/server/runtime/task-dispatch.js +5 -3
- package/dist/server/runtime/task-polling.d.ts +16 -2
- package/dist/server/runtime/task-polling.js +20 -5
- package/dist/server/runtime/websocket-worker.js +8 -0
- package/dist/server/serve-internals.d.ts +8 -0
- package/dist/server/serve-internals.js +4 -2
- package/dist/server/task-state.d.ts +8 -0
- package/dist/service-worker/index.js +28 -28
- package/dist/storage/capabilities.d.ts +10 -2
- package/dist/storage/capabilities.js +2 -2
- package/dist/storage/http.js +2 -2
- package/dist/storage/index.d.ts +7 -1
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.d.ts +40 -0
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +3 -0
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon-value-mapping.d.ts +47 -0
- package/dist/storage/neon-value-mapping.js +11 -0
- package/dist/storage/neon.d.ts +108 -0
- package/dist/storage/neon.js +10 -0
- package/dist/storage/node-sqlite-loader.d.ts +71 -0
- package/dist/storage/node-sqlite-loader.js +41 -0
- package/dist/storage/node-sqlite.d.ts +1 -19
- package/dist/storage/node-sqlite.js +38 -32
- package/dist/storage/postgres-key-value-queries.d.ts +79 -0
- package/dist/storage/postgres-key-value-queries.js +63 -0
- package/dist/storage/resolve.d.ts +2 -165
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/storage-configuration.d.ts +209 -0
- package/dist/storage/storage-configuration.js +0 -0
- package/dist/storage/text-value-store.d.ts +13 -10
- package/dist/storage/turso.js +2 -2
- package/dist/storage/typed-storage.js +1 -1
- package/dist/storage/web-extension.js +1 -1
- package/dist/testing/event-loop.d.ts +36 -2
- package/dist/testing/index.d.ts +31 -1
- package/dist/testing/index.js +33 -33
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/index.js +9 -5
- package/dist/worker/long-poll.js +4 -0
- package/dist/worker/protocol-messages.d.ts +20 -0
- package/dist/worker/protocol-schemas.d.ts +32 -0
- package/dist/worker/protocol-schemas.js +8 -4
- package/dist/worker/protocol-task-result.d.ts +28 -0
- package/dist/worker/protocol-task-result.js +76 -0
- package/dist/worker/protocol.d.ts +4 -15
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/registry/fair-share.d.ts +29 -0
- package/dist/worker/registry/fair-share.js +30 -0
- package/dist/worker/registry/routing.d.ts +18 -0
- package/dist/worker/registry/routing.js +14 -0
- package/dist/worker/registry/types.d.ts +7 -0
- package/dist/worker/registry.d.ts +16 -1
- package/dist/worker/registry.js +24 -36
- package/package.json +17 -4
|
@@ -6,6 +6,7 @@ export {
|
|
|
6
6
|
cleanupTerminalWorkflowSynchronously,
|
|
7
7
|
cleanupWaiters,
|
|
8
8
|
cleanupWorkflowStorage,
|
|
9
|
+
evictSuspendedWorkflowWaiters,
|
|
9
10
|
finalizeScheduledWorkflowTerminal,
|
|
10
11
|
handleCleanupError,
|
|
11
12
|
runDeferredTerminalCleanup
|
|
@@ -21,3 +22,4 @@ export {
|
|
|
21
22
|
terminateWorkflow,
|
|
22
23
|
timeoutWorkflow
|
|
23
24
|
} from "./termination/complete.js";
|
|
25
|
+
export { suspendWorkflow } from "./termination/suspend.js";
|
|
@@ -2,6 +2,7 @@ import { decode } from "../codec.js";
|
|
|
2
2
|
import { isRecord } from "../debug-output.js";
|
|
3
3
|
import { normalizeFailureCategory } from "../failure-categories.js";
|
|
4
4
|
import { coerceStartWorkflowId, parseStartWorkflowDuration } from "../start-workflow-validation.js";
|
|
5
|
+
import { DEFAULT_WORKFLOW_VERSION } from "../versioning.js";
|
|
5
6
|
import { isWorkflowTagArray } from "../workflow-tags.js";
|
|
6
7
|
const WORKFLOW_TIMELINE_STATUSES = new Set([
|
|
7
8
|
"running",
|
|
@@ -55,7 +56,10 @@ export function isValidDecodedTags(value) {
|
|
|
55
56
|
return value === void 0 || isWorkflowTagArray(value);
|
|
56
57
|
}
|
|
57
58
|
export function decodeWorkflowState(bytes) {
|
|
58
|
-
const
|
|
59
|
+
const decoded = decode(bytes);
|
|
60
|
+
if (isRecord(decoded))
|
|
61
|
+
liftFlatVersionTuple(decoded);
|
|
62
|
+
const state = decoded;
|
|
59
63
|
if ("tenant" in state)
|
|
60
64
|
delete state.tenant;
|
|
61
65
|
if (!isValidDecodedTags(state.tags)) {
|
|
@@ -78,6 +82,26 @@ export function decodeWorkflowState(bytes) {
|
|
|
78
82
|
}
|
|
79
83
|
return state;
|
|
80
84
|
}
|
|
85
|
+
function liftFlatVersionTuple(record) {
|
|
86
|
+
const flatVersion = record.version, hasFlatVersion = typeof flatVersion === "string";
|
|
87
|
+
if (isWorkflowVersionTuple(record.versionTuple)) {
|
|
88
|
+
delete record.version;
|
|
89
|
+
delete record.agentVersion;
|
|
90
|
+
delete record.toolVersions;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const { agentVersion: flatAgentVersion, toolVersions: flatToolVersions } = record, versionTuple = {
|
|
94
|
+
workflowVersion: hasFlatVersion ? flatVersion : DEFAULT_WORKFLOW_VERSION,
|
|
95
|
+
...typeof flatAgentVersion === "string" && { agentVersion: flatAgentVersion },
|
|
96
|
+
...Array.isArray(flatToolVersions) && flatToolVersions.every((entry) => typeof entry === "string") && {
|
|
97
|
+
toolVersions: flatToolVersions
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
record.versionTuple = versionTuple;
|
|
101
|
+
delete record.version;
|
|
102
|
+
delete record.agentVersion;
|
|
103
|
+
delete record.toolVersions;
|
|
104
|
+
}
|
|
81
105
|
export function normalizeRetentionDuration(value, fieldName) {
|
|
82
106
|
if (value === void 0)
|
|
83
107
|
return;
|
|
@@ -7,9 +7,11 @@ import type { EngineInternals } from './internals.ts';
|
|
|
7
7
|
*/
|
|
8
8
|
export type WorkflowFeedSelector = 'events' | 'tokens';
|
|
9
9
|
/**
|
|
10
|
-
* Hard-coded stream key for the `tokens` selector.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Hard-coded stream key for the `tokens` selector. Every transport that
|
|
11
|
+
* exposes the token feed — `engine.getStreamChunks(id, 'tokens')` and the REST
|
|
12
|
+
* SSE / WebSocket stream-chunk routes layered on it — keys its stored chunks
|
|
13
|
+
* under this single value, so a resumption cursor issued by one transport
|
|
14
|
+
* round-trips through another.
|
|
13
15
|
*/
|
|
14
16
|
export declare const TOKENS_STREAM_KEY = "tokens";
|
|
15
17
|
/** Record `kind` for every token stream chunk emitted by the feed. */
|
|
@@ -4,7 +4,7 @@ import type { AttributesChangedEvent } from './attribute-events.ts';
|
|
|
4
4
|
import type { SignalDeliveredEvent, SignalReceivedEvent } from './signal-events.ts';
|
|
5
5
|
import type { AlertFiredEvent, AlertResolvedEvent, CheckpointSizeWarningEvent, CleanupWarningEvent, ConstraintViolatedEvent, DevelopmentWarningEvent, StorageSizeReportedEvent } from './system-events.ts';
|
|
6
6
|
import type { UpdateCompletedEvent, UpdateReceivedEvent } from './update-events.ts';
|
|
7
|
-
import type { WorkflowCancelledEvent, WorkflowCompletedEvent, WorkflowFailedEvent, WorkflowRecoverySkippedEvent, WorkflowResumedEvent, WorkflowStartedEvent, WorkflowTimedOutEvent } from './workflow-events.ts';
|
|
7
|
+
import type { WorkflowCancelledEvent, WorkflowCompletedEvent, WorkflowFailedEvent, WorkflowRecoverySkippedEvent, WorkflowResumedEvent, WorkflowStartedEvent, WorkflowSuspendedEvent, WorkflowTimedOutEvent } from './workflow-events.ts';
|
|
8
8
|
/**
|
|
9
9
|
* Record mapping each event-name string the {@link Engine} dispatches to its
|
|
10
10
|
* corresponding typed `Event` subclass. Use this as the type parameter for
|
|
@@ -31,6 +31,7 @@ export type WeftEventMap = {
|
|
|
31
31
|
'workflow:cancelled': WorkflowCancelledEvent;
|
|
32
32
|
'workflow:timed-out': WorkflowTimedOutEvent;
|
|
33
33
|
'workflow:resumed': WorkflowResumedEvent;
|
|
34
|
+
'workflow:suspended': WorkflowSuspendedEvent;
|
|
34
35
|
'workflow:recovery-skipped': WorkflowRecoverySkippedEvent;
|
|
35
36
|
'activity:started': ActivityStartedEvent;
|
|
36
37
|
'activity:completed': ActivityCompletedEvent;
|
|
@@ -160,6 +160,29 @@ export declare class WorkflowResumedEvent extends Event {
|
|
|
160
160
|
readonly fromStep: number;
|
|
161
161
|
constructor(workflowId: string, fromStep: number);
|
|
162
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Fired when a running workflow is explicitly suspended via
|
|
165
|
+
* `handle.suspend()` / `engine.suspend(id)`. Suspension is a non-terminal pause:
|
|
166
|
+
* the workflow keeps its checkpoint and is later resumable. This event is
|
|
167
|
+
* intentionally NOT in {@link WORKFLOW_TERMINAL_EVENT_TYPES} — a suspended
|
|
168
|
+
* workflow has not ended, and `handle.result()` stays pending.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* import { Engine, WorkflowSuspendedEvent } from '@lostgradient/weft';
|
|
173
|
+
*
|
|
174
|
+
* const engine = new Engine();
|
|
175
|
+
* engine.addEventListener('workflow:suspended', (e: Event) => {
|
|
176
|
+
* const ev = e as WorkflowSuspendedEvent;
|
|
177
|
+
* console.log('suspended', ev.workflowId);
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export declare class WorkflowSuspendedEvent extends Event {
|
|
182
|
+
static readonly type: "workflow:suspended";
|
|
183
|
+
readonly workflowId: string;
|
|
184
|
+
constructor(workflowId: string);
|
|
185
|
+
}
|
|
163
186
|
/**
|
|
164
187
|
* Reason carried by {@link WorkflowRecoverySkippedEvent}.
|
|
165
188
|
*
|
|
@@ -77,6 +77,15 @@ export class WorkflowResumedEvent extends Event {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
export class WorkflowSuspendedEvent extends Event {
|
|
81
|
+
static type = "workflow:suspended";
|
|
82
|
+
workflowId;
|
|
83
|
+
constructor(workflowId) {
|
|
84
|
+
super(WorkflowSuspendedEvent.type);
|
|
85
|
+
this.workflowId = workflowId;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
export class WorkflowRecoverySkippedEvent extends Event {
|
|
81
90
|
static type = "workflow:recovery-skipped";
|
|
82
91
|
workflowId;
|
|
@@ -25,6 +25,11 @@ export interface InlineExecutionDependencies {
|
|
|
25
25
|
development?: boolean;
|
|
26
26
|
getComposedWorkflowInterceptor?: () => ComposedWorkflowInterceptor | null;
|
|
27
27
|
registerCancelHandler?: (workflowId: string, handler: () => Promise<void> | void) => () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Look up the non-serialized per-run `services` value for a workflow, exposed
|
|
30
|
+
* to the body as `ctx.services`. Returns `undefined` when none was supplied.
|
|
31
|
+
*/
|
|
32
|
+
getWorkflowServices?: (workflowId: string) => unknown;
|
|
28
33
|
}
|
|
29
34
|
type InlineStartWorkflowParameters = {
|
|
30
35
|
workflowId: string;
|
|
@@ -25,7 +25,8 @@ function createInlineContextOptions(dependencies, registration, parameters, work
|
|
|
25
25
|
...parameters.deadline !== void 0 && { deadline: parameters.deadline },
|
|
26
26
|
...registerCancelHandler !== void 0 && {
|
|
27
27
|
registerCancelHandler: (handler) => registerCancelHandler(parameters.workflowId, handler)
|
|
28
|
-
}
|
|
28
|
+
},
|
|
29
|
+
services: dependencies.getWorkflowServices?.(parameters.workflowId)
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -7,7 +7,8 @@ const WORKFLOW_STATUSES = [
|
|
|
7
7
|
"completed",
|
|
8
8
|
"failed",
|
|
9
9
|
"cancelled",
|
|
10
|
-
"timed-out"
|
|
10
|
+
"timed-out",
|
|
11
|
+
"suspended"
|
|
11
12
|
], ID_PREFIX_PATTERN = /^[A-Za-z0-9_-]+$/, workflowStatusSchema = z.enum(WORKFLOW_STATUSES), failureCategorySchema = z.enum(FAILURE_CATEGORIES), timeRangeSchema = z.object({
|
|
12
13
|
gte: z.number().optional(),
|
|
13
14
|
lte: z.number().optional(),
|
|
@@ -2,11 +2,33 @@ import type { Duration } from './types.ts';
|
|
|
2
2
|
import { WeftError } from './weft-error.ts';
|
|
3
3
|
export declare const MAX_WORKFLOW_TAGS = 32;
|
|
4
4
|
export declare const MAX_WORKFLOW_TAG_BYTES = 128;
|
|
5
|
+
/**
|
|
6
|
+
* Upper bound on a start `idempotencyKey`, in UTF-8 bytes. `startOrSignal`
|
|
7
|
+
* derives a signal id of `start-idem:${key}` (an 11-byte prefix) from the key,
|
|
8
|
+
* and `validateSignalId` caps a signal id at 128 bytes — so the raw key must fit
|
|
9
|
+
* in `128 - 11 = 117` bytes for the derived id to stay within that ceiling.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_IDEMPOTENCY_KEY_BYTES = 117;
|
|
5
12
|
export declare class StartWorkflowValidationError extends WeftError<'StartWorkflowValidationError'> {
|
|
6
13
|
constructor(message: string);
|
|
7
14
|
}
|
|
8
15
|
export declare const assertExclusiveStartWorkflowOptions: (startAt: unknown, startAfter: unknown) => void;
|
|
9
16
|
export declare const coerceStartWorkflowId: (value: unknown, fieldName: string) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Coerce a transport-supplied idempotency key to a non-empty string. The key is
|
|
19
|
+
* a caller-chosen dedup token (it becomes part of a `start-idem:` storage key),
|
|
20
|
+
* so an empty string — which would collide across unrelated starts — is rejected.
|
|
21
|
+
*/
|
|
22
|
+
export declare const coerceStartWorkflowIdempotencyKey: (value: unknown, fieldName: string) => string;
|
|
23
|
+
/**
|
|
24
|
+
* Validate an already-string `idempotencyKey`: non-empty (an empty key would
|
|
25
|
+
* collide across unrelated starts under the shared `start-idem:` mapping) and at
|
|
26
|
+
* most {@link MAX_IDEMPOTENCY_KEY_BYTES} UTF-8 bytes (so the derived
|
|
27
|
+
* `start-idem:${key}` signal id stays within the signal-id ceiling). Shared by
|
|
28
|
+
* the transport coercion and the engine boundary so a direct `engine.start`
|
|
29
|
+
* caller gets the same guarantees as an HTTP caller.
|
|
30
|
+
*/
|
|
31
|
+
export declare const assertValidIdempotencyKey: (value: string, fieldName: string) => string;
|
|
10
32
|
export declare function coerceStartWorkflowTimestamp(value: unknown, fieldName: string): number;
|
|
11
33
|
export declare function parseStartWorkflowDuration(duration: Duration, fieldName: string): number;
|
|
12
34
|
export declare function coerceStartWorkflowDuration(value: unknown, fieldName: string): Duration;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseDuration } from "./scheduler.js";
|
|
2
2
|
import { WeftError } from "./weft-error.js";
|
|
3
3
|
import { assertValidWorkflowId } from "./workflow-identifiers.js";
|
|
4
|
-
export const MAX_WORKFLOW_TAGS = 32, MAX_WORKFLOW_TAG_BYTES = 128;
|
|
4
|
+
export const MAX_WORKFLOW_TAGS = 32, MAX_WORKFLOW_TAG_BYTES = 128, MAX_IDEMPOTENCY_KEY_BYTES = 117;
|
|
5
5
|
const textEncoder = new TextEncoder, EXCLUSIVE_START_WORKFLOW_OPTIONS_ERROR = "Provide only one of startAt or startAfter";
|
|
6
6
|
|
|
7
7
|
export class StartWorkflowValidationError extends WeftError {
|
|
@@ -22,6 +22,16 @@ export const assertExclusiveStartWorkflowOptions = (startAt, startAfter) => {
|
|
|
22
22
|
const message = error instanceof Error ? error.message : String(error);
|
|
23
23
|
throw new StartWorkflowValidationError(message);
|
|
24
24
|
}
|
|
25
|
+
}, coerceStartWorkflowIdempotencyKey = (value, fieldName) => {
|
|
26
|
+
if (typeof value !== "string")
|
|
27
|
+
throw new StartWorkflowValidationError(`${fieldName} must be a string`);
|
|
28
|
+
return assertValidIdempotencyKey(value, fieldName);
|
|
29
|
+
}, assertValidIdempotencyKey = (value, fieldName) => {
|
|
30
|
+
if (value.length === 0)
|
|
31
|
+
throw new StartWorkflowValidationError(`${fieldName} must not be empty`);
|
|
32
|
+
if (textEncoder.encode(value).byteLength > MAX_IDEMPOTENCY_KEY_BYTES)
|
|
33
|
+
throw new StartWorkflowValidationError(`${fieldName} must be at most ${MAX_IDEMPOTENCY_KEY_BYTES} UTF-8 bytes`);
|
|
34
|
+
return value;
|
|
25
35
|
};
|
|
26
36
|
export function coerceStartWorkflowTimestamp(value, fieldName) {
|
|
27
37
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
@@ -7,19 +7,23 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module core/step-context
|
|
9
9
|
*/
|
|
10
|
-
import type { ContextOperationRequest } from './context.ts';
|
|
11
10
|
import type { StepWorkflowContext, WorkflowFunction } from './types.ts';
|
|
12
11
|
interface QueuedOperation {
|
|
13
|
-
|
|
12
|
+
/** Explicit, user-supplied step name. Used as the durable activity label. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** The zero-argument step body. Executed by the engine as an inline activity. */
|
|
15
|
+
fn: () => unknown;
|
|
14
16
|
resolve: (value: unknown) => void;
|
|
15
17
|
reject: (reason: unknown) => void;
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
20
|
* Internal implementation of {@link StepWorkflowContext} for step-based
|
|
19
|
-
* ("progressive disclosure") workflows.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* ("progressive disclosure") workflows. Each `step()` call is a thin async
|
|
22
|
+
* enqueue; the compiled generator (see {@link compileStepWorkflow}) drains the
|
|
23
|
+
* queue and runs each step through the engine's durable activity machinery, so
|
|
24
|
+
* a completed step is replayed from the checkpoint rather than re-executed
|
|
25
|
+
* after a crash. Build via {@link compileStepWorkflow} rather than constructing
|
|
26
|
+
* directly.
|
|
23
27
|
*
|
|
24
28
|
* @example
|
|
25
29
|
* ```ts
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { asConcreteContext, runActivityWithRetry } from "./context/run-operation.js";
|
|
2
|
+
|
|
1
3
|
export class StepContext {
|
|
2
4
|
workflowId;
|
|
3
5
|
signal;
|
|
@@ -9,18 +11,8 @@ export class StepContext {
|
|
|
9
11
|
this.signal = signal;
|
|
10
12
|
}
|
|
11
13
|
async step(name, fn) {
|
|
12
|
-
const
|
|
13
|
-
this.#queue.push({
|
|
14
|
-
request: {
|
|
15
|
-
type: "activity",
|
|
16
|
-
operationId,
|
|
17
|
-
activityName: name,
|
|
18
|
-
fn,
|
|
19
|
-
input: void 0
|
|
20
|
-
},
|
|
21
|
-
resolve,
|
|
22
|
-
reject
|
|
23
|
-
});
|
|
14
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
15
|
+
this.#queue.push({ name, fn, resolve, reject });
|
|
24
16
|
this.#notifyQueue?.();
|
|
25
17
|
return promise;
|
|
26
18
|
}
|
|
@@ -43,8 +35,8 @@ export class StepContext {
|
|
|
43
35
|
}
|
|
44
36
|
}
|
|
45
37
|
export function compileStepWorkflow(stepFunction) {
|
|
46
|
-
return async function* (
|
|
47
|
-
const rawContext =
|
|
38
|
+
return async function* (publicContext, input) {
|
|
39
|
+
const rawContext = asConcreteContext(publicContext), stepContext = new StepContext(rawContext.workflowId, rawContext.signal);
|
|
48
40
|
let workflowResult, workflowError;
|
|
49
41
|
const userPromise = stepFunction(stepContext, input).then((result) => {
|
|
50
42
|
workflowResult = result;
|
|
@@ -59,7 +51,7 @@ export function compileStepWorkflow(stepFunction) {
|
|
|
59
51
|
if (queued === null)
|
|
60
52
|
break;
|
|
61
53
|
try {
|
|
62
|
-
const result = yield queued.
|
|
54
|
+
const result = yield* runActivityWithRetry(rawContext, queued.fn, [], queued.name);
|
|
63
55
|
queued.resolve(result);
|
|
64
56
|
} catch (error) {
|
|
65
57
|
queued.reject(error);
|
|
@@ -218,12 +218,15 @@ export interface ActivityDefinition<TInput = unknown, TOutput = unknown, TName e
|
|
|
218
218
|
/** Activity implementation called by the engine or worker. */
|
|
219
219
|
execute: ActivityFunction<TInput, TOutput>;
|
|
220
220
|
/**
|
|
221
|
-
* Optional
|
|
221
|
+
* Optional verifier, invoked in two phases (see
|
|
222
|
+
* `ActivityVerificationContext.phase`).
|
|
222
223
|
*
|
|
223
224
|
* During normal post-execution validation, return `true` to confirm the
|
|
224
225
|
* activity result or `false` to reject it. During pre-dispatch crash recovery
|
|
225
|
-
* for keyed activities, return an explicit reconciliation state
|
|
226
|
-
*
|
|
226
|
+
* for keyed activities, return an explicit reconciliation state. A boolean is
|
|
227
|
+
* the post-execution result shape, not a Tier-0 reconciliation state, so a
|
|
228
|
+
* boolean returned during pre-dispatch reconciliation fails closed rather than
|
|
229
|
+
* being treated as proof of external completion.
|
|
227
230
|
*/
|
|
228
231
|
verify?: ActivityVerifier<TInput, TOutput>;
|
|
229
232
|
retry?: RetryPolicy;
|
|
@@ -51,5 +51,12 @@ export type FailureCategory = 'application' | 'timeout' | 'cancellation' | 'reso
|
|
|
51
51
|
* Read this off `(await handle.state()).status` to learn whether a workflow
|
|
52
52
|
* is still running, finished cleanly, or failed. Pass it to `engine.list()`
|
|
53
53
|
* filters to scope queries by status.
|
|
54
|
+
*
|
|
55
|
+
* `'suspended'` is a non-terminal, non-running pause: a workflow that was
|
|
56
|
+
* explicitly suspended via {@link WorkflowHandle.suspend} (or
|
|
57
|
+
* `engine.suspend(id)`) keeps its checkpoint and is resumable via
|
|
58
|
+
* {@link WorkflowHandle.resume} (or `engine.resume(id)`), but is NOT
|
|
59
|
+
* auto-recovered by `engine.recoverAll()` — suspension is client-driven
|
|
60
|
+
* preemption, not a fault condition.
|
|
54
61
|
*/
|
|
55
|
-
export type WorkflowStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'timed-out';
|
|
62
|
+
export type WorkflowStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'timed-out' | 'suspended';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch context for a workflow, reconstructed from its persisted state by
|
|
3
|
+
* {@link WorkflowHandle.getLaunchMetadata}. Lets a caller — typically after
|
|
4
|
+
* `engine.recoverAll()` — recover the original input and the launch options that
|
|
5
|
+
* survive in durable state, without keeping a side table that correlates a
|
|
6
|
+
* recovered workflow back to how it was started.
|
|
7
|
+
*
|
|
8
|
+
* `launchOptions` carries only the options recoverable *faithfully* from
|
|
9
|
+
* persisted state: `id` (always) and `tags`. Note `tags` is the run's *current*
|
|
10
|
+
* tag set (tags are mutable via `addTags`/`removeTags`), not necessarily the
|
|
11
|
+
* tags passed at launch. Deliberately excluded: `executionTimeout` (state
|
|
12
|
+
* stores the absolute deadline, not the original duration, so it cannot be
|
|
13
|
+
* reproduced exactly); `idempotencyKey` (no durable trace); `searchAttributes`
|
|
14
|
+
* (live in their own durable index — read them via
|
|
15
|
+
* {@link WorkflowHandle.getAttributes}); and `services` (non-serializable,
|
|
16
|
+
* re-provided on recovery by {@link EngineOptions.resolveWorkflowServices}).
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { type LaunchMetadata } from '@lostgradient/weft';
|
|
21
|
+
*
|
|
22
|
+
* function describe(metadata: LaunchMetadata): string {
|
|
23
|
+
* return `input=${JSON.stringify(metadata.input)} id=${metadata.launchOptions.id}`;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export interface LaunchMetadata {
|
|
28
|
+
input: unknown;
|
|
29
|
+
launchOptions: {
|
|
30
|
+
id: string;
|
|
31
|
+
tags?: string[];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
File without changes
|
|
@@ -111,6 +111,31 @@ export interface SignalOptions<TInput = void> {
|
|
|
111
111
|
export interface SignalDeliveryOptions {
|
|
112
112
|
readonly signalId?: string;
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* The signal half of `startOrSignal` (signal-with-start): a signal `name`, an
|
|
116
|
+
* optional `payload`, and an optional `signalId`. When `signalId` is omitted it
|
|
117
|
+
* is derived from `options.idempotencyKey`. The id is load-bearing for
|
|
118
|
+
* convergence — concurrent callers deliver one signal only when they share it,
|
|
119
|
+
* and independent webhook retries share only the idempotency key — so a
|
|
120
|
+
* caller-supplied `signalId` covers the single-caller case while idempotent
|
|
121
|
+
* convergence relies on the derived-from-key value.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* import type { StartOrSignalSignal } from '@lostgradient/weft';
|
|
126
|
+
*
|
|
127
|
+
* const signal: StartOrSignalSignal = {
|
|
128
|
+
* name: 'webhook',
|
|
129
|
+
* payload: { event: 'payment.succeeded' },
|
|
130
|
+
* };
|
|
131
|
+
* void signal;
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export interface StartOrSignalSignal {
|
|
135
|
+
readonly name: string;
|
|
136
|
+
readonly payload?: unknown;
|
|
137
|
+
readonly signalId?: string;
|
|
138
|
+
}
|
|
114
139
|
/**
|
|
115
140
|
* Create a typed workflow signal handle. When an `inputSchema` is supplied
|
|
116
141
|
* via options, the payload type is inferred from the schema's
|
|
@@ -9,19 +9,28 @@ import type { PayloadSizePolicy } from './payload-size-policy.ts';
|
|
|
9
9
|
import type { Duration, RetentionPolicy } from './retry-retention.ts';
|
|
10
10
|
import type { SearchAttributeHandle, SearchAttributeValue } from './search-attributes.ts';
|
|
11
11
|
import type { Serializer } from './serializer.ts';
|
|
12
|
+
import type { WorkflowServicesResolution, WorkflowServicesResolverInfo } from './services-resolution.ts';
|
|
12
13
|
/**
|
|
13
14
|
* Options accepted by `engine.start(type, input, options?)`.
|
|
14
15
|
*
|
|
15
16
|
* Every field is optional. `id` lets you specify your own workflow ID;
|
|
16
|
-
* `idempotencyKey` enforces
|
|
17
|
-
*
|
|
17
|
+
* `idempotencyKey` enforces at-most-once starts (a repeated key returns the
|
|
18
|
+
* existing run instead of starting a second, even after it reaches a terminal
|
|
19
|
+
* state); `executionTimeout` caps wall-clock time; `startAt`/`startAfter` defer
|
|
18
20
|
* execution; `tags` and `searchAttributes` make the workflow discoverable
|
|
19
|
-
* via filters.
|
|
21
|
+
* via filters. `id` and `idempotencyKey` are mutually exclusive — idempotency
|
|
22
|
+
* assigns its own generated id and dedups through the key.
|
|
20
23
|
*
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* The `idempotencyKey` mapping is durable and permanent: it survives the run
|
|
25
|
+
* reaching a terminal state, so repeat calls keep returning the same handle. When
|
|
26
|
+
* the workflow record is purged or swept by retention the mapping itself survives
|
|
27
|
+
* (purge does not touch the `start-idem:` keyspace) but now points at a gone run;
|
|
28
|
+
* a call with that spent key throws {@link IdempotencyKeyPurgedError} (mapped to
|
|
29
|
+
* HTTP 409 over REST/JSON-RPC) rather than silently starting a new run.
|
|
30
|
+
*
|
|
31
|
+
* Both `LocalClient.start` and `HttpClient.start` forward `idempotencyKey` and
|
|
32
|
+
* `searchAttributes` to the server, so single-execution semantics hold across
|
|
33
|
+
* transports. Idempotent start requires a storage backend with `conditionalBatch`.
|
|
25
34
|
*
|
|
26
35
|
* @example Start a delayed workflow with tags and search attributes
|
|
27
36
|
* ```ts
|
|
@@ -52,6 +61,31 @@ export interface StartOptions {
|
|
|
52
61
|
startAfter?: Duration;
|
|
53
62
|
tags?: string[];
|
|
54
63
|
searchAttributes?: Record<string, SearchAttributeValue>;
|
|
64
|
+
/**
|
|
65
|
+
* Host-supplied, per-run capabilities exposed to the workflow body as
|
|
66
|
+
* `ctx.services` (live clients, closures, tool registries). The value is
|
|
67
|
+
* **never checkpointed**; on a fresh-process recovery it is re-provided by the
|
|
68
|
+
* engine's {@link EngineOptions.resolveWorkflowServices} resolver before the
|
|
69
|
+
* generator advances.
|
|
70
|
+
*
|
|
71
|
+
* Inline execution mode only. Passing `services` under
|
|
72
|
+
* `workflowExecutionMode: 'worker'` throws at `engine.start()`, because a
|
|
73
|
+
* non-serializable value cannot cross to a Worker.
|
|
74
|
+
*/
|
|
75
|
+
services?: unknown;
|
|
76
|
+
/**
|
|
77
|
+
* When `false`, `engine.start()` resolves only after the workflow has begun
|
|
78
|
+
* executing (its generator has been driven its first turn), not merely after
|
|
79
|
+
* the initial state is persisted. The default (`true`) returns a handle as
|
|
80
|
+
* soon as state is written and queues execution onto a macrotask, so a caller
|
|
81
|
+
* cannot assume the run is live without a round-trip. Use `defer: false` when a
|
|
82
|
+
* caller — or a test — must rely on the run being live immediately after
|
|
83
|
+
* `await engine.start(...)`. If the body throws on its first turn, `start()`
|
|
84
|
+
* still resolves; observe the failure via `handle.result()`. Inline mode only;
|
|
85
|
+
* throws at `engine.start()` under `workflowExecutionMode: 'worker'` or with a
|
|
86
|
+
* delayed start (`startAt`/`startAfter`), neither of which has liveness to await.
|
|
87
|
+
*/
|
|
88
|
+
defer?: boolean;
|
|
55
89
|
}
|
|
56
90
|
/**
|
|
57
91
|
* Options for {@link Engine.fork}. Controls which checkpoint step to fork
|
|
@@ -103,6 +137,25 @@ export interface EngineOptions {
|
|
|
103
137
|
retention?: RetentionPolicy;
|
|
104
138
|
retentionSweepInterval?: Duration;
|
|
105
139
|
retentionSweepBatchSize?: number;
|
|
140
|
+
/**
|
|
141
|
+
* Enable a best-effort, warn-only detector for a SECOND engine instance writing
|
|
142
|
+
* to the same durable store — a smoke alarm for singleton misconfiguration (an
|
|
143
|
+
* autoscaler above one replica, or overlapping rolling deploys). Default `false`.
|
|
144
|
+
* This is **liveness, not fencing**: it never blocks boot, gates recovery, or
|
|
145
|
+
* prevents duplicate execution — enforce one instance at the infrastructure layer
|
|
146
|
+
* (one replica + a `Recreate` deploy, or a single systemd unit). When enabled,
|
|
147
|
+
* each engine writes a periodic heartbeat and warns (`process.emitWarning`) when
|
|
148
|
+
* it sees another instance's heartbeat advancing while it runs. The periodic
|
|
149
|
+
* writes are an ongoing cost, so leave it off unless you want the backstop.
|
|
150
|
+
*/
|
|
151
|
+
detectSecondInstance?: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Heartbeat interval for {@link EngineOptions.detectSecondInstance} (default
|
|
154
|
+
* `15s`). A foreign heartbeat must advance across two intervals before warning,
|
|
155
|
+
* so this also sets how long a deploy overlap must last before it warns — keep it
|
|
156
|
+
* well above your deploy drain window. Ignored when detection is disabled.
|
|
157
|
+
*/
|
|
158
|
+
secondInstanceHeartbeatInterval?: Duration;
|
|
106
159
|
/**
|
|
107
160
|
* History circuit-breaker thresholds. When `history.maxEvents` is set, a
|
|
108
161
|
* workflow whose event-log record count would exceed it is forced to a
|
|
@@ -197,6 +250,36 @@ export interface EngineOptions {
|
|
|
197
250
|
* after passing it has no effect.
|
|
198
251
|
*/
|
|
199
252
|
interceptors?: readonly Interceptor[];
|
|
253
|
+
/**
|
|
254
|
+
* Re-provide the non-serialized per-run `services` value (see
|
|
255
|
+
* {@link StartOptions.services}) for a workflow recovered in a fresh process.
|
|
256
|
+
* `engine.recoverAll()` and `engine.resume(id)` call this **before** the
|
|
257
|
+
* generator is driven forward (and the delayed-start timer handler calls it
|
|
258
|
+
* for a `startAfter`/`startAt` run that crashed before firing), so the resumed
|
|
259
|
+
* body can read `ctx.services` exactly as it did before the crash.
|
|
260
|
+
*
|
|
261
|
+
* Return `{ status: 'available', services }` to supply the rebuilt
|
|
262
|
+
* capabilities, or `{ status: 'unavailable', reason }` to fail just that one
|
|
263
|
+
* recovered run — the engine and every other recovered run are unaffected.
|
|
264
|
+
* Without a resolver, a recovered inline workflow that reads `ctx.services`
|
|
265
|
+
* sees `undefined`.
|
|
266
|
+
*
|
|
267
|
+
* Contract a fresh integrator must know:
|
|
268
|
+
* - Fires only for recovered inline runs that were launched WITH `services`
|
|
269
|
+
* (those carrying the durable "expects services" marker). A run started
|
|
270
|
+
* without `services` never reaches the resolver, regardless of what it would
|
|
271
|
+
* return — so a fail-closed resolver does not fail innocent no-services runs.
|
|
272
|
+
* - `{ status: 'unavailable' }` permanently fails the run (terminal `failed`,
|
|
273
|
+
* `system` category) with `reason` as the message — not a "retry later"
|
|
274
|
+
* signal. A resolver *throw* is treated identically (error message → reason).
|
|
275
|
+
* - May be called again on a later boot if a prior recovery left the run still
|
|
276
|
+
* recoverable (e.g. the terminal-fail commit faulted), so keep it idempotent.
|
|
277
|
+
* - Inline only; worker-mode runs never invoke it.
|
|
278
|
+
*
|
|
279
|
+
* Engine-scoped: each engine instance carries its own resolver, so two engines
|
|
280
|
+
* in one process never collide on per-run dependency reconstruction.
|
|
281
|
+
*/
|
|
282
|
+
resolveWorkflowServices?: (info: WorkflowServicesResolverInfo) => WorkflowServicesResolution | Promise<WorkflowServicesResolution>;
|
|
200
283
|
}
|
|
201
284
|
/**
|
|
202
285
|
* Filter criteria for {@link Engine.list}. All fields are optional and
|
|
@@ -54,7 +54,8 @@ export type ReviewStatus = 'pending' | 'completed';
|
|
|
54
54
|
* Filter accepted by `engine.listReviews(filter?)` and the `/v1/reviews`
|
|
55
55
|
* transport surfaces.
|
|
56
56
|
*
|
|
57
|
-
* Omitting `status`
|
|
57
|
+
* Omitting `status` lists pending reviews; pass `status: 'completed'` to list
|
|
58
|
+
* completed reviews instead.
|
|
58
59
|
*
|
|
59
60
|
* @example
|
|
60
61
|
* ```ts
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of {@link EngineOptions.resolveWorkflowServices}. An explicit union
|
|
3
|
+
* rather than a nullable return: `'unavailable'` is a deliberate, named outcome
|
|
4
|
+
* (the run's dependencies cannot be rebuilt in this process) that fails just
|
|
5
|
+
* that recovered run — it does not overload the resolved value with a lifecycle
|
|
6
|
+
* signal.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { type WorkflowServicesResolution } from '@lostgradient/weft';
|
|
11
|
+
*
|
|
12
|
+
* const ok: WorkflowServicesResolution = {
|
|
13
|
+
* status: 'available',
|
|
14
|
+
* services: { db: { query: () => [] } },
|
|
15
|
+
* };
|
|
16
|
+
* const no: WorkflowServicesResolution = { status: 'unavailable', reason: 'no config' };
|
|
17
|
+
* void ok;
|
|
18
|
+
* void no;
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export type WorkflowServicesResolution = {
|
|
22
|
+
status: 'available';
|
|
23
|
+
services: unknown;
|
|
24
|
+
} | {
|
|
25
|
+
status: 'unavailable';
|
|
26
|
+
reason: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Information passed to {@link EngineOptions.resolveWorkflowServices} for each
|
|
30
|
+
* recovered workflow. `input` is the original durable launch input, available
|
|
31
|
+
* at resume time — typically enough to rebuild the run's dependencies (tenant,
|
|
32
|
+
* model, tool registry) without a side table.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { type WorkflowServicesResolverInfo } from '@lostgradient/weft';
|
|
37
|
+
*
|
|
38
|
+
* function describe(info: WorkflowServicesResolverInfo): string {
|
|
39
|
+
* return `${info.workflowType}/${info.workflowId}`;
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export interface WorkflowServicesResolverInfo {
|
|
44
|
+
workflowId: string;
|
|
45
|
+
workflowType: string;
|
|
46
|
+
input: unknown;
|
|
47
|
+
}
|
|
File without changes
|
|
@@ -42,7 +42,17 @@ export interface WorkflowState {
|
|
|
42
42
|
* leaves this `undefined`).
|
|
43
43
|
*/
|
|
44
44
|
terminationReason?: TerminationReason;
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The `(workflowVersion, agentVersion?, toolVersions?)` tuple captured for
|
|
47
|
+
* this workflow. This is the single canonical representation of version
|
|
48
|
+
* metadata on the state: it shares the {@link WorkflowVersionTuple} shape the
|
|
49
|
+
* event log records, so the two cannot drift, and it is rewritten on every
|
|
50
|
+
* versioned persist. `workflowVersion` is always present;
|
|
51
|
+
* `agentVersion`/`toolVersions` are present only when the workflow declares
|
|
52
|
+
* them. Resuming compares this against the currently registered definition to
|
|
53
|
+
* detect version drift.
|
|
54
|
+
*/
|
|
55
|
+
versionTuple: WorkflowVersionTuple;
|
|
46
56
|
/**
|
|
47
57
|
* Owner identifier for execution-scoped durable state. Top-level workflows
|
|
48
58
|
* own their own execution state; durable child workflows inherit the
|
|
@@ -50,16 +60,6 @@ export interface WorkflowState {
|
|
|
50
60
|
* tree.
|
|
51
61
|
*/
|
|
52
62
|
executionStateOwnerId?: string;
|
|
53
|
-
/**
|
|
54
|
-
* Legacy version tuple metadata retained so existing persisted workflow
|
|
55
|
-
* states can still be decoded and compared during recovery.
|
|
56
|
-
*/
|
|
57
|
-
agentVersion?: string;
|
|
58
|
-
/**
|
|
59
|
-
* Legacy version tuple metadata retained so existing persisted workflow
|
|
60
|
-
* states can still be decoded and compared during recovery.
|
|
61
|
-
*/
|
|
62
|
-
toolVersions?: string[];
|
|
63
63
|
createdAt: number;
|
|
64
64
|
startedAt?: number;
|
|
65
65
|
updatedAt: number;
|