experimental-ash 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.54.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8534548: Expose channel `metadata(state)` projection on `DynamicResolveContext.channel.metadata` so dynamic tool resolvers can read channel-produced context. Forward the parent's channel instrumentation projection to subagents via `RunInput` so child agents see the originating channel's kind and metadata.
8
+
9
+ ### Patch Changes
10
+
11
+ - d48f2a6: Enforce connection-level `approval` on connection tools. A connection's
12
+ `approval` callback was previously stored but never consulted, so a
13
+ connection tool would run without the user approval its definition
14
+ required. Discovered connection tools now carry that callback through to
15
+ the harness so `needsApproval` is evaluated before the call executes.
16
+
17
+ `DynamicToolEntry` gains an optional `needsApproval` field and the dynamic
18
+ tool materializer propagates it into the harness tool definition. This is
19
+ honored for step-scoped dynamic tools (whose live `execute` closures reach
20
+ the harness), which is the scope connection tools use; session/turn-scoped
21
+ dynamic tools replay from durable metadata and cannot carry a function
22
+ across replay.
23
+
3
24
  ## 0.53.0
4
25
 
5
26
  ### Minor Changes
@@ -103,7 +103,20 @@ Ash uses three nested concepts:
103
103
  That is why tools, the sandbox, and subagents feel synchronous from authored code even though the
104
104
  overall session remains durable.
105
105
 
106
- Important behavior:
106
+ ### Event dispatch order
107
+
108
+ When the framework emits a stream event (e.g. `turn.started`, `session.started`), four
109
+ things happen in sequence:
110
+
111
+ 1. **Channel adapter handler** — the channel's event handler runs and can mutate adapter state.
112
+ 2. **Metadata projection** — the framework re-evaluates the channel's `metadata(state)` and stores the result.
113
+ 3. **Hook event handlers** — authored hooks subscribed to the event fire.
114
+ 4. **Dynamic resolvers** — dynamic tool, skill, and instruction resolvers subscribed to the event fire. `ctx.channel.metadata` contains the freshly projected metadata from step 2.
115
+
116
+ This ordering is structural. By the time a resolver or hook reads channel metadata, the
117
+ channel has already updated its state and the projection is current.
118
+
119
+ ### Important behavior
107
120
 
108
121
  - follow-up messages reuse the same session through the continuation token
109
122
  - schedules without a channel run in task mode and do not wait for follow-up messages; channel-targeted schedules inherit the channel's mode
@@ -461,6 +461,68 @@ user messages in session history. See
461
461
  thread, or `initialMessage` to anchor a new one. With neither, the first agent post anchors
462
462
  the thread automatically.
463
463
 
