experimental-ash 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -0
  3. package/dist/docs/public/cli-build-and-debugging.md +2 -0
  4. package/dist/docs/public/getting-started.md +1 -0
  5. package/dist/src/cli/run.d.ts +9 -0
  6. package/dist/src/cli/run.js +1 -1
  7. package/dist/src/context/hook-lifecycle.d.ts +23 -67
  8. package/dist/src/context/hook-lifecycle.js +1 -1
  9. package/dist/src/execution/await-authorization-orchestrator.d.ts +18 -29
  10. package/dist/src/execution/await-authorization-orchestrator.js +1 -1
  11. package/dist/src/execution/connection-auth-steps.d.ts +28 -65
  12. package/dist/src/execution/connection-auth-steps.js +1 -1
  13. package/dist/src/execution/create-session-step.d.ts +15 -0
  14. package/dist/src/execution/create-session-step.js +1 -0
  15. package/dist/src/execution/delegated-parent-notification.d.ts +15 -0
  16. package/dist/src/execution/delegated-parent-notification.js +1 -0
  17. package/dist/src/execution/delegated-parent-result.d.ts +14 -0
  18. package/dist/src/execution/delegated-parent-result.js +1 -0
  19. package/dist/src/execution/dispatch-runtime-actions-step.d.ts +20 -0
  20. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -0
  21. package/dist/src/execution/durable-session-migrations/chain.d.ts +31 -0
  22. package/dist/src/execution/durable-session-migrations/chain.js +1 -0
  23. package/dist/src/execution/durable-session-migrations/snapshot.d.ts +22 -0
  24. package/dist/src/execution/durable-session-migrations/snapshot.js +1 -0
  25. package/dist/src/execution/durable-session-store.d.ts +104 -0
  26. package/dist/src/execution/durable-session-store.js +1 -0
  27. package/dist/src/execution/next-driver-action.d.ts +30 -0
  28. package/dist/src/execution/next-driver-action.js +1 -0
  29. package/dist/src/execution/sandbox/prewarm.d.ts +12 -2
  30. package/dist/src/execution/sandbox/prewarm.js +1 -1
  31. package/dist/src/execution/session.d.ts +29 -28
  32. package/dist/src/execution/session.js +1 -1
  33. package/dist/src/execution/subagent-hitl-proxy.d.ts +8 -25
  34. package/dist/src/execution/subagent-hitl-proxy.js +1 -1
  35. package/dist/src/execution/subagent-tool.js +1 -1
  36. package/dist/src/execution/turn-workflow.d.ts +21 -21
  37. package/dist/src/execution/turn-workflow.js +1 -1
  38. package/dist/src/execution/workflow-entry.d.ts +12 -16
  39. package/dist/src/execution/workflow-entry.js +1 -1
  40. package/dist/src/execution/workflow-runtime.js +1 -1
  41. package/dist/src/execution/workflow-steps.d.ts +36 -91
  42. package/dist/src/execution/workflow-steps.js +1 -1
  43. package/dist/src/harness/emission.d.ts +3 -5
  44. package/dist/src/harness/emission.js +1 -1
  45. package/dist/src/harness/input-requests.d.ts +3 -3
  46. package/dist/src/harness/input-requests.js +1 -1
  47. package/dist/src/harness/proxy-input-requests.d.ts +12 -16
  48. package/dist/src/harness/proxy-input-requests.js +1 -1
  49. package/dist/src/harness/runtime-actions.d.ts +17 -24
  50. package/dist/src/harness/runtime-actions.js +1 -1
  51. package/dist/src/harness/tool-loop.js +1 -1
  52. package/dist/src/harness/types.d.ts +3 -1
  53. package/dist/src/internal/application/package.js +1 -1
  54. package/dist/src/internal/nitro/host/start-production-server.d.ts +8 -0
  55. package/dist/src/internal/nitro/host/start-production-server.js +3 -0
  56. package/dist/src/internal/nitro/host/types.d.ts +8 -0
  57. package/dist/src/internal/nitro/host.d.ts +2 -1
  58. package/dist/src/internal/nitro/host.js +1 -1
  59. package/dist/src/internal/nitro/routes/agent-info/load-agent-info-data.js +1 -1
  60. package/dist/src/internal/nitro/routes/info.js +1 -1
  61. package/dist/src/internal/workflow-bundle/builder.d.ts +2 -0
  62. package/dist/src/internal/workflow-bundle/builder.js +1 -1
  63. package/dist/src/runtime/cache-key.js +1 -1
  64. package/dist/src/runtime/framework-channels/index.js +1 -1
  65. package/dist/src/runtime/loaders/bundled-artifacts.d.ts +12 -0
  66. package/dist/src/runtime/loaders/bundled-artifacts.js +1 -1
  67. package/dist/src/runtime/loaders/compile-metadata.js +1 -1
  68. package/dist/src/runtime/loaders/manifest.js +1 -1
  69. package/dist/src/runtime/loaders/module-map.js +1 -1
  70. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e2bb3b9: Move durable `HarnessSession` snapshots out of every Workflow step
