@exaudeus/workrail 0.11.0 → 0.13.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.
Files changed (131) hide show
  1. package/dist/application/services/enhanced-loop-validator.js +3 -3
  2. package/dist/application/services/step-output-decoder.d.ts +6 -0
  3. package/dist/application/services/step-output-decoder.js +49 -0
  4. package/dist/application/services/validation-engine.d.ts +9 -0
  5. package/dist/application/services/validation-engine.js +142 -18
  6. package/dist/application/services/workflow-interpreter.d.ts +1 -1
  7. package/dist/application/services/workflow-interpreter.js +147 -81
  8. package/dist/application/services/workflow-service.d.ts +2 -0
  9. package/dist/application/services/workflow-service.js +3 -3
  10. package/dist/application/use-cases/validate-step-output.d.ts +2 -0
  11. package/dist/config/feature-flags.js +1 -1
  12. package/dist/di/container.js +88 -0
  13. package/dist/di/tokens.d.ts +16 -0
  14. package/dist/di/tokens.js +16 -0
  15. package/dist/domain/execution/state.d.ts +6 -6
  16. package/dist/domain/workflow-id-policy.d.ts +17 -0
  17. package/dist/domain/workflow-id-policy.js +57 -0
  18. package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +33 -6
  19. package/dist/infrastructure/storage/file-workflow-storage.js +3 -1
  20. package/dist/infrastructure/storage/schema-validating-workflow-storage.js +13 -8
  21. package/dist/manifest.json +329 -161
  22. package/dist/mcp/error-mapper.d.ts +3 -8
  23. package/dist/mcp/error-mapper.js +41 -19
  24. package/dist/mcp/handlers/session.js +25 -11
  25. package/dist/mcp/handlers/v2-execution-helpers.d.ts +99 -0
  26. package/dist/mcp/handlers/v2-execution-helpers.js +249 -0
  27. package/dist/mcp/handlers/v2-execution.d.ts +4 -0
  28. package/dist/mcp/handlers/v2-execution.js +1044 -0
  29. package/dist/mcp/handlers/v2-workflow.js +21 -16
  30. package/dist/mcp/handlers/workflow.js +21 -12
  31. package/dist/mcp/index.d.ts +1 -1
  32. package/dist/mcp/index.js +4 -1
  33. package/dist/mcp/output-schemas.d.ts +411 -4
  34. package/dist/mcp/output-schemas.js +57 -1
  35. package/dist/mcp/server.d.ts +1 -1
  36. package/dist/mcp/server.js +96 -65
  37. package/dist/mcp/tool-descriptions.js +32 -15
  38. package/dist/mcp/tools.js +26 -14
  39. package/dist/mcp/types/tool-description-types.d.ts +1 -1
  40. package/dist/mcp/types/tool-description-types.js +7 -5
  41. package/dist/mcp/types.d.ts +40 -3
  42. package/dist/mcp/types.js +32 -3
  43. package/dist/mcp/v2/tool-registry.js +16 -1
  44. package/dist/mcp/v2/tools.d.ts +45 -0
  45. package/dist/mcp/v2/tools.js +21 -1
  46. package/dist/mcp/validation/workflow-next-prevalidate.d.ts +2 -3
  47. package/dist/mcp/validation/workflow-next-prevalidate.js +38 -27
  48. package/dist/v2/durable-core/constants.d.ts +15 -0
  49. package/dist/v2/durable-core/constants.js +18 -0
  50. package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +32 -0
  51. package/dist/v2/durable-core/domain/ack-advance-append-plan.js +95 -0
  52. package/dist/v2/durable-core/domain/loop-runtime.d.ts +50 -0
  53. package/dist/v2/durable-core/domain/loop-runtime.js +95 -0
  54. package/dist/v2/durable-core/domain/notes-markdown.d.ts +4 -0
  55. package/dist/v2/durable-core/domain/notes-markdown.js +46 -0
  56. package/dist/v2/durable-core/domain/outputs.d.ts +12 -0
  57. package/dist/v2/durable-core/domain/outputs.js +18 -0
  58. package/dist/v2/durable-core/ids/index.d.ts +2 -0
  59. package/dist/v2/durable-core/ids/index.js +4 -0
  60. package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +100 -6
  61. package/dist/v2/durable-core/schemas/compiled-workflow/index.js +18 -3
  62. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +113 -113
  63. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +11 -10
  64. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +7129 -0
  65. package/dist/v2/durable-core/schemas/export-bundle/index.js +82 -0
  66. package/dist/v2/durable-core/schemas/lib/decision-trace-ref.d.ts +80 -0
  67. package/dist/v2/durable-core/schemas/lib/decision-trace-ref.js +38 -0
  68. package/dist/v2/durable-core/schemas/lib/dedupe-key.d.ts +8 -0
  69. package/dist/v2/durable-core/schemas/lib/dedupe-key.js +28 -0
  70. package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.d.ts +6 -0
  71. package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.js +12 -0
  72. package/dist/v2/durable-core/schemas/session/events.d.ts +238 -62
  73. package/dist/v2/durable-core/schemas/session/events.js +74 -29
  74. package/dist/v2/durable-core/schemas/session/manifest.d.ts +3 -3
  75. package/dist/v2/durable-core/schemas/session/manifest.js +6 -1
  76. package/dist/v2/durable-core/schemas/session/preferences.d.ts +5 -0
  77. package/dist/v2/durable-core/schemas/session/preferences.js +6 -0
  78. package/dist/v2/durable-core/schemas/session/session-health.d.ts +3 -0
  79. package/dist/v2/durable-core/tokens/index.d.ts +2 -1
  80. package/dist/v2/durable-core/tokens/index.js +4 -4
  81. package/dist/v2/durable-core/tokens/payloads.d.ts +4 -4
  82. package/dist/v2/durable-core/tokens/token-codec.d.ts +3 -2
  83. package/dist/v2/durable-core/tokens/token-codec.js +12 -6
  84. package/dist/v2/durable-core/tokens/token-signer.d.ts +3 -2
  85. package/dist/v2/durable-core/tokens/token-signer.js +8 -9
  86. package/dist/v2/infra/local/base64url/index.d.ts +5 -0
  87. package/dist/v2/infra/local/base64url/index.js +48 -0
  88. package/dist/v2/infra/local/fs/index.js +8 -4
  89. package/dist/v2/infra/local/keyring/index.d.ts +5 -1
  90. package/dist/v2/infra/local/keyring/index.js +41 -32
  91. package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +6 -4
  92. package/dist/v2/infra/local/pinned-workflow-store/index.js +50 -62
  93. package/dist/v2/infra/local/random-entropy/index.d.ts +4 -0
  94. package/dist/v2/infra/local/random-entropy/index.js +10 -0
  95. package/dist/v2/infra/local/session-lock/index.d.ts +3 -1
  96. package/dist/v2/infra/local/session-lock/index.js +5 -4
  97. package/dist/v2/infra/local/session-store/index.d.ts +0 -1
  98. package/dist/v2/infra/local/session-store/index.js +372 -282
  99. package/dist/v2/infra/local/snapshot-store/index.js +20 -25
  100. package/dist/v2/infra/local/time-clock/index.d.ts +5 -0
  101. package/dist/v2/infra/local/time-clock/index.js +12 -0
  102. package/dist/v2/infra/local/utf8/index.d.ts +5 -0
  103. package/dist/v2/infra/local/utf8/index.js +12 -0
  104. package/dist/v2/ports/base64url.port.d.ts +12 -0
  105. package/dist/v2/ports/base64url.port.js +2 -0
  106. package/dist/v2/ports/pinned-workflow-store.port.d.ts +3 -3
  107. package/dist/v2/ports/random-entropy.port.d.ts +3 -0
  108. package/dist/v2/ports/random-entropy.port.js +2 -0
  109. package/dist/v2/ports/session-event-log-store.port.d.ts +1 -1
  110. package/dist/v2/ports/session-lock.port.d.ts +1 -1
  111. package/dist/v2/ports/time-clock.port.d.ts +4 -0
  112. package/dist/v2/ports/time-clock.port.js +2 -0
  113. package/dist/v2/ports/utf8.port.d.ts +3 -0
  114. package/dist/v2/ports/utf8.port.js +2 -0
  115. package/dist/v2/projections/node-outputs.js +28 -11
  116. package/dist/v2/projections/preferences.d.ts +1 -2
  117. package/dist/v2/projections/preferences.js +11 -4
  118. package/dist/v2/projections/run-dag.js +40 -28
  119. package/dist/v2/projections/run-status-signals.d.ts +1 -2
  120. package/dist/v2/read-only/v1-to-v2-shim.d.ts +6 -1
  121. package/dist/v2/read-only/v1-to-v2-shim.js +16 -4
  122. package/dist/v2/usecases/execution-session-gate.d.ts +3 -2
  123. package/dist/v2/usecases/execution-session-gate.js +81 -85
  124. package/package.json +4 -1
  125. package/spec/workflow.schema.json +2 -2
  126. package/workflows/coding-task-workflow-agentic.json +498 -78
  127. package/workflows/design-thinking-workflow-autonomous.agentic.json +1 -1
  128. package/workflows/design-thinking-workflow.json +1 -1
  129. package/workflows/relocation-workflow-us.json +430 -0
  130. package/dist/v2/durable-core/tokens/base64url.d.ts +0 -7
  131. package/dist/v2/durable-core/tokens/base64url.js +0 -16
