experimental-ash 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/docs/internals/hooks.md +13 -16
- package/dist/docs/internals/message-runtime.md +1 -1
- package/dist/docs/public/auth-and-route-protection.md +3 -3
- package/dist/docs/public/typescript-api.md +2 -2
- package/dist/src/channel/types.d.ts +10 -12
- package/dist/src/chunks/{dev-authored-source-watcher-d_35Mp8T.js → dev-authored-source-watcher-B4PaZGUr.js} +1 -1
- package/dist/src/chunks/host-DsW72Q-w.js +65 -0
- package/dist/src/chunks/{paths-YoCQlavu.js → paths-OknjaYR8.js} +24 -24
- package/dist/src/chunks/prewarm-B4YblQ5m.js +6 -0
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +2 -2
- package/dist/src/compiled/@workflow/core/classify-error.d.ts +4 -3
- package/dist/src/compiled/@workflow/core/encryption.d.ts +7 -1
- package/dist/src/compiled/@workflow/core/index.js +2 -2
- package/dist/src/compiled/@workflow/core/package.json +1 -1
- package/dist/src/compiled/@workflow/core/runtime/constants.d.ts +47 -0
- package/dist/src/compiled/@workflow/core/runtime/replay-budget.d.ts +98 -0
- package/dist/src/compiled/@workflow/core/runtime.js +27 -27
- package/dist/src/compiled/@workflow/core/serialization/types.d.ts +14 -0
- package/dist/src/compiled/@workflow/core/serialization.d.ts +8 -0
- package/dist/src/compiled/@workflow/core/symbols.d.ts +16 -0
- package/dist/src/compiled/@workflow/core/version.d.ts +1 -1
- package/dist/src/compiled/@workflow/core/workflow.js +1 -1
- package/dist/src/compiled/@workflow/errors/error-codes.d.ts +5 -1
- package/dist/src/compiled/@workflow/errors/index.d.ts +15 -1
- package/dist/src/compiled/@workflow/errors/index.js +1 -1
- package/dist/src/compiled/@workflow/errors/package.json +1 -1
- package/dist/src/compiled/_chunks/workflow/{context-errors-zbKocOyk.js → context-errors-Bbvvp-li.js} +2 -2
- package/dist/src/compiled/_chunks/workflow/{dist-0iNBqPYp.js → dist-C7wPwOI9.js} +2 -2
- package/dist/src/compiled/_chunks/workflow/{dist-D774SUM4.js → dist-C_oiE-l7.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/resume-hook-C3VWUPii.js +12 -0
- package/dist/src/compiled/_chunks/workflow/sleep-QTkC1VFe.js +1 -0
- package/dist/src/compiled/_chunks/workflow/{symbols-D-4tVV8x.js → symbols-QezhMuLg.js} +1 -1
- package/dist/src/evals/cli/eval.js +1 -1
- package/dist/src/execution/await-authorization-orchestrator.d.ts +2 -1
- package/dist/src/execution/await-authorization-orchestrator.js +4 -0
- package/dist/src/execution/connection-auth-steps.d.ts +4 -0
- package/dist/src/execution/connection-auth-steps.js +9 -11
- package/dist/src/execution/node-step.d.ts +4 -5
- package/dist/src/execution/subagent-adapter.d.ts +0 -27
- package/dist/src/execution/subagent-adapter.js +2 -66
- package/dist/src/execution/subagent-hitl-proxy.d.ts +2 -2
- package/dist/src/execution/subagent-hitl-proxy.js +2 -2
- package/dist/src/execution/task-mode.d.ts +3 -3
- package/dist/src/execution/task-mode.js +3 -3
- package/dist/src/execution/turn-workflow.d.ts +41 -0
- package/dist/src/execution/turn-workflow.js +96 -0
- package/dist/src/execution/workflow-entry.js +77 -87
- package/dist/src/execution/workflow-errors.d.ts +14 -0
- package/dist/src/execution/workflow-errors.js +54 -0
- package/dist/src/execution/workflow-runtime.d.ts +34 -3
- package/dist/src/execution/workflow-runtime.js +52 -10
- package/dist/src/execution/workflow-steps.d.ts +27 -2
- package/dist/src/execution/workflow-steps.js +31 -26
- package/dist/src/harness/instrumentation-config.js +14 -7
- package/dist/src/harness/messages.d.ts +7 -7
- package/dist/src/harness/messages.js +4 -4
- package/dist/src/harness/runtime-actions.d.ts +4 -4
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/workflow-bundle/workflow-builders.d.ts +1 -1
- package/dist/src/internal/workflow-bundle/workflow-builders.js +20 -8
- package/dist/src/internal/workflow-bundle/workflow-transformer.d.ts +13 -0
- package/dist/src/internal/workflow-bundle/workflow-transformer.js +10 -4
- package/package.json +4 -4
- package/dist/src/chunks/host-tji7W0Nn.js +0 -65
- package/dist/src/chunks/prewarm-6duWvvb5.js +0 -6
- package/dist/src/compiled/_chunks/workflow/resume-hook-CL8Ed91K.js +0 -12
- package/dist/src/compiled/_chunks/workflow/sleep-Dn3i9nxI.js +0 -1
- package/dist/src/execution/continuous-entry.d.ts +0 -59
- package/dist/src/execution/continuous-entry.js +0 -487
- package/dist/src/execution/continuous-runtime.d.ts +0 -17
- package/dist/src/execution/continuous-runtime.js +0 -123
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Creates the invariant error raised when task-mode execution attempts to
|
|
3
3
|
* park instead of finishing inside the current invocation.
|
|
4
4
|
*
|
|
5
|
-
* Thrown by `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Thrown by `workflow-entry.ts` when the harness returns `next: null`
|
|
6
|
+
* in task mode. The message is stable so callers can assert against it
|
|
7
|
+
* in tests.
|
|
8
8
|
*/
|
|
9
9
|
export declare function createTaskModeWaitError(): Error;
|
|
@@ -3,9 +3,9 @@ const TASK_MODE_WAIT_ERROR_MESSAGE = "Task mode cannot wait for follow-up input
|
|
|
3
3
|
* Creates the invariant error raised when task-mode execution attempts to
|
|
4
4
|
* park instead of finishing inside the current invocation.
|
|
5
5
|
*
|
|
6
|
-
* Thrown by `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Thrown by `workflow-entry.ts` when the harness returns `next: null`
|
|
7
|
+
* in task mode. The message is stable so callers can assert against it
|
|
8
|
+
* in tests.
|
|
9
9
|
*/
|
|
10
10
|
export function createTaskModeWaitError() {
|
|
11
11
|
return new Error(TASK_MODE_WAIT_ERROR_MESSAGE);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { HookPayload, SessionCapabilities } from "#channel/types.js";
|
|
2
|
+
import type { HarnessSession } from "#harness/types.js";
|
|
3
|
+
import type { RunMode } from "#run-mode.js";
|
|
4
|
+
export interface TurnResultPayload {
|
|
5
|
+
readonly action: "done" | "park";
|
|
6
|
+
readonly kind: "turn-result";
|
|
7
|
+
readonly output?: string;
|
|
8
|
+
readonly serializedContext: Record<string, unknown>;
|
|
9
|
+
readonly session: HarnessSession;
|
|
10
|
+
}
|
|
11
|
+
export interface TurnErrorPayload {
|
|
12
|
+
readonly error: unknown;
|
|
13
|
+
readonly kind: "turn-error";
|
|
14
|
+
}
|
|
15
|
+
export type TurnCompletionPayload = TurnResultPayload | TurnErrorPayload;
|
|
16
|
+
export interface TurnWorkflowInput {
|
|
17
|
+
readonly capabilities: SessionCapabilities | undefined;
|
|
18
|
+
readonly completionToken: string;
|
|
19
|
+
readonly delivery: HookPayload;
|
|
20
|
+
readonly mode: RunMode;
|
|
21
|
+
readonly parentWritable: WritableStream<Uint8Array>;
|
|
22
|
+
readonly serializedContext: Record<string, unknown>;
|
|
23
|
+
readonly session: HarnessSession;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Short-lived workflow that owns one runtime turn for the durable
|
|
27
|
+
* driver.
|
|
28
|
+
*
|
|
29
|
+
* `parentWritable` is received from the driver input object and
|
|
30
|
+
* threaded into every step so the child's writes land directly on the
|
|
31
|
+
* driver run's stream.
|
|
32
|
+
*/
|
|
33
|
+
export declare function turnWorkflow(input: TurnWorkflowInput): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Completes a driver-owned one-shot hook with the result of the child
|
|
36
|
+
* turn workflow.
|
|
37
|
+
*/
|
|
38
|
+
export declare function notifyDriverStep(input: {
|
|
39
|
+
readonly completionToken: string;
|
|
40
|
+
readonly payload: TurnCompletionPayload;
|
|
41
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { hasPendingInputBatch } from "#harness/input-requests.js";
|
|
2
|
+
import { hasPendingRuntimeActionBatch } from "#harness/runtime-actions.js";
|
|
3
|
+
import { awaitAuthorizationAndResolve } from "#execution/await-authorization-orchestrator.js";
|
|
4
|
+
import { createTaskModeWaitError } from "#execution/task-mode.js";
|
|
5
|
+
import { normalizeSerializableError } from "#execution/workflow-errors.js";
|
|
6
|
+
import { turnStep } from "#execution/workflow-steps.js";
|
|
7
|
+
/**
|
|
8
|
+
* Short-lived workflow that owns one runtime turn for the durable
|
|
9
|
+
* driver.
|
|
10
|
+
*
|
|
11
|
+
* `parentWritable` is received from the driver input object and
|
|
12
|
+
* threaded into every step so the child's writes land directly on the
|
|
13
|
+
* driver run's stream.
|
|
14
|
+
*/
|
|
15
|
+
export async function turnWorkflow(input) {
|
|
16
|
+
"use workflow";
|
|
17
|
+
let currentSession = input.session;
|
|
18
|
+
let currentSerializedContext = input.serializedContext;
|
|
19
|
+
let currentInput = input.delivery;
|
|
20
|
+
const parentWritable = input.parentWritable;
|
|
21
|
+
try {
|
|
22
|
+
while (true) {
|
|
23
|
+
const result = await turnStep({
|
|
24
|
+
input: currentInput,
|
|
25
|
+
parentWritable,
|
|
26
|
+
serializedContext: currentSerializedContext,
|
|
27
|
+
session: currentSession,
|
|
28
|
+
});
|
|
29
|
+
currentSession = result.session;
|
|
30
|
+
currentSerializedContext = result.serializedContext;
|
|
31
|
+
if (result.action === "done") {
|
|
32
|
+
await notifyDriverStep({
|
|
33
|
+
completionToken: input.completionToken,
|
|
34
|
+
payload: {
|
|
35
|
+
action: "done",
|
|
36
|
+
kind: "turn-result",
|
|
37
|
+
output: result.output ?? "",
|
|
38
|
+
serializedContext: currentSerializedContext,
|
|
39
|
+
session: currentSession,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (result.action === "park") {
|
|
45
|
+
if (hasPendingRuntimeActionBatch(currentSession) ||
|
|
46
|
+
(hasPendingInputBatch(currentSession) && input.capabilities?.requestInput === true) ||
|
|
47
|
+
input.mode === "conversation") {
|
|
48
|
+
await notifyDriverStep({
|
|
49
|
+
completionToken: input.completionToken,
|
|
50
|
+
payload: {
|
|
51
|
+
action: "park",
|
|
52
|
+
kind: "turn-result",
|
|
53
|
+
serializedContext: currentSerializedContext,
|
|
54
|
+
session: currentSession,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
throw createTaskModeWaitError();
|
|
60
|
+
}
|
|
61
|
+
if (result.action === "await-authorization") {
|
|
62
|
+
const resolved = await awaitAuthorizationAndResolve({
|
|
63
|
+
parentWritable,
|
|
64
|
+
pendingAuths: result.pendingAuths,
|
|
65
|
+
pendingToolCalls: result.pendingToolCalls,
|
|
66
|
+
serializedContext: currentSerializedContext,
|
|
67
|
+
session: currentSession,
|
|
68
|
+
});
|
|
69
|
+
currentSession = resolved.session;
|
|
70
|
+
currentSerializedContext = resolved.serializedContext;
|
|
71
|
+
currentInput = undefined;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
currentInput = undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
await notifyDriverStep({
|
|
79
|
+
completionToken: input.completionToken,
|
|
80
|
+
payload: {
|
|
81
|
+
error: normalizeSerializableError(error),
|
|
82
|
+
kind: "turn-error",
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Completes a driver-owned one-shot hook with the result of the child
|
|
90
|
+
* turn workflow.
|
|
91
|
+
*/
|
|
92
|
+
export async function notifyDriverStep(input) {
|
|
93
|
+
"use step";
|
|
94
|
+
const { resumeHook } = await import("#compiled/@workflow/core/runtime.js");
|
|
95
|
+
await resumeHook(input.completionToken, input.payload);
|
|
96
|
+
}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { createHook, getWorkflowMetadata } from "#compiled/@workflow/core/index.js";
|
|
1
|
+
import { createHook, getWorkflowMetadata, getWritable, } from "#compiled/@workflow/core/index.js";
|
|
2
2
|
import { SUBAGENT_ADAPTER_KIND } from "#execution/subagent-adapter.js";
|
|
3
3
|
import { ChannelKey } from "#context/keys.js";
|
|
4
4
|
import { deserializeContext } from "#context/serialize.js";
|
|
5
|
-
import { hasPendingInputBatch } from "#harness/input-requests.js";
|
|
6
5
|
import { coalesceDeliveries } from "#harness/messages.js";
|
|
7
6
|
import { hasProxyInputRequests } from "#harness/proxy-input-requests.js";
|
|
8
7
|
import { accumulateRuntimeActionResults, hasPendingRuntimeActionBatch, } from "#harness/runtime-actions.js";
|
|
9
8
|
import { toErrorMessage } from "#shared/errors.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { createSessionStep, dispatchPendingRuntimeActionsStep, durableRunStep, emitTerminalSessionFailureStep, routeProxiedDeliverStep, runProxyInputRequestStep, } from "#execution/workflow-steps.js";
|
|
9
|
+
import { normalizeSerializableError, rebuildSerializableError, } from "#execution/workflow-errors.js";
|
|
10
|
+
import { createSessionStep, dispatchTurnStep, dispatchPendingRuntimeActionsStep, emitTerminalSessionFailureStep, routeProxiedDeliverStep, runProxyInputRequestStep, } from "#execution/workflow-steps.js";
|
|
13
11
|
/**
|
|
14
12
|
* Long-lived workflow entrypoint for the durable runtime.
|
|
15
13
|
*
|
|
@@ -30,6 +28,7 @@ export async function workflowEntry(input) {
|
|
|
30
28
|
// failure emitter can stamp it onto the `session.failed` event
|
|
31
29
|
// even if `createSessionStep` itself throws.
|
|
32
30
|
input.serializedContext["ash.sessionId"] = sessionId;
|
|
31
|
+
const driverWritable = getWritable();
|
|
33
32
|
try {
|
|
34
33
|
const session = await createSessionStep({
|
|
35
34
|
compiledArtifactsSource: serializedBundle.source,
|
|
@@ -37,8 +36,9 @@ export async function workflowEntry(input) {
|
|
|
37
36
|
nodeId: serializedBundle.nodeId,
|
|
38
37
|
sessionId,
|
|
39
38
|
});
|
|
40
|
-
return await
|
|
39
|
+
return await runDriverLoop({
|
|
41
40
|
capabilities,
|
|
41
|
+
driverWritable,
|
|
42
42
|
initialInput: {
|
|
43
43
|
kind: "deliver",
|
|
44
44
|
payloads: [{ message: input.input.message, modelContext: input.input.modelContext }],
|
|
@@ -61,6 +61,7 @@ export async function workflowEntry(input) {
|
|
|
61
61
|
// itself cannot import `internal/logging.ts` (node:util gate).
|
|
62
62
|
await emitTerminalSessionFailureStep({
|
|
63
63
|
error: normalizeSerializableError(error),
|
|
64
|
+
parentWritable: driverWritable,
|
|
64
65
|
serializedContext: input.serializedContext,
|
|
65
66
|
});
|
|
66
67
|
await notifyDelegatedParentStep({
|
|
@@ -70,34 +71,19 @@ export async function workflowEntry(input) {
|
|
|
70
71
|
throw error;
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
-
* Reduces an arbitrary throwable to a shape that survives the
|
|
75
|
-
* serialization the workflow devkit performs on step inputs.
|
|
76
|
-
*
|
|
77
|
-
* Native `Error` objects do not cross the step boundary cleanly —
|
|
78
|
-
* their stack, cause chain, and non-enumerable `message` / `name`
|
|
79
|
-
* fields are dropped by structured clone. We project the error onto
|
|
80
|
-
* a plain object whose own properties the downstream step can inspect
|
|
81
|
-
* via the same `util.inspect` dump the logger uses.
|
|
82
|
-
*/
|
|
83
|
-
function normalizeSerializableError(error) {
|
|
84
|
-
if (!(error instanceof Error)) {
|
|
85
|
-
return error;
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
// Enumerable own properties (HTTP statusCode, responseBody, etc.)
|
|
89
|
-
// fan out first so the pinned fields below win on collision.
|
|
90
|
-
...error,
|
|
91
|
-
message: error.message,
|
|
92
|
-
name: error.name,
|
|
93
|
-
stack: error.stack,
|
|
94
|
-
cause: error.cause === undefined ? undefined : normalizeSerializableError(error.cause),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
async function runWorkflowLoop(input) {
|
|
74
|
+
async function runDriverLoop(input) {
|
|
98
75
|
let currentSession = input.session;
|
|
99
76
|
let currentSerializedContext = input.serializedContext;
|
|
100
|
-
let
|
|
77
|
+
let turnGeneration = 0;
|
|
78
|
+
let currentResult = await dispatchAndAwaitTurn({
|
|
79
|
+
capabilities: input.capabilities,
|
|
80
|
+
delivery: input.initialInput,
|
|
81
|
+
mode: input.mode,
|
|
82
|
+
parentWritable: input.driverWritable,
|
|
83
|
+
serializedContext: currentSerializedContext,
|
|
84
|
+
session: currentSession,
|
|
85
|
+
turnGeneration: ++turnGeneration,
|
|
86
|
+
});
|
|
101
87
|
const bufferedDeliveries = [];
|
|
102
88
|
currentSession = currentResult.session;
|
|
103
89
|
currentSerializedContext = currentResult.serializedContext;
|
|
@@ -112,7 +98,7 @@ async function runWorkflowLoop(input) {
|
|
|
112
98
|
// `ctx.session.setContinuationToken(...)` (e.g. Slack auto-anchor on
|
|
113
99
|
// first post). `currentSession.continuationToken` already reflects
|
|
114
100
|
// the latest token thanks to `reconcileSessionContinuationToken`
|
|
115
|
-
// inside `
|
|
101
|
+
// inside `turnStep`, so creating the park hook here lands at
|
|
116
102
|
// the right token from the start.
|
|
117
103
|
if (!currentSession.continuationToken) {
|
|
118
104
|
throw new Error("Cannot park: no continuation token available. The channel must " +
|
|
@@ -153,6 +139,7 @@ async function runWorkflowLoop(input) {
|
|
|
153
139
|
while (true) {
|
|
154
140
|
if (hasPendingRuntimeActionBatch(currentSession)) {
|
|
155
141
|
currentSession = await dispatchPendingRuntimeActionsStep({
|
|
142
|
+
parentWritable: input.driverWritable,
|
|
156
143
|
serializedContext: currentSerializedContext,
|
|
157
144
|
session: currentSession,
|
|
158
145
|
});
|
|
@@ -161,6 +148,7 @@ async function runWorkflowLoop(input) {
|
|
|
161
148
|
getNextPromise,
|
|
162
149
|
consumeNext,
|
|
163
150
|
rekeyHook,
|
|
151
|
+
parentWritable: input.driverWritable,
|
|
164
152
|
serializedContext: currentSerializedContext,
|
|
165
153
|
session: currentSession,
|
|
166
154
|
});
|
|
@@ -169,10 +157,18 @@ async function runWorkflowLoop(input) {
|
|
|
169
157
|
}
|
|
170
158
|
currentSession = runtimeResults.session;
|
|
171
159
|
currentSerializedContext = runtimeResults.serializedContext;
|
|
172
|
-
currentResult = await
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
160
|
+
currentResult = await dispatchAndAwaitTurn({
|
|
161
|
+
capabilities: input.capabilities,
|
|
162
|
+
delivery: {
|
|
163
|
+
kind: "runtime-action-result",
|
|
164
|
+
results: runtimeResults.results,
|
|
165
|
+
},
|
|
166
|
+
mode: input.mode,
|
|
167
|
+
parentWritable: input.driverWritable,
|
|
168
|
+
serializedContext: currentSerializedContext,
|
|
169
|
+
session: currentSession,
|
|
170
|
+
turnGeneration: ++turnGeneration,
|
|
171
|
+
});
|
|
176
172
|
}
|
|
177
173
|
else {
|
|
178
174
|
const nextDeliver = await waitForNextDeliver({
|
|
@@ -185,6 +181,7 @@ async function runWorkflowLoop(input) {
|
|
|
185
181
|
}
|
|
186
182
|
const remainder = await routeDeliverForChildren({
|
|
187
183
|
auth: nextDeliver.auth,
|
|
184
|
+
parentWritable: input.driverWritable,
|
|
188
185
|
payloads: nextDeliver.payloads,
|
|
189
186
|
session: currentSession,
|
|
190
187
|
});
|
|
@@ -193,11 +190,19 @@ async function runWorkflowLoop(input) {
|
|
|
193
190
|
// model turn to run right now.
|
|
194
191
|
continue;
|
|
195
192
|
}
|
|
196
|
-
currentResult = await
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
193
|
+
currentResult = await dispatchAndAwaitTurn({
|
|
194
|
+
capabilities: input.capabilities,
|
|
195
|
+
delivery: {
|
|
196
|
+
auth: nextDeliver.auth,
|
|
197
|
+
kind: "deliver",
|
|
198
|
+
payloads: [remainder],
|
|
199
|
+
},
|
|
200
|
+
mode: input.mode,
|
|
201
|
+
parentWritable: input.driverWritable,
|
|
202
|
+
serializedContext: currentSerializedContext,
|
|
203
|
+
session: currentSession,
|
|
204
|
+
turnGeneration: ++turnGeneration,
|
|
205
|
+
});
|
|
201
206
|
}
|
|
202
207
|
currentSession = currentResult.session;
|
|
203
208
|
currentSerializedContext = currentResult.serializedContext;
|
|
@@ -217,59 +222,41 @@ async function runWorkflowLoop(input) {
|
|
|
217
222
|
}
|
|
218
223
|
return { output: "" };
|
|
219
224
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (result.action === "park") {
|
|
237
|
-
if (hasPendingRuntimeActionBatch(currentSession)) {
|
|
238
|
-
return result;
|
|
239
|
-
}
|
|
240
|
-
if (hasPendingInputBatch(currentSession) && capabilities?.requestInput === true) {
|
|
241
|
-
return result;
|
|
242
|
-
}
|
|
243
|
-
if (mode === "conversation") {
|
|
244
|
-
return result;
|
|
245
|
-
}
|
|
246
|
-
throw createTaskModeWaitError();
|
|
247
|
-
}
|
|
248
|
-
if (result.action === "await-authorization") {
|
|
249
|
-
// Drive the interactive-OAuth authorization cycle in the
|
|
250
|
-
// workflow body so `createHook` is called outside a step
|
|
251
|
-
// (required for stable callback tokens across replay) and
|
|
252
|
-
// awaiting the hook suspends the run.
|
|
253
|
-
const resolved = await awaitAuthorizationAndResolve({
|
|
254
|
-
pendingToolCalls: result.pendingToolCalls,
|
|
255
|
-
pendingAuths: result.pendingAuths,
|
|
256
|
-
serializedContext: currentSerializedContext,
|
|
257
|
-
session: currentSession,
|
|
258
|
-
});
|
|
259
|
-
currentSession = resolved.session;
|
|
260
|
-
currentSerializedContext = resolved.serializedContext;
|
|
261
|
-
currentInput = undefined;
|
|
262
|
-
continue;
|
|
225
|
+
async function dispatchAndAwaitTurn(input) {
|
|
226
|
+
const completionToken = `ash://turn/${input.session.sessionId}/${input.turnGeneration}`;
|
|
227
|
+
const completion = createHook({ token: completionToken });
|
|
228
|
+
try {
|
|
229
|
+
await dispatchTurnStep({
|
|
230
|
+
capabilities: input.capabilities,
|
|
231
|
+
completionToken,
|
|
232
|
+
delivery: input.delivery,
|
|
233
|
+
mode: input.mode,
|
|
234
|
+
parentWritable: input.parentWritable,
|
|
235
|
+
serializedContext: input.serializedContext,
|
|
236
|
+
session: input.session,
|
|
237
|
+
});
|
|
238
|
+
const payload = await awaitHookPayload(completion);
|
|
239
|
+
if (payload.kind === "turn-error") {
|
|
240
|
+
throw rebuildSerializableError(payload.error);
|
|
263
241
|
}
|
|
264
|
-
|
|
242
|
+
return payload;
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
await disposeHook(completion);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function awaitHookPayload(hook) {
|
|
249
|
+
for await (const value of hook) {
|
|
250
|
+
return value;
|
|
265
251
|
}
|
|
252
|
+
throw new Error("Turn completion hook closed before delivering a result.");
|
|
266
253
|
}
|
|
267
254
|
async function waitForPendingRuntimeActionResults(input) {
|
|
268
255
|
let currentSession = input.session;
|
|
269
256
|
// The proxied HITL step persists adapter-state mutations on the
|
|
270
257
|
// context (e.g. Slack's `pendingRequests` cache) and returns the
|
|
271
258
|
// updated serialized context. We thread that forward so subsequent
|
|
272
|
-
// deliveries and the eventual post-wait `
|
|
259
|
+
// deliveries and the eventual post-wait `turnStep` observe
|
|
273
260
|
// the cached state instead of re-deserializing a stale snapshot.
|
|
274
261
|
let currentSerializedContext = input.serializedContext;
|
|
275
262
|
const results = await accumulateRuntimeActionResults({
|
|
@@ -292,6 +279,7 @@ async function waitForPendingRuntimeActionResults(input) {
|
|
|
292
279
|
// parent↔child deadlock this branch exists to prevent.
|
|
293
280
|
const remainder = await routeDeliverForChildren({
|
|
294
281
|
auth: value.auth,
|
|
282
|
+
parentWritable: input.parentWritable,
|
|
295
283
|
payloads: value.payloads,
|
|
296
284
|
session: currentSession,
|
|
297
285
|
});
|
|
@@ -312,6 +300,7 @@ async function waitForPendingRuntimeActionResults(input) {
|
|
|
312
300
|
// parent's adapter, record the routing entry, and keep waiting.
|
|
313
301
|
const proxyResult = await runProxyInputRequestStep({
|
|
314
302
|
hookPayload: value,
|
|
303
|
+
parentWritable: input.parentWritable,
|
|
315
304
|
serializedContext: currentSerializedContext,
|
|
316
305
|
session: currentSession,
|
|
317
306
|
});
|
|
@@ -350,6 +339,7 @@ async function routeDeliverForChildren(input) {
|
|
|
350
339
|
}
|
|
351
340
|
const routed = await routeProxiedDeliverStep({
|
|
352
341
|
auth: input.auth,
|
|
342
|
+
parentWritable: input.parentWritable,
|
|
353
343
|
payload: coalesced,
|
|
354
344
|
session: input.session,
|
|
355
345
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes and rebuilds workflow errors across hook and step
|
|
3
|
+
* boundaries.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Reduces an arbitrary throwable to a shape that survives the
|
|
7
|
+
* serialization the workflow devkit performs on step inputs.
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeSerializableError(error: unknown): unknown;
|
|
10
|
+
/**
|
|
11
|
+
* Rebuilds an {@link Error} from the normalized hook payload sent by a
|
|
12
|
+
* child workflow.
|
|
13
|
+
*/
|
|
14
|
+
export declare function rebuildSerializableError(error: unknown): Error;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes and rebuilds workflow errors across hook and step
|
|
3
|
+
* boundaries.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Reduces an arbitrary throwable to a shape that survives the
|
|
7
|
+
* serialization the workflow devkit performs on step inputs.
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeSerializableError(error) {
|
|
10
|
+
if (!(error instanceof Error)) {
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
const ownProperties = Object.fromEntries(Object.entries(error));
|
|
14
|
+
return {
|
|
15
|
+
...ownProperties,
|
|
16
|
+
cause: error.cause === undefined ? undefined : normalizeSerializableError(error.cause),
|
|
17
|
+
message: error.message,
|
|
18
|
+
name: error.name,
|
|
19
|
+
stack: error.stack,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Rebuilds an {@link Error} from the normalized hook payload sent by a
|
|
24
|
+
* child workflow.
|
|
25
|
+
*/
|
|
26
|
+
export function rebuildSerializableError(error) {
|
|
27
|
+
if (!isRecord(error)) {
|
|
28
|
+
return new Error(String(error));
|
|
29
|
+
}
|
|
30
|
+
const message = typeof error.message === "string" ? error.message : String(error);
|
|
31
|
+
const rebuilt = new Error(message);
|
|
32
|
+
if (typeof error.name === "string") {
|
|
33
|
+
rebuilt.name = error.name;
|
|
34
|
+
}
|
|
35
|
+
if (typeof error.stack === "string") {
|
|
36
|
+
rebuilt.stack = error.stack;
|
|
37
|
+
}
|
|
38
|
+
if ("cause" in error) {
|
|
39
|
+
rebuilt.cause = isRecord(error.cause)
|
|
40
|
+
? rebuildSerializableError(error.cause)
|
|
41
|
+
: error.cause;
|
|
42
|
+
}
|
|
43
|
+
const mutable = rebuilt;
|
|
44
|
+
for (const [key, value] of Object.entries(error)) {
|
|
45
|
+
if (key === "message" || key === "name" || key === "stack" || key === "cause") {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
mutable[key] = value;
|
|
49
|
+
}
|
|
50
|
+
return rebuilt;
|
|
51
|
+
}
|
|
52
|
+
function isRecord(value) {
|
|
53
|
+
return value !== null && typeof value === "object";
|
|
54
|
+
}
|
|
@@ -1,17 +1,48 @@
|
|
|
1
|
+
import type { Run } from "#compiled/@workflow/core/runtime.js";
|
|
2
|
+
import type { WorkflowFunction, WorkflowMetadata } from "#compiled/@workflow/core/runtime/start.js";
|
|
1
3
|
import type { Runtime } from "#channel/types.js";
|
|
2
4
|
import type { RuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";
|
|
5
|
+
export declare const LATEST_DEPLOYMENT_UNSUPPORTED_MESSAGE = "deploymentId 'latest' requires a World that implements resolveLatestDeploymentId()";
|
|
6
|
+
/**
|
|
7
|
+
* Workflow function names whose bundled id is stable across deployments
|
|
8
|
+
* (no `@<pkg.version>` stamp). The bundler reads this set when emitting
|
|
9
|
+
* the workflow id so cross-deployment routing — `start(ref, args, {
|
|
10
|
+
* deploymentId: "latest" })` — finds the same workflow on a newer
|
|
11
|
+
* deployment even when the ash version differs.
|
|
12
|
+
*
|
|
13
|
+
* Both halves of the contract (bundler output and runtime reference
|
|
14
|
+
* template) read this single set so they cannot drift.
|
|
15
|
+
*/
|
|
16
|
+
export declare const STABLE_WORKFLOW_NAMES: ReadonlySet<string>;
|
|
3
17
|
/**
|
|
4
18
|
* Stable workflow reference used by `start()` to locate the workflow
|
|
5
|
-
* entrypoint registered by the Workflow DevKit builder.
|
|
19
|
+
* entrypoint registered by the Workflow DevKit builder. The id omits
|
|
20
|
+
* the package version stamp so the long-lived driver can rotate across
|
|
21
|
+
* deployments without rewriting the registry key.
|
|
6
22
|
*/
|
|
7
23
|
export declare const workflowEntryReference: {
|
|
8
24
|
workflowId: string;
|
|
9
25
|
};
|
|
10
26
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
27
|
+
* Stable workflow reference used by the driver to dispatch per-turn
|
|
28
|
+
* child workflow runs. The id omits the package version stamp so
|
|
29
|
+
* `start(turnWorkflowReference, args, { deploymentId: "latest" })`
|
|
30
|
+
* routes to the latest deployment's turn workflow even when the ash
|
|
31
|
+
* version differs from the caller's deployment.
|
|
32
|
+
*/
|
|
33
|
+
export declare const turnWorkflowReference: {
|
|
34
|
+
workflowId: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Creates a workflow-backed runtime whose long-lived driver owns the
|
|
38
|
+
* session stream and dispatches each turn as a child workflow run.
|
|
13
39
|
*/
|
|
14
40
|
export declare function createWorkflowRuntime(config: {
|
|
15
41
|
readonly compiledArtifactsSource: RuntimeCompiledArtifactsSource;
|
|
16
42
|
readonly nodeId?: string;
|
|
17
43
|
}): Runtime;
|
|
44
|
+
/**
|
|
45
|
+
* Starts a workflow on the latest deployment when the active world supports
|
|
46
|
+
* it, while preserving local/dev worlds that do not implement latest routing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function startWorkflowPreferLatest<TArgs extends unknown[], TResult>(workflow: WorkflowFunction<TArgs, TResult> | WorkflowMetadata, args: TArgs): Promise<Run<unknown> | Run<TResult>>;
|
|
@@ -6,17 +6,46 @@ import { getCompiledRuntimeAgentBundle } from "#runtime/sessions/compiled-agent-
|
|
|
6
6
|
import { buildRunContext } from "#execution/runtime-context.js";
|
|
7
7
|
import { RuntimeNoActiveSessionError } from "#execution/runtime-errors.js";
|
|
8
8
|
const WORKFLOW_ENTRY_NAME = "workflowEntry";
|
|
9
|
+
const TURN_WORKFLOW_NAME = "turnWorkflow";
|
|
9
10
|
const ASH_PACKAGE_INFO = resolveInstalledPackageInfo();
|
|
11
|
+
export const LATEST_DEPLOYMENT_UNSUPPORTED_MESSAGE = "deploymentId 'latest' requires a World that implements resolveLatestDeploymentId()";
|
|
12
|
+
/**
|
|
13
|
+
* Workflow function names whose bundled id is stable across deployments
|
|
14
|
+
* (no `@<pkg.version>` stamp). The bundler reads this set when emitting
|
|
15
|
+
* the workflow id so cross-deployment routing — `start(ref, args, {
|
|
16
|
+
* deploymentId: "latest" })` — finds the same workflow on a newer
|
|
17
|
+
* deployment even when the ash version differs.
|
|
18
|
+
*
|
|
19
|
+
* Both halves of the contract (bundler output and runtime reference
|
|
20
|
+
* template) read this single set so they cannot drift.
|
|
21
|
+
*/
|
|
22
|
+
export const STABLE_WORKFLOW_NAMES = new Set([
|
|
23
|
+
WORKFLOW_ENTRY_NAME,
|
|
24
|
+
TURN_WORKFLOW_NAME,
|
|
25
|
+
]);
|
|
26
|
+
const STABLE_ID_BASE = ASH_PACKAGE_INFO.name;
|
|
10
27
|
/**
|
|
11
28
|
* Stable workflow reference used by `start()` to locate the workflow
|
|
12
|
-
* entrypoint registered by the Workflow DevKit builder.
|
|
29
|
+
* entrypoint registered by the Workflow DevKit builder. The id omits
|
|
30
|
+
* the package version stamp so the long-lived driver can rotate across
|
|
31
|
+
* deployments without rewriting the registry key.
|
|
13
32
|
*/
|
|
14
33
|
export const workflowEntryReference = {
|
|
15
|
-
workflowId: `workflow//${
|
|
34
|
+
workflowId: `workflow//${STABLE_ID_BASE}//${WORKFLOW_ENTRY_NAME}`,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Stable workflow reference used by the driver to dispatch per-turn
|
|
38
|
+
* child workflow runs. The id omits the package version stamp so
|
|
39
|
+
* `start(turnWorkflowReference, args, { deploymentId: "latest" })`
|
|
40
|
+
* routes to the latest deployment's turn workflow even when the ash
|
|
41
|
+
* version differs from the caller's deployment.
|
|
42
|
+
*/
|
|
43
|
+
export const turnWorkflowReference = {
|
|
44
|
+
workflowId: `workflow//${STABLE_ID_BASE}//${TURN_WORKFLOW_NAME}`,
|
|
16
45
|
};
|
|
17
46
|
/**
|
|
18
|
-
* Creates a workflow-backed runtime
|
|
19
|
-
*
|
|
47
|
+
* Creates a workflow-backed runtime whose long-lived driver owns the
|
|
48
|
+
* session stream and dispatches each turn as a child workflow run.
|
|
20
49
|
*/
|
|
21
50
|
export function createWorkflowRuntime(config) {
|
|
22
51
|
return {
|
|
@@ -27,20 +56,15 @@ export function createWorkflowRuntime(config) {
|
|
|
27
56
|
});
|
|
28
57
|
const ctx = buildRunContext({ bundle, run: input });
|
|
29
58
|
const serializedContext = serializeContext(ctx);
|
|
30
|
-
const run = await
|
|
59
|
+
const run = await startWorkflowPreferLatest(workflowEntryReference, [
|
|
31
60
|
{
|
|
32
61
|
input: input.input,
|
|
33
62
|
serializedContext,
|
|
34
63
|
},
|
|
35
64
|
]);
|
|
36
|
-
const result = (async () => {
|
|
37
|
-
const wfResult = (await run.returnValue);
|
|
38
|
-
return { status: "completed", output: wfResult.output };
|
|
39
|
-
})();
|
|
40
65
|
return {
|
|
41
66
|
continuationToken: input.continuationToken ?? run.runId,
|
|
42
67
|
events: parseNdjsonStream(getRun(run.runId).getReadable()),
|
|
43
|
-
result,
|
|
44
68
|
sessionId: run.runId,
|
|
45
69
|
};
|
|
46
70
|
},
|
|
@@ -69,6 +93,24 @@ export function createWorkflowRuntime(config) {
|
|
|
69
93
|
},
|
|
70
94
|
};
|
|
71
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Starts a workflow on the latest deployment when the active world supports
|
|
98
|
+
* it, while preserving local/dev worlds that do not implement latest routing.
|
|
99
|
+
*/
|
|
100
|
+
export async function startWorkflowPreferLatest(workflow, args) {
|
|
101
|
+
try {
|
|
102
|
+
return await start(workflow, args, { deploymentId: "latest" });
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (!isLatestDeploymentUnsupportedError(error)) {
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
return await start(workflow, args);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function isLatestDeploymentUnsupportedError(error) {
|
|
112
|
+
return error instanceof Error && error.message.includes(LATEST_DEPLOYMENT_UNSUPPORTED_MESSAGE);
|
|
113
|
+
}
|
|
72
114
|
function normalizeWorkflowHook(value) {
|
|
73
115
|
if (value === null || typeof value !== "object" || !("runId" in value)) {
|
|
74
116
|
throw new Error("Workflow hook did not include a run id.");
|