8
+ boundary into a per-driver-run `ash.session` stream wrapped in a
9
+ versioned `DurableSessionSnapshot`. Step inputs, step results, workflow
10
+ inputs, and hook payloads carry a small `DurableSessionState` — identity,
11
+ the closed-contract `hasProxyInputRequests` short-circuit, and the
12
+ `emissionState` projection workflow-body orchestrators (eg.
13
+ await-authorization) use to stamp protocol events without taking an
14
+ extra step boundary — and each session-mutating step returns a closed
15
+ `NextDriverAction` union (`done` | `park` | `dispatch-runtime-actions`)
16
+ the driver dispatches on without inspecting session shape. The persisted
17
+ snapshot is narrowed to the durable subset — `agent.modelReference`,
18
+ `agent.tools`, and compaction thresholds are rebuilt every turn from the
19
+ latest bundle rather than baked into every chunk. Together these changes
20
+ eliminate quadratic event-log growth, set up a stable cross-version
21
+ contract so a pinned older driver can dispatch sessions whose latest
22
+ code has added new optional fields, and reserve a `version` discriminator
23
+ (plus a version-walking migration framework) for future shape changes.
24
+ The harness `StepFn` contract is unchanged.
25
+
26
+ ### Patch Changes
27
+
28
+ - e9052aa: Fix `ash dev` returning 401 when run inside `vercel dev`. The framework's
29
+ default auth resolvers for the ash channel and the `/__ash/info`
30
+ endpoint previously gated on `process.env.VERCEL` alone, but `vercel
31
+ dev` inherits `VERCEL=1` into child processes while the REPL strips
32
+ auth on loopback URLs, producing a 401. Both resolvers now also require
33
+ `VERCEL_ENV !== "development"` before defaulting to `vercelOidc()`, so
34
+ local dev under `vercel dev` falls through to `none()` and the REPL
35
+ talks to the dev server.
36
+
37
+ ## 0.29.0
38
+
39
+ ### Minor Changes
40
+
41
+ - 514f981: Add `ash start` for serving built Nitro output and switch smoke test helpers to run against `ash build` plus `ash start` by default.
42
+
3
43
  ## 0.28.1
4
44
 
5
45
  ### Patch Changes
package/README.md CHANGED
@@ -116,6 +116,7 @@ CLI commands:
116
116
 
117
117
  - `ash info` — discovery results and compiled artifacts
118
118
  - `ash build` — compile `.ash/` and build the host output
119
+ - `ash start` — serve the built `.output/` app
119
120
  - `ash dev` — start the local runtime and REPL
120
121
 
121
122
  ## Deploying
@@ -11,6 +11,7 @@ From your app root:
11
11
 
12
12
  - `ash info` - inspect the discovered authored surface and runtime details
13
13
  - `ash build` - compile `.ash/` artifacts and build the host output
14
+ - `ash start` - serve the built `.output/` app
14
15
  - `ash dev` - start the local runtime and open the REPL
