experimental-ash 0.47.0 → 0.48.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 (47) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/docs/public/advanced/hooks.mdx +7 -7
  3. package/dist/docs/public/advanced/typescript-api.md +1 -1
  4. package/dist/docs/public/channels/index.md +2 -2
  5. package/dist/docs/public/channels/slack.mdx +12 -17
  6. package/dist/docs/public/frontend/use-ash-agent.md +13 -17
  7. package/dist/src/channel/adapter.js +1 -1
  8. package/dist/src/channel/routes.d.ts +5 -7
  9. package/dist/src/channel/send.js +1 -1
  10. package/dist/src/channel/types.d.ts +3 -3
  11. package/dist/src/context/build-dynamic-tools.d.ts +13 -0
  12. package/dist/src/context/build-dynamic-tools.js +1 -0
  13. package/dist/src/context/dynamic-instruction-lifecycle.d.ts +13 -13
  14. package/dist/src/context/dynamic-instruction-lifecycle.js +1 -1
  15. package/dist/src/context/dynamic-resolve-context.d.ts +12 -0
  16. package/dist/src/context/dynamic-resolve-context.js +1 -0
  17. package/dist/src/context/dynamic-skill-lifecycle.js +1 -1
  18. package/dist/src/context/dynamic-tool-lifecycle.d.ts +4 -10
  19. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  20. package/dist/src/context/hook-lifecycle.d.ts +2 -2
  21. package/dist/src/context/hook-lifecycle.js +1 -1
  22. package/dist/src/context/keys.d.ts +30 -23
  23. package/dist/src/context/keys.js +1 -1
  24. package/dist/src/execution/workflow-entry.js +1 -1
  25. package/dist/src/harness/code-mode.js +1 -1
  26. package/dist/src/harness/compaction.js +1 -1
  27. package/dist/src/harness/messages.js +1 -1
  28. package/dist/src/harness/prompt-cache.d.ts +11 -1
  29. package/dist/src/harness/prompt-cache.js +1 -1
  30. package/dist/src/harness/tool-loop.js +1 -1
  31. package/dist/src/harness/types.d.ts +4 -5
  32. package/dist/src/internal/application/package.js +1 -1
  33. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  34. package/dist/src/public/channels/ash.js +2 -2
  35. package/dist/src/public/channels/discord/discordChannel.d.ts +1 -2
  36. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  37. package/dist/src/public/channels/slack/slackChannel.d.ts +3 -7
  38. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  39. package/dist/src/public/channels/teams/teamsChannel.d.ts +1 -2
  40. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  41. package/dist/src/public/channels/telegram/telegramChannel.d.ts +1 -2
  42. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  43. package/dist/src/public/definitions/instructions.d.ts +7 -12
  44. package/dist/src/public/definitions/skill.js +1 -1
  45. package/dist/src/shared/dynamic-tool-definition.d.ts +20 -0
  46. package/dist/src/shared/dynamic-tool-definition.js +1 -1
  47. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.48.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 28259a5: Align dynamic tools, instructions, and skills with consistent additive semantics and strict role boundaries.
8
+
9
+ **Breaking changes:**
10
+
11
+ - `InstructionsDefinition.modelContext` removed — instructions produce system messages only via `markdown`
12
+ - `DeliverPayload.modelContext` replaced with `context: string[]` — channel context is now user messages persisted in session history
13
+ - `StepInput.modelContext` replaced with `context: string[]`
14
+ - `SendPayload.modelContext` replaced with `context: string[]`
15
+ - Dynamic instructions restricted to `session.started` and `turn.started` events (no `step.started`)
16
+ - Dynamic skills restricted to `session.started` and `turn.started` events (no `step.started`)
17
+ - Compaction output changed from `role: "system"` to a user/assistant turn in conversation history
18
+
19
+ **New features:**
20
+
21
+ - `defineInstructions` contributions persist across workflow step boundaries via scoped durable keys
22
+ - `defineSkill` stamps `SKILL_BRAND` for consistent entry detection (replaces duck-typing)
23
+ - System cache breakpoint added (4th Anthropic cache slot) to preserve system prefix when tools change
24
+ - Null-returning tool resolvers correctly clear durable metadata
25
+ - Skill announcements use virtual context with durable backing (rebuild from manifest on step boundaries)
26
+ - Shared `buildResolveContext` extracted for all three dynamic lifecycle dispatchers
27
+
3
28
  ## 0.47.0
4
29
 
5
30
  ### Minor Changes
@@ -14,15 +14,15 @@ Use a hook when you want to run code **around** a turn (before the model
14
14
  runs, around stream events) without writing a tool, a context provider,
15
15
  or a channel adapter handler.
16
16
 
17
- <CopyPrompt text="Add an Ash hook to the user's project. Ash hooks live under agent/hooks, use defineHook from experimental-ash/hooks, and can subscribe to lifecycle.session, lifecycle.turn, or stream events. Use lifecycle hooks when the model needs ephemeral modelContext before the next model call; use events handlers for observe-only side effects after events are durably recorded. Inspect existing hooks and agent/lib, choose the smallest hook file and path-derived slug, use defineState only when durable shared state is needed, wrap best-effort side effects so they do not fail sessions accidentally, verify the relevant lifecycle or stream event behavior, and do not commit unless the user asks.">
17
+ <CopyPrompt text="Add an Ash hook to the user's project. Ash hooks live under agent/hooks, use defineHook from experimental-ash/hooks, and can subscribe to lifecycle.session, lifecycle.turn, or stream events. Use lifecycle hooks when the model needs side effects before the next model call (e.g. setting state); use events handlers for observe-only side effects after events are durably recorded. Inspect existing hooks and agent/lib, choose the smallest hook file and path-derived slug, use defineState only when durable shared state is needed, wrap best-effort side effects so they do not fail sessions accidentally, verify the relevant lifecycle or stream event behavior, and do not commit unless the user asks.">
18
18
  Add an Ash hook to the user's project. Ash hooks live under agent/hooks, use defineHook from
19
19
  experimental-ash/hooks, and can subscribe to lifecycle.session, lifecycle.turn, or stream events.
20
- Use lifecycle hooks when the model needs ephemeral modelContext before the next model call; use
21
- events handlers for observe-only side effects after events are durably recorded. Inspect existing
22
- hooks and agent/lib, choose the smallest hook file and path-derived slug, use defineState only
23
- when durable shared state is needed, wrap best-effort side effects so they do not fail sessions
24
- accidentally, verify the relevant lifecycle or stream event behavior, and do not commit unless the
25
- user asks.
20
+ Use lifecycle hooks when the model needs side effects before the next model call (e.g. setting
21
+ state); use events handlers for observe-only side effects after events are durably recorded.
22
+ Inspect existing hooks and agent/lib, choose the smallest hook file and path-derived slug, use
23
+ defineState only when durable shared state is needed, wrap best-effort side effects so they do not
24
+ fail sessions accidentally, verify the relevant lifecycle or stream event behavior, and do not
25
+ commit unless the user asks.
26
26
  </CopyPrompt>
27
27
 
28
28
  ## The Main API
@@ -112,7 +112,7 @@ Channel and Slack types exported from `experimental-ash/channels/slack`:
112
112
  `since: (message) => boolean` for a custom boundary.
113
113
  - `SlackInteractionAction` - action type for `onInteraction`
114
114
  - `SlackMentionResult` / `SlackInboundResult` - return type of `onAppMention` / `onDirectMessage`
115
- (`{ auth, modelContext? } | null`)
115
+ (`{ auth, context? } | null`)
116
116
  - `defaultSlackAuth(message, ctx)` - default Slack actor-to-session-auth projection
117
117
  - `Card`, `Button`, `Actions`, `Section`, `Modal`, `Table`, etc. - card builders re-exported for
118
118
  rendering Slack messages
