experimental-ash 0.29.0 → 0.30.1
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 +40 -0
- package/dist/src/context/hook-lifecycle.d.ts +23 -67
- package/dist/src/context/hook-lifecycle.js +1 -1
- package/dist/src/execution/await-authorization-orchestrator.d.ts +18 -29
- package/dist/src/execution/await-authorization-orchestrator.js +1 -1
- package/dist/src/execution/connection-auth-steps.d.ts +28 -65
- package/dist/src/execution/connection-auth-steps.js +1 -1
- package/dist/src/execution/create-session-step.d.ts +15 -0
- package/dist/src/execution/create-session-step.js +1 -0
- package/dist/src/execution/delegated-parent-notification.d.ts +15 -0
- package/dist/src/execution/delegated-parent-notification.js +1 -0
- package/dist/src/execution/delegated-parent-result.d.ts +14 -0
- package/dist/src/execution/delegated-parent-result.js +1 -0
- package/dist/src/execution/dispatch-runtime-actions-step.d.ts +20 -0
- package/dist/src/execution/dispatch-runtime-actions-step.js +1 -0
- package/dist/src/execution/durable-session-migrations/chain.d.ts +31 -0
- package/dist/src/execution/durable-session-migrations/chain.js +1 -0
- package/dist/src/execution/durable-session-migrations/snapshot.d.ts +22 -0
- package/dist/src/execution/durable-session-migrations/snapshot.js +1 -0
- package/dist/src/execution/durable-session-store.d.ts +104 -0
- package/dist/src/execution/durable-session-store.js +1 -0
- package/dist/src/execution/next-driver-action.d.ts +30 -0
- package/dist/src/execution/next-driver-action.js +1 -0
- package/dist/src/execution/remote-agent-dispatch.js +1 -1
- package/dist/src/execution/session.d.ts +29 -28
- package/dist/src/execution/session.js +1 -1
- package/dist/src/execution/subagent-hitl-proxy.d.ts +8 -25
- package/dist/src/execution/subagent-hitl-proxy.js +1 -1
- package/dist/src/execution/subagent-tool.js +1 -1
- package/dist/src/execution/turn-workflow.d.ts +21 -21
- package/dist/src/execution/turn-workflow.js +1 -1
- package/dist/src/execution/workflow-callback-url.d.ts +12 -0
- package/dist/src/execution/workflow-callback-url.js +1 -0
- package/dist/src/execution/workflow-entry.d.ts +12 -16
- package/dist/src/execution/workflow-entry.js +1 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/execution/workflow-steps.d.ts +36 -91
- package/dist/src/execution/workflow-steps.js +1 -1
- package/dist/src/harness/emission.d.ts +3 -5
- package/dist/src/harness/emission.js +1 -1
- package/dist/src/harness/input-requests.d.ts +3 -3
- package/dist/src/harness/input-requests.js +1 -1
- package/dist/src/harness/proxy-input-requests.d.ts +12 -16
- package/dist/src/harness/proxy-input-requests.js +1 -1
- package/dist/src/harness/runtime-actions.d.ts +17 -24
- package/dist/src/harness/runtime-actions.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/harness/types.d.ts +3 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/nitro/routes/info.js +1 -1
- package/dist/src/internal/workflow-bundle/builder.d.ts +2 -0
- package/dist/src/internal/workflow-bundle/builder.js +1 -1
- package/dist/src/runtime/framework-channels/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.30.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- eec18ad: Use Vercel production callback URLs for production Ash workflows and include the Vercel automation bypass secret on generated callback URLs when configured.
|
|
8
|
+
|
|
9
|
+
## 0.30.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- e2bb3b9: Move durable `HarnessSession` snapshots out of every Workflow step
|
|
14
|
+
boundary into a per-driver-run `ash.session` stream wrapped in a
|
|
15
|
+
versioned `DurableSessionSnapshot`. Step inputs, step results, workflow
|
|
16
|
+
inputs, and hook payloads carry a small `DurableSessionState` — identity,
|
|
17
|
+
the closed-contract `hasProxyInputRequests` short-circuit, and the
|
|
18
|
+
`emissionState` projection workflow-body orchestrators (eg.
|
|
19
|
+
await-authorization) use to stamp protocol events without taking an
|
|
20
|
+
extra step boundary — and each session-mutating step returns a closed
|
|
21
|
+
`NextDriverAction` union (`done` | `park` | `dispatch-runtime-actions`)
|
|
22
|
+
the driver dispatches on without inspecting session shape. The persisted
|
|
23
|
+
snapshot is narrowed to the durable subset — `agent.modelReference`,
|
|
24
|
+
`agent.tools`, and compaction thresholds are rebuilt every turn from the
|
|
25
|
+
latest bundle rather than baked into every chunk. Together these changes
|
|
26
|
+
eliminate quadratic event-log growth, set up a stable cross-version
|
|
27
|
+
contract so a pinned older driver can dispatch sessions whose latest
|
|
28
|
+
code has added new optional fields, and reserve a `version` discriminator
|
|
29
|
+
(plus a version-walking migration framework) for future shape changes.
|
|
30
|
+
The harness `StepFn` contract is unchanged.
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- e9052aa: Fix `ash dev` returning 401 when run inside `vercel dev`. The framework's
|
|
35
|
+
default auth resolvers for the ash channel and the `/__ash/info`
|
|
36
|
+
endpoint previously gated on `process.env.VERCEL` alone, but `vercel
|
|
37
|
+
dev` inherits `VERCEL=1` into child processes while the REPL strips
|
|
38
|
+
auth on loopback URLs, producing a 401. Both resolvers now also require
|
|
39
|
+
`VERCEL_ENV !== "development"` before defaulting to `vercelOidc()`, so
|
|
40
|
+
local dev under `vercel dev` falls through to `none()` and the REPL
|
|
41
|
+
talks to the dev server.
|
|
42
|
+
|
|
3
43
|
## 0.29.0
|
|
4
44
|
|
|
5
45
|
### Minor Changes
|
|
@@ -4,18 +4,11 @@ import type { RunMode } from "#shared/run-mode.js";
|
|
|
4
4
|
import type { RuntimeHookRegistry } from "#runtime/hooks/registry.js";
|
|
5
5
|
import type { ContextContainer } from "./container.js";
|
|
6
6
|
/**
|
|
7
|
-
* Outcome of
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* system-history announcements for hook-contributed skills.
|
|
13
|
-
*
|
|
14
|
-
* `turn-failed` indicates a `lifecycle.turn` hook threw. The dispatcher
|
|
15
|
-
* has already emitted the recoverable `turn.failed` cascade
|
|
16
|
-
* (`session.started` once → `turn.started` → `message.received` →
|
|
17
|
-
* `step.failed` → `turn.failed` → `session.waiting`); the caller
|
|
18
|
-
* lowers this outcome to a parking {@link StepResult}.
|
|
7
|
+
* Outcome of {@link dispatchHookLifecycle}. `proceed` carries the
|
|
8
|
+
* input augmented with merged `modelContext` and any hook-contributed
|
|
9
|
+
* skill announcements on the session. `turn-failed` means a
|
|
10
|
+
* `lifecycle.turn` hook threw; the recoverable `turn.failed` cascade
|
|
11
|
+
* has already been emitted.
|
|
19
12
|
*/
|
|
20
13
|
export type HookLifecycleOutcome = {
|
|
21
14
|
readonly kind: "proceed";
|
|
@@ -26,9 +19,6 @@ export type HookLifecycleOutcome = {
|
|
|
26
19
|
readonly message: string;
|
|
27
20
|
readonly nextSession: HarnessSession;
|
|
28
21
|
};
|
|
29
|
-
/**
|
|
30
|
-
* Input for {@link dispatchHookLifecycle}.
|
|
31
|
-
*/
|
|
32
22
|
export interface DispatchHookLifecycleInput {
|
|
33
23
|
readonly ctx: ContextContainer;
|
|
34
24
|
readonly registry: RuntimeHookRegistry;
|
|
@@ -39,51 +29,24 @@ export interface DispatchHookLifecycleInput {
|
|
|
39
29
|
readonly runtimeIdentity?: RuntimeIdentity;
|
|
40
30
|
}
|
|
41
31
|
/**
|
|
42
|
-
* Runs the per-turn hook lifecycle inside the active ALS scope
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* 2. `lifecycle.turn` — runs once per fresh delivery (the caller
|
|
55
|
-
* gates dispatch with `isHarnessBetweenTurns(session)`). Each
|
|
56
|
-
* hook may return `{ modelContext, skills }`; model-context
|
|
57
|
-
* contributions are concatenated in registry order and skills are
|
|
58
|
-
* materialized into the live sandbox before the model call. A
|
|
59
|
-
* thrown hook is caught here, the recoverable `turn.failed`
|
|
60
|
-
* cascade is emitted, and the dispatcher returns
|
|
61
|
-
* `kind: "turn-failed"` so the caller can park the session.
|
|
62
|
-
*
|
|
63
|
-
* Errors:
|
|
64
|
-
*
|
|
65
|
-
* - `lifecycle.session` throws → re-thrown. Runtime escalates to
|
|
66
|
-
* `session.failed`.
|
|
67
|
-
* - `lifecycle.turn` throws → caught, recoverable `turn.failed`
|
|
68
|
-
* emitted, outcome `kind: "turn-failed"`. Session keeps living.
|
|
69
|
-
*
|
|
70
|
-
* Stream-event hook errors are not handled here — they propagate
|
|
71
|
-
* through {@link dispatchStreamEventHooks} and are caught wherever the
|
|
72
|
-
* emit composer sits inside the harness.
|
|
32
|
+
* Runs the per-turn hook lifecycle inside the active ALS scope.
|
|
33
|
+
*
|
|
34
|
+
* - `lifecycle.session` runs once per session, gated by
|
|
35
|
+
* {@link SessionPreparedKey}. The flag is set **before** the chain
|
|
36
|
+
* runs so a thrown hook does not retry. A throw re-propagates and
|
|
37
|
+
* the runtime escalates to terminal `session.failed`.
|
|
38
|
+
* - `lifecycle.turn` runs once per fresh delivery. Each hook may
|
|
39
|
+
* return `{ modelContext, skills }`; model-context contributions
|
|
40
|
+
* are concatenated in registry order, skills are materialized into
|
|
41
|
+
* the sandbox. A thrown hook emits the recoverable `turn.failed`
|
|
42
|
+
* cascade and returns `kind: "turn-failed"`.
|
|
73
43
|
*/
|
|
74
44
|
export declare function dispatchHookLifecycle(input: DispatchHookLifecycleInput): Promise<HookLifecycleOutcome>;
|
|
75
45
|
/**
|
|
76
|
-
* Fans one runtime stream event out to every matching subscriber
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* is the natural caller; existing harness error paths catch propagated
|
|
81
|
-
* exceptions and emit the recoverable `turn.failed` cascade. If a
|
|
82
|
-
* hook subscribed to a failure-cascade event itself throws, the
|
|
83
|
-
* outer runtime escalates to `session.failed`.
|
|
84
|
-
*
|
|
85
|
-
* Caller is responsible for running this inside an active ALS scope so
|
|
86
|
-
* hook code can read the same context the rest of the step sees.
|
|
46
|
+
* Fans one runtime stream event out to every matching subscriber.
|
|
47
|
+
* Errors propagate — harness error paths convert them into the
|
|
48
|
+
* recoverable `turn.failed` cascade. Caller must hold an active ALS
|
|
49
|
+
* scope so hooks see the same context as the rest of the step.
|
|
87
50
|
*/
|
|
88
51
|
export declare function dispatchStreamEventHooks(input: {
|
|
89
52
|
readonly ctx: ContextContainer;
|
|
@@ -91,15 +54,8 @@ export declare function dispatchStreamEventHooks(input: {
|
|
|
91
54
|
readonly event: HandleMessageStreamEvent;
|
|
92
55
|
}): Promise<void>;
|
|
93
56
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* the (possibly augmented) input.
|
|
98
|
-
*
|
|
99
|
-
* The `Step` suffix signals the return type — both runtime entry points
|
|
100
|
-
* call this from inside their `runStep` callback so the
|
|
101
|
-
* recoverable-failure-to-`StepResult` mapping lives in one place.
|
|
102
|
-
*
|
|
103
|
-
* Caller must already be inside the active ALS scope.
|
|
57
|
+
* Runs the per-turn hook lifecycle, lowers a `turn-failed` outcome
|
|
58
|
+
* into a parking {@link StepResult}, and otherwise hands off to
|
|
59
|
+
* `body` with the (possibly augmented) input.
|
|
104
60
|
*/
|
|
105
61
|
export declare function runHookLifecycleStep(input: DispatchHookLifecycleInput, body: (session: HarnessSession, input: StepInput) => Promise<StepResult>): Promise<StepResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ContinuationTokenKey,SandboxKey,SessionIdKey,SessionPreparedKey}from"./keys.js";import{createLogger}from"#internal/logging.js";import{toErrorMessage}from"#shared/errors.js";import{normalizeSkillPackage,writeSkillPackageToSandbox}from"#shared/skill-package.js";import{getAdapterKind}from"#channel/adapter.js";import{emitRecoverableFailedTurn,emitTurnPreamble,getHarnessEmissionState,setHarnessEmissionState}from"#harness/emission.js";import{formatAvailableSkillsSection}from"#execution/skills/instructions.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";const log=createLogger(`hooks.lifecycle`);async function dispatchHookLifecycle(e){let{ctx:t,registry:n,emit:i}=e,o=getHarnessEmissionState(e.session),s=`turn_${o.sequence}`,c=o.sequence,l=buildHookContext(t),u={session:{sessionId:e.session.sessionId},turn:{sequence:c,turnId:s}},d=[],f=e.session;if(n.session.length>0&&t.get(SessionPreparedKey)!==!0){t.set(SessionPreparedKey,!0);let e=[];for(let r of n.session){let n=await r.handler(u,l);n?.modelContext!==void 0&&n.modelContext.length>0&&d.push(...n.modelContext),e.push(...normalizeLifecycleSkillResults(t,n))}f=await materializeLifecycleSkills(t,f,e)}let p=!1,m=o;try{let e=[];for(let r of n.turn){let n=await r.handler(u,l);n?.modelContext!==void 0&&n.modelContext.length>0&&d.push(...n.modelContext),e.push(...normalizeLifecycleSkillResults(t,n))}f=await materializeLifecycleSkills(t,f,e)}catch(t){let n=toErrorMessage(t);try{p||=(m=await emitTurnPreamble(i,{message:e.input.message},o,e.runtimeIdentity),!0);let t=await emitRecoverableFailedTurn(i,m,{code:`HOOK_TURN_FAILED`,message:n});return{kind:`turn-failed`,message:n,nextSession:setHarnessEmissionState(f,t)}}catch(e){throw log.error(`Event hook threw while emitting the turn.failed cascade for a lifecycle.turn failure — escalating to session.failed.`,{error:toErrorMessage(e)}),e}}let h=e.input.modelContext??[],g=h.length===0?d:[...h,...d];return{kind:`proceed`,input:g.length>0?{...e.input,modelContext:g}:e.input,nextSession:f}}function normalizeLifecycleSkillResults(e,t){if(t?.skills===void 0||t.skills.length===0)return[];let n=new Set(e.require(BundleKey).resolvedAgent.skills.map(e=>e.name)),r=new Map;for(let e of t.skills){let t=normalizeSkillPackage(e);if(n.has(t.name))throw Error(`Hook-contributed skill "${t.name}" conflicts with an authored skill.`);r.set(t.name,t)}return[...r.values()]}async function materializeLifecycleSkills(e,n,r){if(r.length===0)return n;let i=new Map(r.map(e=>[e.name,e])),a=await e.require(SandboxKey).get();if(a===null)throw Error(`Dynamic skills require a sandbox for the current agent.`);for(let e of i.values())await writeSkillPackageToSandbox({sandbox:a,skill:e});let o=formatAvailableSkillsSection([...i.values()]);return o===null?n:{...n,history:[...n.history,{role:`system`,content:o}]}}async function dispatchStreamEventHooks(e){let t=e.registry.streamEventsByType.get(e.event.type)??[],n=e.registry.streamEventsWildcard;if(t.length===0&&n.length===0)return;let r=buildHookContext(e.ctx);for(let n of t)await n.handler(e.event,r);for(let t of n)await t.handler(e.event,r)}function buildHookContext(t){let r=t.require(BundleKey),i=t.get(ChannelKey),a=t.get(ContinuationTokenKey),o=i===void 0?void 0:getAdapterKind(i);return{agent:{name:r.resolvedAgent.config.name??`agent`,nodeId:r.nodeId},channel:{kind:o,continuationToken:a},session:{sessionId:t.get(SessionIdKey)??``},ash:t}}async function runHookLifecycleStep(e,t){let n=await dispatchHookLifecycle(e);return n.kind===`turn-failed`?{next:e.mode===`conversation`?null:{done:!0,output:n.message},session:n.nextSession}:t(n.nextSession,n.input)}export{dispatchHookLifecycle,dispatchStreamEventHooks,runHookLifecycleStep};
|
|
1
|
+
import{ContinuationTokenKey,SandboxKey,SessionIdKey,SessionPreparedKey}from"./keys.js";import{createLogger}from"#internal/logging.js";import{toErrorMessage}from"#shared/errors.js";import{normalizeSkillPackage,writeSkillPackageToSandbox}from"#shared/skill-package.js";import{getAdapterKind}from"#channel/adapter.js";import{emitRecoverableFailedTurn,emitTurnPreamble,getHarnessEmissionState,setHarnessEmissionState}from"#harness/emission.js";import{formatAvailableSkillsSection}from"#execution/skills/instructions.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";const log=createLogger(`hooks.lifecycle`);async function dispatchHookLifecycle(e){let{ctx:t,registry:n,emit:i}=e,o=getHarnessEmissionState(e.session.state),s=`turn_${o.sequence}`,c=o.sequence,l=buildHookContext(t),u={session:{sessionId:e.session.sessionId},turn:{sequence:c,turnId:s}},d=[],f=e.session;if(n.session.length>0&&t.get(SessionPreparedKey)!==!0){t.set(SessionPreparedKey,!0);let e=[];for(let r of n.session){let n=await r.handler(u,l);n?.modelContext!==void 0&&n.modelContext.length>0&&d.push(...n.modelContext),e.push(...normalizeLifecycleSkillResults(t,n))}f=await materializeLifecycleSkills(t,f,e)}let p=!1,m=o;try{let e=[];for(let r of n.turn){let n=await r.handler(u,l);n?.modelContext!==void 0&&n.modelContext.length>0&&d.push(...n.modelContext),e.push(...normalizeLifecycleSkillResults(t,n))}f=await materializeLifecycleSkills(t,f,e)}catch(t){let n=toErrorMessage(t);try{p||=(m=await emitTurnPreamble(i,{message:e.input.message},o,e.runtimeIdentity),!0);let t=await emitRecoverableFailedTurn(i,m,{code:`HOOK_TURN_FAILED`,message:n});return{kind:`turn-failed`,message:n,nextSession:setHarnessEmissionState(f,t)}}catch(e){throw log.error(`Event hook threw while emitting the turn.failed cascade for a lifecycle.turn failure — escalating to session.failed.`,{error:toErrorMessage(e)}),e}}let h=e.input.modelContext??[],g=h.length===0?d:[...h,...d];return{kind:`proceed`,input:g.length>0?{...e.input,modelContext:g}:e.input,nextSession:f}}function normalizeLifecycleSkillResults(e,t){if(t?.skills===void 0||t.skills.length===0)return[];let n=new Set(e.require(BundleKey).resolvedAgent.skills.map(e=>e.name)),r=new Map;for(let e of t.skills){let t=normalizeSkillPackage(e);if(n.has(t.name))throw Error(`Hook-contributed skill "${t.name}" conflicts with an authored skill.`);r.set(t.name,t)}return[...r.values()]}async function materializeLifecycleSkills(e,n,r){if(r.length===0)return n;let i=new Map(r.map(e=>[e.name,e])),a=await e.require(SandboxKey).get();if(a===null)throw Error(`Dynamic skills require a sandbox for the current agent.`);for(let e of i.values())await writeSkillPackageToSandbox({sandbox:a,skill:e});let o=formatAvailableSkillsSection([...i.values()]);return o===null?n:{...n,history:[...n.history,{role:`system`,content:o}]}}async function dispatchStreamEventHooks(e){let t=e.registry.streamEventsByType.get(e.event.type)??[],n=e.registry.streamEventsWildcard;if(t.length===0&&n.length===0)return;let r=buildHookContext(e.ctx);for(let n of t)await n.handler(e.event,r);for(let t of n)await t.handler(e.event,r)}function buildHookContext(t){let r=t.require(BundleKey),i=t.get(ChannelKey),a=t.get(ContinuationTokenKey),o=i===void 0?void 0:getAdapterKind(i);return{agent:{name:r.resolvedAgent.config.name??`agent`,nodeId:r.nodeId},channel:{kind:o,continuationToken:a},session:{sessionId:t.get(SessionIdKey)??``},ash:t}}async function runHookLifecycleStep(e,t){let n=await dispatchHookLifecycle(e);return n.kind===`turn-failed`?{next:e.mode===`conversation`?null:{done:!0,output:n.message},session:n.nextSession}:t(n.nextSession,n.input)}export{dispatchHookLifecycle,dispatchStreamEventHooks,runHookLifecycleStep};
|
|
@@ -1,37 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workflow-body orchestrator for the interactive OAuth
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* The workflow body owns hook creation and waiting; model-facing side
|
|
6
|
-
* effects stay inside durable steps.
|
|
2
|
+
* Workflow-body orchestrator for the interactive OAuth flow.
|
|
3
|
+
* Owns hook creation and waiting; side effects stay inside steps.
|
|
7
4
|
*/
|
|
8
|
-
import type {
|
|
5
|
+
import type { DurableSessionSnapshot, DurableSessionState } from "#execution/durable-session-store.js";
|
|
9
6
|
import type { PendingConnectionToolCall } from "#runtime/framework-tools/pending-connection-tool-calls.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* so the next turn step sees the spliced history and
|
|
14
|
-
* cached tokens.
|
|
15
|
-
*/
|
|
16
|
-
export interface AwaitAuthorizationResolveResult {
|
|
7
|
+
export interface AwaitAuthorizationInput {
|
|
8
|
+
readonly parentWritable: WritableStream<Uint8Array>;
|
|
9
|
+
readonly pendingToolCalls: readonly PendingConnectionToolCall[];
|
|
17
10
|
readonly serializedContext: Record<string, unknown>;
|
|
18
|
-
readonly
|
|
11
|
+
readonly sessionState: DurableSessionState;
|
|
12
|
+
readonly sessionWritable: WritableStream<DurableSessionSnapshot>;
|
|
13
|
+
}
|
|
14
|
+
export interface AwaitAuthorizationResult {
|
|
15
|
+
readonly serializedContext: Record<string, unknown>;
|
|
16
|
+
readonly sessionState: DurableSessionState;
|
|
19
17
|
}
|
|
20
18
|
/**
|
|
21
|
-
* Orchestrates one
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* surface via `drainPendingConnectionAuthorizations` on the harness
|
|
25
|
-
* step boundary and rely on out-of-band auth.
|
|
19
|
+
* Orchestrates one authorization cycle for every interactive
|
|
20
|
+
* connection with pending tool calls. `getToken`-only connections
|
|
21
|
+
* are handled elsewhere via `drainPendingConnectionAuthorizations`.
|
|
26
22
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `completeAuthorization` surface a structured error result on the
|
|
30
|
-
* spliced tool call rather than an exception in the workflow body.
|
|
23
|
+
* One connection's failure surfaces as a structured error result on
|
|
24
|
+
* the spliced tool call; it never aborts the workflow body.
|
|
31
25
|
*/
|
|
32
|
-
export declare function awaitAuthorizationAndResolve(input:
|
|
33
|
-
readonly parentWritable: WritableStream<Uint8Array>;
|
|
34
|
-
readonly pendingToolCalls: readonly PendingConnectionToolCall[];
|
|
35
|
-
readonly serializedContext: Record<string, unknown>;
|
|
36
|
-
readonly session: HarnessSession;
|
|
37
|
-
}): Promise<AwaitAuthorizationResolveResult>;
|
|
26
|
+
export declare function awaitAuthorizationAndResolve(input: AwaitAuthorizationInput): Promise<AwaitAuthorizationResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createAshConnectionCallbackRoutePath}from"#protocol/routes.js";import{
|
|
1
|
+
import{createAshConnectionCallbackRoutePath}from"#protocol/routes.js";import{createHook,getWorkflowMetadata}from"#compiled/@workflow/core/index.js";import{completeAuthorizationForConnectionStep,emitConnectionAuthorizationPendingStep,resolvePendingToolCallsStep,startAuthorizationForConnectionStep}from"#execution/connection-auth-steps.js";import{resolveVercelProductionCallbackBaseUrl}from"#execution/workflow-callback-url.js";async function awaitAuthorizationAndResolve(c){let l=uniqueConnectionNames(c.pendingToolCalls);if(l.length===0)return{serializedContext:c.serializedContext,sessionState:c.sessionState};let u=c.sessionState.emissionState,d=resolveVercelProductionCallbackBaseUrl()??getWorkflowMetadata().url,f=l.map(n=>{let r=createHook();return{connectionName:n,hook:r,webhookUrl:new URL(createAshConnectionCallbackRoutePath(n,r.token),d).toString()}}),p=await Promise.all(f.map(async({connectionName:e,hook:t,webhookUrl:n})=>({connectionName:e,hook:t,webhookUrl:n,start:await startAuthorizationForConnectionStep({connectionName:e,emissionState:u,parentWritable:c.parentWritable,serializedContext:c.serializedContext,webhookUrl:n})}))),m=p.flatMap(e=>e.start.ok?[{connectionName:e.connectionName,hook:e.hook,principal:e.start.principal,serializedContext:e.start.serializedContext,state:e.start.state,webhookUrl:e.webhookUrl}]:[]);m.length>0&&await emitConnectionAuthorizationPendingStep({connectionNames:m.map(e=>e.connectionName),emissionState:u,parentWritable:c.parentWritable,serializedContext:c.serializedContext});let h=await Promise.all(m.map(async({connectionName:e,hook:t,principal:n,serializedContext:i,state:a,webhookUrl:o})=>{try{let s=await awaitHookRequest(t);return{complete:await completeAuthorizationForConnectionStep({connectionName:e,emissionState:u,parentWritable:c.parentWritable,principal:n,request:s,serializedContext:i,state:a,webhookUrl:o}),connectionName:e,principal:n}}finally{t.dispose()}})),g={},_={},v={},y=[];for(let e of p)e.start.ok||(v[e.connectionName]={reason:e.start.reason,retryable:!1});for(let e of h)e.complete.ok?(g[e.connectionName]=e.complete.token,_[e.connectionName]=e.principal,y.push(e.connectionName)):v[e.connectionName]={reason:e.complete.reason,retryable:e.complete.retryable};return resolvePendingToolCallsStep({failedConnections:v,parentWritable:c.parentWritable,pendingCalls:c.pendingToolCalls,principals:_,resolvedConnectionNames:y,serializedContext:c.serializedContext,sessionState:c.sessionState,sessionWritable:c.sessionWritable,tokens:g})}function uniqueConnectionNames(e){let t=new Set;for(let n of e){let e=n.kind===`connection-execute`?[n.connectionName]:n.connectionNames;for(let n of e)t.add(n)}return[...t]}async function awaitHookRequest(e){let t=await e[Symbol.asyncIterator]().next();if(t.done===!0||t.value===void 0)throw Error(`Connection callback hook closed before delivering a request.`);return t.value}export{awaitAuthorizationAndResolve};
|
|
@@ -2,32 +2,21 @@
|
|
|
2
2
|
* Durable step boundaries that make up the interactive OAuth
|
|
3
3
|
* await-authorization-and-splice flow.
|
|
4
4
|
*/
|
|
5
|
+
import { type DurableSessionSnapshot, type DurableSessionState } from "#execution/durable-session-store.js";
|
|
5
6
|
import type { HarnessEmissionState } from "#harness/emission.js";
|
|
6
|
-
import type { HarnessSession } from "#harness/types.js";
|
|
7
7
|
import type { JsonValue } from "#public/types/json.js";
|
|
8
8
|
import type { AuthorizationCallbackRequest, ConnectionPrincipal, TokenResult } from "#runtime/connections/types.js";
|
|
9
9
|
import { type PendingConnectionToolCall } from "#runtime/framework-tools/pending-connection-tool-calls.js";
|
|
10
10
|
/**
|
|
11
11
|
* Result of one `startAuthorization` step.
|
|
12
12
|
*
|
|
13
|
-
* On success the
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* step. The `serializedContext` carries any channel-state mutations
|
|
21
|
-
* the `connection.authorization_required` handler made (e.g. the
|
|
22
|
-
* Slack base layer's tracked status-message ts) forward into the
|
|
23
|
-
* matching `completeAuthorization` step so the handler can edit that
|
|
24
|
-
* same surface in place.
|
|
25
|
-
*
|
|
26
|
-
* On failure the runtime journals `{ ok: false, reason }` and the
|
|
27
|
-
* workflow body skips webhook provisioning for this connection. The
|
|
28
|
-
* workflow still emits a corresponding
|
|
29
|
-
* `connection.authorization_completed` event with
|
|
30
|
-
* `outcome: "failed"` so channels clean up their UI.
|
|
13
|
+
* On success carries the resolved `principal`, the IdP `state`, and
|
|
14
|
+
* the post-emit `serializedContext` (so channel-state mutations made
|
|
15
|
+
* by the `connection.authorization_required` handler — eg. tracked
|
|
16
|
+
* status-message ts — survive into the matching
|
|
17
|
+
* `completeAuthorization` step). Failures emit a
|
|
18
|
+
* `connection.authorization_completed` event with `outcome: "failed"`
|
|
19
|
+
* so channels clean up their UI.
|
|
31
20
|
*/
|
|
32
21
|
export type StartAuthorizationStepResult = {
|
|
33
22
|
readonly ok: true;
|
|
@@ -39,13 +28,10 @@ export type StartAuthorizationStepResult = {
|
|
|
39
28
|
readonly reason: string;
|
|
40
29
|
};
|
|
41
30
|
/**
|
|
42
|
-
* Runs one connection's `startAuthorization` callback
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* Failures are captured as {@link StartAuthorizationStepResult.ok} =
|
|
47
|
-
* `false` instead of thrown, so one connection's misconfigured
|
|
48
|
-
* authorization does not abort the entire authorization cycle.
|
|
31
|
+
* Runs one connection's `startAuthorization` callback and emits the
|
|
32
|
+
* matching `connection.authorization_required` event. Failures are
|
|
33
|
+
* captured as `ok: false` so one connection's misconfig does not
|
|
34
|
+
* abort the entire cycle.
|
|
49
35
|
*/
|
|
50
36
|
export declare function startAuthorizationForConnectionStep(input: {
|
|
51
37
|
readonly connectionName: string;
|
|
@@ -54,11 +40,7 @@ export declare function startAuthorizationForConnectionStep(input: {
|
|
|
54
40
|
readonly serializedContext: Record<string, unknown>;
|
|
55
41
|
readonly webhookUrl: string;
|
|
56
42
|
}): Promise<StartAuthorizationStepResult>;
|
|
57
|
-
/**
|
|
58
|
-
* Emits the `connection.authorization_pending` event once per
|
|
59
|
-
* authorization cycle, after every `authorization_required` has been
|
|
60
|
-
* written.
|
|
61
|
-
*/
|
|
43
|
+
/** Emits the cycle's single `connection.authorization_pending` event. */
|
|
62
44
|
export declare function emitConnectionAuthorizationPendingStep(input: {
|
|
63
45
|
readonly connectionNames: readonly string[];
|
|
64
46
|
readonly emissionState: HarnessEmissionState;
|
|
@@ -68,8 +50,10 @@ export declare function emitConnectionAuthorizationPendingStep(input: {
|
|
|
68
50
|
/**
|
|
69
51
|
* Result of one `completeAuthorization` step.
|
|
70
52
|
*
|
|
71
|
-
* `
|
|
72
|
-
*
|
|
53
|
+
* Failures default to `retryable: true` so the model can re-prompt for
|
|
54
|
+
* authorization; only explicit
|
|
55
|
+
* `ConnectionAuthorizationFailedError({ retryable: false })` opts out
|
|
56
|
+
* (canonically: user clicked "Cancel").
|
|
73
57
|
*/
|
|
74
58
|
export type CompleteAuthorizationStepResult = {
|
|
75
59
|
readonly ok: true;
|
|
@@ -77,27 +61,12 @@ export type CompleteAuthorizationStepResult = {
|
|
|
77
61
|
} | {
|
|
78
62
|
readonly ok: false;
|
|
79
63
|
readonly reason: string;
|
|
80
|
-
/**
|
|
81
|
-
* When `true`, downstream pending tool calls for this connection
|
|
82
|
-
* are spliced with a soft `authorization_required` result so the
|
|
83
|
-
* model can prompt the user to retry authorization rather than
|
|
84
|
-
* treating the turn as terminally failed. This is the default
|
|
85
|
-
* for any `completeAuthorization` throw — the runtime only marks
|
|
86
|
-
* a failure as non-retryable when the author explicitly sets
|
|
87
|
-
* `ConnectionAuthorizationFailedError({ retryable: false })`
|
|
88
|
-
* (canonically: user clicked "Cancel", `reason: "access_denied"`).
|
|
89
|
-
*/
|
|
90
64
|
readonly retryable: boolean;
|
|
91
65
|
};
|
|
92
66
|
/**
|
|
93
|
-
* Runs one connection's `completeAuthorization` callback
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* Any error surfaces as a failure result with an appropriate
|
|
98
|
-
* {@link ConnectionAuthorizationOutcome}. The workflow body continues
|
|
99
|
-
* even when one connection fails, so the remainder of the authorization
|
|
100
|
-
* cycle still resolves.
|
|
67
|
+
* Runs one connection's `completeAuthorization` callback and emits
|
|
68
|
+
* the matching `connection.authorization_completed` event. One
|
|
69
|
+
* connection's failure does not abort the cycle.
|
|
101
70
|
*/
|
|
102
71
|
export declare function completeAuthorizationForConnectionStep(input: {
|
|
103
72
|
readonly connectionName: string;
|
|
@@ -110,15 +79,9 @@ export declare function completeAuthorizationForConnectionStep(input: {
|
|
|
110
79
|
readonly webhookUrl: string;
|
|
111
80
|
}): Promise<CompleteAuthorizationStepResult>;
|
|
112
81
|
/**
|
|
113
|
-
* Structured failure
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* `retryable: true` (the default for any `completeAuthorization`
|
|
117
|
-
* throw) means the model should be prompted to retry authorization.
|
|
118
|
-
* `retryable: false` means the failure is terminal for this turn
|
|
119
|
-
* (canonically: user clicked "Cancel" on the IdP consent screen, so
|
|
120
|
-
* the author threw `ConnectionAuthorizationFailedError` with
|
|
121
|
-
* `{ reason: "access_denied", retryable: false }`).
|
|
82
|
+
* Structured failure for one connection's authorization round-trip.
|
|
83
|
+
* `retryable: true` lets the model re-prompt; `retryable: false` is
|
|
84
|
+
* terminal for this turn (canonically: user clicked "Cancel").
|
|
122
85
|
*/
|
|
123
86
|
export interface ConnectionAuthorizationFailure {
|
|
124
87
|
readonly reason: string;
|
|
@@ -126,9 +89,8 @@ export interface ConnectionAuthorizationFailure {
|
|
|
126
89
|
}
|
|
127
90
|
/**
|
|
128
91
|
* Retries every pending tool call whose connection has a freshly
|
|
129
|
-
* minted token, splices the real results over the
|
|
130
|
-
*
|
|
131
|
-
* the new session + serialized context.
|
|
92
|
+
* minted token, splices the real results over the placeholders in
|
|
93
|
+
* history, and clears the resolved cycle's context entries.
|
|
132
94
|
*/
|
|
133
95
|
export declare function resolvePendingToolCallsStep(input: {
|
|
134
96
|
readonly failedConnections: Readonly<Record<string, ConnectionAuthorizationFailure>>;
|
|
@@ -137,9 +99,10 @@ export declare function resolvePendingToolCallsStep(input: {
|
|
|
137
99
|
readonly principals: Readonly<Record<string, ConnectionPrincipal>>;
|
|
138
100
|
readonly resolvedConnectionNames: readonly string[];
|
|
139
101
|
readonly serializedContext: Record<string, unknown>;
|
|
140
|
-
readonly
|
|
102
|
+
readonly sessionState: DurableSessionState;
|
|
103
|
+
readonly sessionWritable: WritableStream<DurableSessionSnapshot>;
|
|
141
104
|
readonly tokens: Readonly<Record<string, TokenResult>>;
|
|
142
105
|
}): Promise<{
|
|
143
106
|
readonly serializedContext: Record<string, unknown>;
|
|
144
|
-
readonly
|
|
107
|
+
readonly sessionState: DurableSessionState;
|
|
145
108
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createConnectionAuthorizationCompletedEvent,createConnectionAuthorizationPendingEvent,createConnectionAuthorizationRequiredEvent,encodeMessageStreamEvent,timestampHandleMessageStreamEvent}from"#protocol/message.js";import{contextStorage}from"#context/container.js";import{callAdapterEventHandler}from"#channel/adapter.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{ConnectionRegistryImpl}from"#runtime/connections/registry.js";import{ConnectionRegistryKey,executeConnectionSearch}from"#runtime/framework-tools/connection-search.js";import{getActiveRuntimeNode}from"#context/node.js";import{PendingConnectionToolCallsKey,isConnectionAuthorizationPlaceholder}from"#runtime/framework-tools/pending-connection-tool-calls.js";import{buildAdapterContext}from"#channel/adapter-context.js";import{deserializeContext,serializeContext}from"#context/serialize.js";import{isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{withConnectionPrincipalOverride}from"#runtime/connections/principal-context.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";import{withDefaultAuthorizationInstructions}from"#execution/authorization-challenge-defaults.js";import{splicePendingToolResults}from"#execution/await-authorization-splice.js";async function startAuthorizationForConnectionStep(t){"use step";let r=await deserializeContext(t.serializedContext),i=findConnection(r,t.connectionName);if(i?.authorization?.startAuthorization===void 0){let n=`Connection "${t.connectionName}" does not define startAuthorization.`;return await emitAuthorizationEvent(r,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`failed`,reason:n,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!1,reason:n}}let a=i.description??t.connectionName,o,s,c;try{o=resolveConnectionPrincipal(t.connectionName,i.authorization,r);let e=await i.authorization.startAuthorization({principal:o,connection:{url:i.url},callbackUrl:t.webhookUrl});s=withDefaultAuthorizationInstructions(e.challenge,t.connectionName),c=e.state}catch(n){let i=n instanceof Error?n.message:String(n);return await emitAuthorizationEvent(r,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`failed`,reason:i,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!1,reason:i}}return await emitAuthorizationEvent(r,createConnectionAuthorizationRequiredEvent({authorization:s,connectionName:t.connectionName,description:a,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId,webhookUrl:t.webhookUrl}),t.parentWritable),{ok:!0,principal:o,serializedContext:serializeContext(r),state:c}}async function emitConnectionAuthorizationPendingStep(e){"use step";e.connectionNames.length!==0&&await emitAuthorizationEvent(await deserializeContext(e.serializedContext),createConnectionAuthorizationPendingEvent({connectionNames:e.connectionNames,sequence:e.emissionState.sequence,stepIndex:e.emissionState.stepIndex,turnId:e.emissionState.turnId}),e.parentWritable)}async function completeAuthorizationForConnectionStep(t){"use step";let n=await deserializeContext(t.serializedContext),r=findConnection(n,t.connectionName);if(r?.authorization?.completeAuthorization===void 0){let r=`Connection "${t.connectionName}" does not define completeAuthorization.`;return await emitAuthorizationEvent(n,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`failed`,reason:r,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!1,reason:r,retryable:!1}}try{let i=await r.authorization.completeAuthorization({principal:t.principal,connection:{url:r.url},request:t.request,state:t.state,callbackUrl:t.webhookUrl});return await emitAuthorizationEvent(n,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`authorized`,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!0,token:i}}catch(r){let i=isConnectionAuthorizationFailedError(r)?r:null,a=i?.retryable??!0,o=i?.reason??(r instanceof Error?r.message:String(r));return await emitAuthorizationEvent(n,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`failed`,reason:o,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!1,reason:o,retryable:a}}}async function resolvePendingToolCallsStep(e){"use step";let t=await deserializeContext(e.serializedContext),n=new ConnectionRegistryImpl(getActiveRuntimeNode(
|
|
1
|
+
import{createConnectionAuthorizationCompletedEvent,createConnectionAuthorizationPendingEvent,createConnectionAuthorizationRequiredEvent,encodeMessageStreamEvent,timestampHandleMessageStreamEvent}from"#protocol/message.js";import{contextStorage}from"#context/container.js";import{callAdapterEventHandler}from"#channel/adapter.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{ConnectionRegistryImpl}from"#runtime/connections/registry.js";import{ConnectionRegistryKey,executeConnectionSearch}from"#runtime/framework-tools/connection-search.js";import{getActiveRuntimeNode}from"#context/node.js";import{PendingConnectionToolCallsKey,isConnectionAuthorizationPlaceholder}from"#runtime/framework-tools/pending-connection-tool-calls.js";import{buildAdapterContext}from"#channel/adapter-context.js";import{deserializeContext,serializeContext}from"#context/serialize.js";import{readDurableSession,writeDurableSession}from"#execution/durable-session-store.js";import{hydrateDurableSession}from"#execution/session.js";import{isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{withConnectionPrincipalOverride}from"#runtime/connections/principal-context.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";import{withDefaultAuthorizationInstructions}from"#execution/authorization-challenge-defaults.js";import{splicePendingToolResults}from"#execution/await-authorization-splice.js";async function startAuthorizationForConnectionStep(t){"use step";let r=await deserializeContext(t.serializedContext),i=findConnection(r,t.connectionName);if(i?.authorization?.startAuthorization===void 0){let n=`Connection "${t.connectionName}" does not define startAuthorization.`;return await emitAuthorizationEvent(r,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`failed`,reason:n,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!1,reason:n}}let a=i.description??t.connectionName,o,s,c;try{o=resolveConnectionPrincipal(t.connectionName,i.authorization,r);let e=await i.authorization.startAuthorization({principal:o,connection:{url:i.url},callbackUrl:t.webhookUrl});s=withDefaultAuthorizationInstructions(e.challenge,t.connectionName),c=e.state}catch(n){let i=n instanceof Error?n.message:String(n);return await emitAuthorizationEvent(r,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`failed`,reason:i,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!1,reason:i}}return await emitAuthorizationEvent(r,createConnectionAuthorizationRequiredEvent({authorization:s,connectionName:t.connectionName,description:a,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId,webhookUrl:t.webhookUrl}),t.parentWritable),{ok:!0,principal:o,serializedContext:serializeContext(r),state:c}}async function emitConnectionAuthorizationPendingStep(e){"use step";e.connectionNames.length!==0&&await emitAuthorizationEvent(await deserializeContext(e.serializedContext),createConnectionAuthorizationPendingEvent({connectionNames:e.connectionNames,sequence:e.emissionState.sequence,stepIndex:e.emissionState.stepIndex,turnId:e.emissionState.turnId}),e.parentWritable)}async function completeAuthorizationForConnectionStep(t){"use step";let n=await deserializeContext(t.serializedContext),r=findConnection(n,t.connectionName);if(r?.authorization?.completeAuthorization===void 0){let r=`Connection "${t.connectionName}" does not define completeAuthorization.`;return await emitAuthorizationEvent(n,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`failed`,reason:r,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!1,reason:r,retryable:!1}}try{let i=await r.authorization.completeAuthorization({principal:t.principal,connection:{url:r.url},request:t.request,state:t.state,callbackUrl:t.webhookUrl});return await emitAuthorizationEvent(n,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`authorized`,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!0,token:i}}catch(r){let i=isConnectionAuthorizationFailedError(r)?r:null,a=i?.retryable??!0,o=i?.reason??(r instanceof Error?r.message:String(r));return await emitAuthorizationEvent(n,createConnectionAuthorizationCompletedEvent({connectionName:t.connectionName,outcome:`failed`,reason:o,sequence:t.emissionState.sequence,stepIndex:t.emissionState.stepIndex,turnId:t.emissionState.turnId}),t.parentWritable),{ok:!1,reason:o,retryable:a}}}async function resolvePendingToolCallsStep(e){"use step";let t=await readDurableSession(e.sessionState),n=await deserializeContext(e.serializedContext),r=n.require(BundleKey),i=hydrateDurableSession({compactionOverrides:{thresholdPercent:r.resolvedAgent.config.compaction?.thresholdPercent},durable:t,turnAgent:r.turnAgent}),o=new ConnectionRegistryImpl(getActiveRuntimeNode(n).agent?.connections??[]);n.setVirtualContext(ConnectionRegistryKey,o);for(let[t,r]of Object.entries(e.tokens)){let i=e.principals[t];if(i===void 0)throw Error(`Internal error: missing resolved principal for connection "${t}".`);writeCachedToken(n,t,principalKey(i),r)}let c={},d=new Set(e.resolvedConnectionNames);await contextStorage.run(n,()=>withConnectionPrincipalOverride(n,e.principals,async()=>{for(let t of e.pendingCalls){let n=t.kind===`connection-execute`?[t.connectionName]:t.connectionNames,r;for(let t of n){let n=e.failedConnections[t];if(n!==void 0){r={connectionName:t,...n};break}}if(r!==void 0&&t.kind===`connection-execute`){c[t.toolCallId]=r.retryable?{error:`authorization_required`,retryable:!0}:{connectionName:r.connectionName,error:`authorization_failed`,message:r.reason,retryable:!1};continue}if(!n.every(e=>d.has(e))&&t.kind===`connection-execute`){c[t.toolCallId]={error:`authorization_required`,retryable:!0};continue}try{if(t.kind===`connection-execute`){let e=await o.getClient(t.connectionName).executeTool(t.toolName,t.args);c[t.toolCallId]=normalizeToolResult(e)}else{let e=await executeConnectionSearch(t.args,{toolCallId:t.toolCallId});isConnectionAuthorizationPlaceholder(e)?c[t.toolCallId]={error:`authorization_required`,retryable:!0}:c[t.toolCallId]=normalizeToolResult(e)}}catch(e){if(isConnectionAuthorizationRequiredError(e)){c[t.toolCallId]={error:`authorization_required`,retryable:!0};continue}let n=e instanceof Error?e.message:String(e);c[t.toolCallId]={error:`tool_execution_failed`,message:n,retryable:!1}}}}));let f=splicePendingToolResults(i,c),p=e.pendingCalls.filter(t=>(t.kind===`connection-execute`?[t.connectionName]:t.connectionNames).some(t=>!d.has(t)&&e.failedConnections[t]===void 0));n.set(PendingConnectionToolCallsKey,p);let m=await writeDurableSession({session:f,writable:e.sessionWritable});return{serializedContext:serializeContext(n),sessionState:m}}function findConnection(e,t){return e.get(BundleKey)===void 0?void 0:(getActiveRuntimeNode(e).agent?.connections??[]).find(e=>e.connectionName===t)}async function emitAuthorizationEvent(e,t,n){let a=e.require(ChannelKey),s=buildAdapterContext(a,e),l=await callAdapterEventHandler(a,t,s);e.set(ChannelKey,{...a,state:{...s.state}});let u=n.getWriter();try{await u.write(encodeMessageStreamEvent(timestampHandleMessageStreamEvent(l)))}finally{u.releaseLock()}}function normalizeToolResult(e){if(e===void 0)return null;try{return JSON.parse(JSON.stringify(e))}catch{return{error:`tool_result_not_serializable`,retryable:!1}}}export{completeAuthorizationForConnectionStep,emitConnectionAuthorizationPendingStep,resolvePendingToolCallsStep,startAuthorizationForConnectionStep};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";
|
|
2
|
+
import { type DurableSessionSnapshot, type DurableSessionState } from "#execution/durable-session-store.js";
|
|
3
|
+
/**
|
|
4
|
+
* Creates the durable session and writes the initial snapshot to the
|
|
5
|
+
* `ash.session` stream before the workflow enters its turn loop.
|
|
6
|
+
* `nodeId` targets a subagent node in the compiled graph; omitted for
|
|
7
|
+
* the root agent.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createSessionStep(input: {
|
|
10
|
+
readonly compiledArtifactsSource: RuntimeCompiledArtifactsSource;
|
|
11
|
+
readonly continuationToken: string;
|
|
12
|
+
readonly nodeId?: string;
|
|
13
|
+
readonly sessionId: string;
|
|
14
|
+
readonly sessionWritable: WritableStream<DurableSessionSnapshot>;
|
|
15
|
+
}): Promise<DurableSessionState>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{writeDurableSession}from"#execution/durable-session-store.js";import{createSession}from"#execution/session.js";import{getCompiledRuntimeAgentBundle}from"#runtime/sessions/compiled-agent-cache.js";async function createSessionStep(e){"use step";let t=await getCompiledRuntimeAgentBundle({compiledArtifactsSource:e.compiledArtifactsSource,nodeId:e.nodeId});return await writeDurableSession({session:createSession({compactionOverrides:{thresholdPercent:t.resolvedAgent.config.compaction?.thresholdPercent},continuationToken:e.continuationToken,sessionId:e.sessionId,turnAgent:t.turnAgent}),writable:e.sessionWritable})}export{createSessionStep};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridges a delegated subagent's terminal outcome back to its parent
|
|
3
|
+
* driver via the subagent-result hook. Pure projection helpers live
|
|
4
|
+
* in `delegated-parent-result.ts` so the workflow step-proxy transform
|
|
5
|
+
* doesn't strip them from this file.
|
|
6
|
+
*/
|
|
7
|
+
import type { RuntimeSubagentResultActionResult } from "#runtime/actions/types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Resumes the parent driver's hook with a delegated subagent result.
|
|
10
|
+
* No-op for root sessions.
|
|
11
|
+
*/
|
|
12
|
+
export declare function notifyDelegatedParentStep(input: {
|
|
13
|
+
readonly result: RuntimeSubagentResultActionResult | undefined;
|
|
14
|
+
readonly serializedContext: Record<string, unknown>;
|
|
15
|
+
}): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{deserializeContext}from"#context/serialize.js";import{SUBAGENT_ADAPTER_KIND}from"#execution/subagent-adapter.js";async function notifyDelegatedParentStep(e){"use step";if(e.result===void 0)return;let t=(await deserializeContext(e.serializedContext)).get(ChannelKey);if(t?.kind!==SUBAGENT_ADAPTER_KIND)return;let n=String(t.state?.parentContinuationToken??``);if(n===``)return;let{resumeHook:r}=await import(`#compiled/@workflow/core/runtime.js`);await r(n,{kind:`runtime-action-result`,results:[e.result]})}export{notifyDelegatedParentStep};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers that project a delegated subagent's terminal output
|
|
3
|
+
* into the runtime-action result shape its parent driver expects.
|
|
4
|
+
* Lives in its own (non-directive) file to escape the workflow
|
|
5
|
+
* step-proxy transform.
|
|
6
|
+
*/
|
|
7
|
+
import type { RuntimeSubagentResultActionResult } from "#runtime/actions/types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Builds the success-shaped {@link RuntimeSubagentResultActionResult}.
|
|
10
|
+
* Returns `undefined` for root sessions (no parent to notify).
|
|
11
|
+
*/
|
|
12
|
+
export declare function createDelegatedSubagentSuccessResult(serializedContext: Record<string, unknown>, output: string): RuntimeSubagentResultActionResult | undefined;
|
|
13
|
+
/** Failure-path mirror of {@link createDelegatedSubagentSuccessResult}. */
|
|
14
|
+
export declare function createDelegatedSubagentErrorResult(serializedContext: Record<string, unknown>, error: unknown): RuntimeSubagentResultActionResult | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{toErrorMessage}from"#shared/errors.js";import{SUBAGENT_ADAPTER_KIND}from"#execution/subagent-adapter.js";function createDelegatedSubagentSuccessResult(e,n){let r=e[`ash.channel`];if(r?.kind===SUBAGENT_ADAPTER_KIND)return{callId:String(r.state?.callId??``),kind:`subagent-result`,output:n,subagentName:String(r.state?.subagentName??``)}}function createDelegatedSubagentErrorResult(t,n){let r=createDelegatedSubagentSuccessResult(t,``);if(r!==void 0)return{...r,isError:!0,output:{code:`SUBAGENT_EXECUTION_FAILED`,message:toErrorMessage(n)}}}export{createDelegatedSubagentErrorResult,createDelegatedSubagentSuccessResult};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts every pending runtime action for the parked parent session.
|
|
3
|
+
*
|
|
4
|
+
* Each child run starts in task mode, emits a parent `subagent.called`
|
|
5
|
+
* control-plane event, and then runs independently on its own child
|
|
6
|
+
* stream. Records each child's continuation token on the parent
|
|
7
|
+
* session and appends the updated snapshot to `ash.session`.
|
|
8
|
+
*/
|
|
9
|
+
import type { RuntimeSubagentResultActionResult } from "#runtime/actions/types.js";
|
|
10
|
+
import { type DurableSessionSnapshot, type DurableSessionState } from "#execution/durable-session-store.js";
|
|
11
|
+
export declare function dispatchRuntimeActionsStep(input: {
|
|
12
|
+
readonly callbackBaseUrl?: string;
|
|
13
|
+
readonly parentWritable: WritableStream<Uint8Array>;
|
|
14
|
+
readonly serializedContext: Record<string, unknown>;
|
|
15
|
+
readonly sessionState: DurableSessionState;
|
|
16
|
+
readonly sessionWritable: WritableStream<DurableSessionSnapshot>;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
readonly results: readonly RuntimeSubagentResultActionResult[];
|
|
19
|
+
readonly sessionState: DurableSessionState;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{toErrorMessage}from"#shared/errors.js";import{AuthKey,CapabilitiesKey,InitiatorAuthKey}from"#context/keys.js";import{createSubagentCalledEvent,encodeMessageStreamEvent,timestampHandleMessageStreamEvent}from"#protocol/message.js";import{callAdapterEventHandler}from"#channel/adapter.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{buildAdapterContext}from"#channel/adapter-context.js";import{deserializeContext}from"#context/serialize.js";import{readDurableSession,writeDurableSession}from"#execution/durable-session-store.js";import{hydrateDurableSession}from"#execution/session.js";import{getPendingRuntimeActionBatch,recordPendingSubagentChildToken}from"#harness/runtime-actions.js";import{resolveRemoteAgentForAction,startRemoteAgentSession}from"#execution/remote-agent-dispatch.js";import{buildSubagentRunInput}from"#execution/subagent-tool.js";import{createWorkflowRuntime,workflowEntryReference}from"#execution/workflow-runtime.js";async function dispatchRuntimeActionsStep(e){"use step";let u=await readDurableSession(e.sessionState),d=getPendingRuntimeActionBatch(u.state);if(d===void 0||d.actions.length===0)return{results:[],sessionState:e.sessionState};let f=await deserializeContext(e.serializedContext),p=f.require(BundleKey),m=hydrateDurableSession({compactionOverrides:{thresholdPercent:p.resolvedAgent.config.compaction?.thresholdPercent},durable:u,turnAgent:p.turnAgent}),h=f.require(ChannelKey),g=f.get(AuthKey)??null,_=f.get(CapabilitiesKey),v=f.get(InitiatorAuthKey)??null,y=e.parentWritable.getWriter(),b=buildAdapterContext(h,f),x=m,S=[];try{for(let t of d.actions){let n,r,s,c;switch(t.kind){case`subagent-call`:{let e=createWorkflowRuntime({compiledArtifactsSource:p.compiledArtifactsSource,nodeId:t.nodeId}),{childContinuationToken:i,runInput:a}=buildSubagentRunInput({action:t,auth:g,batchEvent:d.event,capabilities:_,initiatorAuth:v,session:m}),o=await e.run(a);x=recordPendingSubagentChildToken({callId:t.callId,childContinuationToken:i,session:x}),n=o.sessionId,r=t.name,c=t.subagentName;break}case`remote-agent-call`:{let i;try{i=resolveRemoteAgentForAction({nodeId:t.nodeId,remoteAgentName:t.remoteAgentName,registry:p.subagentRegistry.subagentsByNodeId}),n=await startRemoteAgentSession({action:t,callbackBaseUrl:e.callbackBaseUrl,remote:i,session:m})}catch(e){S.push(createRemoteAgentStartFailureResult({action:t,error:e}));continue}r=t.name,s={url:i.url},c=t.remoteAgentName;break}default:throw Error(`Unsupported runtime action kind "${t.kind}" in workflow runtime.`)}let l=await callAdapterEventHandler(h,createSubagentCalledEvent({callId:t.callId,childSessionId:n,name:r,remote:s,sequence:d.event.sequence,sessionId:m.sessionId,toolName:c,turnId:d.event.turnId,workflowId:workflowEntryReference.workflowId}),b);await y.write(encodeMessageStreamEvent(timestampHandleMessageStreamEvent(l)))}}finally{y.releaseLock()}return{results:S,sessionState:x===m?e.sessionState:await writeDurableSession({session:x,writable:e.sessionWritable})}}function createRemoteAgentStartFailureResult(t){return{callId:t.action.callId,isError:!0,kind:`subagent-result`,output:{code:`REMOTE_AGENT_START_FAILED`,message:toErrorMessage(t.error)},subagentName:t.action.remoteAgentName}}export{dispatchRuntimeActionsStep};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic version-walking migration framework for versioned durable
|
|
3
|
+
* wire shapes. Each migration steps a value one version forward; the
|
|
4
|
+
* runner chains them so a value written at any historic version can
|
|
5
|
+
* be read at the current one.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* One migration step in a version chain. `to` MUST equal `from + 1`
|
|
9
|
+
* and `migrate` MUST stamp the returned value's `version` field with
|
|
10
|
+
* `to`; the runner verifies both.
|
|
11
|
+
*/
|
|
12
|
+
export interface VersionMigration {
|
|
13
|
+
readonly from: number;
|
|
14
|
+
readonly to: number;
|
|
15
|
+
migrate: (prior: unknown) => {
|
|
16
|
+
readonly version: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Walks `value` through `migrations` until its `version` matches
|
|
21
|
+
* `targetVersion`. Throws on missing migrations, malformed steps, or
|
|
22
|
+
* a value newer than the runner supports.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runMigrationChain<TOut extends {
|
|
25
|
+
readonly version: number;
|
|
26
|
+
}>(input: {
|
|
27
|
+
readonly value: unknown;
|
|
28
|
+
readonly migrations: readonly VersionMigration[];
|
|
29
|
+
readonly targetVersion: number;
|
|
30
|
+
readonly label: string;
|
|
31
|
+
}): TOut;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function runMigrationChain(e){if(typeof e.value!=`object`||e.value===null||!(`version`in e.value)||typeof e.value.version!=`number`)throw Error(`${e.label}: value has no numeric "version" field.`);let t=e.value;if(!Number.isInteger(t.version)||t.version<1)throw Error(`${e.label}: version ${t.version} is not a positive integer.`);if(t.version>e.targetVersion)throw Error(`${e.label}: encountered version ${t.version}, which is newer than the supported version ${e.targetVersion}. This usually indicates the wire was written by a newer Ash deployment than the one reading it.`);for(;t.version<e.targetVersion;){let n=e.migrations.find(e=>e.from===t.version);if(!n)throw Error(`${e.label}: no migration registered for version ${t.version} → ${t.version+1}.`);if(n.to!==n.from+1)throw Error(`${e.label}: migration ${n.from} → ${n.to} must step exactly one version at a time.`);let r=n.migrate(t);if(r.version!==n.to)throw Error(`${e.label}: migration ${n.from} → ${n.to} produced a value with version ${r.version}.`);t=r}return t}export{runMigrationChain};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable session snapshot migrations.
|
|
3
|
+
*
|
|
4
|
+
* ## Adding a new snapshot version
|
|
5
|
+
*
|
|
6
|
+
* When a shape change cannot be expressed as a purely additive field:
|
|
7
|
+
*
|
|
8
|
+
* 1. Bump `DURABLE_SESSION_VERSION` in `durable-session-store.ts`.
|
|
9
|
+
* 2. Update {@link DurableSessionSnapshot}, {@link DurableSession},
|
|
10
|
+
* `projectToDurableSession`, and `hydrateDurableSession`.
|
|
11
|
+
* 3. Add `snapshot-v{N}-to-v{N+1}.ts` exporting one
|
|
12
|
+
* {@link VersionMigration} (`from: N`, `to: N + 1`, pure
|
|
13
|
+
* function, stamps the new `version`).
|
|
14
|
+
* 4. Append the migration to {@link snapshotMigrations}.
|
|
15
|
+
* 5. Cover it in `snapshot.test.ts`.
|
|
16
|
+
*/
|
|
17
|
+
import type { DurableSessionSnapshot } from "#execution/durable-session-store.js";
|
|
18
|
+
/**
|
|
19
|
+
* Migrates a {@link DurableSessionSnapshot} up to
|
|
20
|
+
* {@link DURABLE_SESSION_VERSION}. Pure; safe to call inline.
|
|
21
|
+
*/
|
|
22
|
+
export declare function migrateDurableSessionSnapshot(value: unknown): DurableSessionSnapshot;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{runMigrationChain}from"./chain.js";import{DURABLE_SESSION_VERSION}from"#execution/durable-session-store.js";const snapshotMigrations=[];function migrateDurableSessionSnapshot(e){return runMigrationChain({label:`durable session snapshot`,migrations:snapshotMigrations,targetVersion:DURABLE_SESSION_VERSION,value:e})}export{migrateDurableSessionSnapshot};
|