@@ -0,0 +1,1044 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleV2StartWorkflow = handleV2StartWorkflow;
37
+ exports.handleV2ContinueWorkflow = handleV2ContinueWorkflow;
38
+ const os = __importStar(require("os"));
39
+ const crypto_1 = require("crypto");
40
+ const types_js_1 = require("../types.js");
41
+ const output_schemas_js_1 = require("../output-schemas.js");
42
+ const snapshot_state_js_1 = require("../../v2/durable-core/projections/snapshot-state.js");
43
+ const step_instance_key_js_1 = require("../../v2/durable-core/schemas/execution-snapshot/step-instance-key.js");
44
+ const index_js_1 = require("../../v2/durable-core/tokens/index.js");
45
+ const index_js_2 = require("../../v2/durable-core/tokens/index.js");
46
+ const workflow_js_1 = require("../../types/workflow.js");
47
+ const index_js_3 = require("../../v2/durable-core/ids/index.js");
48
+ const neverthrow_1 = require("neverthrow");
49
+ const v1_to_v2_shim_js_1 = require("../../v2/read-only/v1-to-v2-shim.js");
50
+ const hashing_js_1 = require("../../v2/durable-core/canonical/hashing.js");
51
+ const jcs_js_1 = require("../../v2/durable-core/canonical/jcs.js");
52
+ const constants_js_1 = require("../../v2/durable-core/constants.js");
53
+ const notes_markdown_js_1 = require("../../v2/durable-core/domain/notes-markdown.js");
54
+ const outputs_js_1 = require("../../v2/durable-core/domain/outputs.js");
55
+ const ack_advance_append_plan_js_1 = require("../../v2/durable-core/domain/ack-advance-append-plan.js");
56
+ const workflow_source_js_1 = require("../../types/workflow-source.js");
57
+ const workflow_definition_js_1 = require("../../types/workflow-definition.js");
58
+ const workflow_compiler_js_1 = require("../../application/services/workflow-compiler.js");
59
+ const workflow_interpreter_js_1 = require("../../application/services/workflow-interpreter.js");
60
+ const v2_execution_helpers_js_1 = require("./v2-execution-helpers.js");
61
+ function normalizeTokenErrorMessage(message) {
62
+ return message.split(os.homedir()).join('~');
63
+ }
64
+ const MAX_CONTEXT_BYTES_V2 = constants_js_1.MAX_CONTEXT_BYTES;
65
+ function validateJsonValueOrIssue(value, path, depth, seen) {
66
+ if (depth > constants_js_1.MAX_CONTEXT_DEPTH)
67
+ return { kind: 'too_deep', path, maxDepth: constants_js_1.MAX_CONTEXT_DEPTH };
68
+ if (value === null)
69
+ return null;
70
+ const t = typeof value;
71
+ if (t === 'string' || t === 'boolean')
72
+ return null;
73
+ if (t === 'number') {
74
+ if (!Number.isFinite(value)) {
75
+ return { kind: 'non_finite_number', path, value: String(value) };
76
+ }
77
+ return null;
78
+ }
79
+ if (t === 'object') {
80
+ if (Array.isArray(value)) {
81
+ if (seen.has(value))
82
+ return { kind: 'circular_reference', path };
83
+ seen.add(value);
84
+ for (let i = 0; i < value.length; i++) {
85
+ const child = validateJsonValueOrIssue(value[i], `${path}[${i}]`, depth + 1, seen);
86
+ if (child)
87
+ return child;
88
+ }
89
+ return null;
90
+ }
91
+ if (seen.has(value))
92
+ return { kind: 'circular_reference', path };
93
+ seen.add(value);
94
+ for (const [k, v] of Object.entries(value)) {
95
+ const child = validateJsonValueOrIssue(v, path === '$' ? `$.${k}` : `${path}.${k}`, depth + 1, seen);
96
+ if (child)
97
+ return child;
98
+ }
99
+ return null;
100
+ }
101
+ return { kind: 'unsupported_value', path, valueType: t };
102
+ }
103
+ function checkContextBudget(args) {
104
+ if (args.context === undefined)
105
+ return { ok: true };
106
+ if (typeof args.context !== 'object' || args.context === null || Array.isArray(args.context)) {
107
+ const details = {
108
+ kind: 'context_invalid_shape',
109
+ tool: args.tool,
110
+ expected: 'object',
111
+ };
112
+ return {
113
+ ok: false,
114
+ error: (0, types_js_1.errNotRetryable)('VALIDATION_ERROR', `context must be a JSON object for ${args.tool}.`, {
115
+ suggestion: 'Pass context as an object of external inputs (e.g., {"ticketId":"...","repoPath":"..."}). Do not pass arrays or primitives.',
116
+ details,
117
+ }),
118
+ };
119
+ }
120
+ const contextObj = args.context;
121
+ const issue = validateJsonValueOrIssue(contextObj, '$', 0, new WeakSet());
122
+ if (issue) {
123
+ const details = (() => {
124
+ switch (issue.kind) {
125
+ case 'unsupported_value':
126
+ return {
127
+ kind: 'context_unsupported_value',
128
+ tool: args.tool,
129
+ path: issue.path,
130
+ valueType: issue.valueType,
131
+ };
132
+ case 'non_finite_number':
133
+ return {
134
+ kind: 'context_non_finite_number',
135
+ tool: args.tool,
136
+ path: issue.path,
137
+ value: issue.value,
138
+ };
139
+ case 'circular_reference':
140
+ return {
141
+ kind: 'context_circular_reference',
142
+ tool: args.tool,
143
+ path: issue.path,
144
+ };
145
+ case 'too_deep':
146
+ return {
147
+ kind: 'context_too_deep',
148
+ tool: args.tool,
149
+ path: issue.path,
150
+ maxDepth: issue.maxDepth,
151
+ };
152
+ default: {
153
+ const _exhaustive = issue;
154
+ return {
155
+ kind: 'context_invalid_shape',
156
+ tool: args.tool,
157
+ expected: 'object',
158
+ };
159
+ }
160
+ }
161
+ })();
162
+ return {
163
+ ok: false,
164
+ error: (0, types_js_1.errNotRetryable)('VALIDATION_ERROR', normalizeTokenErrorMessage(`context is not JSON-serializable for ${args.tool} (see details).`), {
165
+ suggestion: 'Remove non-JSON values (undefined/functions/symbols), circular references, and non-finite numbers. Keep context to plain JSON objects/arrays/primitives only.',
166
+ details: details,
167
+ }),
168
+ };
169
+ }
170
+ const canonicalRes = (0, jcs_js_1.toCanonicalBytes)(contextObj);
171
+ if (canonicalRes.isErr()) {
172
+ const details = {
173
+ kind: 'context_not_canonical_json',
174
+ tool: args.tool,
175
+ measuredAs: 'jcs_utf8_bytes',
176
+ code: canonicalRes.error.code,
177
+ message: canonicalRes.error.message,
178
+ };
179
+ const suggestion = canonicalRes.error.code === 'CANONICAL_JSON_NON_FINITE_NUMBER'
180
+ ? 'Remove NaN/Infinity/-Infinity from context. Canonical JSON forbids non-finite numbers.'
181
+ : 'Ensure context contains only JSON primitives, arrays, and objects (no undefined/functions/symbols).';
182
+ return {
183
+ ok: false,
184
+ error: (0, types_js_1.errNotRetryable)('VALIDATION_ERROR', normalizeTokenErrorMessage(`context cannot be canonicalized for ${args.tool}: ${canonicalRes.error.code}`), {
185
+ suggestion,
186
+ details,
187
+ }),
188
+ };
189
+ }
190
+ const measuredBytes = canonicalRes.value.length;
191
+ if (measuredBytes > MAX_CONTEXT_BYTES_V2) {
192
+ const details = {
193
+ kind: 'context_budget_exceeded',
194
+ tool: args.tool,
195
+ measuredBytes,
196
+ maxBytes: MAX_CONTEXT_BYTES_V2,
197
+ measuredAs: 'jcs_utf8_bytes',
198
+ };
199
+ return {
200
+ ok: false,
201
+ error: (0, types_js_1.errNotRetryable)('VALIDATION_ERROR', `context is too large for ${args.tool}: ${measuredBytes} bytes (max ${MAX_CONTEXT_BYTES_V2}). Size is measured as UTF-8 bytes of RFC 8785 (JCS) canonical JSON.`, {
202
+ suggestion: 'Remove large blobs from context (docs/logs/diffs). Pass references instead (file paths, IDs, hashes). If you must include text, include only the minimal excerpt, then retry.',
203
+ details,
204
+ }),
205
+ };
206
+ }
207
+ return { ok: true };
208
+ }
209
+ function isInternalError(e) {
210
+ if (typeof e !== 'object' || e === null || !('kind' in e))
211
+ return false;
212
+ const kind = e.kind;
213
+ return (kind === 'missing_node_or_run' ||
214
+ kind === 'workflow_hash_mismatch' ||
215
+ kind === 'missing_snapshot' ||
216
+ kind === 'no_pending_step' ||
217
+ kind === 'invariant_violation' ||
218
+ kind === 'advance_apply_failed' ||
219
+ kind === 'advance_next_failed');
220
+ }
221
+ function mapInternalErrorToToolError(e) {
222
+ switch (e.kind) {
223
+ case 'missing_node_or_run':
224
+ return (0, types_js_1.errNotRetryable)('PRECONDITION_FAILED', 'No durable run/node state was found for this stateToken. Advancement cannot be recorded.', { suggestion: 'Use a stateToken returned by WorkRail for an existing run/node.' });
225
+ case 'workflow_hash_mismatch':
226
+ return (0, types_js_1.errNotRetryable)('TOKEN_WORKFLOW_HASH_MISMATCH', 'workflowHash mismatch for this node.', { suggestion: 'Use the stateToken returned by WorkRail for this node.' });
227
+ case 'missing_snapshot':
228
+ case 'no_pending_step':
229
+ return internalError('Incomplete execution state.', 'Retry; if this persists, treat as invariant violation.');
230
+ case 'invariant_violation':
231
+ return internalError(normalizeTokenErrorMessage(e.message), 'Treat as invariant violation.');
232
+ case 'advance_apply_failed':
233
+ case 'advance_next_failed':
234
+ return internalError(normalizeTokenErrorMessage(e.message), 'Retry; if this persists, treat as invariant violation.');
235
+ default:
236
+ const _exhaustive = e;
237
+ return internalError('Unknown internal error kind', 'Treat as invariant violation.');
238
+ }
239
+ }
240
+ function replayFromRecordedAdvance(args) {
241
+ const { recordedEvent, truth, sessionId, runId, nodeId, workflowHash, attemptId, inputStateToken, inputAckToken, pinnedWorkflow, snapshotStore, keyring, hmac, base64url, } = args;
242
+ const checkpointTokenRes = signTokenOrErr({
243
+ unsignedPrefix: 'chk.v1.',
244
+ payload: { tokenVersion: 1, tokenKind: 'checkpoint', sessionId, runId, nodeId, attemptId },
245
+ keyring,
246
+ hmac,
247
+ base64url,
248
+ });
249
+ if (checkpointTokenRes.isErr()) {
250
+ return (0, neverthrow_1.errAsync)({ kind: 'token_signing_failed', cause: checkpointTokenRes.error });
251
+ }
252
+ const checkpointToken = checkpointTokenRes.value;
253
+ if (recordedEvent.data.outcome.kind === 'blocked') {
254
+ const blockers = recordedEvent.data.outcome.blockers;
255
+ const snapNode = truth.events.find((e) => e.kind === 'node_created' && e.scope?.nodeId === String(nodeId));
256
+ const snapRA = snapNode
257
+ ? snapshotStore.getExecutionSnapshotV1(snapNode.data.snapshotRef).mapErr((cause) => ({ kind: 'snapshot_load_failed', cause }))
258
+ : (0, neverthrow_1.okAsync)(null);
259
+ return snapRA.map((snap) => {
260
+ const pendingNow = snap ? (0, snapshot_state_js_1.derivePendingStep)(snap.enginePayload.engineState) : null;
261
+ const isCompleteNow = snap ? (0, snapshot_state_js_1.deriveIsComplete)(snap.enginePayload.engineState) : false;
262
+ return output_schemas_js_1.V2ContinueWorkflowOutputSchema.parse({
263
+ kind: 'blocked',
264
+ stateToken: inputStateToken,
265
+ ackToken: inputAckToken,
266
+ checkpointToken,
267
+ isComplete: isCompleteNow,
268
+ pending: pendingNow
269
+ ? { stepId: String(pendingNow.stepId), title: String(pendingNow.stepId), prompt: `Pending step: ${String(pendingNow.stepId)}` }
270
+ : null,
271
+ blockers,
272
+ });
273
+ });
274
+ }
275
+ const toNodeId = recordedEvent.data.outcome.toNodeId;
276
+ const toNodeIdBranded = (0, index_js_3.asNodeId)(String(toNodeId));
277
+ const toNode = truth.events.find((e) => e.kind === 'node_created' && e.scope?.nodeId === String(toNodeId));
278
+ if (!toNode) {
279
+ return (0, neverthrow_1.errAsync)({
280
+ kind: 'invariant_violation',
281
+ message: 'Missing node_created for advanced toNodeId (invariant violation).',
282
+ suggestion: 'Retry; if this persists, treat as invariant violation.',
283
+ });
284
+ }
285
+ return snapshotStore
286
+ .getExecutionSnapshotV1(toNode.data.snapshotRef)
287
+ .mapErr((cause) => ({ kind: 'snapshot_load_failed', cause }))
288
+ .andThen((snap) => {
289
+ if (!snap) {
290
+ return (0, neverthrow_1.errAsync)({
291
+ kind: 'invariant_violation',
292
+ message: 'Missing execution snapshot for advanced node (invariant violation).',
293
+ suggestion: 'Retry; if this persists, treat as invariant violation.',
294
+ });
295
+ }
296
+ const pending = (0, snapshot_state_js_1.derivePendingStep)(snap.enginePayload.engineState);
297
+ const isComplete = (0, snapshot_state_js_1.deriveIsComplete)(snap.enginePayload.engineState);
298
+ const nextAttemptId = attemptIdForNextNode(attemptId);
299
+ const nextAckTokenRes = signTokenOrErr({
300
+ unsignedPrefix: 'ack.v1.',
301
+ payload: { tokenVersion: 1, tokenKind: 'ack', sessionId, runId, nodeId: toNodeIdBranded, attemptId: nextAttemptId },
302
+ keyring,
303
+ hmac,
304
+ base64url,
305
+ });
306
+ if (nextAckTokenRes.isErr()) {
307
+ return (0, neverthrow_1.errAsync)({ kind: 'token_signing_failed', cause: nextAckTokenRes.error });
308
+ }
309
+ const nextCheckpointTokenRes = signTokenOrErr({
310
+ unsignedPrefix: 'chk.v1.',
311
+ payload: { tokenVersion: 1, tokenKind: 'checkpoint', sessionId, runId, nodeId: toNodeIdBranded, attemptId: nextAttemptId },
312
+ keyring,
313
+ hmac,
314
+ base64url,
315
+ });
316
+ if (nextCheckpointTokenRes.isErr()) {
317
+ return (0, neverthrow_1.errAsync)({ kind: 'token_signing_failed', cause: nextCheckpointTokenRes.error });
318
+ }
319
+ const nextStateTokenRes = signTokenOrErr({
320
+ unsignedPrefix: 'st.v1.',
321
+ payload: { tokenVersion: 1, tokenKind: 'state', sessionId, runId, nodeId: toNodeIdBranded, workflowHash },
322
+ keyring,
323
+ hmac,
324
+ base64url,
325
+ });
326
+ if (nextStateTokenRes.isErr()) {
327
+ return (0, neverthrow_1.errAsync)({ kind: 'token_signing_failed', cause: nextStateTokenRes.error });
328
+ }
329
+ const { stepId, title, prompt } = extractStepMetadata(pinnedWorkflow, pending ? String(pending.stepId) : null);
330
+ return (0, neverthrow_1.okAsync)(output_schemas_js_1.V2ContinueWorkflowOutputSchema.parse({
331
+ kind: 'ok',
332
+ stateToken: nextStateTokenRes.value,
333
+ ackToken: nextAckTokenRes.value,
334
+ checkpointToken: nextCheckpointTokenRes.value,
335
+ isComplete,
336
+ pending: stepId ? { stepId, title, prompt } : null,
337
+ }));
338
+ });
339
+ }
340
+ function advanceAndRecord(args) {
341
+ const { truth, sessionId, runId, nodeId, attemptId, workflowHash, dedupeKey, inputContext, inputOutput, lock, pinnedWorkflow, snapshotStore, sessionStore } = args;
342
+ const hasRun = truth.events.some((e) => e.kind === 'run_started' && e.scope?.runId === String(runId));
343
+ const hasNode = truth.events.some((e) => e.kind === 'node_created' && e.scope?.runId === String(runId) && e.scope?.nodeId === String(nodeId));
344
+ if (!hasRun || !hasNode) {
345
+ return errAsync({ kind: 'missing_node_or_run' });
346
+ }
347
+ const compiler = new workflow_compiler_js_1.WorkflowCompiler();
348
+ const interpreter = new workflow_interpreter_js_1.WorkflowInterpreter();
349
+ const compiledWf = compiler.compile(pinnedWorkflow);
350
+ if (compiledWf.isErr()) {
351
+ return errAsync({ kind: 'advance_apply_failed', message: compiledWf.error.message });
352
+ }
353
+ const nodeCreated = truth.events.find((e) => e.kind === 'node_created' && e.scope?.nodeId === String(nodeId));
354
+ if (!nodeCreated) {
355
+ return errAsync({ kind: 'missing_node_or_run' });
356
+ }
357
+ if (String(nodeCreated.data.workflowHash) !== String(workflowHash)) {
358
+ return errAsync({ kind: 'workflow_hash_mismatch' });
359
+ }
360
+ return snapshotStore.getExecutionSnapshotV1(nodeCreated.data.snapshotRef).andThen((snap) => {
361
+ if (!snap)
362
+ return errAsync({ kind: 'missing_snapshot' });
363
+ const currentState = toV1ExecutionState(snap.enginePayload.engineState);
364
+ const pendingStep = (currentState.kind === 'running' && currentState.pendingStep) ? currentState.pendingStep : null;
365
+ if (!pendingStep) {
366
+ return errAsync({ kind: 'no_pending_step' });
367
+ }
368
+ const event = { kind: 'step_completed', stepInstanceId: pendingStep };
369
+ const advanced = interpreter.applyEvent(currentState, event);
370
+ if (advanced.isErr()) {
371
+ return errAsync({ kind: 'advance_apply_failed', message: advanced.error.message });
372
+ }
373
+ const ctxObj = inputContext && typeof inputContext === 'object' && inputContext !== null && !Array.isArray(inputContext)
374
+ ? inputContext
375
+ : {};
376
+ const nextRes = interpreter.next(compiledWf.value, advanced.value, ctxObj);
377
+ if (nextRes.isErr()) {
378
+ return errAsync({ kind: 'advance_next_failed', message: nextRes.error.message });
379
+ }
380
+ const out = nextRes.value;
381
+ const newEngineState = fromV1ExecutionState(out.state);
382
+ const snapshotFile = {
383
+ v: 1,
384
+ kind: 'execution_snapshot',
385
+ enginePayload: { v: 1, engineState: newEngineState },
386
+ };
387
+ return snapshotStore.putExecutionSnapshotV1(snapshotFile).andThen((newSnapshotRef) => {
388
+ const toNodeId = `node_${(0, crypto_1.randomUUID)()}`;
389
+ const nextEventIndex = truth.events.length === 0 ? 0 : truth.events[truth.events.length - 1].eventIndex + 1;
390
+ const evtAdvanceRecorded = `evt_${(0, crypto_1.randomUUID)()}`;
391
+ const evtNodeCreated = `evt_${(0, crypto_1.randomUUID)()}`;
392
+ const evtEdgeCreated = `evt_${(0, crypto_1.randomUUID)()}`;
393
+ const hasChildren = truth.events.some((e) => e.kind === 'edge_created' && e.data.fromNodeId === String(nodeId));
394
+ const causeKind = hasChildren ? 'non_tip_advance' : 'intentional_fork';
395
+ const outputId = (0, index_js_1.asOutputId)(`out_recap_${String(attemptId)}`);
396
+ const outputsToAppend = inputOutput?.notesMarkdown
397
+ ? [
398
+ {
399
+ outputId: String(outputId),
400
+ outputChannel: 'recap',
401
+ payload: {
402
+ payloadKind: 'notes',
403
+ notesMarkdown: (0, notes_markdown_js_1.toNotesMarkdownV1)(inputOutput.notesMarkdown),
404
+ },
405
+ },
406
+ ]
407
+ : [];
408
+ const normalizedOutputs = (0, outputs_js_1.normalizeOutputsForAppend)(outputsToAppend);
409
+ const outputEventIds = normalizedOutputs.map(() => `evt_${(0, crypto_1.randomUUID)()}`);
410
+ const planRes = (0, ack_advance_append_plan_js_1.buildAckAdvanceAppendPlanV1)({
411
+ sessionId: String(sessionId),
412
+ runId: String(runId),
413
+ fromNodeId: String(nodeId),
414
+ workflowHash,
415
+ attemptId: String(attemptId),
416
+ nextEventIndex,
417
+ toNodeId,
418
+ snapshotRef: newSnapshotRef,
419
+ causeKind,
420
+ minted: {
421
+ advanceRecordedEventId: evtAdvanceRecorded,
422
+ nodeCreatedEventId: evtNodeCreated,
423
+ edgeCreatedEventId: evtEdgeCreated,
424
+ outputEventIds,
425
+ },
426
+ outputsToAppend,
427
+ });
428
+ if (planRes.isErr())
429
+ return errAsync({ kind: 'invariant_violation', message: planRes.error.message });
430
+ return sessionStore.append(lock, planRes.value);
431
+ });
432
+ });
433
+ }
434
+ function errAsync(e) {
435
+ return (0, neverthrow_1.errAsync)(e);
436
+ }
437
+ function extractStepMetadata(workflow, stepId, options) {
438
+ const resolvedStepId = stepId ?? '';
439
+ const step = stepId ? (0, workflow_js_1.getStepById)(workflow, stepId) : null;
440
+ const hasStringProp = (obj, prop) => typeof obj === 'object' &&
441
+ obj !== null &&
442
+ prop in obj &&
443
+ typeof obj[prop] === 'string';
444
+ const title = hasStringProp(step, 'title')
445
+ ? String(step.title)
446
+ : options?.defaultTitle ?? resolvedStepId;
447
+ const prompt = hasStringProp(step, 'prompt')
448
+ ? String(step.prompt)
449
+ : options?.defaultPrompt ?? (stepId ? `Pending step: ${stepId}` : '');
450
+ return { stepId: resolvedStepId, title, prompt };
451
+ }
452
+ function internalError(message, suggestion) {
453
+ return (0, types_js_1.errNotRetryable)('INTERNAL_ERROR', normalizeTokenErrorMessage(message), suggestion ? { suggestion } : undefined);
454
+ }
455
+ function sessionStoreErrorToToolError(e) {
456
+ switch (e.code) {
457
+ case 'SESSION_STORE_LOCK_BUSY':
458
+ return (0, types_js_1.errRetryAfterMs)('INTERNAL_ERROR', normalizeTokenErrorMessage(e.message), e.retry.afterMs, {
459
+ suggestion: 'Another WorkRail process may be writing to this session; retry.',
460
+ });
461
+ case 'SESSION_STORE_CORRUPTION_DETECTED':
462
+ return (0, types_js_1.errNotRetryable)('SESSION_NOT_HEALTHY', `Session corruption detected: ${e.reason.code}`, {
463
+ suggestion: 'Execution requires a healthy session. Export salvage view, then recreate.',
464
+ details: (0, types_js_1.detailsSessionHealth)({ kind: e.location === 'head' ? 'corrupt_head' : 'corrupt_tail', reason: e.reason }),
465
+ });
466
+ case 'SESSION_STORE_IO_ERROR':
467
+ return internalError(e.message, 'Retry; check filesystem permissions.');
468
+ case 'SESSION_STORE_INVARIANT_VIOLATION':
469
+ return internalError(e.message, 'Treat as invariant violation.');
470
+ default:
471
+ const _exhaustive = e;
472
+ return internalError('Unknown session store error', 'Treat as invariant violation.');
473
+ }
474
+ }
475
+ function gateErrorToToolError(e) {
476
+ switch (e.code) {
477
+ case 'SESSION_LOCKED':
478
+ return (0, types_js_1.errRetryAfterMs)('TOKEN_SESSION_LOCKED', e.message, e.retry.afterMs, { suggestion: 'Retry in 1–3 seconds; if this persists >10s, ensure no other WorkRail process is running.' });
479
+ case 'LOCK_RELEASE_FAILED':
480
+ return (0, types_js_1.errRetryAfterMs)('TOKEN_SESSION_LOCKED', e.message, e.retry.afterMs, { suggestion: 'Retry in 1–3 seconds; if this persists >10s, ensure no other WorkRail process is running.' });
481
+ case 'SESSION_NOT_HEALTHY':
482
+ return (0, types_js_1.errNotRetryable)('SESSION_NOT_HEALTHY', e.message, { suggestion: 'Execution requires healthy session.', details: (0, types_js_1.detailsSessionHealth)(e.health) });
483
+ case 'SESSION_LOAD_FAILED':
484
+ case 'SESSION_LOCK_REENTRANT':
485
+ case 'LOCK_ACQUIRE_FAILED':
486
+ case 'GATE_CALLBACK_FAILED':
487
+ return internalError(e.message, 'Retry; if persists, treat as invariant violation.');
488
+ default:
489
+ const _exhaustive = e;
490
+ return internalError('Unknown gate error', 'Treat as invariant violation.');
491
+ }
492
+ }
493
+ function snapshotStoreErrorToToolError(e, suggestion) {
494
+ return internalError(`Snapshot store error: ${e.message}`, suggestion);
495
+ }
496
+ function pinnedWorkflowStoreErrorToToolError(e, suggestion) {
497
+ return internalError(`Pinned workflow store error: ${e.message}`, suggestion);
498
+ }
499
+ function parseStateTokenOrFail(raw, keyring, hmac, base64url) {
500
+ const parsedRes = (0, index_js_1.parseTokenV1)(raw, base64url);
501
+ if (parsedRes.isErr()) {
502
+ return { ok: false, failure: (0, v2_execution_helpers_js_1.mapTokenDecodeErrorToToolError)(parsedRes.error) };
503
+ }
504
+ const verified = (0, index_js_1.verifyTokenSignatureV1)(parsedRes.value, keyring, hmac, base64url);
505
+ if (verified.isErr()) {
506
+ return { ok: false, failure: (0, v2_execution_helpers_js_1.mapTokenVerifyErrorToToolError)(verified.error) };
507
+ }
508
+ if (parsedRes.value.payload.tokenKind !== 'state') {
509
+ return {
510
+ ok: false,
511
+ failure: (0, types_js_1.errNotRetryable)('TOKEN_INVALID_FORMAT', 'Expected a state token (st.v1.*).', {
512
+ suggestion: 'Use the stateToken returned by WorkRail.',
513
+ }),
514
+ };
515
+ }
516
+ return { ok: true, token: parsedRes.value };
517
+ }
518
+ function parseAckTokenOrFail(raw, keyring, hmac, base64url) {
519
+ const parsedRes = (0, index_js_1.parseTokenV1)(raw, base64url);
520
+ if (parsedRes.isErr()) {
521
+ return { ok: false, failure: (0, v2_execution_helpers_js_1.mapTokenDecodeErrorToToolError)(parsedRes.error) };
522
+ }
523
+ const verified = (0, index_js_1.verifyTokenSignatureV1)(parsedRes.value, keyring, hmac, base64url);
524
+ if (verified.isErr()) {
525
+ return { ok: false, failure: (0, v2_execution_helpers_js_1.mapTokenVerifyErrorToToolError)(verified.error) };
526
+ }
527
+ if (parsedRes.value.payload.tokenKind !== 'ack') {
528
+ return {
529
+ ok: false,
530
+ failure: (0, types_js_1.errNotRetryable)('TOKEN_INVALID_FORMAT', 'Expected an ack token (ack.v1.*).', {
531
+ suggestion: 'Use the ackToken returned by WorkRail.',
532
+ }),
533
+ };
534
+ }
535
+ return { ok: true, token: parsedRes.value };
536
+ }
537
+ function newAttemptId() {
538
+ return (0, index_js_1.asAttemptId)(`attempt_${(0, crypto_1.randomUUID)()}`);
539
+ }
540
+ function attemptIdForNextNode(parentAttemptId) {
541
+ return (0, index_js_1.asAttemptId)(`next_${parentAttemptId}`);
542
+ }
543
+ function signTokenOrErr(args) {
544
+ const bytes = (0, index_js_2.encodeTokenPayloadV1)(args.payload);
545
+ if (bytes.isErr())
546
+ return (0, neverthrow_1.err)(bytes.error);
547
+ const token = (0, index_js_2.signTokenV1)(args.unsignedPrefix, bytes.value, args.keyring, args.hmac, args.base64url);
548
+ if (token.isErr())
549
+ return (0, neverthrow_1.err)(token.error);
550
+ return (0, neverthrow_1.ok)(String(token.value));
551
+ }
552
+ function toV1ExecutionState(engineState) {
553
+ if (engineState.kind === 'init')
554
+ return { kind: 'init' };
555
+ if (engineState.kind === 'complete')
556
+ return { kind: 'complete' };
557
+ const pendingStep = engineState.pending.kind === 'some'
558
+ ? {
559
+ stepId: String(engineState.pending.step.stepId),
560
+ loopPath: engineState.pending.step.loopPath.map((f) => ({
561
+ loopId: String(f.loopId),
562
+ iteration: f.iteration,
563
+ })),
564
+ }
565
+ : undefined;
566
+ return {
567
+ kind: 'running',
568
+ completed: [...engineState.completed.values].map(String),
569
+ loopStack: engineState.loopStack.map((f) => ({
570
+ loopId: String(f.loopId),
571
+ iteration: f.iteration,
572
+ bodyIndex: f.bodyIndex,
573
+ })),
574
+ pendingStep,
575
+ };
576
+ }
577
+ function convertRunningExecutionStateToEngineState(state) {
578
+ const completedArray = [...state.completed].sort((a, b) => a.localeCompare(b));
579
+ const completed = completedArray.map(s => (0, step_instance_key_js_1.stepInstanceKeyFromParts)((0, step_instance_key_js_1.asDelimiterSafeIdV1)(s), []));
580
+ const loopStack = state.loopStack.map((f) => ({
581
+ loopId: (0, step_instance_key_js_1.asDelimiterSafeIdV1)(f.loopId),
582
+ iteration: f.iteration,
583
+ bodyIndex: f.bodyIndex,
584
+ }));
585
+ const pending = state.pendingStep
586
+ ? {
587
+ kind: 'some',
588
+ step: {
589
+ stepId: (0, step_instance_key_js_1.asDelimiterSafeIdV1)(state.pendingStep.stepId),
590
+ loopPath: state.pendingStep.loopPath.map((p) => ({
591
+ loopId: (0, step_instance_key_js_1.asDelimiterSafeIdV1)(p.loopId),
592
+ iteration: p.iteration,
593
+ })),
594
+ },
595
+ }
596
+ : { kind: 'none' };
597
+ return {
598
+ kind: 'running',
599
+ completed: { kind: 'set', values: completed },
600
+ loopStack,
601
+ pending,
602
+ };
603
+ }
604
+ function fromV1ExecutionState(state) {
605
+ if (state.kind === 'init') {
606
+ return { kind: 'init' };
607
+ }
608
+ if (state.kind === 'complete') {
609
+ return { kind: 'complete' };
610
+ }
611
+ return convertRunningExecutionStateToEngineState(state);
612
+ }
613
+ const workflowSourceKindMap = {
614
+ bundled: 'bundled',
615
+ user: 'user',
616
+ project: 'project',
617
+ remote: 'remote',
618
+ plugin: 'plugin',
619
+ git: 'remote',
620
+ custom: 'project',
621
+ };
622
+ function mapWorkflowSourceKind(kind) {
623
+ const mapped = workflowSourceKindMap[kind];
624
+ return mapped ?? 'project';
625
+ }
626
+ async function handleV2StartWorkflow(input, ctx) {
627
+ return executeStartWorkflow(input, ctx).match((payload) => (0, types_js_1.success)(payload), (e) => (0, v2_execution_helpers_js_1.mapStartWorkflowErrorToToolError)(e));
628
+ }
629
+ function executeStartWorkflow(input, ctx) {
630
+ if (!ctx.v2) {
631
+ return (0, neverthrow_1.errAsync)({ kind: 'precondition_failed', message: 'v2 tools disabled', suggestion: 'Enable v2Tools flag' });
632
+ }
633
+ const { gate, sessionStore, snapshotStore, pinnedStore, keyring, crypto, hmac, base64url } = ctx.v2;
634
+ const ctxCheck = checkContextBudget({ tool: 'start_workflow', context: input.context });
635
+ if (!ctxCheck.ok)
636
+ return (0, neverthrow_1.errAsync)({ kind: 'validation_failed', failure: ctxCheck.error });
637
+ return neverthrow_1.ResultAsync.fromPromise(ctx.workflowService.getWorkflowById(input.workflowId), (e) => ({
638
+ kind: 'precondition_failed',
639
+ message: e instanceof Error ? e.message : String(e),
640
+ }))
641
+ .andThen((workflow) => {
642
+ if (!workflow) {
643
+ return (0, neverthrow_1.errAsync)({ kind: 'workflow_not_found', workflowId: (0, index_js_3.asWorkflowId)(input.workflowId) });
644
+ }
645
+ const firstStep = workflow.definition.steps[0];
646
+ if (!firstStep) {
647
+ return (0, neverthrow_1.errAsync)({ kind: 'workflow_has_no_steps', workflowId: (0, index_js_3.asWorkflowId)(input.workflowId) });
648
+ }
649
+ return (0, neverthrow_1.okAsync)({ workflow, firstStep });
650
+ })
651
+ .andThen(({ workflow, firstStep }) => {
652
+ const compiled = (0, v1_to_v2_shim_js_1.compileV1WorkflowToPinnedSnapshot)(workflow);
653
+ const workflowHashRes = (0, hashing_js_1.workflowHashForCompiledSnapshot)(compiled, crypto);
654
+ if (workflowHashRes.isErr()) {
655
+ return (0, neverthrow_1.errAsync)({ kind: 'hash_computation_failed', message: workflowHashRes.error.message });
656
+ }
657
+ const workflowHash = workflowHashRes.value;
658
+ return pinnedStore.get(workflowHash)
659
+ .mapErr((cause) => ({ kind: 'pinned_workflow_store_failed', cause }))
660
+ .andThen((existingPinned) => {
661
+ if (!existingPinned) {
662
+ return pinnedStore.put(workflowHash, compiled)
663
+ .mapErr((cause) => ({ kind: 'pinned_workflow_store_failed', cause }));
664
+ }
665
+ return (0, neverthrow_1.okAsync)(undefined);
666
+ })
667
+ .andThen(() => pinnedStore.get(workflowHash).mapErr((cause) => ({ kind: 'pinned_workflow_store_failed', cause })))
668
+ .andThen((pinned) => {
669
+ if (!pinned || pinned.sourceKind !== 'v1_pinned' || !(0, workflow_definition_js_1.hasWorkflowDefinitionShape)(pinned.definition)) {
670
+ return (0, neverthrow_1.errAsync)({
671
+ kind: 'invariant_violation',
672
+ message: 'Failed to pin executable workflow snapshot (missing or invalid pinned workflow).',
673
+ suggestion: 'Retry start_workflow; if this persists, treat as invariant violation.',
674
+ });
675
+ }
676
+ const pinnedWorkflow = (0, workflow_js_1.createWorkflow)(pinned.definition, (0, workflow_source_js_1.createBundledSource)());
677
+ return (0, neverthrow_1.okAsync)({ workflow, firstStep, workflowHash, pinnedWorkflow });
678
+ });
679
+ })
680
+ .andThen(({ workflow, firstStep, workflowHash, pinnedWorkflow }) => {
681
+ const sessionId = (0, index_js_3.asSessionId)(`sess_${(0, crypto_1.randomUUID)()}`);
682
+ const runId = (0, index_js_3.asRunId)(`run_${(0, crypto_1.randomUUID)()}`);
683
+ const nodeId = (0, index_js_3.asNodeId)(`node_${(0, crypto_1.randomUUID)()}`);
684
+ const snapshot = {
685
+ v: 1,
686
+ kind: 'execution_snapshot',
687
+ enginePayload: {
688
+ v: 1,
689
+ engineState: {
690
+ kind: 'running',
691
+ completed: { kind: 'set', values: [] },
692
+ loopStack: [],
693
+ pending: { kind: 'some', step: { stepId: (0, step_instance_key_js_1.asDelimiterSafeIdV1)(firstStep.id), loopPath: [] } },
694
+ },
695
+ },
696
+ };
697
+ return snapshotStore.putExecutionSnapshotV1(snapshot)
698
+ .mapErr((cause) => ({ kind: 'snapshot_creation_failed', cause }))
699
+ .andThen((snapshotRef) => {
700
+ const evtSessionCreated = `evt_${(0, crypto_1.randomUUID)()}`;
701
+ const evtRunStarted = `evt_${(0, crypto_1.randomUUID)()}`;
702
+ const evtNodeCreated = `evt_${(0, crypto_1.randomUUID)()}`;
703
+ return gate.withHealthySessionLock(sessionId, (lock) => {
704
+ const eventsArray = [
705
+ {
706
+ v: 1,
707
+ eventId: evtSessionCreated,
708
+ eventIndex: 0,
709
+ sessionId,
710
+ kind: 'session_created',
711
+ dedupeKey: `session_created:${sessionId}`,
712
+ data: {},
713
+ },
714
+ {
715
+ v: 1,
716
+ eventId: evtRunStarted,
717
+ eventIndex: 1,
718
+ sessionId,
719
+ kind: 'run_started',
720
+ dedupeKey: `run_started:${sessionId}:${runId}`,
721
+ scope: { runId },
722
+ data: {
723
+ workflowId: workflow.definition.id,
724
+ workflowHash,
725
+ workflowSourceKind: mapWorkflowSourceKind(workflow.source.kind),
726
+ workflowSourceRef: workflow.source.kind === 'user' || workflow.source.kind === 'project' || workflow.source.kind === 'custom'
727
+ ? workflow.source.directoryPath
728
+ : workflow.source.kind === 'git'
729
+ ? `${workflow.source.repositoryUrl}#${workflow.source.branch}`
730
+ : workflow.source.kind === 'remote'
731
+ ? workflow.source.registryUrl
732
+ : workflow.source.kind === 'plugin'
733
+ ? `${workflow.source.pluginName}@${workflow.source.pluginVersion}`
734
+ : '(bundled)',
735
+ },
736
+ },
737
+ {
738
+ v: 1,
739
+ eventId: evtNodeCreated,
740
+ eventIndex: 2,
741
+ sessionId,
742
+ kind: 'node_created',
743
+ dedupeKey: `node_created:${sessionId}:${runId}:${nodeId}`,
744
+ scope: { runId, nodeId },
745
+ data: {
746
+ nodeKind: 'step',
747
+ parentNodeId: null,
748
+ workflowHash,
749
+ snapshotRef,
750
+ },
751
+ },
752
+ ];
753
+ return sessionStore.append(lock, {
754
+ events: eventsArray,
755
+ snapshotPins: [{ snapshotRef, eventIndex: 2, createdByEventId: evtNodeCreated }],
756
+ });
757
+ })
758
+ .mapErr((cause) => ({ kind: 'session_append_failed', cause }))
759
+ .map(() => ({ workflow, firstStep, workflowHash, pinnedWorkflow, sessionId, runId, nodeId }));
760
+ });
761
+ })
762
+ .andThen(({ pinnedWorkflow, firstStep, workflowHash, sessionId, runId, nodeId }) => {
763
+ const statePayload = {
764
+ tokenVersion: 1,
765
+ tokenKind: 'state',
766
+ sessionId,
767
+ runId,
768
+ nodeId,
769
+ workflowHash,
770
+ };
771
+ const attemptId = newAttemptId();
772
+ const ackPayload = {
773
+ tokenVersion: 1,
774
+ tokenKind: 'ack',
775
+ sessionId,
776
+ runId,
777
+ nodeId,
778
+ attemptId,
779
+ };
780
+ const checkpointPayload = {
781
+ tokenVersion: 1,
782
+ tokenKind: 'checkpoint',
783
+ sessionId,
784
+ runId,
785
+ nodeId,
786
+ attemptId,
787
+ };
788
+ const stateToken = signTokenOrErr({ unsignedPrefix: 'st.v1.', payload: statePayload, keyring, hmac, base64url });
789
+ if (stateToken.isErr())
790
+ return (0, neverthrow_1.errAsync)({ kind: 'token_signing_failed', cause: stateToken.error });
791
+ const ackToken = signTokenOrErr({ unsignedPrefix: 'ack.v1.', payload: ackPayload, keyring, hmac, base64url });
792
+ if (ackToken.isErr())
793
+ return (0, neverthrow_1.errAsync)({ kind: 'token_signing_failed', cause: ackToken.error });
794
+ const checkpointToken = signTokenOrErr({ unsignedPrefix: 'chk.v1.', payload: checkpointPayload, keyring, hmac, base64url });
795
+ if (checkpointToken.isErr())
796
+ return (0, neverthrow_1.errAsync)({ kind: 'token_signing_failed', cause: checkpointToken.error });
797
+ const { stepId, title, prompt } = extractStepMetadata(pinnedWorkflow, firstStep.id);
798
+ const pending = { stepId, title, prompt };
799
+ return (0, neverthrow_1.okAsync)(output_schemas_js_1.V2StartWorkflowOutputSchema.parse({
800
+ stateToken: stateToken.value,
801
+ ackToken: ackToken.value,
802
+ checkpointToken: checkpointToken.value,
803
+ isComplete: false,
804
+ pending,
805
+ }));
806
+ });
807
+ }
808
+ async function handleV2ContinueWorkflow(input, ctx) {
809
+ return executeContinueWorkflow(input, ctx).match((payload) => (0, types_js_1.success)(payload), (e) => (0, v2_execution_helpers_js_1.mapContinueWorkflowErrorToToolError)(e));
810
+ }
811
+ function executeContinueWorkflow(input, ctx) {
812
+ if (!ctx.v2) {
813
+ return (0, neverthrow_1.errAsync)({ kind: 'precondition_failed', message: 'v2 tools disabled', suggestion: 'Enable v2Tools flag' });
814
+ }
815
+ const { gate, sessionStore, snapshotStore, pinnedStore, keyring, crypto, hmac, base64url } = ctx.v2;
816
+ const stateRes = parseStateTokenOrFail(input.stateToken, keyring, hmac, base64url);
817
+ if (!stateRes.ok)
818
+ return (0, neverthrow_1.errAsync)({ kind: 'validation_failed', failure: stateRes.failure });
819
+ const state = stateRes.token;
820
+ const ctxCheck = checkContextBudget({ tool: 'continue_workflow', context: input.context });
821
+ if (!ctxCheck.ok)
822
+ return (0, neverthrow_1.errAsync)({ kind: 'validation_failed', failure: ctxCheck.error });
823
+ const sessionId = (0, index_js_3.asSessionId)(state.payload.sessionId);
824
+ const runId = (0, index_js_3.asRunId)(state.payload.runId);
825
+ const nodeId = (0, index_js_3.asNodeId)(state.payload.nodeId);
826
+ const workflowHash = (0, index_js_3.asWorkflowHash)((0, index_js_3.asSha256Digest)(state.payload.workflowHash));
827
+ if (!input.ackToken) {
828
+ return sessionStore.load(sessionId)
829
+ .mapErr((cause) => ({ kind: 'session_load_failed', cause }))
830
+ .andThen((truth) => {
831
+ const runStarted = truth.events.find((e) => e.kind === 'run_started' && e.scope.runId === String(runId));
832
+ const workflowId = runStarted?.data.workflowId;
833
+ if (!runStarted || typeof workflowId !== 'string' || workflowId.trim() === '') {
834
+ return (0, neverthrow_1.errAsync)({
835
+ kind: 'token_unknown_node',
836
+ message: 'No durable run state was found for this stateToken (missing run_started).',
837
+ suggestion: 'Use start_workflow to mint a new run, or use a stateToken returned by WorkRail for an existing run.',
838
+ });
839
+ }
840
+ if (String(runStarted.data.workflowHash) !== String(workflowHash)) {
841
+ return (0, neverthrow_1.errAsync)({ kind: 'precondition_failed', message: 'workflowHash mismatch for this run.', suggestion: 'Use the stateToken returned by WorkRail for this run.' });
842
+ }
843
+ const nodeCreated = truth.events.find((e) => e.kind === 'node_created' && e.scope.nodeId === String(nodeId) && e.scope.runId === String(runId));
844
+ if (!nodeCreated) {
845
+ return (0, neverthrow_1.errAsync)({
846
+ kind: 'token_unknown_node',
847
+ message: 'No durable node state was found for this stateToken (missing node_created).',
848
+ suggestion: 'Use a stateToken returned by WorkRail for an existing node.',
849
+ });
850
+ }
851
+ if (String(nodeCreated.data.workflowHash) !== String(workflowHash)) {
852
+ return (0, neverthrow_1.errAsync)({ kind: 'precondition_failed', message: 'workflowHash mismatch for this node.', suggestion: 'Use the stateToken returned by WorkRail for this node.' });
853
+ }
854
+ return snapshotStore.getExecutionSnapshotV1(nodeCreated.data.snapshotRef)
855
+ .mapErr((cause) => ({ kind: 'snapshot_load_failed', cause }))
856
+ .andThen((snapshot) => {
857
+ if (!snapshot) {
858
+ return (0, neverthrow_1.errAsync)({
859
+ kind: 'token_unknown_node',
860
+ message: 'No execution snapshot was found for this node.',
861
+ suggestion: 'Use a stateToken returned by WorkRail for an existing node.',
862
+ });
863
+ }
864
+ const engineState = snapshot.enginePayload.engineState;
865
+ const pending = (0, snapshot_state_js_1.derivePendingStep)(engineState);
866
+ const isComplete = (0, snapshot_state_js_1.deriveIsComplete)(engineState);
867
+ const attemptId = newAttemptId();
868
+ const ackTokenRes = signTokenOrErr({
869
+ unsignedPrefix: 'ack.v1.',
870
+ payload: { tokenVersion: 1, tokenKind: 'ack', sessionId, runId, nodeId, attemptId },
871
+ keyring,
872
+ hmac,
873
+ base64url,
874
+ });
875
+ if (ackTokenRes.isErr())
876
+ return (0, neverthrow_1.errAsync)({ kind: 'token_signing_failed', cause: ackTokenRes.error });
877
+ const checkpointTokenRes = signTokenOrErr({
878
+ unsignedPrefix: 'chk.v1.',
879
+ payload: { tokenVersion: 1, tokenKind: 'checkpoint', sessionId, runId, nodeId, attemptId },
880
+ keyring,
881
+ hmac,
882
+ base64url,
883
+ });
884
+ if (checkpointTokenRes.isErr())
885
+ return (0, neverthrow_1.errAsync)({ kind: 'token_signing_failed', cause: checkpointTokenRes.error });
886
+ if (!pending) {
887
+ return (0, neverthrow_1.okAsync)(output_schemas_js_1.V2ContinueWorkflowOutputSchema.parse({
888
+ kind: 'ok',
889
+ stateToken: input.stateToken,
890
+ ackToken: ackTokenRes.value,
891
+ checkpointToken: checkpointTokenRes.value,
892
+ isComplete,
893
+ pending: null,
894
+ }));
895
+ }
896
+ return pinnedStore.get(workflowHash)
897
+ .mapErr((cause) => ({ kind: 'pinned_workflow_store_failed', cause }))
898
+ .andThen((pinned) => {
899
+ if (!pinned)
900
+ return (0, neverthrow_1.errAsync)({ kind: 'pinned_workflow_missing', workflowHash: (0, index_js_3.asWorkflowHash)((0, index_js_3.asSha256Digest)(String(workflowHash))) });
901
+ if (pinned.sourceKind !== 'v1_pinned')
902
+ return (0, neverthrow_1.errAsync)({ kind: 'precondition_failed', message: 'Pinned workflow snapshot is read-only (v1_preview) and cannot be executed.' });
903
+ if (!(0, workflow_definition_js_1.hasWorkflowDefinitionShape)(pinned.definition)) {
904
+ return (0, neverthrow_1.errAsync)({
905
+ kind: 'precondition_failed',
906
+ message: 'Pinned workflow snapshot has an invalid workflow definition shape.',
907
+ suggestion: 'Re-pin the workflow via start_workflow.',
908
+ });
909
+ }
910
+ const wf = (0, workflow_js_1.createWorkflow)(pinned.definition, (0, workflow_source_js_1.createBundledSource)());
911
+ const { stepId, title, prompt } = extractStepMetadata(wf, String(pending.stepId));
912
+ return (0, neverthrow_1.okAsync)(output_schemas_js_1.V2ContinueWorkflowOutputSchema.parse({
913
+ kind: 'ok',
914
+ stateToken: input.stateToken,
915
+ ackToken: ackTokenRes.value,
916
+ checkpointToken: checkpointTokenRes.value,
917
+ isComplete,
918
+ pending: { stepId, title, prompt },
919
+ }));
920
+ });
921
+ });
922
+ });
923
+ }
924
+ const ackRes = parseAckTokenOrFail(input.ackToken, keyring, hmac, base64url);
925
+ if (!ackRes.ok)
926
+ return (0, neverthrow_1.errAsync)({ kind: 'validation_failed', failure: ackRes.failure });
927
+ const ack = ackRes.token;
928
+ const scopeRes = (0, index_js_1.assertTokenScopeMatchesState)(state, ack);
929
+ if (scopeRes.isErr())
930
+ return (0, neverthrow_1.errAsync)({ kind: 'validation_failed', failure: (0, v2_execution_helpers_js_1.mapTokenDecodeErrorToToolError)(scopeRes.error) });
931
+ const attemptId = (0, index_js_1.asAttemptId)(ack.payload.attemptId);
932
+ const dedupeKey = `advance_recorded:${sessionId}:${nodeId}:${attemptId}`;
933
+ return sessionStore.load(sessionId)
934
+ .mapErr((cause) => ({ kind: 'session_load_failed', cause }))
935
+ .andThen((truth) => {
936
+ const existing = truth.events.find((e) => e.kind === 'advance_recorded' && e.dedupeKey === dedupeKey);
937
+ return pinnedStore.get(workflowHash)
938
+ .mapErr((cause) => ({ kind: 'pinned_workflow_store_failed', cause }))
939
+ .andThen((compiled) => {
940
+ if (!compiled)
941
+ return (0, neverthrow_1.errAsync)({ kind: 'pinned_workflow_missing', workflowHash });
942
+ if (compiled.sourceKind !== 'v1_pinned')
943
+ return (0, neverthrow_1.errAsync)({ kind: 'precondition_failed', message: 'Pinned workflow snapshot is read-only (v1_preview) and cannot be executed.' });
944
+ if (!(0, workflow_definition_js_1.hasWorkflowDefinitionShape)(compiled.definition)) {
945
+ return (0, neverthrow_1.errAsync)({
946
+ kind: 'precondition_failed',
947
+ message: 'Pinned workflow snapshot has an invalid workflow definition shape.',
948
+ suggestion: 'Re-pin the workflow via start_workflow.',
949
+ });
950
+ }
951
+ const pinnedWorkflow = (0, workflow_js_1.createWorkflow)(compiled.definition, (0, workflow_source_js_1.createBundledSource)());
952
+ if (existing) {
953
+ return replayFromRecordedAdvance({
954
+ recordedEvent: existing,
955
+ truth,
956
+ sessionId,
957
+ runId,
958
+ nodeId,
959
+ workflowHash,
960
+ attemptId,
961
+ inputStateToken: input.stateToken,
962
+ inputAckToken: input.ackToken,
963
+ pinnedWorkflow,
964
+ snapshotStore,
965
+ keyring,
966
+ hmac,
967
+ base64url,
968
+ });
969
+ }
970
+ return gate
971
+ .withHealthySessionLock(sessionId, (lock) => sessionStore.load(sessionId).andThen((truthLocked) => {
972
+ const existingLocked = truthLocked.events.find((e) => e.kind === 'advance_recorded' && e.dedupeKey === dedupeKey);
973
+ if (existingLocked)
974
+ return (0, neverthrow_1.okAsync)({ kind: 'replay', truth: truthLocked, recordedEvent: existingLocked });
975
+ return advanceAndRecord({
976
+ truth: truthLocked,
977
+ sessionId,
978
+ runId,
979
+ nodeId,
980
+ attemptId,
981
+ workflowHash,
982
+ dedupeKey,
983
+ inputContext: input.context,
984
+ inputOutput: input.output,
985
+ lock,
986
+ pinnedWorkflow,
987
+ snapshotStore,
988
+ sessionStore,
989
+ }).andThen(() => sessionStore
990
+ .load(sessionId)
991
+ .map((truthAfter) => ({ kind: 'replay', truth: truthAfter, recordedEvent: null })));
992
+ }))
993
+ .mapErr((cause) => {
994
+ if (isInternalError(cause)) {
995
+ return {
996
+ kind: 'invariant_violation',
997
+ message: `Advance failed due to internal invariant violation: ${cause.kind}`,
998
+ suggestion: 'Retry; if this persists, treat as invariant violation.',
999
+ };
1000
+ }
1001
+ if (typeof cause === 'object' && cause !== null && 'code' in cause) {
1002
+ const code = cause.code;
1003
+ if (code.startsWith('SNAPSHOT_STORE_')) {
1004
+ return { kind: 'snapshot_load_failed', cause: cause };
1005
+ }
1006
+ return { kind: 'advance_execution_failed', cause: cause };
1007
+ }
1008
+ return {
1009
+ kind: 'invariant_violation',
1010
+ message: 'Advance failed with an unknown error shape (invariant violation).',
1011
+ suggestion: 'Retry; if this persists, treat as invariant violation.',
1012
+ };
1013
+ })
1014
+ .andThen((res) => {
1015
+ const truth2 = res.truth;
1016
+ const recordedEvent = res.recordedEvent ??
1017
+ truth2.events.find((e) => e.kind === 'advance_recorded' && e.dedupeKey === dedupeKey);
1018
+ if (!recordedEvent) {
1019
+ return (0, neverthrow_1.errAsync)({
1020
+ kind: 'invariant_violation',
1021
+ message: 'Missing recorded advance outcome after successful append (invariant violation).',
1022
+ suggestion: 'Retry; if this persists, treat as invariant violation.',
1023
+ });
1024
+ }
1025
+ return replayFromRecordedAdvance({
1026
+ recordedEvent,
1027
+ truth: truth2,
1028
+ sessionId,
1029
+ runId,
1030
+ nodeId,
1031
+ workflowHash,
1032
+ attemptId,
1033
+ inputStateToken: input.stateToken,
1034
+ inputAckToken: input.ackToken,
1035
+ pinnedWorkflow,
1036
+ snapshotStore,
1037
+ keyring,
1038
+ hmac,
1039
+ base64url,
1040
+ });
1041
+ });
1042
+ });
1043
+ });
1044
+ }