464
+ ## Channel Metadata
465
+
466
+ Channels can project a subset of their adapter state as **metadata** available to
467
+ instrumentation resolvers, dynamic tool resolvers, and dynamic skill/instruction resolvers.
468
+ Define a `metadata(state)` function on the channel config:
469
+
470
+ ```ts
471
+ import { defineChannel, POST } from "experimental-ash/channels";
472
+
473
+ export default defineChannel({
474
+ state: {
475
+ topic: null as string | null,
476
+ contextMessages: [] as string[],
477
+ internalCounter: 0,
478
+ },
479
+
480
+ metadata(state) {
481
+ // Curated projection — internalCounter is withheld.
482
+ return {
483
+ topic: state.topic,
484
+ contextMessages: state.contextMessages,
485
+ };
486
+ },
487
+
488
+ routes: [
489
+ POST("/start", async (req, { send }) => {
490
+ const body = await req.json();
491
+ await send(body.message, {
492
+ auth: null,
493
+ continuationToken: body.token,
494
+ state: { topic: body.topic, contextMessages: body.context, internalCounter: 0 },
495
+ });
496
+ return new Response("ok");
497
+ }),
498
+ ],
499
+ events: {
500
+ "turn.started"(_event, ctx) {
501
+ ctx.state.internalCounter += 1;
502
+ },
503
+ },
504
+ });
505
+ ```
506
+
507
+ The projection is re-evaluated whenever adapter state changes (after channel event handlers
508
+ run). Dynamic tool resolvers read it via `ctx.channel.metadata` and narrow it with
509
+ `isChannel`. See [Dynamic Tools — Channel Metadata](../tools.mdx#channel-metadata) for the
510
+ full consumption pattern.
511
+
512
+ ### Subagent propagation
513
+
514
+ When a parent agent dispatches a subagent, the framework forwards the parent's channel
515
+ metadata projection to the child. The subagent's dynamic tool resolvers see the originating
516
+ channel's `kind` and `metadata` — not `kind: "subagent"` with empty metadata. This lets
517
+ subagents conditionally resolve tools based on which channel is driving the conversation.
518
+
519
+ ### Shared with instrumentation
520
+
521
+ The same `metadata(state)` projector serves both dynamic resolvers and the instrumentation
522
+ `metadata["step.started"]` resolver. Each consumer picks what it needs: instrumentation
523
+ flattens to `Record<string, string>` for OTel span attributes; tool resolvers use the full
524
+ structured object.
525
+
464
526
  ## Continuation Tokens
465
527
 
466
528
  Each call to `send(message, { auth, continuationToken, state? })` from a channel route
@@ -427,6 +427,36 @@ Dynamic tool names are derived from the file path and the definition function:
427
427
 
428
428
  `defineDynamic` with a single return always produces one tool named after the file slug — identical to static tools. `defineDynamic` with a map return always uses `slug__key`, even when the resolver returns a single entry. This keeps names stable: adding a second entry to a `defineDynamic` file does not rename the first.
429
429
 
430
+ ### Channel Metadata
431
+
432
+ Dynamic resolvers receive `ctx.channel.metadata` — the channel's `metadata(state)` projection. Use `isChannel` with the channel import to narrow metadata to its declared shape:
433
+
434
+ ```ts
435
+ import { isChannel } from "experimental-ash/channels";
436
+ import supportChannel from "../channels/support.js";
437
+
438
+ export default defineDynamic({
439
+ events: {
440
+ "turn.started": (_event, ctx) => {
441
+ if (!isChannel(ctx.channel, supportChannel)) return null;
442
+
443
+ const { topic, contextMessages } = ctx.channel.metadata;
444
+ if (!topic) return null;
445
+
446
+ return defineTool({
447
+ description: `Answer questions about ${topic}.`,
448
+ inputSchema: z.object({ question: z.string() }),
449
+ async execute(input) {
450
+ return lookupAnswer(topic, input.question);
451
+ },
452
+ });
453
+ },
454
+ },
455
+ });
456
+ ```
457
+
458
+ The metadata is available in subagents too — the framework forwards the parent's channel metadata projection so the same tool file works in both the root agent and its subagents. See [Channel Metadata](/channels#channel-metadata) for the full pattern including execution order.
459
+
430
460
  ### Events
431
461
 
432
462
  The `events` object supports three event types:
@@ -7,6 +7,7 @@ import type { ChannelAdapter } from "#channel/adapter.js";
7
7
  import type { JsonObject } from "#shared/json.js";
8
8
  export type { ContextAccessor } from "#context/key.js";
9
9
  export type { ChannelInstrumentationProjection } from "#channel/instrumentation.js";
10
+ import type { ChannelInstrumentationProjection } from "#channel/instrumentation.js";
10
11
  /**
11
12
  * Identifies one turn within a session.
12
13
  */
@@ -184,6 +185,7 @@ export interface RunInput {
184
185
  * adapter kind (`http`, `schedule`, `subagent`) directly.
185
186
  */
186
187
  readonly channelName?: string;
188
+ readonly channelMetadata?: ChannelInstrumentationProjection;
187
189
  /**
188
190
  * Authenticated caller principal for this session. `null` means the
189
191
  * request was accepted with no credentials.
@@ -1 +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
+ import{getAdapterKind}from"#channel/adapter.js";import{AuthKey,ChannelInstrumentationKey,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),s=e.get(ChannelInstrumentationKey);return{session:{id:n,auth:{current:r,initiator:i}},channel:{kind:a===void 0?void 0:getAdapterKind(a),continuationToken:o,metadata:s?.metadata},messages:t}}export{buildResolveContext};
@@ -1 +1 @@
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 getStepRegistry(){let e=Symbol.for(`@workflow/core//registeredSteps`),t=globalThis,n=t[e];return n===void 0&&(n=new Map,t[e]=n),n}function lookupStepFunction(e){try{return getStepRegistry().get(e)||null}catch{return null}}function registerStepFunction(e,t){getStepRegistry().set(e,t)}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,o=i?.stepId,l=a===void 0?void 0:safeSerialize(a);if(o===void 0){let e=`ash:framework-dynamic:${t.slug}:${n}`,i=r.execute.bind(r);registerStepFunction(e,(e,t,n)=>i(t,n)),o=e,l={}}s.push({name:e,description:r.description,inputSchema:normalizeJsonSchemaDefinition(r.inputSchema),resolverSlug:t.slug,entryKey:n,executeStepFnName:o,closureVars:l})}}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};
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,needsApproval:t.needsApproval,...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 getStepRegistry(){let e=Symbol.for(`@workflow/core//registeredSteps`),t=globalThis,n=t[e];return n===void 0&&(n=new Map,t[e]=n),n}function lookupStepFunction(e){try{return getStepRegistry().get(e)||null}catch{return null}}function registerStepFunction(e,t){getStepRegistry().set(e,t)}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,o=i?.stepId,l=a===void 0?void 0:safeSerialize(a);if(o===void 0){let e=`ash:framework-dynamic:${t.slug}:${n}`,i=r.execute.bind(r);registerStepFunction(e,(e,t,n)=>i(t,n)),o=e,l={}}s.push({name:e,description:r.description,inputSchema:normalizeJsonSchemaDefinition(r.inputSchema),resolverSlug:t.slug,entryKey:n,executeStepFnName:o,closureVars:l})}}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};
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{callAdapterEventHandler}from"#channel/adapter.js";import{AuthKey,CapabilitiesKey,InitiatorAuthKey}from"#context/keys.js";import{createSubagentCalledEvent,encodeMessageStreamEvent,timestampHandleMessageStreamEvent}from"#protocol/message.js";import{toErrorMessage}from"#shared/errors.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{createDurableSessionState,readDurableSession}from"#execution/durable-session-store.js";import{hydrateDurableSession}from"#execution/session.js";import{deserializeContext}from"#context/serialize.js";import{buildAdapterContext}from"#channel/adapter-context.js";import{getPendingRuntimeActionBatch,recordPendingSubagentChildToken}from"#harness/runtime-actions.js";import{resolveRemoteAgentForAction,startRemoteAgentSession}from"#execution/remote-agent-dispatch.js";import{buildSubagentRunInput}from"#execution/subagent-tool.js";import{createWorkflowRuntime,workflowEntryReference}from"#execution/workflow-runtime.js";const log=createLogger(`execution.dispatch-runtime-actions`);async function dispatchRuntimeActionsStep(e){"use step";let s=await readDurableSession(e.sessionState),u=getPendingRuntimeActionBatch(s.state);if(u===void 0||u.actions.length===0)return{results:[],sessionState:e.sessionState};let d=await deserializeContext(e.serializedContext),f=d.require(BundleKey),p=hydrateDurableSession({compactionOverrides:{thresholdPercent:f.resolvedAgent.config.compaction?.thresholdPercent},durable:s,turnAgent:f.turnAgent}),m=d.require(ChannelKey),h=d.get(AuthKey)??null,g=d.get(CapabilitiesKey),_=d.get(InitiatorAuthKey)??null,v=e.parentWritable.getWriter(),y=buildAdapterContext(m,d),b=p,x=[];try{for(let r of u.actions){let i,a,s,c;switch(r.kind){case`subagent-call`:{let e=createWorkflowRuntime({compiledArtifactsSource:f.compiledArtifactsSource,nodeId:r.nodeId}),{childContinuationToken:t,runInput:n}=buildSubagentRunInput({action:r,auth:h,batchEvent:u.event,capabilities:g,initiatorAuth:_,session:p}),o=await e.run(n);b=recordPendingSubagentChildToken({callId:r.callId,childContinuationToken:t,session:b}),i=o.sessionId,a=r.name,c=r.subagentName;break}case`remote-agent-call`:{let n;try{n=resolveRemoteAgentForAction({nodeId:r.nodeId,remoteAgentName:r.remoteAgentName,registry:f.subagentRegistry.subagentsByNodeId}),i=await startRemoteAgentSession({action:r,callbackBaseUrl:e.callbackBaseUrl,remote:n,session:p})}catch(e){logError(log,`remote agent start failed`,e,{remoteAgentName:r.remoteAgentName,nodeId:r.nodeId,callId:r.callId}),x.push(createRemoteAgentStartFailureResult({action:r,error:e}));continue}a=r.name,s={url:n.url},c=r.remoteAgentName;break}default:throw Error(`Unsupported runtime action kind "${r.kind}" in workflow runtime.`)}let l=await callAdapterEventHandler(m,createSubagentCalledEvent({callId:r.callId,childSessionId:i,name:a,remote:s,sequence:u.event.sequence,sessionId:p.sessionId,toolName:c,turnId:u.event.turnId,workflowId:workflowEntryReference.workflowId}),y);await v.write(encodeMessageStreamEvent(timestampHandleMessageStreamEvent(l)))}}finally{v.releaseLock()}return{results:x,sessionState:b===p?e.sessionState:createDurableSessionState({session:b})}}function createRemoteAgentStartFailureResult(e){return{callId:e.action.callId,isError:!0,kind:`subagent-result`,output:{code:`REMOTE_AGENT_START_FAILED`,message:toErrorMessage(e.error)},subagentName:e.action.remoteAgentName}}export{dispatchRuntimeActionsStep};
1
+ import{createLogger,logError}from"#internal/logging.js";import{callAdapterEventHandler}from"#channel/adapter.js";import{AuthKey,CapabilitiesKey,ChannelInstrumentationKey,InitiatorAuthKey}from"#context/keys.js";import{createSubagentCalledEvent,encodeMessageStreamEvent,timestampHandleMessageStreamEvent}from"#protocol/message.js";import{toErrorMessage}from"#shared/errors.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{createDurableSessionState,readDurableSession}from"#execution/durable-session-store.js";import{hydrateDurableSession}from"#execution/session.js";import{deserializeContext}from"#context/serialize.js";import{buildAdapterContext}from"#channel/adapter-context.js";import{getPendingRuntimeActionBatch,recordPendingSubagentChildToken}from"#harness/runtime-actions.js";import{resolveRemoteAgentForAction,startRemoteAgentSession}from"#execution/remote-agent-dispatch.js";import{buildSubagentRunInput}from"#execution/subagent-tool.js";import{createWorkflowRuntime,workflowEntryReference}from"#execution/workflow-runtime.js";const log=createLogger(`execution.dispatch-runtime-actions`);async function dispatchRuntimeActionsStep(e){"use step";let s=await readDurableSession(e.sessionState),l=getPendingRuntimeActionBatch(s.state);if(l===void 0||l.actions.length===0)return{results:[],sessionState:e.sessionState};let u=await deserializeContext(e.serializedContext),d=u.require(BundleKey),f=hydrateDurableSession({compactionOverrides:{thresholdPercent:d.resolvedAgent.config.compaction?.thresholdPercent},durable:s,turnAgent:d.turnAgent}),p=u.require(ChannelKey),m=u.get(AuthKey)??null,h=u.get(CapabilitiesKey),g=u.get(ChannelInstrumentationKey),_=u.get(InitiatorAuthKey)??null,v=e.parentWritable.getWriter(),y=buildAdapterContext(p,u),b=f,x=[];try{for(let r of l.actions){let i,a,o,s;switch(r.kind){case`subagent-call`:{let e=createWorkflowRuntime({compiledArtifactsSource:d.compiledArtifactsSource,nodeId:r.nodeId}),{childContinuationToken:t,runInput:n}=buildSubagentRunInput({action:r,auth:m,batchEvent:l.event,capabilities:h,channelMetadata:g,initiatorAuth:_,session:f}),o=await e.run(n);b=recordPendingSubagentChildToken({callId:r.callId,childContinuationToken:t,session:b}),i=o.sessionId,a=r.name,s=r.subagentName;break}case`remote-agent-call`:{let n;try{n=resolveRemoteAgentForAction({nodeId:r.nodeId,remoteAgentName:r.remoteAgentName,registry:d.subagentRegistry.subagentsByNodeId}),i=await startRemoteAgentSession({action:r,callbackBaseUrl:e.callbackBaseUrl,remote:n,session:f})}catch(e){logError(log,`remote agent start failed`,e,{remoteAgentName:r.remoteAgentName,nodeId:r.nodeId,callId:r.callId}),x.push(createRemoteAgentStartFailureResult({action:r,error:e}));continue}a=r.name,o={url:n.url},s=r.remoteAgentName;break}default:throw Error(`Unsupported runtime action kind "${r.kind}" in workflow runtime.`)}let c=await callAdapterEventHandler(p,createSubagentCalledEvent({callId:r.callId,childSessionId:i,name:a,remote:o,sequence:l.event.sequence,sessionId:f.sessionId,toolName:s,turnId:l.event.turnId,workflowId:workflowEntryReference.workflowId}),y);await v.write(encodeMessageStreamEvent(timestampHandleMessageStreamEvent(c)))}}finally{v.releaseLock()}return{results:x,sessionState:b===f?e.sessionState:createDurableSessionState({session:b})}}function createRemoteAgentStartFailureResult(e){return{callId:e.action.callId,isError:!0,kind:`subagent-result`,output:{code:`REMOTE_AGENT_START_FAILED`,message:toErrorMessage(e.error)},subagentName:e.action.remoteAgentName}}export{dispatchRuntimeActionsStep};
@@ -1 +1 @@
1
- import{AuthKey,CapabilitiesKey,ContinuationTokenKey,InitiatorAuthKey,ModeKey,ParentSessionKey,SessionCallbackKey}from"#context/keys.js";import{ContextContainer}from"#context/container.js";import{BundleKey}from"#runtime/sessions/runtime-context-keys.js";import{setChannelContext}from"#execution/channel-context.js";function buildRunContext(e){let{bundle:t,run:n}=e,r=new ContextContainer,i=n.auth;return r.set(BundleKey,t),setChannelContext(r,n.adapter,{channelName:n.channelName}),r.set(ContinuationTokenKey,n.continuationToken??``),r.set(ModeKey,n.mode),r.set(AuthKey,i),r.set(InitiatorAuthKey,n.initiatorAuth??i),n.capabilities!==void 0&&r.set(CapabilitiesKey,n.capabilities),n.callback!==void 0&&r.set(SessionCallbackKey,n.callback),n.parent!==void 0&&r.set(ParentSessionKey,n.parent),r}export{buildRunContext};
1
+ import{AuthKey,CapabilitiesKey,ChannelInstrumentationKey,ContinuationTokenKey,InitiatorAuthKey,ModeKey,ParentSessionKey,SessionCallbackKey}from"#context/keys.js";import{ContextContainer}from"#context/container.js";import{BundleKey}from"#runtime/sessions/runtime-context-keys.js";import{setChannelContext}from"#execution/channel-context.js";function buildRunContext(t){let{bundle:n,run:r}=t,i=new ContextContainer,a=r.auth;if(i.set(BundleKey,n),setChannelContext(i,r.adapter,{channelName:r.channelName}),r.channelMetadata!==void 0){let e=i.get(ChannelInstrumentationKey);i.set(ChannelInstrumentationKey,{kind:e?.kind??r.channelMetadata.kind,metadata:r.channelMetadata.metadata})}return i.set(ContinuationTokenKey,r.continuationToken??``),i.set(ModeKey,r.mode),i.set(AuthKey,a),i.set(InitiatorAuthKey,r.initiatorAuth??a),r.capabilities!==void 0&&i.set(CapabilitiesKey,r.capabilities),r.callback!==void 0&&i.set(SessionCallbackKey,r.callback),r.parent!==void 0&&i.set(ParentSessionKey,r.parent),i}export{buildRunContext};
@@ -1,4 +1,4 @@
1
- import type { RunInput, SessionAuthContext, SessionCapabilities } from "#channel/types.js";
1
+ import type { ChannelInstrumentationProjection, RunInput, SessionAuthContext, SessionCapabilities } from "#channel/types.js";
2
2
  import type { HarnessSession } from "#harness/types.js";
3
3
  import type { RuntimeSubagentCallActionRequest } from "#runtime/actions/types.js";
4
4
  /**
@@ -32,6 +32,7 @@ export declare function buildSubagentRunInput(input: {
32
32
  * parent capabilities produce an undefined child capability set.
33
33
  */
34
34
  readonly capabilities?: SessionCapabilities;
35
+ readonly channelMetadata?: ChannelInstrumentationProjection;
35
36
  readonly initiatorAuth: SessionAuthContext | null;
36
37
  readonly session: HarnessSession;
37
38
  }): SubagentRunInputBuild;