15
16
  - `ash eval` - run eval suites against the current app or a remote target
16
17
 
@@ -22,6 +23,7 @@ For most work:
22
23
  2. run `ash info` when you want to confirm discovery or diagnostics
23
24
  3. run `ash dev` while iterating locally
24
25
  4. run `ash build` before shipping
26
+ 5. run `ash start` when you want to smoke-test the built output locally
25
27
 
26
28
  When `ash build` fails due to discovery errors, the CLI now prints the full discovery diagnostics
27
29
  report (severity, message, source path) and the diagnostics artifact path.
@@ -88,6 +88,7 @@ Useful commands:
88
88
 
89
89
  - `ash info`: show the active routes and compiled artifacts
90
90
  - `ash build`: compile the agent into `.ash/` and build the host output
91
+ - `ash start`: serve the built `.output/` app
91
92
  - `ash dev`: start the local runtime and open the REPL
92
93
 
93
94
  ## Send A Message
@@ -6,6 +6,11 @@ interface DevelopmentServerHandle {
6
6
  readonly url: string;
7
7
  close(): Promise<void>;
8
8
  }
9
+ interface ProductionServerHandle {
10
+ readonly url: string;
11
+ close(): Promise<void>;
12
+ wait(): Promise<void>;
13
+ }
9
14
  interface CliRuntimeDependencies {
10
15
  buildHost(appRoot: string): Promise<string>;
11
16
  printApplicationInfo(logger: CliLogger, appRoot: string): Promise<void>;
@@ -18,6 +23,10 @@ interface CliRuntimeDependencies {
18
23
  port?: number;
19
24
  schedules?: boolean;
20
25
  }): Promise<DevelopmentServerHandle>;
26
+ startProductionHost(appRoot: string, options?: {
27
+ host?: string;
28
+ port?: number;
29
+ }): Promise<ProductionServerHandle>;
21
30
  }
22
31
  type CliRuntimeOverrides = Partial<CliRuntimeDependencies>;