@@ -412,8 +412,8 @@ await args.receive(slack, {
412
412
  ```
413
413
 
414
414
  For inbound mentions in an existing Slack thread, `onAppMention` and
415
- `onDirectMessage` may return `modelContext` to inject fetched thread
416
- history into the next model call without persisting it. See
415
+ `onDirectMessage` may return `context` to inject thread history as
416
+ user messages in session history. See
417
417
  [Slack thread context](/docs/channels/slack#thread-context).
418
418
 
419
419
  `threadTs` and `initialMessage` are mutually exclusive: pass `threadTs` to join an existing
@@ -188,8 +188,8 @@ export default slackChannel({
188
188
  - **`onAppMention(ctx, message)`** -- decides whether to dispatch a turn for an inbound
189
189
  `app_mention`, with what `auth` context, and runs any pre-dispatch side effects (typing
190
190
  indicators, logging, feature-flag lookups). Return `{ auth }` to dispatch or `null` to silently
191
- drop the mention. Return `{ auth, modelContext }` to add ephemeral messages to the next model
192
- call without writing them to durable session history. May be sync or async; the framework awaits
191
+ drop the mention. Return `{ auth, context }` to append context strings as user messages in
192
+ session history before the delivery message. May be sync or async; the framework awaits
193
193
  the result before dispatching. Thrown errors are caught, logged, and drop the mention -- wrap
194
194
  best-effort side effects in `try/catch` if you want them to be non-fatal. The default
195
195
  `onAppMention` derives a workspace-scoped auth from the Slack actor and posts a `"Thinking…"`
@@ -212,19 +212,18 @@ run inside the workflow context, not on the inbound webhook side.
212
212
 
213
213
  When the bot is mentioned in an existing Slack thread, the triggering mention is delivered to the
214
214
  agent by default, but prior thread replies are not injected automatically. Fetch thread history in
215
- `onAppMention` or `onDirectMessage` and return `modelContext` when the agent should see that
216
- background on the next model call.
215
+ `onAppMention` or `onDirectMessage` and return `context` when the agent should see that
216
+ background.
217
217
 
218
218
  Use `since: "last-agent-reply"` for repeated tags in the same thread. It returns only messages
219
219
  after the agent's last Slack reply and before the current mention, so the injected context stays
220
- small and can be modeled as a `role: "user"` message without changing the system prompt.
220
+ small.
221
221
 
222
222
  ```ts
223
223
  import {
224
224
  defaultSlackAuth,
225
225
  loadThreadContextMessages,
226
226
  slackChannel,
227
- type ModelMessage,
228
227
  } from "experimental-ash/channels/slack";
229
228
 
230
229
  export default slackChannel({
@@ -242,23 +241,19 @@ export default slackChannel({
242
241
  .map((entry) => `${entry.isMe ? "you" : (entry.user ?? "user")}: ${entry.markdown}`)
243
242
  .join("\n");
244
243
 
245
- const modelContext: ModelMessage[] = [
246
- {
247
- role: "user",
248
- content:
249
- "Recent Slack thread messages since your last reply, oldest first. " +
250
- "Use them as background context for the current mention.\n\n" +
251
- transcript,
252
- },
244
+ const context = [
245
+ "Recent Slack thread messages since your last reply, oldest first. " +
246
+ "Use them as background context for the current mention.\n\n" +
247
+ transcript,
253
248
  ];
254
249
 
255
- return { auth, modelContext };
250
+ return { auth, context };
256
251
  },
257
252
  });
258
253
  ```
259
254
 
260
- `modelContext` is one-shot context: it is used for the model call that dispatches this inbound
261
- message and is never persisted to `session.history`.
255
+ `context` strings are appended as user messages to `session.history` before the delivery message.
256
+ They persist across the session and are visible to the model on all subsequent turns.
262
257
 
263
258
  ### Direct Messages
264
259
 
@@ -193,26 +193,22 @@ const agent = useAshAgent({
193
193
  });
194
194
  ```
195
195
 
196
- `clientContext` is rendered as ephemeral model context for the next turn only.
197
- It is never persisted to durable message history.
196
+ `clientContext` is appended as user messages to session history before
197
+ the delivery message. It persists across the session and is visible
198
+ to the model on all subsequent turns (until compaction summarizes it).
198
199
 
199
- Use authored lifecycle hooks for server-side enrichment:
200
+ Use dynamic instructions for server-side system prompt enrichment:
200
201
 
201
202
  ```ts
202
- // agent/hooks/profile.ts
203
- import { defineHook } from "experimental-ash/hooks";
204
-
205
- export default defineHook({
206
- lifecycle: {
207
- async session(_input, ctx) {
208
- return {
209
- modelContext: [
210
- {
211
- role: "system",
212
- content: `Session ${ctx.session.sessionId} has server-side profile context.`,
213
- },
214
- ],
215
- };
203
+ // agent/instructions/profile.ts
204
+ import { defineDynamic, defineInstructions } from "experimental-ash/instructions";
205
+
206
+ export default defineDynamic({
207
+ events: {
208
+ async "session.started"(_event, ctx) {
209
+ return defineInstructions({
210
+ markdown: `Session ${ctx.session.id} has server-side profile context.`,
211
+ });
216
212
  },
217
213
  },
218
214
  });
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";const log=createLogger(`channel.adapter`);function defaultDeliverResult(e){let{inputResponses:t,message:n,modelContext:r,outputSchema:i}=e;if(!(n===void 0&&!t?.length&&!r?.length&&i===void 0))return{inputResponses:t,message:n,modelContext:r,outputSchema:i}}function getAdapterKind(e){return e.kind}async function callAdapterEventHandler(e,n,r){let i=e[n.type];if(i===void 0)return n;try{await i(`data`in n?n.data:void 0,r)}catch(r){log.error(`adapter event handler threw — event swallowed`,{adapterKind:getAdapterKind(e),eventType:n.type,error:r})}return n}export{callAdapterEventHandler,defaultDeliverResult,getAdapterKind};
1
+ import{createLogger}from"#internal/logging.js";const log=createLogger(`channel.adapter`);function defaultDeliverResult(e){if(e.message!==void 0)return{inputResponses:e.inputResponses,message:e.message,context:e.context,outputSchema:e.outputSchema};if(e.inputResponses!==void 0&&e.inputResponses.length>0)return{inputResponses:e.inputResponses,context:e.context,outputSchema:e.outputSchema};if(e.context!==void 0&&e.context.length>0)return{context:e.context,outputSchema:e.outputSchema};if(e.outputSchema!==void 0)return{outputSchema:e.outputSchema}}function getAdapterKind(e){return e.kind}async function callAdapterEventHandler(e,t,n){let r=e[t.type];if(r===void 0)return t;try{await r(`data`in t?t.data:void 0,n)}catch(n){log.error(`adapter event handler threw — event swallowed`,{adapterKind:getAdapterKind(e),eventType:t.type,error:n})}return t}export{callAdapterEventHandler,defaultDeliverResult,getAdapterKind};
@@ -1,4 +1,4 @@
1
- import type { ModelMessage, UserContent } from "ai";
1
+ import type { UserContent } from "ai";
2
2
  import type { CrossChannelReceiveFn } from "#channel/cross-channel-receive.js";
3
3
  import type { SessionAuthContext, SessionCallback } from "#channel/types.js";
4
4
  import type { InputResponse } from "#runtime/input/types.js";
@@ -24,13 +24,11 @@ export interface SendPayload {
24
24
  readonly message?: string | UserContent;
25
25
  readonly inputResponses?: readonly InputResponse[];
26
26
  /**
27
- * Ephemeral messages contributed by the channel, appended to the
28
- * dispatched turn's first model call via `StepInput.modelContext`.
29
- * Never persisted to durable session history. Use `role: "system"`
30
- * for background context that should land before the user message;
31
- * other roles land after the user message.
27
+ * Context strings contributed by the channel. Each entry is appended
28
+ * as a `role: "user"` message to `session.history` before the
29
+ * delivery message, persisted across the session.
32
30
  */
33
- readonly modelContext?: readonly ModelMessage[];
31
+ readonly context?: readonly string[];
34
32
  /**
35
33
  * Run-scoped JSON schema the turn's result must match. Orthogonal to
36
34
  * {@link BaseSendOptions.mode}: a schema is enforced in either mode. Mode
@@ -1 +1 @@
1
- import{createSession}from"#channel/session.js";import{createLogger}from"#internal/logging.js";import{isRuntimeNoActiveSessionError}from"#execution/runtime-errors.js";import{serializeUrlFilePart}from"#internal/attachments/url-refs.js";const log=createLogger(`channel.send`);function createSendFn(t,r,i){return async(a,o)=>{let s=o.auth,c=o.callback,l=o.mode??`conversation`,u=o.state,d=o.continuationToken,f=`${i}:${d}`,{message:p,inputResponses:m,modelContext:h,outputSchema:g}=normalizeSendInput(a),_=serializeUrlFilePartsInMessage(p);try{let{sessionId:n}=await t.deliver({auth:s,continuationToken:f,payload:{inputResponses:m,message:_,modelContext:h,outputSchema:g}});return createSession(n,d,t)}catch(e){isRuntimeNoActiveSessionError(e)||log.warn(`deliver failed, falling back to starting a new session`,{continuationToken:f})}if(m&&m.length>0)throw Error(`Cannot deliver inputResponses — the target session was not found via continuation token.`);let v=u?{...r,state:{...r.state,...u}}:r;return createSession((await t.run({adapter:v,auth:s,capabilities:l===`conversation`?{requestInput:!0}:void 0,channelName:i,callback:c,continuationToken:f,input:{message:_??``,modelContext:h,outputSchema:g},mode:l})).sessionId,d,t)}}function serializeUrlFilePartsInMessage(e){if(e===void 0||typeof e==`string`)return e;let t=!1,n=e.map(e=>e.type===`file`&&e.data instanceof URL&&e.data.protocol!==`data:`?(t=!0,{...e,data:serializeUrlFilePart(e.data)}):e);return t?n:e}function normalizeSendInput(e){return typeof e==`string`||Array.isArray(e)?{message:e}:e}export{createSendFn};
1
+ import{createSession}from"#channel/session.js";import{createLogger}from"#internal/logging.js";import{isRuntimeNoActiveSessionError}from"#execution/runtime-errors.js";import{serializeUrlFilePart}from"#internal/attachments/url-refs.js";const log=createLogger(`channel.send`);function createSendFn(t,r,i){return async(a,o)=>{let s=o.auth,c=o.callback,l=o.mode??`conversation`,u=o.state,d=o.continuationToken,f=`${i}:${d}`,{message:p,inputResponses:m,context:h,outputSchema:g}=normalizeSendInput(a),_=serializeUrlFilePartsInMessage(p);try{let{sessionId:n}=await t.deliver({auth:s,continuationToken:f,payload:{inputResponses:m,message:_,context:h,outputSchema:g}});return createSession(n,d,t)}catch(e){isRuntimeNoActiveSessionError(e)||log.warn(`deliver failed, falling back to starting a new session`,{continuationToken:f})}if(m&&m.length>0)throw Error(`Cannot deliver inputResponses — the target session was not found via continuation token.`);let v=u?{...r,state:{...r.state,...u}}:r;return createSession((await t.run({adapter:v,auth:s,capabilities:l===`conversation`?{requestInput:!0}:void 0,channelName:i,callback:c,continuationToken:f,input:{message:_??``,context:h,outputSchema:g},mode:l})).sessionId,d,t)}}function serializeUrlFilePartsInMessage(e){if(e===void 0||typeof e==`string`)return e;let t=!1,n=e.map(e=>e.type===`file`&&e.data instanceof URL&&e.data.protocol!==`data:`?(t=!0,{...e,data:serializeUrlFilePart(e.data)}):e);return t?n:e}function normalizeSendInput(e){return typeof e==`string`||Array.isArray(e)?{message:e}:e}export{createSendFn};
@@ -1,4 +1,4 @@
1
- import type { ModelMessage, UserContent } from "ai";
1
+ import type { UserContent } from "ai";
2
2
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
3
3
  import type { RunMode } from "#shared/run-mode.js";
4
4
  import type { RuntimeActionResult } from "#runtime/actions/types.js";
@@ -70,7 +70,7 @@ export type EventEmitFn = (event: HandleMessageStreamEvent) => Promise<void>;
70
70
  export interface DeliverPayload {
71
71
  readonly inputResponses?: readonly InputResponse[];
72
72
  readonly message?: string | UserContent;
73
- readonly modelContext?: readonly ModelMessage[];
73
+ readonly context?: readonly string[];
74
74
  readonly outputSchema?: JsonObject;
75
75
  readonly [key: string]: unknown;
76
76
  }
@@ -215,7 +215,7 @@ export interface RunInput {
215
215
  readonly initiatorAuth?: SessionAuthContext | null;
216
216
  readonly input: {
217
217
  readonly message: string | UserContent;
218
- readonly modelContext?: readonly ModelMessage[];
218
+ readonly context?: readonly string[];
219
219
  readonly outputSchema?: JsonObject;
220
220
  };
221
221
  readonly mode: RunMode;
@@ -0,0 +1,13 @@
1
+ import type { HarnessToolDefinition } from "#harness/execute-tool.js";
2
+ import type { ContextKey } from "#context/key.js";
3
+ /**
4
+ * Builds live dynamic tool definitions. Narrower scopes appear first
5
+ * so they win on name collision (the tool-loop uses `??=` for dedup).
6
+ *
7
+ * Step tools are live closures (re-resolved every step via
8
+ * `LiveStepToolsKey`). Session/turn tools are replayed from durable
9
+ * metadata via the bundler's registered step functions.
10
+ */
11
+ export declare function buildDynamicTools(ctx: {
12
+ get<T>(key: ContextKey<T>): T | undefined;
13
+ }): readonly HarnessToolDefinition[];
@@ -0,0 +1 @@
1
+ import{createLogger}from"#internal/logging.js";import{LiveStepToolsKey,SessionDynamicToolMetadataKey,TurnDynamicToolMetadataKey}from"#context/keys.js";import{jsonSchema}from"ai";import{buildCallbackContext}from"#context/build-callback-context.js";const log=createLogger(`dynamic-tools`);function lookupStepFunction(e){try{let t=globalThis[Symbol.for(`@workflow/core//registeredSteps`)];return t===void 0?null:t.get(e)||null}catch{return null}}function replayTools(e){let t=[];for(let n of e){if(!n.executeStepFnName||!n.closureVars){log.warn(`Dynamic tool "${n.name}" has no registered step function — skipping on this step. The bundler transform may not have processed this tool file.`);continue}let e=lookupStepFunction(n.executeStepFnName);if(!e){log.warn(`Dynamic tool "${n.name}" references step function "${n.executeStepFnName}" which is not registered — skipping on this step.`);continue}t.push({description:n.description,execute:t=>e(n.closureVars,t,buildCallbackContext()),inputSchema:jsonSchema(n.inputSchema),name:n.name})}return t}function buildDynamicTools(e){let r=e.get(LiveStepToolsKey)??[],i=replayTools(e.get(TurnDynamicToolMetadataKey)??[]),a=replayTools(e.get(SessionDynamicToolMetadataKey)??[]);return[...r,...i,...a]}export{buildDynamicTools};
@@ -1,22 +1,22 @@
1
- import type { ModelMessage } from "ai";
1
+ import type { ModelMessage, SystemModelMessage } from "ai";
2
2
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
3
3
  import type { ResolvedDynamicInstructionsResolver } from "#runtime/types.js";
4
4
  import type { ContextContainer } from "#context/container.js";
5
- import { ContextKey } from "#context/key.js";
5
+ import type { ContextKey } from "#context/key.js";
6
6
  /**
7
- * Virtual (non-serialized) pending dynamic instruction messages. Set by
8
- * {@link dispatchDynamicInstructionEvent} when resolvers produce
9
- * messages. Read by the tool-loop to inject into the next model call.
10
- *
11
- * Virtual keys are cleared at workflow step boundaries, so messages
12
- * are never persisted or duplicated across steps.
7
+ * Builds the flattened system messages from session + turn durable keys.
8
+ * Session-scoped entries appear first.
13
9
  */
14
- export declare const PendingDynamicInstructionMessagesKey: ContextKey<ModelMessage[]>;
10
+ export declare function buildDynamicInstructionMessages(ctx: {
11
+ get<T>(key: ContextKey<T>): T | undefined;
12
+ }): SystemModelMessage[];
15
13
  /**
16
- * Dispatches a stream event to dynamic instruction resolvers. On a
17
- * matching event: runs handlers, validates branded returns, lowers
18
- * results to messages, and stores them on a virtual context key for
19
- * the tool-loop to inject.
14
+ * Dispatches a stream event to dynamic instruction resolvers.
15
+ *
16
+ * Each resolver's output replaces its own slot (keyed by slug) in the
17
+ * scope-appropriate durable key (session or turn). The tool-loop calls
18
+ * {@link buildDynamicInstructionMessages} to assemble the flattened
19
+ * result for the model call.
20
20
  */
21
21
  export declare function dispatchDynamicInstructionEvent(input: {
22
22
  readonly ctx: ContextContainer;
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{getAdapterKind}from"#channel/adapter.js";import{AuthKey,ContinuationTokenKey,InitiatorAuthKey,SessionIdKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_TOOL_EVENTS,isBrandedInstructionsEntry}from"#shared/dynamic-tool-definition.js";import{ContextKey}from"#context/key.js";import{ChannelKey}from"#runtime/sessions/runtime-context-keys.js";const log=createLogger(`dynamic-instructions`),PendingDynamicInstructionMessagesKey=new ContextKey(`ash.pendingDynamicInstructionMessages`);function buildResolveContext(e,o){let s=e.get(SessionIdKey)??``,c=e.get(AuthKey)??null,l=e.get(InitiatorAuthKey)??null,d=e.get(ChannelKey),f=e.get(ContinuationTokenKey);return{session:{id:s,auth:{current:c,initiator:l}},channel:{kind:d===void 0?void 0:getAdapterKind(d),continuationToken:f},messages:o}}function lowerToMessages(e){let t=[];return e.markdown!==void 0&&e.markdown.trim().length>0&&t.push({role:`system`,content:e.markdown}),e.modelContext!==void 0&&t.push(...e.modelContext),t}async function dispatchDynamicInstructionEvent(e){let{ctx:t,resolvers:n,event:r,messages:i}=e;if(!ALLOWED_DYNAMIC_TOOL_EVENTS.has(r.type))return;let a=n.filter(e=>e.eventNames.includes(r.type));if(a.length===0)return;let l=buildResolveContext(t,i),u=[];for(let e of a){let t=e.events[r.type];if(t!==void 0)try{let n=await t(r,l);if(n==null)continue;if(!isBrandedInstructionsEntry(n)){log.error(`Dynamic instructions resolver "${e.slug}" returned an unbranded value — wrap with defineInstructions().`);continue}u.push(...lowerToMessages(n))}catch(t){log.error(`Dynamic instructions resolver "${e.slug}" (${r.type}) threw — skipping.`,{error:toErrorMessage(t)})}}if(u.length>0){let e=t.get(PendingDynamicInstructionMessagesKey)??[];t.setVirtualContext(PendingDynamicInstructionMessagesKey,[...e,...u])}}export{PendingDynamicInstructionMessagesKey,dispatchDynamicInstructionEvent};
1
+ import{createLogger}from"#internal/logging.js";import{SessionDynamicInstructionsKey,TurnDynamicInstructionsKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_INSTRUCTION_EVENTS,isBrandedInstructionsEntry}from"#shared/dynamic-tool-definition.js";import{buildResolveContext}from"#context/dynamic-resolve-context.js";const log=createLogger(`dynamic-instructions`);function lowerToSystemMessage(e){let t=e.markdown.trim();if(t.length!==0)return{role:`system`,content:t}}function durableKeyForEvent(e){switch(e){case`session.started`:return SessionDynamicInstructionsKey;case`turn.started`:return TurnDynamicInstructionsKey;default:return}}function buildDynamicInstructionMessages(e){let r=e.get(SessionDynamicInstructionsKey)??{},i=e.get(TurnDynamicInstructionsKey)??{};return[...Object.values(r).flat(),...Object.values(i).flat()]}async function dispatchDynamicInstructionEvent(e){let{ctx:t,resolvers:n,event:a,messages:o}=e;if(!ALLOWED_DYNAMIC_INSTRUCTION_EVENTS.has(a.type))return;let s=n.filter(e=>e.eventNames.includes(a.type));if(s.length===0)return;let c=durableKeyForEvent(a.type);if(c===void 0)return;let l=buildResolveContext(t,o),u=await Promise.allSettled(s.map(async e=>{let t=e.events[a.type];if(t===void 0)return null;let n=await t(a,l);return n==null?{resolver:e,message:void 0}:isBrandedInstructionsEntry(n)?{resolver:e,message:lowerToSystemMessage(n)}:(log.error(`Dynamic instructions resolver "${e.slug}" returned an unbranded value — wrap with defineInstructions().`),null)})),d={...t.get(c)};for(let e of u){if(e.status===`rejected`){log.error(`Dynamic instructions resolver (${a.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}if(e.value===null)continue;let{resolver:t,message:n}=e.value;n===void 0?delete d[t.slug]:d[t.slug]=[n]}t.set(c,d)}export{buildDynamicInstructionMessages,dispatchDynamicInstructionEvent};
@@ -0,0 +1,12 @@
1
+ import type { ModelMessage } from "ai";
2
+ import type { DynamicResolveContext } from "#shared/dynamic-tool-definition.js";
3
+ import type { AlsContext } from "#context/container.js";
4
+ type ReadableContext = Pick<AlsContext, "get">;
5
+ /**
6
+ * Builds the {@link DynamicResolveContext} from the active ALS context.
7
+ *
8
+ * Shared by all three dynamic lifecycle dispatchers (tools, skills,
9
+ * instructions) so resolver handlers receive a consistent context shape.
10
+ */
11
+ export declare function buildResolveContext(ctx: ReadableContext, messages: readonly ModelMessage[]): DynamicResolveContext;
12
+ export {};
@@ -0,0 +1 @@
1
+ import{getAdapterKind}from"#channel/adapter.js";import{AuthKey,ContinuationTokenKey,InitiatorAuthKey,SessionIdKey}from"#context/keys.js";import{ChannelKey}from"#runtime/sessions/runtime-context-keys.js";function buildResolveContext(e,t){let n=e.get(SessionIdKey)??``,r=e.get(AuthKey)??null,i=e.get(InitiatorAuthKey)??null,a=e.get(ChannelKey),o=e.get(ContinuationTokenKey);return{session:{id:n,auth:{current:r,initiator:i}},channel:{kind:a===void 0?void 0:getAdapterKind(a),continuationToken:o},messages:t}}export{buildResolveContext};
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{getAdapterKind}from"#channel/adapter.js";import{AuthKey,ContinuationTokenKey,DynamicSkillManifestKey,InitiatorAuthKey,SandboxKey,SessionIdKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_TOOL_EVENTS}from"#shared/dynamic-tool-definition.js";import{normalizeSkillPackage,removeSkillPackageFromSandbox,writeSkillPackageToSandbox}from"#shared/skill-package.js";import{ContextKey}from"#context/key.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{formatAvailableSkillsSection}from"#execution/skills/instructions.js";const log=createLogger(`dynamic-skills`);function buildResolveContext(e,i){let o=e.get(SessionIdKey)??``,c=e.get(AuthKey)??null,l=e.get(InitiatorAuthKey)??null,u=e.get(ChannelKey),d=e.get(ContinuationTokenKey);return{session:{id:o,auth:{current:c,initiator:l}},channel:{kind:u===void 0?void 0:getAdapterKind(u),continuationToken:d},messages:i}}function qualifyDynamicSkillNames(e,t,n){let r=Object.keys(n),i=[];if(r.length===0)return i;if(t||r.length===1)return i.push({name:e,entryKey:r[0],entry:n[r[0]]}),i;for(let t of r)i.push({name:`${e}__${t}`,entryKey:t,entry:n[t]});return i}function formatDynamicSkillAnnouncement(e){return formatAvailableSkillsSection(Object.values(e).flat())??``}function isSkillEntry(e){return typeof e==`object`&&!!e&&typeof e.markdown==`string`&&typeof e.description==`string`}const PendingSkillAnnouncementKey=new ContextKey(`ash.pendingSkillAnnouncement`);async function dispatchDynamicSkillEvent(e){let{ctx:t,resolvers:n,event:r,messages:a}=e;if(!ALLOWED_DYNAMIC_TOOL_EVENTS.has(r.type))return;let s=n.filter(e=>e.eventNames.includes(r.type));if(s.length===0)return;let f=buildResolveContext(t,a),p=new Set(t.require(BundleKey).resolvedAgent.skills.map(e=>e.name)),m=t.get(DynamicSkillManifestKey)??{},h=[],g=await Promise.allSettled(s.map(async e=>{let t=e.events[r.type];if(t===void 0)return null;let n=await t(r,f);if(n==null)return{resolver:e,named:[]};let i,a;return isSkillEntry(n)?(i={_single:n},a=!0):(i=n,a=!1),{resolver:e,named:qualifyDynamicSkillNames(e.slug,a,i)}}));for(let e of g){if(e.status===`rejected`){log.error(`Dynamic skill resolver (${r.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}e.value!==null&&h.push({resolver:e.value.resolver,skills:e.value.named.map(({name:e,entry:t})=>normalizeSkillPackage({...t,name:e}))})}if(h.length===0)return;let _={...m};for(let{resolver:e,skills:t}of h)t.length===0?delete _[e.slug]:_[e.slug]=t.map(e=>({description:e.description,name:e.name}));let v=new Map;for(let[e,t]of Object.entries(_))for(let{name:n}of t){if(p.has(n))throw Error(`Dynamic skill "${n}" from resolver "${e}" conflicts with an authored skill.`);let t=v.get(n);if(t!==void 0)throw Error(`Dynamic skill "${n}" from resolver "${e}" conflicts with dynamic resolver "${t}".`);v.set(n,e)}let y=await t.require(SandboxKey).get();if(y!==null)for(let{resolver:e,skills:t}of h){let n=new Set((m[e.slug]??[]).map(e=>e.name)),r=new Set(t.map(e=>e.name));for(let e of n)r.has(e)||await removeSkillPackageFromSandbox({sandbox:y,name:e});for(let e of t)await writeSkillPackageToSandbox({sandbox:y,skill:e})}t.set(DynamicSkillManifestKey,_),t.set(PendingSkillAnnouncementKey,formatDynamicSkillAnnouncement(_))}export{PendingSkillAnnouncementKey,dispatchDynamicSkillEvent};
1
+ import{createLogger}from"#internal/logging.js";import{DynamicSkillManifestKey,SandboxKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_SKILL_EVENTS,isBrandedSkillEntry}from"#shared/dynamic-tool-definition.js";import{normalizeSkillPackage,writeSkillPackageToSandbox}from"#shared/skill-package.js";import{ContextKey}from"#context/key.js";import{buildResolveContext}from"#context/dynamic-resolve-context.js";import{BundleKey}from"#runtime/sessions/runtime-context-keys.js";import{formatAvailableSkillsSection}from"#execution/skills/instructions.js";const log=createLogger(`dynamic-skills`);function qualifyDynamicSkillNames(e,t,n){let r=Object.keys(n),i=[];if(r.length===0)return i;if(t||r.length===1)return i.push({name:e,entryKey:r[0],entry:n[r[0]]}),i;for(let t of r)i.push({name:`${e}__${t}`,entryKey:t,entry:n[t]});return i}function formatDynamicSkillAnnouncement(e){return formatAvailableSkillsSection(Object.values(e).flat())??``}const PendingSkillAnnouncementKey=new ContextKey(`ash.pendingSkillAnnouncement`);async function dispatchDynamicSkillEvent(e){let{ctx:a,resolvers:o,event:s,messages:c}=e;if(a.get(PendingSkillAnnouncementKey)===void 0){let e=a.get(DynamicSkillManifestKey);e!==void 0&&Object.keys(e).length>0&&a.setVirtualContext(PendingSkillAnnouncementKey,formatDynamicSkillAnnouncement(e))}if(!ALLOWED_DYNAMIC_SKILL_EVENTS.has(s.type))return;let l=o.filter(e=>e.eventNames.includes(s.type));if(l.length===0)return;let u=buildResolveContext(a,c),d=new Set(a.require(BundleKey).resolvedAgent.skills.map(e=>e.name)),f=a.get(DynamicSkillManifestKey)??{},p=[],m=await Promise.allSettled(l.map(async e=>{let t=e.events[s.type];if(t===void 0)return null;let n=await t(s,u);if(n==null)return{resolver:e,named:[]};let r,i;return isBrandedSkillEntry(n)?(r={_single:n},i=!0):(r=n,i=!1),{resolver:e,named:qualifyDynamicSkillNames(e.slug,i,r)}}));for(let e of m){if(e.status===`rejected`){log.error(`Dynamic skill resolver (${s.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}e.value!==null&&p.push({resolver:e.value.resolver,skills:e.value.named.map(({name:e,entry:t})=>normalizeSkillPackage({...t,name:e}))})}if(p.length===0)return;let h={...f};for(let{resolver:e,skills:t}of p)t.length===0?delete h[e.slug]:h[e.slug]=t.map(e=>({description:e.description,name:e.name}));let g=new Map;for(let[e,t]of Object.entries(h))for(let{name:n}of t){if(d.has(n))throw Error(`Dynamic skill "${n}" from resolver "${e}" conflicts with an authored skill.`);let t=g.get(n);if(t!==void 0)throw Error(`Dynamic skill "${n}" from resolver "${e}" conflicts with dynamic resolver "${t}".`);g.set(n,e)}let _=await a.require(SandboxKey).get();if(_!==null)for(let{skills:e}of p)for(let t of e)await writeSkillPackageToSandbox({sandbox:_,skill:t});a.set(DynamicSkillManifestKey,h),a.setVirtualContext(PendingSkillAnnouncementKey,formatDynamicSkillAnnouncement(h))}export{PendingSkillAnnouncementKey,dispatchDynamicSkillEvent};
@@ -12,16 +12,10 @@ import type { DurableDynamicToolMetadata } from "#context/keys.js";
12
12
  */
13
13
  export declare function replayDynamicSessionTools(metadata: readonly DurableDynamicToolMetadata[], _resolvers: readonly ResolvedDynamicToolResolver[]): readonly HarnessToolDefinition[];
14
14
  /**
15
- * Unified dynamic tool event dispatch. Called by `handleEvent` for
16
- * every stream event. Two phases:
17
- *
18
- * 1. **Build phase**: reconstruct tools from durable metadata when
19
- * the virtual key is empty (workflow step boundary crossed).
20
- * 2. **Resolve phase**: run resolver handlers for the current event,
21
- * capture closures as durable metadata, produce live tools.
22
- *
23
- * The tool-loop reads {@link DynamicToolsKey} right before each model
24
- * call. Events update it; the tool-loop picks up whatever is current.
15
+ * Dispatches a stream event to dynamic tool resolvers. Each
16
+ * resolver's metadata replaces its slot (by slug) in the
17
+ * scope-appropriate durable key. The tool-loop calls
18
+ * {@link buildDynamicTools} to assemble the effective toolset.
25
19
  */
26
20
  export declare function dispatchDynamicToolEvent(input: {
27
21
  readonly ctx: ContextContainer;
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{getAdapterKind}from"#channel/adapter.js";import{AuthKey,ContinuationTokenKey,DynamicSessionToolMetadataKey,DynamicToolsKey,InitiatorAuthKey,SessionIdKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_TOOL_EVENTS,isBrandedToolEntry}from"#shared/dynamic-tool-definition.js";import{ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{jsonSchema,zodSchema}from"ai";import{normalizeJsonSchemaDefinition}from"#internal/json-schema.js";import{buildCallbackContext}from"#context/build-callback-context.js";const log=createLogger(`dynamic-tools`);function buildResolveContext(e,i){let a=e.get(SessionIdKey)??``,c=e.get(AuthKey)??null,l=e.get(InitiatorAuthKey)??null,u=e.get(ChannelKey),f=e.get(ContinuationTokenKey);return{session:{id:a,auth:{current:c,initiator:l}},channel:{kind:u===void 0?void 0:getAdapterKind(u),continuationToken:f},messages:i}}function toHarnessToolDefinition(e,t){return{description:t.description,execute:e=>t.execute(e,buildCallbackContext()),inputSchema:convertInputSchema(t.inputSchema),name:e,...t.toModelOutput===void 0?{}:{toModelOutput:t.toModelOutput}}}function convertInputSchema(e){return typeof e==`object`&&e&&`~standard`in e?zodSchema(e):jsonSchema(e)}function qualifyDynamicToolNames(e,t,n){let r=Object.keys(n),i=[];if(r.length===0)return i;if(t)return i.push({name:e,entryKey:r[0],entry:n[r[0]]}),i;for(let t of r)i.push({name:`${e}__${t}`,entryKey:t,entry:n[t]});return i}function replayDynamicSessionTools(e,t){let n=[];for(let t of e){if(!t.executeStepFnName||!t.closureVars){log.warn(`Dynamic tool "${t.name}" has no registered step function — skipping on this step. The bundler transform may not have processed this tool file.`);continue}let e=lookupStepFunction(t.executeStepFnName);if(!e){log.warn(`Dynamic tool "${t.name}" references step function "${t.executeStepFnName}" which is not registered — skipping on this step.`);continue}n.push({description:t.description,execute:n=>e(t.closureVars,n,buildCallbackContext()),inputSchema:jsonSchema(t.inputSchema),name:t.name})}return n}function lookupStepFunction(e){try{let t=globalThis[Symbol.for(`@workflow/core//registeredSteps`)];return t===void 0?null:t.get(e)||null}catch{return null}}function safeSerialize(e){try{return JSON.parse(JSON.stringify(e))}catch{return{}}}function matchesAnySlug(e,t){for(let n of t)if(e===n||e.startsWith(`${n}__`))return!0;return!1}async function resolveToolsFromEvent(e,t,n,r){let a=await Promise.allSettled(t.map(async t=>{let i=t.events[n.type];if(i===void 0)return null;let a=await i(n,buildResolveContext(e,r));if(a==null)return null;let o,s;return isBrandedToolEntry(a)?(o={_single:a},s=!0):(o=a,s=!1),{resolver:t,entries:o,isSingle:s}})),o=[],s=[];for(let e of a){if(e.status===`rejected`){log.error(`Dynamic tool resolver (${n.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}if(e.value===null)continue;let{resolver:t,entries:r,isSingle:i}=e.value,a=qualifyDynamicToolNames(t.slug,i,r);for(let{name:e,entryKey:n,entry:r}of a){o.push(toHarnessToolDefinition(e,r));let i=`__executeStepFn`in r?r.__executeStepFn:void 0,a=`__closureVars`in r?r.__closureVars:void 0;s.push({name:e,description:r.description,inputSchema:normalizeJsonSchemaDefinition(r.inputSchema),resolverSlug:t.slug,entryKey:n,executeStepFnName:i?.stepId,closureVars:a===void 0?void 0:safeSerialize(a)})}}if(s.length>0){let t=e.get(DynamicSessionToolMetadataKey)??[],n=new Set(s.map(e=>e.resolverSlug)),r=t.filter(e=>!n.has(e.resolverSlug));e.set(DynamicSessionToolMetadataKey,[...r,...s])}return o}async function dispatchDynamicToolEvent(e){let{ctx:t,resolvers:n,event:r,messages:o}=e;if(t.get(DynamicToolsKey)===void 0){let e=t.get(DynamicSessionToolMetadataKey);if(e!==void 0&&e.length>0){let r=replayDynamicSessionTools(e,n);r.length>0&&t.setVirtualContext(DynamicToolsKey,[...r])}}if(!ALLOWED_DYNAMIC_TOOL_EVENTS.has(r.type))return;let s=n.filter(e=>e.eventNames.includes(r.type));if(s.length===0)return;let c=await resolveToolsFromEvent(t,s,r,o);if(c.length===0)return;let u=new Set(s.map(e=>e.slug)),d=(t.get(DynamicToolsKey)??[]).filter(e=>!matchesAnySlug(e.name,u));t.setVirtualContext(DynamicToolsKey,[...d,...c])}export{dispatchDynamicToolEvent,replayDynamicSessionTools};
1
+ import{createLogger}from"#internal/logging.js";import{LiveStepToolsKey,SessionDynamicToolMetadataKey,TurnDynamicToolMetadataKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_TOOL_EVENTS,isBrandedToolEntry}from"#shared/dynamic-tool-definition.js";import{jsonSchema,zodSchema}from"ai";import{buildCallbackContext}from"#context/build-callback-context.js";import{buildResolveContext}from"#context/dynamic-resolve-context.js";import{normalizeJsonSchemaDefinition}from"#internal/json-schema.js";const log=createLogger(`dynamic-tools`);function toHarnessToolDefinition(e,t){return{description:t.description,execute:e=>t.execute(e,buildCallbackContext()),inputSchema:convertInputSchema(t.inputSchema),name:e,...t.toModelOutput===void 0?{}:{toModelOutput:t.toModelOutput}}}function convertInputSchema(e){return typeof e==`object`&&e&&`~standard`in e?zodSchema(e):jsonSchema(e)}function qualifyDynamicToolNames(e,t,n){let r=Object.keys(n),i=[];if(r.length===0)return i;if(t)return i.push({name:e,entryKey:r[0],entry:n[r[0]]}),i;for(let t of r)i.push({name:`${e}__${t}`,entryKey:t,entry:n[t]});return i}function replayDynamicSessionTools(e,t){let n=[];for(let t of e){if(!t.executeStepFnName||!t.closureVars){log.warn(`Dynamic tool "${t.name}" has no registered step function — skipping on this step. The bundler transform may not have processed this tool file.`);continue}let e=lookupStepFunction(t.executeStepFnName);if(!e){log.warn(`Dynamic tool "${t.name}" references step function "${t.executeStepFnName}" which is not registered — skipping on this step.`);continue}n.push({description:t.description,execute:n=>e(t.closureVars,n,buildCallbackContext()),inputSchema:jsonSchema(t.inputSchema),name:t.name})}return n}function lookupStepFunction(e){try{let t=globalThis[Symbol.for(`@workflow/core//registeredSteps`)];return t===void 0?null:t.get(e)||null}catch{return null}}function safeSerialize(e){try{return JSON.parse(JSON.stringify(e))}catch{return{}}}function durableKeyForEvent(e){switch(e){case`session.started`:return SessionDynamicToolMetadataKey;case`turn.started`:return TurnDynamicToolMetadataKey;default:return}}async function resolveToolsFromEvent(e,t,n,r){let a=await Promise.allSettled(t.map(async t=>{let i=t.events[n.type];if(i===void 0)return null;let a=await i(n,buildResolveContext(e,r));if(a==null)return null;let s,c;return isBrandedToolEntry(a)?(s={_single:a},c=!0):(s=a,c=!1),{resolver:t,entries:s,isSingle:c}})),s=[],c=[];for(let e of a){if(e.status===`rejected`){log.error(`Dynamic tool resolver (${n.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}if(e.value===null)continue;let{resolver:t,entries:r,isSingle:a}=e.value,o=qualifyDynamicToolNames(t.slug,a,r);for(let{name:e,entryKey:n,entry:r}of o){c.push(toHarnessToolDefinition(e,r));let i=`__executeStepFn`in r?r.__executeStepFn:void 0,a=`__closureVars`in r?r.__closureVars:void 0;s.push({name:e,description:r.description,inputSchema:normalizeJsonSchemaDefinition(r.inputSchema),resolverSlug:t.slug,entryKey:n,executeStepFnName:i?.stepId,closureVars:a===void 0?void 0:safeSerialize(a)})}}return{metadata:s,liveTools:c}}async function dispatchDynamicToolEvent(e){let{ctx:n,resolvers:r,event:i,messages:o}=e;if(!ALLOWED_DYNAMIC_TOOL_EVENTS.has(i.type))return;let s=r.filter(e=>e.eventNames.includes(i.type));if(s.length===0)return;let{metadata:c,liveTools:l}=await resolveToolsFromEvent(n,s,i,o);if(i.type===`step.started`){n.setVirtualContext(LiveStepToolsKey,l);return}let u=durableKeyForEvent(i.type);if(u===void 0)return;let d=new Set(s.map(e=>e.slug)),f=(n.get(u)??[]).filter(e=>!d.has(e.resolverSlug));n.set(u,[...f,...c])}export{dispatchDynamicToolEvent,replayDynamicSessionTools};
@@ -5,7 +5,7 @@ import type { RuntimeHookRegistry } from "#runtime/hooks/registry.js";
5
5
  import type { ContextContainer } from "./container.js";
6
6
  /**
7
7
  * Outcome of {@link dispatchHookLifecycle}. `proceed` carries the
8
- * input augmented with merged `modelContext`. `turn-failed` means a
8
+ * input augmented with merged context. `turn-failed` means a
9
9
  * `lifecycle.turn` hook threw; the recoverable `turn.failed` cascade
10
10
  * has already been emitted.
11
11
  */
@@ -35,7 +35,7 @@ export interface DispatchHookLifecycleInput {
35
35
  * runs so a thrown hook does not retry. A throw re-propagates and
36
36
  * the runtime escalates to terminal `session.failed`.
37
37
  * - `lifecycle.turn` runs once per fresh delivery. Each hook may
38
- * return `{ modelContext }`; model-context contributions are
38
+ * return `{ context }`; context contributions are
39
39
  * concatenated in registry order. A thrown hook emits the recoverable
40
40
  * `turn.failed` cascade and returns `kind: "turn-failed"`.
41
41
  */
@@ -1 +1 @@
1
- import{ContinuationTokenKey,SessionPreparedKey}from"./keys.js";import{createLogger}from"#internal/logging.js";import{getAdapterKind}from"#channel/adapter.js";import{toErrorMessage}from"#shared/errors.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{emitRecoverableFailedTurn,emitTurnPreamble,getHarnessEmissionState,setHarnessEmissionState}from"#harness/emission.js";const log=createLogger(`hooks.lifecycle`);async function dispatchHookLifecycle(e){let{ctx:n,registry:r,emit:a}=e,o=getHarnessEmissionState(e.session.state),s=buildHookContext(n),u=e.session;if(r.session.length>0&&n.get(SessionPreparedKey)!==!0){n.set(SessionPreparedKey,!0);for(let e of r.session)await e.handler(s)}let d=!1,f=o;try{for(let e of r.turn)await e.handler(s)}catch(t){let n=toErrorMessage(t);try{return d||=(f=await emitTurnPreamble(a,{message:e.input.message},o,e.runtimeIdentity),!0),{kind:`turn-failed`,message:n,nextSession:setHarnessEmissionState(u,await emitRecoverableFailedTurn(a,f,{code:`HOOK_TURN_FAILED`,message:n}))}}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}}return{kind:`proceed`,input:e.input,nextSession:u}}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 n=t.require(BundleKey),i=t.get(ChannelKey),c=t.get(ContinuationTokenKey),l=i===void 0?void 0:getAdapterKind(i);return{...buildCallbackContext(),agent:{name:n.resolvedAgent.config.name??`agent`,nodeId:n.nodeId},channel:{kind:l,continuationToken:c}}}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,SessionPreparedKey}from"./keys.js";import{createLogger}from"#internal/logging.js";import{getAdapterKind}from"#channel/adapter.js";import{toErrorMessage}from"#shared/errors.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{emitRecoverableFailedTurn,emitTurnPreamble,getHarnessEmissionState,setHarnessEmissionState}from"#harness/emission.js";const log=createLogger(`hooks.lifecycle`);async function dispatchHookLifecycle(e){let{ctx:n,registry:r,emit:a}=e,o=getHarnessEmissionState(e.session.state),s=buildHookContext(n),u=e.session;if(r.session.length>0&&n.get(SessionPreparedKey)!==!0){n.set(SessionPreparedKey,!0);for(let e of r.session)await e.handler(s)}let d=!1,f=o;try{for(let e of r.turn)await e.handler(s)}catch(t){let n=toErrorMessage(t);try{return d||=(f=await emitTurnPreamble(a,{message:e.input.message},o,e.runtimeIdentity),!0),{kind:`turn-failed`,message:n,nextSession:setHarnessEmissionState(u,await emitRecoverableFailedTurn(a,f,{code:`HOOK_TURN_FAILED`,message:n}))}}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}}return{kind:`proceed`,input:e.input,nextSession:u}}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 n=t.require(BundleKey),i=t.get(ChannelKey),c=t.get(ContinuationTokenKey),l=i===void 0?void 0:getAdapterKind(i);return{...buildCallbackContext(),agent:{name:n.resolvedAgent.config.name??`agent`,nodeId:n.nodeId},channel:{kind:l,continuationToken:c}}}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};
@@ -3,6 +3,8 @@
3
3
  * tier. Codec-carrying keys (`ChannelKey`, `BundleKey`) live in
4
4
  * `#runtime/sessions/runtime-context-keys.ts`.
5
5
  */
6
+ import type { SystemModelMessage } from "ai";
7
+ import type { JsonObject } from "#shared/json.js";
6
8
  import type { ChannelInstrumentationProjection, SessionAuthContext, SessionCallback, SessionCapabilities, SessionParent, SessionTurn } from "#channel/types.js";
7
9
  import { ContextKey } from "#context/key.js";
8
10
  import type { SandboxAccess } from "#sandbox/state.js";
@@ -56,37 +58,32 @@ export declare const SessionCallbackKey: ContextKey<SessionCallback>;
56
58
  export declare const SessionPreparedKey: ContextKey<boolean>;
57
59
  export declare const SessionKey: ContextKey<Session>;
58
60
  export declare const SandboxKey: ContextKey<SandboxAccess>;
59
- /**
60
- * Virtual (non-serialized) live dynamic tool definitions. Updated by
61
- * {@link dispatchDynamicToolEvent} whenever a subscribed stream event
62
- * fires. Read by the tool-loop when building the effective toolset.
63
- */
64
- export declare const DynamicToolsKey: ContextKey<import("#harness/execute-tool.js").HarnessToolDefinition[]>;
65
- /**
66
- * Durable (serialized) metadata for session-scoped dynamic tools.
67
- * Set on the first step when resolvers run. Read on subsequent steps
68
- * to reconstruct tool definitions with lazy execute wrappers.
69
- */
70
61
  export interface DurableDynamicToolMetadata {
71
62
  readonly name: string;
72
63
  readonly description: string;
73
- readonly inputSchema: import("#shared/json.js").JsonObject;
64
+ readonly inputSchema: JsonObject;
74
65
  readonly resolverSlug: string;
75
66
  readonly entryKey: string;
76
- /**
77
- * Name of the hoisted execute step function (e.g.
78
- * `__ash_dynamic_exec_0`). Used to look up the registered step
79
- * function via `getStepFunction` on replay.
80
- */
81
67
  readonly executeStepFnName?: string;
82
- /**
83
- * Serialized closure variables captured from the resolver scope on
84
- * the first run. Passed as the `__vars` parameter to the hoisted
85
- * step function on replay.
86
- */
87
68
  readonly closureVars?: Record<string, unknown>;
88
69
  }
89
- export declare const DynamicSessionToolMetadataKey: ContextKey<readonly DurableDynamicToolMetadata[]>;
70
+ /**
71
+ * Session-scoped dynamic tool metadata (from `session.started`).
72
+ * Persists for the session lifetime.
73
+ */
74
+ export declare const SessionDynamicToolMetadataKey: ContextKey<readonly DurableDynamicToolMetadata[]>;
75
+ /**
76
+ * Turn-scoped dynamic tool metadata (from `turn.started`).
77
+ * Replaced each turn.
78
+ */
79
+ export declare const TurnDynamicToolMetadataKey: ContextKey<readonly DurableDynamicToolMetadata[]>;
80
+ /**
81
+ * Virtual (non-serialized) live step-scoped tool definitions from
82
+ * `step.started` resolvers. Carries original execute closures so
83
+ * framework tools (which lack bundler step-function metadata) work.
84
+ * Re-resolved every step — no cross-step persistence needed.
85
+ */
86
+ export declare const LiveStepToolsKey: ContextKey<import("#harness/execute-tool.js").HarnessToolDefinition[]>;
90
87
  /**
91
88
  * Durable metadata for one session-scoped dynamic skill.
92
89
  */
@@ -100,3 +97,13 @@ export interface DurableDynamicSkillMetadata {
100
97
  * and rebuild the model-visible announcement across turns.
101
98
  */
102
99
  export declare const DynamicSkillManifestKey: ContextKey<Record<string, readonly DurableDynamicSkillMetadata[]>>;
100
+ /**
101
+ * Durable session-scoped instruction messages (from `session.started`
102
+ * resolvers). Keyed by resolver slug. Persists for the session lifetime.
103
+ */
104
+ export declare const SessionDynamicInstructionsKey: ContextKey<Record<string, readonly SystemModelMessage[]>>;
105
+ /**
106
+ * Durable turn-scoped instruction messages (from `turn.started`
107
+ * resolvers). Keyed by resolver slug. Replaced each turn.
108
+ */
109
+ export declare const TurnDynamicInstructionsKey: ContextKey<Record<string, readonly SystemModelMessage[]>>;
@@ -1 +1 @@
1
- import{ContextKey}from"#context/key.js";const AuthKey=new ContextKey(`ash.auth`),InitiatorAuthKey=new ContextKey(`ash.initiatorAuth`),SessionIdKey=new ContextKey(`ash.sessionId`),ContinuationTokenKey=new ContextKey(`ash.continuationToken`),ChannelInstrumentationKey=new ContextKey(`ash.channelInstrumentation`),ModeKey=new ContextKey(`ash.mode`),ParentSessionKey=new ContextKey(`ash.parentSession`),CapabilitiesKey=new ContextKey(`ash.capabilities`),SessionCallbackKey=new ContextKey(`ash.sessionCallback`),SessionPreparedKey=new ContextKey(`ash.sessionPrepared`),SessionKey=new ContextKey(`ash.session`),SandboxKey=new ContextKey(`ash.sandbox`),DynamicToolsKey=new ContextKey(`ash.dynamicTools`),DynamicSessionToolMetadataKey=new ContextKey(`ash.dynamicSessionToolMetadata`),DynamicSkillManifestKey=new ContextKey(`ash.dynamicSkillManifest`);export{AuthKey,CapabilitiesKey,ChannelInstrumentationKey,ContinuationTokenKey,DynamicSessionToolMetadataKey,DynamicSkillManifestKey,DynamicToolsKey,InitiatorAuthKey,ModeKey,ParentSessionKey,SandboxKey,SessionCallbackKey,SessionIdKey,SessionKey,SessionPreparedKey};
1
+ import{ContextKey}from"#context/key.js";const AuthKey=new ContextKey(`ash.auth`),InitiatorAuthKey=new ContextKey(`ash.initiatorAuth`),SessionIdKey=new ContextKey(`ash.sessionId`),ContinuationTokenKey=new ContextKey(`ash.continuationToken`),ChannelInstrumentationKey=new ContextKey(`ash.channelInstrumentation`),ModeKey=new ContextKey(`ash.mode`),ParentSessionKey=new ContextKey(`ash.parentSession`),CapabilitiesKey=new ContextKey(`ash.capabilities`),SessionCallbackKey=new ContextKey(`ash.sessionCallback`),SessionPreparedKey=new ContextKey(`ash.sessionPrepared`),SessionKey=new ContextKey(`ash.session`),SandboxKey=new ContextKey(`ash.sandbox`),SessionDynamicToolMetadataKey=new ContextKey(`ash.sessionDynamicToolMetadata`),TurnDynamicToolMetadataKey=new ContextKey(`ash.turnDynamicToolMetadata`),LiveStepToolsKey=new ContextKey(`ash.liveStepTools`),DynamicSkillManifestKey=new ContextKey(`ash.dynamicSkillManifest`),SessionDynamicInstructionsKey=new ContextKey(`ash.sessionDynamicInstructions`),TurnDynamicInstructionsKey=new ContextKey(`ash.turnDynamicInstructions`);export{AuthKey,CapabilitiesKey,ChannelInstrumentationKey,ContinuationTokenKey,DynamicSkillManifestKey,InitiatorAuthKey,LiveStepToolsKey,ModeKey,ParentSessionKey,SandboxKey,SessionCallbackKey,SessionDynamicInstructionsKey,SessionDynamicToolMetadataKey,SessionIdKey,SessionKey,SessionPreparedKey,TurnDynamicInstructionsKey,TurnDynamicToolMetadataKey};
@@ -1 +1 @@
1
- import{ASH_SESSION_STREAM_NAMESPACE}from"#execution/durable-session-store.js";import{readRootSessionId}from"#execution/ash-workflow-attributes.js";import{accumulateRuntimeActionResults}from"#harness/runtime-actions.js";import{resolveVercelProductionCallbackBaseUrl}from"#execution/workflow-callback-url.js";import{normalizeSerializableError,rebuildSerializableError}from"#execution/workflow-errors.js";import{dispatchTurnStep,emitTerminalSessionFailureStep,routeProxiedDeliverStep,runProxyInputRequestStep}from"#execution/workflow-steps.js";import{createHook,getWorkflowMetadata,getWritable}from"#compiled/@workflow/core/index.js";import{coalesceDeliveries}from"#harness/messages.js";import{notifyDelegatedParentStep}from"#execution/delegated-parent-notification.js";import{createDelegatedSubagentErrorResult,createDelegatedSubagentSuccessResult}from"#execution/delegated-parent-result.js";import{createSessionStep}from"#execution/create-session-step.js";import{dispatchRuntimeActionsStep}from"#execution/dispatch-runtime-actions-step.js";import{fireSessionCallbackStep}from"#execution/session-callback-step.js";async function workflowEntry(n){"use workflow";let{workflowRunId:r}=getWorkflowMetadata(),a=n.serializedContext[`ash.continuationToken`]||``,o=n.serializedContext[`ash.mode`],c=n.serializedContext[`ash.capabilities`],l=n.serializedContext[`ash.bundle`];n.serializedContext[`ash.sessionId`]=r;let u=getWritable(),d=getWritable({namespace:ASH_SESSION_STREAM_NAMESPACE});try{let e=readRootSessionId(n.serializedContext),{state:i}=await createSessionStep({compiledArtifactsSource:l.source,continuationToken:a,inputMessage:n.input.message,nodeId:l.nodeId,outputSchema:n.input.outputSchema,rootSessionId:e,serializedContext:n.serializedContext,sessionId:r,sessionWritable:d});return await runDriverLoop({capabilities:c,driverWritable:u,initialInput:{kind:`deliver`,payloads:[{message:n.input.message,modelContext:n.input.modelContext,outputSchema:n.input.outputSchema}]},mode:o,serializedContext:n.serializedContext,sessionState:i,sessionWritable:d})}catch(e){throw await emitTerminalSessionFailureStep({error:normalizeSerializableError(e),parentWritable:u,serializedContext:n.serializedContext}),await fireSessionCallbackStep({error:normalizeSerializableError(e),serializedContext:n.serializedContext,status:`failed`}),await notifyDelegatedParentStep({result:createDelegatedSubagentErrorResult(n.serializedContext,e),serializedContext:n.serializedContext}),e}}async function runDriverLoop(e){let t=createHook({token:`${e.sessionState.sessionId}:auth`}),n=t[Symbol.asyncIterator](),i=e.sessionState.continuationToken,a=createHook({token:i}),o=a[Symbol.asyncIterator](),s=null,c=[],getNextPromise=()=>(s??=o.next(),s),consumeNext=()=>{s=null},rekeyHook=async e=>{e===i||!e||(await closeHookIterator(o),await disposeHook(a),i=e,a=createHook({token:i}),o=a[Symbol.asyncIterator](),s=null)},l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:e.initialInput,mode:e.mode,parentWritable:e.driverWritable,serializedContext:e.serializedContext,sessionState:e.sessionState,sessionWritable:e.sessionWritable});if(l.kind===`done`)return await closeHookIterator(n),await disposeHook(t),await closeHookIterator(o),await disposeHook(a),await finalizeDone({action:l,driverWritable:e.driverWritable});if(!l.sessionState.continuationToken)throw Error("Cannot park: no continuation token available. The channel must post the first message during the initial turn (anchoring the session) or `send()` must be called with an explicit continuationToken.");await rekeyHook(l.sessionState.continuationToken);try{for(;;)switch(l.kind){case`done`:return await finalizeDone({action:l,driverWritable:e.driverWritable});case`dispatch-runtime-actions`:{let t=await dispatchRuntimeActionsStep({callbackBaseUrl:resolveVercelProductionCallbackBaseUrl()??getWorkflowMetadata().url,parentWritable:e.driverWritable,serializedContext:l.serializedContext,sessionState:l.sessionState,sessionWritable:e.sessionWritable}),n=await waitForPendingRuntimeActionResults({bufferedDeliveries:c,consumeNext,getNextPromise,initialResults:t.results,parentWritable:e.driverWritable,pendingActionKeys:l.pendingActionKeys,rekeyHook,serializedContext:l.serializedContext,sessionState:t.sessionState,sessionWritable:e.sessionWritable});if(n===null)return{output:``};l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:{kind:`runtime-action-result`,results:n.results},mode:e.mode,parentWritable:e.driverWritable,serializedContext:n.serializedContext,sessionState:n.sessionState,sessionWritable:e.sessionWritable}),await rekeyHook(l.sessionState.continuationToken);break}case`park`:{if(l.authorizationNames&&l.authorizationNames.length>0){let t=l.authorizationNames.length,r=[];for(;r.length<t;){let e=await n.next();if(e.done)break;e.value.kind===`deliver`&&r.push(...e.value.payloads)}l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:{kind:`deliver`,payloads:r},mode:e.mode,parentWritable:e.driverWritable,serializedContext:l.serializedContext,sessionState:l.sessionState,sessionWritable:e.sessionWritable}),await rekeyHook(l.sessionState.continuationToken);break}let t=await waitForNextDeliver({bufferedDeliveries:c,consumeNext,getNextPromise});if(t===null)return{output:``};let r=await routeDeliverForChildren({auth:t.auth,parentWritable:e.driverWritable,payloads:t.payloads,sessionState:l.sessionState});if(r===void 0)continue;l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:{auth:t.auth,kind:`deliver`,payloads:[r]},mode:e.mode,parentWritable:e.driverWritable,serializedContext:l.serializedContext,sessionState:l.sessionState,sessionWritable:e.sessionWritable}),await rekeyHook(l.sessionState.continuationToken);break}}}finally{await closeHookIterator(o),await disposeHook(a),await closeHookIterator(n),await disposeHook(t)}}async function finalizeDone(e){let{output:t,serializedContext:n}=e.action,r=e.action.isError===!0;return await fireSessionCallbackStep({error:r?t:void 0,output:r?void 0:t,serializedContext:n,status:r?`failed`:`completed`}),await notifyDelegatedParentStep({result:r?createDelegatedSubagentErrorResult(n,t):createDelegatedSubagentSuccessResult(n,t),serializedContext:n}),{output:t}}async function dispatchAndAwaitTurn(e){let t=createHook(),n=t.token;try{await dispatchTurnStep({capabilities:e.capabilities,completionToken:n,delivery:e.delivery,mode:e.mode,parentWritable:e.parentWritable,serializedContext:e.serializedContext,sessionState:e.sessionState,sessionWritable:e.sessionWritable});let r=await awaitHookPayload(t);if(r.kind===`turn-error`)throw rebuildSerializableError(r.error);return r.action}finally{await disposeHook(t)}}async function awaitHookPayload(e){for await(let t of e)return t;throw Error(`Turn completion hook closed before delivering a result.`)}async function waitForPendingRuntimeActionResults(e){let t=e.sessionState,r=e.serializedContext,i=await accumulateRuntimeActionResults({bufferedDeliveries:e.bufferedDeliveries,async getNext(){for(;;){let n=await e.getNextPromise();if(e.consumeNext(),n.done)return null;let i=n.value;if(i.kind===`deliver`){let n=await routeDeliverForChildren({auth:i.auth,parentWritable:e.parentWritable,payloads:i.payloads,sessionState:t});if(n===void 0)continue;return{kind:`deliver`,value:{...i,payloads:[n]}}}if(i.kind===`runtime-action-result`)return{kind:`runtime-action-result`,results:i.results};let a=await runProxyInputRequestStep({hookPayload:i,parentWritable:e.parentWritable,serializedContext:r,sessionState:t,sessionWritable:e.sessionWritable});t=a.sessionState,r=a.serializedContext,await e.rekeyHook(t.continuationToken)}},initialResults:e.initialResults,pendingActionKeys:e.pendingActionKeys});return i===null?null:{results:i,serializedContext:r,sessionState:t}}async function routeDeliverForChildren(e){let t=coalescePayloads(e.payloads);return e.sessionState.hasProxyInputRequests?(await routeProxiedDeliverStep({auth:e.auth,parentWritable:e.parentWritable,payload:t,sessionState:e.sessionState})).remainder:t}async function waitForNextDeliver(e){if(e.bufferedDeliveries.length>0)return coalesceDeliveries(e.bufferedDeliveries.splice(0));for(;;){let t=await e.getNextPromise();if(e.consumeNext(),t.done)return null;if(t.value.kind!==`deliver`)continue;let n=t.value;for(;;){let t=await takeReadyPayload(e.getNextPromise());if(t===NO_READY_MESSAGE||(e.consumeNext(),t.done))break;t.value.kind===`deliver`&&(n=coalesceDeliveries([n,t.value]))}return n}}function coalescePayloads(e){if(e.length===0)return{};if(e.length===1)return e[0]??{};let t={},n=[];for(let r of e){for(let[e,n]of Object.entries(r))e!==`inputResponses`&&n!==void 0&&(t[e]=n);r.inputResponses!==void 0&&n.push(...r.inputResponses)}return n.length>0&&(t.inputResponses=n),t}const NO_READY_MESSAGE=Symbol(`no-ready-message`);async function takeReadyPayload(e){return await Promise.resolve(),await Promise.race([e,Promise.resolve(NO_READY_MESSAGE)])}async function closeHookIterator(e){typeof e.return==`function`&&await e.return(void 0)}async function disposeHook(e){let t=e.dispose;if(typeof t==`function`){await t.call(e);return}let n=e[Symbol.dispose];typeof n==`function`&&await n.call(e)}export{workflowEntry};
1
+ import{ASH_SESSION_STREAM_NAMESPACE}from"#execution/durable-session-store.js";import{readRootSessionId}from"#execution/ash-workflow-attributes.js";import{accumulateRuntimeActionResults}from"#harness/runtime-actions.js";import{resolveVercelProductionCallbackBaseUrl}from"#execution/workflow-callback-url.js";import{normalizeSerializableError,rebuildSerializableError}from"#execution/workflow-errors.js";import{dispatchTurnStep,emitTerminalSessionFailureStep,routeProxiedDeliverStep,runProxyInputRequestStep}from"#execution/workflow-steps.js";import{createHook,getWorkflowMetadata,getWritable}from"#compiled/@workflow/core/index.js";import{coalesceDeliveries}from"#harness/messages.js";import{notifyDelegatedParentStep}from"#execution/delegated-parent-notification.js";import{createDelegatedSubagentErrorResult,createDelegatedSubagentSuccessResult}from"#execution/delegated-parent-result.js";import{createSessionStep}from"#execution/create-session-step.js";import{dispatchRuntimeActionsStep}from"#execution/dispatch-runtime-actions-step.js";import{fireSessionCallbackStep}from"#execution/session-callback-step.js";async function workflowEntry(n){"use workflow";let{workflowRunId:r}=getWorkflowMetadata(),a=n.serializedContext[`ash.continuationToken`]||``,o=n.serializedContext[`ash.mode`],c=n.serializedContext[`ash.capabilities`],l=n.serializedContext[`ash.bundle`];n.serializedContext[`ash.sessionId`]=r;let u=getWritable(),d=getWritable({namespace:ASH_SESSION_STREAM_NAMESPACE});try{let e=readRootSessionId(n.serializedContext),{state:i}=await createSessionStep({compiledArtifactsSource:l.source,continuationToken:a,inputMessage:n.input.message,nodeId:l.nodeId,outputSchema:n.input.outputSchema,rootSessionId:e,serializedContext:n.serializedContext,sessionId:r,sessionWritable:d});return await runDriverLoop({capabilities:c,driverWritable:u,initialInput:{kind:`deliver`,payloads:[{message:n.input.message,context:n.input.context,outputSchema:n.input.outputSchema}]},mode:o,serializedContext:n.serializedContext,sessionState:i,sessionWritable:d})}catch(e){throw await emitTerminalSessionFailureStep({error:normalizeSerializableError(e),parentWritable:u,serializedContext:n.serializedContext}),await fireSessionCallbackStep({error:normalizeSerializableError(e),serializedContext:n.serializedContext,status:`failed`}),await notifyDelegatedParentStep({result:createDelegatedSubagentErrorResult(n.serializedContext,e),serializedContext:n.serializedContext}),e}}async function runDriverLoop(e){let t=createHook({token:`${e.sessionState.sessionId}:auth`}),n=t[Symbol.asyncIterator](),i=e.sessionState.continuationToken,a=createHook({token:i}),o=a[Symbol.asyncIterator](),s=null,c=[],getNextPromise=()=>(s??=o.next(),s),consumeNext=()=>{s=null},rekeyHook=async e=>{e===i||!e||(await closeHookIterator(o),await disposeHook(a),i=e,a=createHook({token:i}),o=a[Symbol.asyncIterator](),s=null)},l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:e.initialInput,mode:e.mode,parentWritable:e.driverWritable,serializedContext:e.serializedContext,sessionState:e.sessionState,sessionWritable:e.sessionWritable});if(l.kind===`done`)return await closeHookIterator(n),await disposeHook(t),await closeHookIterator(o),await disposeHook(a),await finalizeDone({action:l,driverWritable:e.driverWritable});if(!l.sessionState.continuationToken)throw Error("Cannot park: no continuation token available. The channel must post the first message during the initial turn (anchoring the session) or `send()` must be called with an explicit continuationToken.");await rekeyHook(l.sessionState.continuationToken);try{for(;;)switch(l.kind){case`done`:return await finalizeDone({action:l,driverWritable:e.driverWritable});case`dispatch-runtime-actions`:{let t=await dispatchRuntimeActionsStep({callbackBaseUrl:resolveVercelProductionCallbackBaseUrl()??getWorkflowMetadata().url,parentWritable:e.driverWritable,serializedContext:l.serializedContext,sessionState:l.sessionState,sessionWritable:e.sessionWritable}),n=await waitForPendingRuntimeActionResults({bufferedDeliveries:c,consumeNext,getNextPromise,initialResults:t.results,parentWritable:e.driverWritable,pendingActionKeys:l.pendingActionKeys,rekeyHook,serializedContext:l.serializedContext,sessionState:t.sessionState,sessionWritable:e.sessionWritable});if(n===null)return{output:``};l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:{kind:`runtime-action-result`,results:n.results},mode:e.mode,parentWritable:e.driverWritable,serializedContext:n.serializedContext,sessionState:n.sessionState,sessionWritable:e.sessionWritable}),await rekeyHook(l.sessionState.continuationToken);break}case`park`:{if(l.authorizationNames&&l.authorizationNames.length>0){let t=l.authorizationNames.length,r=[];for(;r.length<t;){let e=await n.next();if(e.done)break;e.value.kind===`deliver`&&r.push(...e.value.payloads)}l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:{kind:`deliver`,payloads:r},mode:e.mode,parentWritable:e.driverWritable,serializedContext:l.serializedContext,sessionState:l.sessionState,sessionWritable:e.sessionWritable}),await rekeyHook(l.sessionState.continuationToken);break}let t=await waitForNextDeliver({bufferedDeliveries:c,consumeNext,getNextPromise});if(t===null)return{output:``};let r=await routeDeliverForChildren({auth:t.auth,parentWritable:e.driverWritable,payloads:t.payloads,sessionState:l.sessionState});if(r===void 0)continue;l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:{auth:t.auth,kind:`deliver`,payloads:[r]},mode:e.mode,parentWritable:e.driverWritable,serializedContext:l.serializedContext,sessionState:l.sessionState,sessionWritable:e.sessionWritable}),await rekeyHook(l.sessionState.continuationToken);break}}}finally{await closeHookIterator(o),await disposeHook(a),await closeHookIterator(n),await disposeHook(t)}}async function finalizeDone(e){let{output:t,serializedContext:n}=e.action,r=e.action.isError===!0;return await fireSessionCallbackStep({error:r?t:void 0,output:r?void 0:t,serializedContext:n,status:r?`failed`:`completed`}),await notifyDelegatedParentStep({result:r?createDelegatedSubagentErrorResult(n,t):createDelegatedSubagentSuccessResult(n,t),serializedContext:n}),{output:t}}async function dispatchAndAwaitTurn(e){let t=createHook(),n=t.token;try{await dispatchTurnStep({capabilities:e.capabilities,completionToken:n,delivery:e.delivery,mode:e.mode,parentWritable:e.parentWritable,serializedContext:e.serializedContext,sessionState:e.sessionState,sessionWritable:e.sessionWritable});let r=await awaitHookPayload(t);if(r.kind===`turn-error`)throw rebuildSerializableError(r.error);return r.action}finally{await disposeHook(t)}}async function awaitHookPayload(e){for await(let t of e)return t;throw Error(`Turn completion hook closed before delivering a result.`)}async function waitForPendingRuntimeActionResults(e){let t=e.sessionState,r=e.serializedContext,i=await accumulateRuntimeActionResults({bufferedDeliveries:e.bufferedDeliveries,async getNext(){for(;;){let n=await e.getNextPromise();if(e.consumeNext(),n.done)return null;let i=n.value;if(i.kind===`deliver`){let n=await routeDeliverForChildren({auth:i.auth,parentWritable:e.parentWritable,payloads:i.payloads,sessionState:t});if(n===void 0)continue;return{kind:`deliver`,value:{...i,payloads:[n]}}}if(i.kind===`runtime-action-result`)return{kind:`runtime-action-result`,results:i.results};let a=await runProxyInputRequestStep({hookPayload:i,parentWritable:e.parentWritable,serializedContext:r,sessionState:t,sessionWritable:e.sessionWritable});t=a.sessionState,r=a.serializedContext,await e.rekeyHook(t.continuationToken)}},initialResults:e.initialResults,pendingActionKeys:e.pendingActionKeys});return i===null?null:{results:i,serializedContext:r,sessionState:t}}async function routeDeliverForChildren(e){let t=coalescePayloads(e.payloads);return e.sessionState.hasProxyInputRequests?(await routeProxiedDeliverStep({auth:e.auth,parentWritable:e.parentWritable,payload:t,sessionState:e.sessionState})).remainder:t}async function waitForNextDeliver(e){if(e.bufferedDeliveries.length>0)return coalesceDeliveries(e.bufferedDeliveries.splice(0));for(;;){let t=await e.getNextPromise();if(e.consumeNext(),t.done)return null;if(t.value.kind!==`deliver`)continue;let n=t.value;for(;;){let t=await takeReadyPayload(e.getNextPromise());if(t===NO_READY_MESSAGE||(e.consumeNext(),t.done))break;t.value.kind===`deliver`&&(n=coalesceDeliveries([n,t.value]))}return n}}function coalescePayloads(e){if(e.length===0)return{};if(e.length===1)return e[0]??{};let t={},n=[];for(let r of e){for(let[e,n]of Object.entries(r))e!==`inputResponses`&&n!==void 0&&(t[e]=n);r.inputResponses!==void 0&&n.push(...r.inputResponses)}return n.length>0&&(t.inputResponses=n),t}const NO_READY_MESSAGE=Symbol(`no-ready-message`);async function takeReadyPayload(e){return await Promise.resolve(),await Promise.race([e,Promise.resolve(NO_READY_MESSAGE)])}async function closeHookIterator(e){typeof e.return==`function`&&await e.return(void 0)}async function disposeHook(e){let t=e.dispose;if(typeof t==`function`){await t.call(e);return}let n=e[Symbol.dispose];typeof n==`function`&&await n.call(e)}export{workflowEntry};
@@ -1 +1 @@
1
- import{DynamicToolsKey}from"#context/keys.js";import{contextStorage}from"#context/container.js";import"ai";import{CODE_MODE_TOOL_NAME,loadCodeModeModule}from"#shared/code-mode.js";import{isAuthorizationSignal}from"#harness/authorization.js";import{CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,markCodeModeToolExecutionOptions,toCodeModeConnectionAuthArgs}from"#runtime/framework-tools/code-mode-connection-auth.js";import{buildToolSet}from"#harness/tools.js";function createAshCodeModeOptions(e={}){let t={approval:{mode:`interrupt`}};return e.lifecycle!==void 0&&(t.lifecycle=e.lifecycle),t}async function applyCodeModeToToolSet(e){let t={},i={};for(let[n,r]of Object.entries(e.tools)){if(isDirectTool(r,e.harnessTools.get(n))){i[n]=r;continue}t[n]=wrapHostToolForCodeMode(r)}if(Object.keys(t).length>0){let{createCodeModeTool:a}=await loadCodeModeModule();i[CODE_MODE_TOOL_NAME]=a(t,createAshCodeModeOptions({lifecycle:e.lifecycle}))}return{hostTools:t,modelTools:i}}async function buildCodeModeHostTools(n){let r=buildToolSet({approvedTools:n.approvedTools,capabilities:n.capabilities,tools:n.tools}),i=contextStorage.getStore();if(i!==void 0){let t=i.get(DynamicToolsKey);if(t!==void 0)for(let e of t)r[e.name]??={description:e.description,inputSchema:e.inputSchema,execute:e.execute}}return(await applyCodeModeToToolSet({harnessTools:n.tools,tools:r})).hostTools}function isDirectTool(e,t){return e.execute===void 0||t?.runtimeAction!==void 0}function wrapHostToolForCodeMode(e){let t=e.execute,n=e.toModelOutput;return t===void 0?e:{...e,execute:async(e,o)=>{let s=await resolveExecuteOutput(t(e,markCodeModeToolExecutionOptions(o)));if(isAuthorizationSignal(s)){let{requestCodeModeInterrupt:t}=await loadCodeModeModule(),n=s.challenges[0]?.name;n&&t({args:toCodeModeConnectionAuthArgs(e),challenges:s.challenges,connectionName:n,kind:CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,toolName:``})}if(n===void 0)return s;let c=await n({output:s});return isModelOutput(c)?c.value:c}}}async function resolveExecuteOutput(e){if(isAsyncIterable(e)){let t;for await(let n of e)t=n;return t}return await e}function isAsyncIterable(e){return typeof e==`object`&&!!e&&Symbol.asyncIterator in e&&typeof e[Symbol.asyncIterator]==`function`}function isModelOutput(e){if(typeof e!=`object`||!e)return!1;let t=e;return(t.type===`json`||t.type===`text`)&&Object.hasOwn(t,`value`)}export{applyCodeModeToToolSet,buildCodeModeHostTools,createAshCodeModeOptions};
1
+ import{contextStorage}from"#context/container.js";import"ai";import{CODE_MODE_TOOL_NAME,loadCodeModeModule}from"#shared/code-mode.js";import{isAuthorizationSignal}from"#harness/authorization.js";import{CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,markCodeModeToolExecutionOptions,toCodeModeConnectionAuthArgs}from"#runtime/framework-tools/code-mode-connection-auth.js";import{buildDynamicTools}from"#context/build-dynamic-tools.js";import{buildToolSet}from"#harness/tools.js";function createAshCodeModeOptions(e={}){let t={approval:{mode:`interrupt`}};return e.lifecycle!==void 0&&(t.lifecycle=e.lifecycle),t}async function applyCodeModeToToolSet(e){let r={},i={};for(let[t,n]of Object.entries(e.tools)){if(isDirectTool(n,e.harnessTools.get(t))){i[t]=n;continue}r[t]=wrapHostToolForCodeMode(n)}if(Object.keys(r).length>0){let{createCodeModeTool:a}=await loadCodeModeModule();i[CODE_MODE_TOOL_NAME]=a(r,createAshCodeModeOptions({lifecycle:e.lifecycle}))}return{hostTools:r,modelTools:i}}async function buildCodeModeHostTools(t){let n=buildToolSet({approvedTools:t.approvedTools,capabilities:t.capabilities,tools:t.tools}),r=contextStorage.getStore();if(r!==void 0){let e=buildDynamicTools(r);for(let t of e)n[t.name]??={description:t.description,inputSchema:t.inputSchema,execute:t.execute}}return(await applyCodeModeToToolSet({harnessTools:t.tools,tools:n})).hostTools}function isDirectTool(e,t){return e.execute===void 0||t?.runtimeAction!==void 0}function wrapHostToolForCodeMode(e){let t=e.execute,o=e.toModelOutput;return t===void 0?e:{...e,execute:async(e,s)=>{let c=await resolveExecuteOutput(t(e,markCodeModeToolExecutionOptions(s)));if(isAuthorizationSignal(c)){let{requestCodeModeInterrupt:t}=await loadCodeModeModule(),r=c.challenges[0]?.name;r&&t({args:toCodeModeConnectionAuthArgs(e),challenges:c.challenges,connectionName:r,kind:CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,toolName:``})}if(o===void 0)return c;let l=await o({output:c});return isModelOutput(l)?l.value:l}}}async function resolveExecuteOutput(e){if(isAsyncIterable(e)){let t;for await(let n of e)t=n;return t}return await e}function isAsyncIterable(e){return typeof e==`object`&&!!e&&Symbol.asyncIterator in e&&typeof e[Symbol.asyncIterator]==`function`}function isModelOutput(e){if(typeof e!=`object`||!e)return!1;let t=e;return(t.type===`json`||t.type===`text`)&&Object.hasOwn(t,`value`)}export{applyCodeModeToToolSet,buildCodeModeHostTools,createAshCodeModeOptions};