@@ -1 +1 @@
1
- import{mintSubagentContinuationToken}from"#execution/session.js";import{SUBAGENT_ADAPTER_KIND}from"#execution/subagent-adapter.js";import{formatSubagentInvocation}from"#execution/subagent-invocation.js";function buildSubagentRunInput(n){let{action:r,auth:i,batchEvent:a,capabilities:o,initiatorAuth:s,session:c}=n,l=mintSubagentContinuationToken(`${c.sessionId}:${r.callId}`),u=c.rootSessionId??c.sessionId;return{childContinuationToken:l,runInput:{adapter:{kind:SUBAGENT_ADAPTER_KIND,state:{callId:r.callId,parentContinuationToken:c.continuationToken,parentSessionId:c.sessionId,subagentName:r.subagentName}},auth:i,capabilities:o,continuationToken:l,initiatorAuth:s,input:{message:formatSubagentCallInputMessage(r)},mode:`task`,parent:{callId:r.callId,rootSessionId:u,sessionId:c.sessionId,turn:{id:a.turnId,sequence:a.sequence}}}}}function formatSubagentCallInputMessage(e){let{message:t}=e.input;return formatSubagentInvocation({description:e.description,message:t,name:e.subagentName}).message}export{buildSubagentRunInput};
1
+ import{mintSubagentContinuationToken}from"#execution/session.js";import{SUBAGENT_ADAPTER_KIND}from"#execution/subagent-adapter.js";import{formatSubagentInvocation}from"#execution/subagent-invocation.js";function buildSubagentRunInput(n){let{action:r,auth:i,batchEvent:a,capabilities:o,channelMetadata:s,initiatorAuth:c,session:l}=n,u=mintSubagentContinuationToken(`${l.sessionId}:${r.callId}`),d=l.rootSessionId??l.sessionId;return{childContinuationToken:u,runInput:{adapter:{kind:SUBAGENT_ADAPTER_KIND,state:{callId:r.callId,parentContinuationToken:l.continuationToken,parentSessionId:l.sessionId,subagentName:r.subagentName}},auth:i,capabilities:o,channelMetadata:s,continuationToken:u,initiatorAuth:c,input:{message:formatSubagentCallInputMessage(r)},mode:`task`,parent:{callId:r.callId,rootSessionId:d,sessionId:l.sessionId,turn:{id:a.turnId,sequence:a.sequence}}}}}function formatSubagentCallInputMessage(e){let{message:t}=e.input;return formatSubagentInvocation({description:e.description,message:t,name:e.subagentName}).message}export{buildSubagentRunInput};
@@ -1 +1 @@
1
- import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.53.0`}const FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
1
+ import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.54.0`}const FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
@@ -1,4 +1,4 @@
1
- import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.53.0`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.159`,nextPackageVersion:e?.nextPackageVersion??`16.2.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,tsgoPackageVersion:e?.tsgoPackageVersion??`7.0.0-dev.20260523.1`,typesNodePackageVersion:e?.typesNodePackageVersion??`25.9.1`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatAshDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(e,t){if(!await pathExists(e))return[];assertStampedVersion(`ashPackageVersion`,t.ashPackageVersion),assertStampedVersion(`aiPackageVersion`,t.aiPackageVersion),assertStampedVersion(`nextPackageVersion`,t.nextPackageVersion),assertStampedVersion(`reactPackageVersion`,t.reactPackageVersion),assertStampedVersion(`reactDomPackageVersion`,t.reactDomPackageVersion),assertStampedVersion(`streamdownPackageVersion`,t.streamdownPackageVersion),assertStampedVersion(`zodPackageVersion`,t.zodPackageVersion),assertStampedVersion(`tsgoPackageVersion`,t.tsgoPackageVersion),assertStampedVersion(`typesNodePackageVersion`,t.typesNodePackageVersion),assertStampedVersion(`typesReactPackageVersion`,t.typesReactPackageVersion),assertStampedVersion(`typesReactDomPackageVersion`,t.typesReactDomPackageVersion);let r={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:t.aiPackageVersion,"experimental-ash":formatAshDependencySpecifier(t.ashPackageVersion),next:t.nextPackageVersion,react:t.reactPackageVersion,"react-dom":t.reactDomPackageVersion,streamdown:t.streamdownPackageVersion,zod:t.zodPackageVersion},i={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":t.typesNodePackageVersion,"@types/react":t.typesReactPackageVersion,"@types/react-dom":t.typesReactDomPackageVersion,"@typescript/native-preview":t.tsgoPackageVersion},a=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts;return await patchPackageJson(e,{dependencies:r,devDependencies:i,scripts:a}),[{path:e,dependencies:Object.keys(r),devDependencies:Object.keys(i),scripts:Object.keys(a)}]}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){return`import { connectSlackCredentials } from "@vercel/connect/ash";
1
+ import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.54.0`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.159`,nextPackageVersion:e?.nextPackageVersion??`16.2.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,tsgoPackageVersion:e?.tsgoPackageVersion??`7.0.0-dev.20260523.1`,typesNodePackageVersion:e?.typesNodePackageVersion??`25.9.1`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatAshDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(e,t){if(!await pathExists(e))return[];assertStampedVersion(`ashPackageVersion`,t.ashPackageVersion),assertStampedVersion(`aiPackageVersion`,t.aiPackageVersion),assertStampedVersion(`nextPackageVersion`,t.nextPackageVersion),assertStampedVersion(`reactPackageVersion`,t.reactPackageVersion),assertStampedVersion(`reactDomPackageVersion`,t.reactDomPackageVersion),assertStampedVersion(`streamdownPackageVersion`,t.streamdownPackageVersion),assertStampedVersion(`zodPackageVersion`,t.zodPackageVersion),assertStampedVersion(`tsgoPackageVersion`,t.tsgoPackageVersion),assertStampedVersion(`typesNodePackageVersion`,t.typesNodePackageVersion),assertStampedVersion(`typesReactPackageVersion`,t.typesReactPackageVersion),assertStampedVersion(`typesReactDomPackageVersion`,t.typesReactDomPackageVersion);let r={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:t.aiPackageVersion,"experimental-ash":formatAshDependencySpecifier(t.ashPackageVersion),next:t.nextPackageVersion,react:t.reactPackageVersion,"react-dom":t.reactDomPackageVersion,streamdown:t.streamdownPackageVersion,zod:t.zodPackageVersion},i={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":t.typesNodePackageVersion,"@types/react":t.typesReactPackageVersion,"@types/react-dom":t.typesReactDomPackageVersion,"@typescript/native-preview":t.tsgoPackageVersion},a=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts;return await patchPackageJson(e,{dependencies:r,devDependencies:i,scripts:a}),[{path:e,dependencies:Object.keys(r),devDependencies:Object.keys(i),scripts:Object.keys(a)}]}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){return`import { connectSlackCredentials } from "@vercel/connect/ash";
2
2
  import { slackChannel } from "experimental-ash/channels/slack";