23
32
  interface EvalCliOptions {
@@ -1,3 +1,3 @@
1
- import{createCliTheme,renderCliTaggedLine}from"#cli/ui/output.js";import{Command,CommanderError,InvalidArgumentError}from"#compiled/commander/index.js";import{resolveApplicationRoot}from"#internal/application/paths.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{parseDevelopmentServerUrl}from"#cli/dev/url.js";async function loadBuildHost(){return(await import(`#internal/nitro/host.js`)).buildApplication}async function loadPrintApplicationInfo(){return(await import(`#cli/commands/info.js`)).printApplicationInfo}async function loadRunDevelopmentRepl(){return(await import(`#cli/dev/repl.js`)).runDevelopmentRepl}async function loadRunEvalCommand(){return(await import(`#evals/cli/eval.js`)).runEvalCommand}async function loadStartHost(){return(await import(`#internal/nitro/host.js`)).startDevelopmentServer}function shouldPrintCliBootBanner(e){return e.name()===`info`||e.name()===`dev`}async function waitForShutdownSignal(e){await new Promise((t,n)=>{let r=!1,cleanup=()=>{process.off(`SIGINT`,handleSignal),process.off(`SIGTERM`,handleSignal)},handleSignal=()=>{r||(r=!0,cleanup(),e.close().then(t,n))};process.once(`SIGINT`,handleSignal),process.once(`SIGTERM`,handleSignal)})}function parsePortOption(e){if(!/^-?\d+$/.test(e))throw new InvalidArgumentError(`Expected a numeric port, received "${e}".`);let t=Number(e);if(t<0||t>65535)throw new InvalidArgumentError(`Expected a port between 0 and 65535, received "${e}".`);return t}function hasInteractiveTerminal(){return!!(process.stdin.isTTY&&process.stdout.isTTY)}function rewriteDevelopmentUrlShorthand(e){let t=e[1];return e[0]!==`dev`||e.length!==2||t===void 0||t.startsWith(`-`)?[...e]:[`dev`,`--url`,t]}function resolveRemoteDevelopmentServerUrl(e){if(e.url){if(e.host!==void 0)throw new InvalidArgumentError(`The --host option cannot be used with --url.`);if(e.port!==void 0)throw new InvalidArgumentError(`The --port option cannot be used with --url.`);if(e.repl===!1)throw new InvalidArgumentError(`The --no-repl option cannot be used with --url.`);return e.url}}function createCliProgram(r,i){let c=resolveApplicationRoot(),l=resolveInstalledPackageInfo().version,u=new Command,d=createCliTheme();u.name(`ash`).description(`Build and run an Ash application.`).version(l).showHelpAfterError().exitOverride().hook(`preAction`,(e,t)=>{shouldPrintCliBootBanner(t)&&r.log(`Ash (v${l})`)}).configureOutput({writeErr:e=>{r.error(e.trimEnd())},writeOut:e=>{r.log(e.trimEnd())}});let f=u.command(`channels`).description(`Manage user-authored channels in the current project.`);return f.command(`add [kind]`).description(`Scaffold a channel (slack) into the current project.`).option(`-f, --force`,`Overwrite existing channel files`).action(async(e,t)=>{let{runChannelsAddCommand:n}=await import(`#cli/commands/channels.js`);await n(r,c,{kind:e,options:t})}),f.command(`list`).description(`List user-authored channels in the current project.`).option(`--json`,`Output as JSON`).action(async e=>{let{runChannelsListCommand:t}=await import(`#cli/commands/channels.js`);await t(r,c,e)}),u.command(`build`).description(`Build the current Ash application.`).action(async()=>{let{loadDevelopmentEnvironmentFiles:e}=await import(`#cli/dev/environment.js`);e(c);let n=await(i.buildHost??await loadBuildHost())(c);r.log(renderCliTaggedLine(d,{message:`built output at ${n}`,tag:`build`,tone:`success`}))}),u.command(`dev`).description(`Start the Ash development server or connect the REPL to an existing URL.`).option(`--host <host>`,`Host interface to bind`).option(`--no-repl`,`Start the server without the interactive REPL`).option(`--port <port>`,`Port to listen on (defaults to $PORT, then 3000)`,parsePortOption).option(`--schedules`,`Run scheduled tasks during development (off by default)`).option(`-u, --url <url>`,`Connect the REPL to an existing server URL`,parseDevelopmentServerUrl).addHelpText(`after`,`
1
+ import{createCliTheme,renderCliTaggedLine}from"#cli/ui/output.js";import{Command,CommanderError,InvalidArgumentError}from"#compiled/commander/index.js";import{resolveApplicationRoot}from"#internal/application/paths.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{parseDevelopmentServerUrl}from"#cli/dev/url.js";async function loadBuildHost(){return(await import(`#internal/nitro/host.js`)).buildApplication}async function loadPrintApplicationInfo(){return(await import(`#cli/commands/info.js`)).printApplicationInfo}async function loadRunDevelopmentRepl(){return(await import(`#cli/dev/repl.js`)).runDevelopmentRepl}async function loadRunEvalCommand(){return(await import(`#evals/cli/eval.js`)).runEvalCommand}async function loadStartHost(){return(await import(`#internal/nitro/host.js`)).startDevelopmentServer}async function loadStartProductionHost(){return(await import(`#internal/nitro/host.js`)).startProductionServer}function shouldPrintCliBootBanner(e){return e.name()===`info`||e.name()===`dev`}async function waitForShutdownSignal(e){await new Promise((t,n)=>{let r=!1,cleanup=()=>{process.off(`SIGINT`,handleSignal),process.off(`SIGTERM`,handleSignal)},handleSignal=()=>{r||(r=!0,cleanup(),e.close().then(t,n))};process.once(`SIGINT`,handleSignal),process.once(`SIGTERM`,handleSignal)})}async function waitForProductionServer(e){await Promise.race([e.wait(),waitForShutdownSignal({close:()=>e.close()})])}function parsePortOption(e){if(!/^-?\d+$/.test(e))throw new InvalidArgumentError(`Expected a numeric port, received "${e}".`);let t=Number(e);if(t<0||t>65535)throw new InvalidArgumentError(`Expected a port between 0 and 65535, received "${e}".`);return t}function hasInteractiveTerminal(){return!!(process.stdin.isTTY&&process.stdout.isTTY)}function rewriteDevelopmentUrlShorthand(e){let t=e[1];return e[0]!==`dev`||e.length!==2||t===void 0||t.startsWith(`-`)?[...e]:[`dev`,`--url`,t]}function resolveRemoteDevelopmentServerUrl(e){if(e.url){if(e.host!==void 0)throw new InvalidArgumentError(`The --host option cannot be used with --url.`);if(e.port!==void 0)throw new InvalidArgumentError(`The --port option cannot be used with --url.`);if(e.repl===!1)throw new InvalidArgumentError(`The --no-repl option cannot be used with --url.`);return e.url}}function createCliProgram(r,i){let c=resolveApplicationRoot(),l=resolveInstalledPackageInfo().version,u=new Command,d=createCliTheme();u.name(`ash`).description(`Build and run an Ash application.`).version(l).showHelpAfterError().exitOverride().hook(`preAction`,(e,t)=>{shouldPrintCliBootBanner(t)&&r.log(`Ash (v${l})`)}).configureOutput({writeErr:e=>{r.error(e.trimEnd())},writeOut:e=>{r.log(e.trimEnd())}});let f=u.command(`channels`).description(`Manage user-authored channels in the current project.`);return f.command(`add [kind]`).description(`Scaffold a channel (slack) into the current project.`).option(`-f, --force`,`Overwrite existing channel files`).action(async(e,t)=>{let{runChannelsAddCommand:n}=await import(`#cli/commands/channels.js`);await n(r,c,{kind:e,options:t})}),f.command(`list`).description(`List user-authored channels in the current project.`).option(`--json`,`Output as JSON`).action(async e=>{let{runChannelsListCommand:t}=await import(`#cli/commands/channels.js`);await t(r,c,e)}),u.command(`build`).description(`Build the current Ash application.`).action(async()=>{let{loadDevelopmentEnvironmentFiles:e}=await import(`#cli/dev/environment.js`);e(c);let n=await(i.buildHost??await loadBuildHost())(c);r.log(renderCliTaggedLine(d,{message:`built output at ${n}`,tag:`build`,tone:`success`}))}),u.command(`start`).description(`Start a built Ash application.`).option(`--host <host>`,`Host interface to bind`).option(`--port <port>`,`Port to listen on (defaults to $PORT, then 3000)`,parsePortOption).action(async e=>{let{loadDevelopmentEnvironmentFiles:n}=await import(`#cli/dev/environment.js`);n(c);let a=await(i.startProductionHost??await loadStartProductionHost())(c,{host:e.host,port:e.port});r.log(renderCliTaggedLine(d,{message:`server listening at ${a.url}`,tag:`start`,tone:`success`})),await waitForProductionServer(a)}),u.command(`dev`).description(`Start the Ash development server or connect the REPL to an existing URL.`).option(`--host <host>`,`Host interface to bind`).option(`--no-repl`,`Start the server without the interactive REPL`).option(`--port <port>`,`Port to listen on (defaults to $PORT, then 3000)`,parsePortOption).option(`--schedules`,`Run scheduled tasks during development (off by default)`).option(`-u, --url <url>`,`Connect the REPL to an existing server URL`,parseDevelopmentServerUrl).addHelpText(`after`,`
2
2
  You can also pass a bare URL as the only argument, for example: ash dev https://example.com
3
3
  `).action(async e=>{let n=resolveRemoteDevelopmentServerUrl(e),{loadDevelopmentEnvironmentFiles:a}=await import(`#cli/dev/environment.js`);if(a(c),n){if(r.log(renderCliTaggedLine(d,{message:`REPL connecting to ${n}`,tag:`dev`,tone:`info`})),!hasInteractiveTerminal()){r.log(renderCliTaggedLine(d,{message:`Interactive REPL disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`}));return}r.log(``),await(i.runDevelopmentRepl??await loadRunDevelopmentRepl())({serverUrl:n});return}let o=await(i.startHost??await loadStartHost())(c,{host:e.host,port:e.port,schedules:e.schedules===!0}),s=!1,closeServer=async()=>{s||(s=!0,await o.close())};try{if(r.log(renderCliTaggedLine(d,{message:`server listening at ${o.url}`,tag:`dev`,tone:`success`})),e.repl===!1)return await waitForShutdownSignal({close:closeServer});if(!hasInteractiveTerminal())return r.log(renderCliTaggedLine(d,{message:`Interactive REPL disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`})),await waitForShutdownSignal({close:closeServer});r.log(``),await(i.runDevelopmentRepl??await loadRunDevelopmentRepl())({serverUrl:o.url})}finally{await closeServer()}}),u.command(`info`).description(`Print resolved application information.`).action(async()=>{await(i.printApplicationInfo??await loadPrintApplicationInfo())(r,c)}),u.command(`eval`).description(`Run eval suites against an Ash agent.`).option(`--suite <id...>`,`Suite IDs to run (repeatable)`).option(`--all`,`Run all discovered suites`).option(`--url <url>`,`Remote agent URL (skip local host startup)`).option(`--timeout <ms>`,`Per-case timeout in milliseconds`).option(`--max-concurrency <n>`,`Max concurrent case executions per suite`).option(`--json`,`Output results as JSON`).option(`--skip-report`,`Skip suite-defined reporters (e.g. Braintrust)`).action(async e=>{await(i.runEvalCommand??await loadRunEvalCommand())(e,r)}),u}async function runCli(e=process.argv.slice(2),t=console,n={}){let i=createCliProgram(t,n),a=e.length===0?[`info`]:rewriteDevelopmentUrlShorthand(e);try{await i.parseAsync(a,{from:`user`})}catch(e){if(e instanceof CommanderError){if(e.exitCode===0)return;throw Error(e.message)}throw e}}export{runCli};
@@ -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 dispatching the per-turn hook lifecycle.
8
- *
9
- * `proceed` carries the input augmented with any `modelContext`
10
- * concatenated from `lifecycle.session` (first turn only) and
11
- * `lifecycle.turn` returns. The next session may also include durable
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 and
43
- * returns the outcome.
44
- *
45
- * Stages:
46
- *
47
- * 1. `lifecycle.session` runs once per durable session, gated by the
48
- * framework-owned {@link SessionPreparedKey} flag. The dispatcher
49
- * sets the flag **before** running the chain so a thrown hook does
50
- * not retry on the next turn `lifecycle.session` errors
51
- * propagate through this function and are interpreted by the
52
- * runtime as a terminal session failure (`session.failed`).
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 on the
77
- * registry.
78
- *
79
- * Errors propagate out of this function. The harness's emit composer
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
- * Runtime adapter on top of {@link dispatchHookLifecycle}: runs the
95
- * per-turn hook lifecycle, lowers a `turn-failed` outcome into a
96
- * parking {@link StepResult}, and otherwise hands off to `body` with
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
- * await-authorization-and-splice flow.
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 { HarnessSession } from "#harness/types.js";
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
- * Return value of {@link awaitAuthorizationAndResolve}. The workflow
12
- * body replaces its current session + serialized context with these
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 session: HarnessSession;
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 await-authorization-and-splice cycle for all
22
- * interactive-authorization connections that have pending tool calls.
23
- * Connections that only implement `getToken` are ignored here — they
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
- * Error behaviour: one connection's failure does not block the
28
- * others. Connections that fail `startAuthorization` or
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{getHarnessEmissionState}from"#harness/emission.js";import{createHook,getWorkflowMetadata}from"#compiled/@workflow/core/index.js";import{completeAuthorizationForConnectionStep,emitConnectionAuthorizationPendingStep,resolvePendingToolCallsStep,startAuthorizationForConnectionStep}from"#execution/connection-auth-steps.js";async function awaitAuthorizationAndResolve(s){let c=getHarnessEmissionState(s.session),l=uniqueConnectionNames(s.pendingToolCalls);if(l.length===0)return{serializedContext:s.serializedContext,session:s.session};let u=trimTrailingSlash(getWorkflowMetadata().url),d=l.map(t=>{let r=createHook();return{connectionName:t,hook:r,webhookUrl:`${u}${createAshConnectionCallbackRoutePath(t,r.token)}`}}),f=await Promise.all(d.map(async({connectionName:e,hook:t,webhookUrl:n})=>({connectionName:e,hook:t,webhookUrl:n,start:await startAuthorizationForConnectionStep({connectionName:e,emissionState:c,parentWritable:s.parentWritable,serializedContext:s.serializedContext,webhookUrl:n})}))),p=f.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}]:[]);p.length>0&&await emitConnectionAuthorizationPendingStep({connectionNames:p.map(e=>e.connectionName),emissionState:c,parentWritable:s.parentWritable,serializedContext:s.serializedContext});let m=await Promise.all(p.map(async({connectionName:e,hook:t,principal:n,serializedContext:r,state:i,webhookUrl:a})=>{try{let o=await awaitHookRequest(t);return{complete:await completeAuthorizationForConnectionStep({connectionName:e,emissionState:c,parentWritable:s.parentWritable,principal:n,request:o,serializedContext:r,state:i,webhookUrl:a}),connectionName:e,principal:n}}finally{t.dispose()}})),h={},g={},_={},v=[];for(let e of f)e.start.ok||(_[e.connectionName]={reason:e.start.reason,retryable:!1});for(let e of m)e.complete.ok?(h[e.connectionName]=e.complete.token,g[e.connectionName]=e.principal,v.push(e.connectionName)):_[e.connectionName]={reason:e.complete.reason,retryable:e.complete.retryable};return resolvePendingToolCallsStep({failedConnections:_,parentWritable:s.parentWritable,pendingCalls:s.pendingToolCalls,principals:g,resolvedConnectionNames:v,serializedContext:s.serializedContext,session:s.session,tokens:h})}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}function trimTrailingSlash(e){return e.endsWith(`/`)?e.slice(0,-1):e}export{awaitAuthorizationAndResolve};
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";async function awaitAuthorizationAndResolve(s){let c=uniqueConnectionNames(s.pendingToolCalls);if(c.length===0)return{serializedContext:s.serializedContext,sessionState:s.sessionState};let l=s.sessionState.emissionState,u=trimTrailingSlash(getWorkflowMetadata().url),d=c.map(n=>{let r=createHook();return{connectionName:n,hook:r,webhookUrl:`${u}${createAshConnectionCallbackRoutePath(n,r.token)}`}}),f=await Promise.all(d.map(async({connectionName:e,hook:t,webhookUrl:n})=>({connectionName:e,hook:t,webhookUrl:n,start:await startAuthorizationForConnectionStep({connectionName:e,emissionState:l,parentWritable:s.parentWritable,serializedContext:s.serializedContext,webhookUrl:n})}))),p=f.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}]:[]);p.length>0&&await emitConnectionAuthorizationPendingStep({connectionNames:p.map(e=>e.connectionName),emissionState:l,parentWritable:s.parentWritable,serializedContext:s.serializedContext});let m=await Promise.all(p.map(async({connectionName:e,hook:t,principal:n,serializedContext:i,state:a,webhookUrl:o})=>{try{let c=await awaitHookRequest(t);return{complete:await completeAuthorizationForConnectionStep({connectionName:e,emissionState:l,parentWritable:s.parentWritable,principal:n,request:c,serializedContext:i,state:a,webhookUrl:o}),connectionName:e,principal:n}}finally{t.dispose()}})),h={},g={},_={},v=[];for(let e of f)e.start.ok||(_[e.connectionName]={reason:e.start.reason,retryable:!1});for(let e of m)e.complete.ok?(h[e.connectionName]=e.complete.token,g[e.connectionName]=e.principal,v.push(e.connectionName)):_[e.connectionName]={reason:e.complete.reason,retryable:e.complete.retryable};return resolvePendingToolCallsStep({failedConnections:_,parentWritable:s.parentWritable,pendingCalls:s.pendingToolCalls,principals:g,resolvedConnectionNames:v,serializedContext:s.serializedContext,sessionState:s.sessionState,sessionWritable:s.sessionWritable,tokens:h})}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}function trimTrailingSlash(e){return e.endsWith(`/`)?e.slice(0,-1):e}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 runtime journals
14
- * `{ ok: true, principal, state, serializedContext }`. The
15
- * `principal` is the framework-resolved {@link ConnectionPrincipal}
16
- * captured at `startAuthorization` time; the orchestrator carries it
17
- * forward so `completeAuthorization` and the post-resume retry observe
18
- * the same identity even when the webhook-hit session has different
19
- * auth. The `state` travels to the matching `completeAuthorization`
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 inside a durable
43
- * step and emits the matching `connection.authorization_required`
44
- * event.
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
- * `ok: true` carries the freshly minted {@link TokenResult}; the
72
- * workflow caches it on the context before retrying pending tool calls.
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 inside a
94
- * durable step and emits the matching
95
- * `connection.authorization_completed` event.
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 information for a single connection whose
114
- * authorization round-trip did not produce a token.
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 placeholder outputs
130
- * in history, clears the resolved cycle's context entries, and returns
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 session: HarnessSession;
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 session: HarnessSession;
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(t).agent?.connections??[]);t.setVirtualContext(ConnectionRegistryKey,n);for(let[n,r]of Object.entries(e.tokens)){let i=e.principals[n];if(i===void 0)throw Error(`Internal error: missing resolved principal for connection "${n}".`);writeCachedToken(t,n,principalKey(i),r)}let r={},i=new Set(e.resolvedConnectionNames);await contextStorage.run(t,()=>withConnectionPrincipalOverride(t,e.principals,async()=>{for(let t of e.pendingCalls){let a=t.kind===`connection-execute`?[t.connectionName]:t.connectionNames,o;for(let t of a){let n=e.failedConnections[t];if(n!==void 0){o={connectionName:t,...n};break}}if(o!==void 0&&t.kind===`connection-execute`){r[t.toolCallId]=o.retryable?{error:`authorization_required`,retryable:!0}:{connectionName:o.connectionName,error:`authorization_failed`,message:o.reason,retryable:!1};continue}if(!a.every(e=>i.has(e))&&t.kind===`connection-execute`){r[t.toolCallId]={error:`authorization_required`,retryable:!0};continue}try{if(t.kind===`connection-execute`){let e=await n.getClient(t.connectionName).executeTool(t.toolName,t.args);r[t.toolCallId]=normalizeToolResult(e)}else{let e=await executeConnectionSearch(t.args,{toolCallId:t.toolCallId});isConnectionAuthorizationPlaceholder(e)?r[t.toolCallId]={error:`authorization_required`,retryable:!0}:r[t.toolCallId]=normalizeToolResult(e)}}catch(e){if(isConnectionAuthorizationRequiredError(e)){r[t.toolCallId]={error:`authorization_required`,retryable:!0};continue}let n=e instanceof Error?e.message:String(e);r[t.toolCallId]={error:`tool_execution_failed`,message:n,retryable:!1}}}}));let o=splicePendingToolResults(e.session,r),s=e.pendingCalls.filter(t=>(t.kind===`connection-execute`?[t.connectionName]:t.connectionNames).some(t=>!i.has(t)&&e.failedConnections[t]===void 0));return t.set(PendingConnectionToolCallsKey,s),{serializedContext:serializeContext(t),session:o}}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};
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};