3
3
 
4
4
  export default slackChannel({
@@ -1 +1,2 @@
1
1
  export { defineChannel, GET, POST, PUT, PATCH, DELETE, type Channel, type ChannelDefinition, type ChannelSessionOps, type ChannelEvents, type InferChannelMetadata, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, } from "#public/definitions/defineChannel.js";
2
+ export { isChannel } from "#public/instrumentation/index.js";
@@ -1 +1 @@
1
- import{DELETE,GET,PATCH,POST,PUT,defineChannel}from"#public/definitions/defineChannel.js";export{DELETE,GET,PATCH,POST,PUT,defineChannel};
1
+ import{DELETE,GET,PATCH,POST,PUT,defineChannel}from"#public/definitions/defineChannel.js";import{isChannel}from"#public/instrumentation/index.js";export{DELETE,GET,PATCH,POST,PUT,defineChannel,isChannel};
@@ -177,12 +177,15 @@ export interface InstrumentationDefinition {
177
177
  */
178
178
  export declare function defineInstrumentation<T extends InstrumentationDefinition>(definition: ExactDefinition<T, InstrumentationDefinition>): T;
179
179
  /**
180
- * Narrows an instrumentation channel by comparing it to an app-owned channel
181
- * value imported from `agent/channels/*`.
180
+ * Narrows a channel by comparing it to an app-owned channel value imported
181
+ * from `agent/channels/*`.
182
182
  *
183
- * The comparison uses the compiler's path-derived `channel:<slug>` identity.
184
- * It does not read durable channel state; metadata remains the explicit
185
- * projection returned by the channel's `metadata(state)` function.
183
+ * Works with both instrumentation resolver inputs (`input.channel`) and
184
+ * dynamic resolver inputs (`ctx.channel`). The comparison uses the
185
+ * compiler's path-derived `channel:<slug>` identity. Metadata narrows to
186
+ * the channel's declared `ChannelMetadataMap` entry on success.
186
187
  */
187
- export declare function isChannel<TChannel extends Channel<any, any, any>>(channel: InstrumentationChannel, target: TChannel): channel is InstrumentationChannelForChannel<TChannel>;
188
+ export declare function isChannel<TChannel extends Channel<any, any, any>>(channel: InstrumentationChannel | {
189
+ readonly kind?: string;
190
+ }, target: TChannel): channel is InstrumentationChannelForChannel<TChannel>;
188
191
  export {};
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";import{ConnectionRegistryKey}from"#context/providers/connection.js";import{getAuthorizationResult,getHookUrl,requestAuthorization}from"#harness/authorization.js";import{supportsInteractiveAuthorization}from"#runtime/connections/types.js";import{isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";const logger=createLogger(`framework.connection-search-dynamic`),ConnectionSearchResultsKey=new ContextKey(`ash.connectionSearchResults`);function qualifiedConnectionToolName(e,t){return`${e}__${t}`}function tokenize(e){return e.toLowerCase().split(/[\s_\-./]+/).filter(e=>e.length>1)}function scoreMatch(e,t){let n=tokenize(t.name),r=tokenize(t.description),i=0;for(let t of e){for(let e of n)(e.includes(t)||t.includes(e))&&(i+=3);for(let e of r)(e.includes(t)||t.includes(e))&&(i+=1)}return i}function resolveInteractiveAuth(e,t){let n=e.getConnections().find(e=>e.connectionName===t);if(n?.authorization&&supportsInteractiveAuthorization(n.authorization))return n.authorization}async function completePendingAuthorizations(e){let n=loadContext();for(let t of e.getConnections()){let r=getAuthorizationResult(t.connectionName);if(!r)continue;let a=resolveInteractiveAuth(e,t.connectionName);if(!a)continue;let o=resolveConnectionPrincipal(t.connectionName,a),s=await a.completeAuthorization({callbackUrl:r.hookUrl,connection:{url:t.url??``},principal:o,request:r.callback,state:r.state});writeCachedToken(n,t.connectionName,principalKey(o),s)}}async function executeConnectionSearch(e){let n=loadContext(),i=n.get(ConnectionRegistryKey);if(i===void 0)return[];await completePendingAuthorizations(i);let s=e.limit??10,l=tokenize(e.keywords),u=[],p=[],m=e.connection!==void 0&&e.connection!==``?i.getConnections().filter(t=>t.connectionName===e.connection):i.getConnections(),h=[];for(let e of m){let t;try{t=await i.getClient(e.connectionName).getToolMetadata()}catch(t){if(isConnectionAuthorizationRequiredError(t)){let t=resolveInteractiveAuth(i,e.connectionName);if(t){let n=getHookUrl(e.connectionName);if(n){let r=resolveConnectionPrincipal(e.connectionName,t);try{let{challenge:i,state:a}=await t.startAuthorization({callbackUrl:n,connection:{url:e.url??``},principal:r});h.push({name:e.connectionName,challenge:i,hookUrl:n,state:a})}catch(t){logger.warn(`startAuthorization failed`,{connection:e.connectionName,error:t instanceof Error?t:Error(String(t))})}}}p.push({connection:e.connectionName,description:e.description,needsAuthorization:!0});continue}if(isConnectionAuthorizationFailedError(t)){logger.warn(`connection authorization failed`,{connection:e.connectionName,reason:t.reason,retryable:t.retryable,error:t}),p.push({connection:e.connectionName,description:e.description,error:`Authorization failed for ${e.connectionName}: ${t.message}`});continue}let n=t instanceof Error?t.message:`unknown error`;logger.warn(`failed to load connection tools`,{connection:e.connectionName,error:t instanceof Error?t:Error(n)}),p.push({connection:e.connectionName,description:e.description,error:`Failed to load tools for "${e.connectionName}": ${n}`});continue}for(let n of t){let t=scoreMatch(l,n);t>0&&u.push({item:{connection:e.connectionName,description:n.description,inputSchema:n.inputSchema,qualifiedName:`connection__${qualifiedConnectionToolName(e.connectionName,n.name)}`,tool:n.name},score:t})}}if(h.length>0)return requestAuthorization(h);u.sort((e,t)=>t.score-e.score);let g=u.slice(0,s).map(e=>e.item);if(g.length>0){let e=[...g,...p],t=n.get(ConnectionSearchResultsKey)??[],r=new Map(t.map(e=>[e.qualifiedName,e]));for(let e of g)e.qualifiedName&&r.set(e.qualifiedName,e);return n.set(ConnectionSearchResultsKey,[...r.values()]),e}return m.map(e=>p.find(t=>t.connection===e.connectionName)||{connection:e.connectionName,description:e.description})}function extractDiscoveredTools(e){let t=new Map;for(let n of e){if(n.role!==`tool`)continue;let e=n.content;for(let n of e){if(n.type!==`tool-result`||n.toolName!==`connection__search`)continue;let e=n.output;if(e==null)continue;let r=typeof e==`object`&&`type`in e&&`value`in e?e.value:e;if(Array.isArray(r))for(let e of r)e.tool&&e.qualifiedName&&t.set(e.qualifiedName,e)}}return[...t.values()]}function createConnectionSearchEvents(){return{"step.started":async(e,n)=>{let c=loadContext().get(ConnectionRegistryKey);if(!c||c.getConnections().length===0)return null;let d=c.getConnections().map(e=>e.connectionName),p=extractDiscoveredTools(n.messages),m=loadContext().get(ConnectionSearchResultsKey)??[],h=new Map;for(let e of m)e.qualifiedName&&h.set(e.qualifiedName,e);for(let e of p)e.qualifiedName&&h.set(e.qualifiedName,e);let g=[...h.values()],_={};_.search={description:`Search for tools across your connections. Discovered tools become directly callable by their qualified name (e.g. \`connection__linear__list_issues\`) in your next response. Available connections: ${d.join(`, `)}.`,inputSchema:{type:`object`,additionalProperties:!1,properties:{keywords:{description:`Search keywords and expanded aliases. Distill intent into keywords; avoid stop words like 'a', 'the', 'in'.`,type:`string`},connection:{description:`Optional: limit search to a specific connection name.`,type:`string`},limit:{description:`Max results to return. Default 10.`,type:`number`}},required:[`keywords`]},async execute(e){return executeConnectionSearch(e)}};for(let e of g){let n=e.connection,c=e.tool;_[qualifiedConnectionToolName(n,c)]={description:e.description,inputSchema:e.inputSchema??{type:`object`},async execute(e){let d=loadContext().get(ConnectionRegistryKey),f=d.getConnections().find(e=>e.connectionName===n),p=f?.authorization&&supportsInteractiveAuthorization(f.authorization)?f.authorization:void 0;if(p){let e=getAuthorizationResult(n);if(e){let r=loadContext(),i=resolveConnectionPrincipal(n,p),a=await p.completeAuthorization({callbackUrl:e.hookUrl,connection:{url:f?.url??``},principal:i,request:e.callback,state:e.state});writeCachedToken(r,n,principalKey(i),a)}}try{let t=(await d.getClient(n).getTools())[c];if(!t?.execute)throw Error(`Connection tool "${qualifiedConnectionToolName(n,c)}" has no execute function.`);return t.execute(e,{})}catch(e){if(!isConnectionAuthorizationRequiredError(e)||!p)throw e;let t=getHookUrl(n);if(!t)throw e;let r=resolveConnectionPrincipal(n,p),{challenge:i,state:s}=await p.startAuthorization({callbackUrl:t,connection:{url:f?.url??``},principal:r});return requestAuthorization([{name:n,challenge:i,hookUrl:t,state:s}])}}}}return _}}}function createConnectionSearchResolver(){let e=createConnectionSearchEvents();return{slug:`connection`,eventNames:Object.keys(e),events:e,sourceId:`ash:connection-search-dynamic`,sourceKind:`module`,logicalPath:`ash:framework/connection-search-dynamic`}}export{createConnectionSearchEvents,createConnectionSearchResolver,extractDiscoveredTools};
1
+ import{createLogger}from"#internal/logging.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";import{ConnectionRegistryKey}from"#context/providers/connection.js";import{getAuthorizationResult,getHookUrl,requestAuthorization}from"#harness/authorization.js";import{supportsInteractiveAuthorization}from"#runtime/connections/types.js";import{isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";const logger=createLogger(`framework.connection-search-dynamic`),ConnectionSearchResultsKey=new ContextKey(`ash.connectionSearchResults`);function qualifiedConnectionToolName(e,t){return`${e}__${t}`}function tokenize(e){return e.toLowerCase().split(/[\s_\-./]+/).filter(e=>e.length>1)}function scoreMatch(e,t){let n=tokenize(t.name),r=tokenize(t.description),i=0;for(let t of e){for(let e of n)(e.includes(t)||t.includes(e))&&(i+=3);for(let e of r)(e.includes(t)||t.includes(e))&&(i+=1)}return i}function resolveInteractiveAuth(e,t){let n=e.getConnections().find(e=>e.connectionName===t);if(n?.authorization&&supportsInteractiveAuthorization(n.authorization))return n.authorization}async function completePendingAuthorizations(e){let n=loadContext();for(let t of e.getConnections()){let r=getAuthorizationResult(t.connectionName);if(!r)continue;let a=resolveInteractiveAuth(e,t.connectionName);if(!a)continue;let o=resolveConnectionPrincipal(t.connectionName,a),s=await a.completeAuthorization({callbackUrl:r.hookUrl,connection:{url:t.url??``},principal:o,request:r.callback,state:r.state});writeCachedToken(n,t.connectionName,principalKey(o),s)}}async function executeConnectionSearch(e){let n=loadContext(),i=n.get(ConnectionRegistryKey);if(i===void 0)return[];await completePendingAuthorizations(i);let s=e.limit??10,l=tokenize(e.keywords),u=[],p=[],m=e.connection!==void 0&&e.connection!==``?i.getConnections().filter(t=>t.connectionName===e.connection):i.getConnections(),h=[];for(let e of m){let t;try{t=await i.getClient(e.connectionName).getToolMetadata()}catch(t){if(isConnectionAuthorizationRequiredError(t)){let t=resolveInteractiveAuth(i,e.connectionName);if(t){let n=getHookUrl(e.connectionName);if(n){let r=resolveConnectionPrincipal(e.connectionName,t);try{let{challenge:i,state:a}=await t.startAuthorization({callbackUrl:n,connection:{url:e.url??``},principal:r});h.push({name:e.connectionName,challenge:i,hookUrl:n,state:a})}catch(t){logger.warn(`startAuthorization failed`,{connection:e.connectionName,error:t instanceof Error?t:Error(String(t))})}}}p.push({connection:e.connectionName,description:e.description,needsAuthorization:!0});continue}if(isConnectionAuthorizationFailedError(t)){logger.warn(`connection authorization failed`,{connection:e.connectionName,reason:t.reason,retryable:t.retryable,error:t}),p.push({connection:e.connectionName,description:e.description,error:`Authorization failed for ${e.connectionName}: ${t.message}`});continue}let n=t instanceof Error?t.message:`unknown error`;logger.warn(`failed to load connection tools`,{connection:e.connectionName,error:t instanceof Error?t:Error(n)}),p.push({connection:e.connectionName,description:e.description,error:`Failed to load tools for "${e.connectionName}": ${n}`});continue}for(let n of t){let t=scoreMatch(l,n);t>0&&u.push({item:{connection:e.connectionName,description:n.description,inputSchema:n.inputSchema,qualifiedName:`connection__${qualifiedConnectionToolName(e.connectionName,n.name)}`,tool:n.name},score:t})}}if(h.length>0)return requestAuthorization(h);u.sort((e,t)=>t.score-e.score);let g=u.slice(0,s).map(e=>e.item);if(g.length>0){let e=[...g,...p],t=n.get(ConnectionSearchResultsKey)??[],r=new Map(t.map(e=>[e.qualifiedName,e]));for(let e of g)e.qualifiedName&&r.set(e.qualifiedName,e);return n.set(ConnectionSearchResultsKey,[...r.values()]),e}return m.map(e=>p.find(t=>t.connection===e.connectionName)||{connection:e.connectionName,description:e.description})}function extractDiscoveredTools(e){let t=new Map;for(let n of e){if(n.role!==`tool`)continue;let e=n.content;for(let n of e){if(n.type!==`tool-result`||n.toolName!==`connection__search`)continue;let e=n.output;if(e==null)continue;let r=typeof e==`object`&&`type`in e&&`value`in e?e.value:e;if(Array.isArray(r))for(let e of r)e.tool&&e.qualifiedName&&t.set(e.qualifiedName,e)}}return[...t.values()]}function createConnectionSearchEvents(){return{"step.started":async(e,n)=>{let c=loadContext().get(ConnectionRegistryKey);if(!c||c.getConnections().length===0)return null;let d=c.getConnections().map(e=>e.connectionName),p=extractDiscoveredTools(n.messages),m=loadContext().get(ConnectionSearchResultsKey)??[],h=new Map;for(let e of m)e.qualifiedName&&h.set(e.qualifiedName,e);for(let e of p)e.qualifiedName&&h.set(e.qualifiedName,e);let g=[...h.values()],_={};_.search={description:`Search for tools across your connections. Discovered tools become directly callable by their qualified name (e.g. \`connection__linear__list_issues\`) in your next response. Available connections: ${d.join(`, `)}.`,inputSchema:{type:`object`,additionalProperties:!1,properties:{keywords:{description:`Search keywords and expanded aliases. Distill intent into keywords; avoid stop words like 'a', 'the', 'in'.`,type:`string`},connection:{description:`Optional: limit search to a specific connection name.`,type:`string`},limit:{description:`Max results to return. Default 10.`,type:`number`}},required:[`keywords`]},async execute(e){return executeConnectionSearch(e)}};for(let e of g){let n=e.connection,d=e.tool,f=c.getConnectionApproval(n);_[qualifiedConnectionToolName(n,d)]={description:e.description,inputSchema:e.inputSchema??{type:`object`},needsApproval:f,async execute(e){let c=loadContext().get(ConnectionRegistryKey),f=c.getConnections().find(e=>e.connectionName===n),p=f?.authorization&&supportsInteractiveAuthorization(f.authorization)?f.authorization:void 0;if(p){let e=getAuthorizationResult(n);if(e){let r=loadContext(),i=resolveConnectionPrincipal(n,p),a=await p.completeAuthorization({callbackUrl:e.hookUrl,connection:{url:f?.url??``},principal:i,request:e.callback,state:e.state});writeCachedToken(r,n,principalKey(i),a)}}try{let t=(await c.getClient(n).getTools())[d];if(!t?.execute)throw Error(`Connection tool "${qualifiedConnectionToolName(n,d)}" has no execute function.`);return t.execute(e,{})}catch(e){if(!isConnectionAuthorizationRequiredError(e)||!p)throw e;let t=getHookUrl(n);if(!t)throw e;let r=resolveConnectionPrincipal(n,p),{challenge:i,state:s}=await p.startAuthorization({callbackUrl:t,connection:{url:f?.url??``},principal:r});return requestAuthorization([{name:n,challenge:i,hookUrl:t,state:s}])}}}}return _}}}function createConnectionSearchResolver(){let e=createConnectionSearchEvents();return{slug:`connection`,eventNames:Object.keys(e),events:e,sourceId:`ash:connection-search-dynamic`,sourceKind:`module`,logicalPath:`ash:framework/connection-search-dynamic`}}export{createConnectionSearchEvents,createConnectionSearchResolver,extractDiscoveredTools};
@@ -1,6 +1,7 @@
1
1
  import type { ModelMessage } from "ai";
2
2
  import type { PublicToolInputSchema, ToolModelOutput } from "#shared/tool-definition.js";
3
3
  import type { SessionContext } from "#public/definitions/callback-context.js";
4
+ import type { NeedsApprovalContext } from "#public/definitions/tool.js";
4
5
  import type { SessionAuth } from "#context/keys.js";
5
6
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
6
7
  type ToolContext = SessionContext;
@@ -35,6 +36,7 @@ export interface DynamicResolveContext {
35
36
  readonly channel: {
36
37
  readonly kind?: string;
37
38
  readonly continuationToken?: string;
39
+ readonly metadata?: Readonly<Record<string, unknown>>;
38
40
  };
39
41
  readonly messages: readonly ModelMessage[];
40
42
  }
@@ -55,6 +57,15 @@ export interface DynamicToolEntry<TInput = Record<string, unknown>, TOutput = an
55
57
  readonly inputSchema: PublicToolInputSchema<TInput>;
56
58
  execute(input: TInput, ctx: ToolContext): TOutput | Promise<TOutput>;
57
59
  readonly toModelOutput?: (output: TOutput) => ToolModelOutput | Promise<ToolModelOutput>;
60
+ /**
61
+ * Optional per-call approval gate. Mirrors the authored-tool
62
+ * `needsApproval` contract: return `true` to require user approval
63
+ * before the call executes. Only honored for step-scoped dynamic
64
+ * tools (whose live `execute` closures survive into the harness);
65
+ * session/turn-scoped tools replay from durable metadata and cannot
66
+ * carry a function across replay.
67
+ */
68
+ readonly needsApproval?: (ctx: NeedsApprovalContext) => boolean;
58
69
  }
59
70
  /**
60
71
  * A resolved tool set: keys are entry identifiers, values are tool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "experimental-ash",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "bin": {
5
5
  "ash": "./bin/ash.js",
6
6
  "experimental-ash": "./bin/ash.js"