experimental-ash 0.18.2 → 0.19.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 (65) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/docs/internals/context.md +7 -0
  3. package/dist/docs/public/channels/README.md +5 -0
  4. package/dist/docs/public/channels/slack.md +58 -4
  5. package/dist/docs/public/hooks.md +4 -2
  6. package/dist/docs/public/sandbox.md +71 -49
  7. package/dist/docs/public/typescript-api.md +6 -1
  8. package/dist/src/channel/adapter.js +12 -2
  9. package/dist/src/channel/routes.d.ts +9 -1
  10. package/dist/src/channel/send.js +3 -3
  11. package/dist/src/channel/types.d.ts +3 -1
  12. package/dist/src/chunks/{dev-authored-source-watcher-j7YWh2Gx.js → dev-authored-source-watcher-L3_pagDa.js} +1 -1
  13. package/dist/src/chunks/{host-DkTSR6YJ.js → host-e2GUqnTr.js} +3 -3
  14. package/dist/src/chunks/{paths-Dwv0Eash.js → paths-BBleOGpa.js} +25 -25
  15. package/dist/src/chunks/{prewarm-CQYfka30.js → prewarm-DEymC5M-.js} +1 -1
  16. package/dist/src/cli/commands/info.js +1 -1
  17. package/dist/src/cli/run.js +1 -1
  18. package/dist/src/compiled/.vendor-stamp.json +1 -1
  19. package/dist/src/compiled/@vercel/sandbox/index.d.ts +6 -1
  20. package/dist/src/context/hook-lifecycle.js +5 -1
  21. package/dist/src/evals/cli/eval.js +1 -1
  22. package/dist/src/execution/sandbox/bindings/local.d.ts +14 -1
  23. package/dist/src/execution/sandbox/bindings/local.js +5 -1
  24. package/dist/src/execution/sandbox/bindings/vercel.d.ts +6 -0
  25. package/dist/src/execution/sandbox/bindings/vercel.js +12 -1
  26. package/dist/src/execution/sandbox/lazy-backend.d.ts +15 -0
  27. package/dist/src/execution/sandbox/lazy-backend.js +33 -0
  28. package/dist/src/execution/workflow-entry.d.ts +2 -4
  29. package/dist/src/execution/workflow-entry.js +1 -1
  30. package/dist/src/harness/messages.js +15 -0
  31. package/dist/src/harness/types.d.ts +6 -7
  32. package/dist/src/internal/application/package.js +1 -1
  33. package/dist/src/internal/authored-definition/sandbox.d.ts +8 -2
  34. package/dist/src/internal/authored-definition/sandbox.js +10 -2
  35. package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +0 -2
  36. package/dist/src/public/channels/slack/api.d.ts +2 -27
  37. package/dist/src/public/channels/slack/api.js +6 -82
  38. package/dist/src/public/channels/slack/defaults.js +2 -2
  39. package/dist/src/public/channels/slack/hitl.js +6 -3
  40. package/dist/src/public/channels/slack/inbound.js +1 -1
  41. package/dist/src/public/channels/slack/index.d.ts +3 -0
  42. package/dist/src/public/channels/slack/index.js +2 -0
  43. package/dist/src/public/channels/slack/limits.d.ts +19 -0
  44. package/dist/src/public/channels/slack/limits.js +23 -0
  45. package/dist/src/public/channels/slack/mrkdwn.d.ts +38 -0
  46. package/dist/src/public/channels/slack/mrkdwn.js +89 -0
  47. package/dist/src/public/channels/slack/slackChannel.d.ts +12 -4
  48. package/dist/src/public/channels/slack/slackChannel.js +5 -8
  49. package/dist/src/public/channels/slack/thread.d.ts +26 -0
  50. package/dist/src/public/channels/slack/thread.js +45 -0
  51. package/dist/src/public/definitions/defineChannel.d.ts +1 -1
  52. package/dist/src/public/sandbox/backends/default.d.ts +16 -1
  53. package/dist/src/public/sandbox/backends/default.js +7 -19
  54. package/dist/src/public/sandbox/backends/local.d.ts +7 -4
  55. package/dist/src/public/sandbox/backends/local.js +7 -5
  56. package/dist/src/public/sandbox/backends/vercel.d.ts +9 -3
  57. package/dist/src/public/sandbox/backends/vercel.js +9 -3
  58. package/dist/src/public/sandbox/index.d.ts +2 -1
  59. package/dist/src/public/sandbox/local-sandbox.d.ts +7 -0
  60. package/dist/src/public/sandbox/local-sandbox.js +1 -0
  61. package/dist/src/public/sandbox/vercel-sandbox.d.ts +13 -1
  62. package/dist/src/runtime/resolve-sandbox.js +5 -1
  63. package/dist/src/runtime/types.d.ts +10 -1
  64. package/dist/src/shared/sandbox-definition.d.ts +16 -1
  65. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import{E as e,S as t,T as n,_ as r,a as i,c as a,d as o,g as s,h as c,l,m as u,p as ee,s as te,u as d,x as f,y as p}from"./paths-Dwv0Eash.js";import{t as m}from"./authored-module-loader-XcFLnl49.js";import{t as h}from"./errors-DsO9xmQL.js";import{i as g,t as _}from"./package-DmsQgn4v.js";import{join as v,posix as y}from"node:path";import{mkdir as ne,readFile as re,readdir as ie,realpath as b,writeFile as x}from"node:fs/promises";import{createHash as S}from"node:crypto";import{existsSync as C}from"node:fs";function w(e){return e.dev?{appRoot:e.appRoot,dev:e.dev,moduleMapLoaderPath:g(`src/internal/authored-module-map-loader.ts`)}:{appRoot:e.appRoot,dev:e.dev}}const T=`#ash-channel/`;function E(e){let t=e.compileResult.manifest.channels,n=new Set,r=[],i=new Set,o=a();for(let e of t){if(e.kind===`disabled`){if(!o.has(e.name))throw Error(`agent/channels/${e.name}.ts exports disableRoute() but "${e.name}" is not a framework channel. Rename the file to one of: ${[...o].sort().join(`, `)}.`);i.add(e.name);continue}n.add(e.name),r.push({method:e.method,route:e.urlPath})}let s=l().filter(e=>!n.has(e.name)&&!i.has(e.name)).map(e=>({method:e.method,route:e.urlPath})),c=new Set,u=[];for(let e of[...s,...r]){let t=k(e);c.has(t)||(c.add(t),u.push(e))}return u}function D(e,t){for(let n of t.registrations)A(e,{artifactsConfig:t.artifactsConfig,method:n.method,route:n.route})}function O(e,t){return N(t.previous,t.next)?!1:(j(e),D(e,{artifactsConfig:t.artifactsConfig,registrations:t.next}),e.routing.sync(),!0)}function k(e){return`${e.method.toUpperCase()} ${e.route}`}function A(e,t){let r=k(t),i=`${T}${r}`,a=n(g(`src/internal/nitro/routes/channel-dispatch.ts`));e.options.handlers.push({handler:i,method:t.method,route:t.route}),e.options.virtual[i]=[`import { dispatchChannelRequest } from ${a};`,`const config = ${JSON.stringify(t.artifactsConfig)};`,`export default (event) => dispatchChannelRequest(event, ${JSON.stringify(r)}, config);`].join(`
1
+ import{E as e,S as t,T as n,_ as r,a as i,c as a,d as o,g as s,h as c,l,m as u,p as ee,s as te,u as d,x as f,y as p}from"./paths-BBleOGpa.js";import{t as m}from"./authored-module-loader-XcFLnl49.js";import{t as h}from"./errors-DsO9xmQL.js";import{i as g,t as _}from"./package-DmsQgn4v.js";import{join as v,posix as y}from"node:path";import{mkdir as ne,readFile as re,readdir as ie,realpath as b,writeFile as x}from"node:fs/promises";import{createHash as S}from"node:crypto";import{existsSync as C}from"node:fs";function w(e){return e.dev?{appRoot:e.appRoot,dev:e.dev,moduleMapLoaderPath:g(`src/internal/authored-module-map-loader.ts`)}:{appRoot:e.appRoot,dev:e.dev}}const T=`#ash-channel/`;function E(e){let t=e.compileResult.manifest.channels,n=new Set,r=[],i=new Set,o=a();for(let e of t){if(e.kind===`disabled`){if(!o.has(e.name))throw Error(`agent/channels/${e.name}.ts exports disableRoute() but "${e.name}" is not a framework channel. Rename the file to one of: ${[...o].sort().join(`, `)}.`);i.add(e.name);continue}n.add(e.name),r.push({method:e.method,route:e.urlPath})}let s=l().filter(e=>!n.has(e.name)&&!i.has(e.name)).map(e=>({method:e.method,route:e.urlPath})),c=new Set,u=[];for(let e of[...s,...r]){let t=k(e);c.has(t)||(c.add(t),u.push(e))}return u}function D(e,t){for(let n of t.registrations)A(e,{artifactsConfig:t.artifactsConfig,method:n.method,route:n.route})}function O(e,t){return N(t.previous,t.next)?!1:(j(e),D(e,{artifactsConfig:t.artifactsConfig,registrations:t.next}),e.routing.sync(),!0)}function k(e){return`${e.method.toUpperCase()} ${e.route}`}function A(e,t){let r=k(t),i=`${T}${r}`,a=n(g(`src/internal/nitro/routes/channel-dispatch.ts`));e.options.handlers.push({handler:i,method:t.method,route:t.route}),e.options.virtual[i]=[`import { dispatchChannelRequest } from ${a};`,`const config = ${JSON.stringify(t.artifactsConfig)};`,`export default (event) => dispatchChannelRequest(event, ${JSON.stringify(r)}, config);`].join(`
2
2
  `)}function j(e){for(let t=e.options.handlers.length-1;t>=0;--t){let n=e.options.handlers[t];n!==void 0&&M(n)&&e.options.handlers.splice(t,1)}for(let t of Object.keys(e.options.virtual))t.startsWith(T)&&delete e.options.virtual[t]}function M(e){return e.handler.startsWith(T)}function N(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1){let r=e[n],i=t[n];if(r===void 0||i===void 0||r.method!==i.method||r.route!==i.route)return!1}return!0}const P=`ash.schedule.`;var F=class extends Error{scheduleId;sourceId;taskName;constructor(e,t={}){super(e),this.name=`ScheduleRegistrationError`,t.scheduleId!==void 0&&(this.scheduleId=t.scheduleId),t.sourceId!==void 0&&(this.sourceId=t.sourceId),t.taskName!==void 0&&(this.taskName=t.taskName)}};function I(e){let t=e.map(e=>({cron:e.cron,description:`Run Ash schedule "${e.name}" from "${e.logicalPath}".`,logicalPath:e.logicalPath,scheduleId:e.name,sourceId:e.sourceId,taskName:R(e.sourceId)})).sort((e,t)=>e.sourceId.localeCompare(t.sourceId));return L(t),t}function L(e){let t=new Map;for(let n of e){let e=t.get(n.scheduleId);if(e===void 0){t.set(n.scheduleId,n);continue}throw new F(`Duplicate authored schedule id "${n.scheduleId}" found in "${e.logicalPath}" and "${n.logicalPath}".`,{scheduleId:n.scheduleId,sourceId:n.sourceId,taskName:n.taskName})}}function R(e){return`${P}${Buffer.from(e,`utf8`).toString(`base64url`)}`}const z=`#ash-schedule-task/`;function B(e,t){if(t.registrations.length!==0){e.options.experimental.tasks=!0;for(let n of t.registrations)U(e,{artifactsConfig:t.artifactsConfig,dispatchModulePath:t.dispatchModulePath,registration:n})}}function V(e,t){let n=!G(t.previous,t.next);return H(e),B(e,{artifactsConfig:t.artifactsConfig,dispatchModulePath:t.dispatchModulePath,registrations:t.next}),n}function H(e){for(let t of Object.keys(e.options.tasks))t.startsWith(`ash.schedule.`)&&delete e.options.tasks[t];for(let t of Object.keys(e.options.virtual))t.startsWith(z)&&delete e.options.virtual[t];for(let[t,n]of Object.entries(e.options.scheduledTasks)){let r=W(n).filter(e=>!e.startsWith(P));if(r.length===0){delete e.options.scheduledTasks[t];continue}if(r.length===1){let[n]=r;n!==void 0&&(e.options.scheduledTasks[t]=n);continue}e.options.scheduledTasks[t]=r}}function U(e,t){let r=`${z}${t.registration.taskName}`,i=n(t.dispatchModulePath);e.options.tasks[t.registration.taskName]={description:t.registration.description,handler:r},e.options.virtual[r]=[`import { dispatchScheduleTask } from ${i};`,`const config = ${JSON.stringify(t.artifactsConfig)};`,`export default {`,` meta: { description: ${JSON.stringify(t.registration.description)} },`,` async run(event) {`,` return { result: await dispatchScheduleTask(event.name, config) };`,` },`,`};`].join(`
3
3
  `),ae(e,t.registration.cron,t.registration.taskName)}function ae(e,t,n){let r=e.options.scheduledTasks[t];if(r===void 0){e.options.scheduledTasks[t]=n;return}if(typeof r==`string`){e.options.scheduledTasks[t]=[r,n];return}r.includes(n)||r.push(n)}function W(e){return typeof e==`string`?[e]:[...e]}function G(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1){let r=e[n],i=t[n];if(r===void 0||i===void 0||r.cron!==i.cron||r.description!==i.description||r.logicalPath!==i.logicalPath||r.scheduleId!==i.scheduleId||r.sourceId!==i.sourceId||r.taskName!==i.taskName)return!1}return!0}async function K(e){return[...e.manifest.schedules].map(e=>{let t={cron:e.cron,hasRun:e.hasRun,logicalPath:e.logicalPath,name:e.name,sourceId:e.sourceId,sourceKind:e.sourceKind};return e.markdown===void 0?t:{...t,markdown:e.markdown}})}async function q(e){return await K({manifest:await o({compiledArtifactsSource:e.compiledArtifactsSource})})}async function J(e){let t=v(e.outDir,`compiled-artifacts-bootstrap.mjs`),n=v(e.outDir,`compiled-artifacts-instrumentation.mjs`),r=se(e.compileResult.manifest.agentRoot);await ne(e.outDir,{recursive:!0}),await x(t,await le({compileResult:e.compileResult,installModulePath:g(`src/runtime/loaders/bundled-artifacts.ts`),moduleMapPath:t,metadata:e.compileResult.metadata})),r!==void 0&&await x(n,ue({agentName:e.compileResult.manifest.config.name,instrumentationPath:r,registerConfigPath:g(`src/harness/instrumentation-config.ts`)}));let i={bootstrapPath:t};return r!==void 0&&(i.instrumentationPluginPath=n,i.instrumentationSourcePath=r),i}const oe=[`.ts`,`.mts`,`.js`,`.mjs`];function se(e){for(let t of oe){let n=v(e,`instrumentation${t}`);if(C(n))return n}}function ce(e){return e.replace(/^export const moduleMap = /m,`const moduleMap = `).replace(/\nexport default moduleMap;\n?$/,`
4
4
  `)}async function le(e){let r=ce(t({importSpecifierStyle:`absolute`,manifest:e.compileResult.manifest,moduleMapPath:e.moduleMapPath})).trim();return[`// Generated by Ash. Do not edit by hand.`,`import { installBundledCompiledArtifacts } from ${n(e.installModulePath)};`,``,r,``,`const metadata = ${JSON.stringify(e.metadata,null,2)};`,``,`const manifest = ${JSON.stringify(e.compileResult.manifest,null,2)};`,``,`export function installCompiledArtifactsBootstrap() {`,` installBundledCompiledArtifacts({`,` manifest,`,` metadata,`,` moduleMap,`,` });`,`}`,``,`installCompiledArtifactsBootstrap();`,``,`// Default export satisfies the Nitro plugin contract so this file`,`// can be used directly as a Nitro plugin without a separate wrapper.`,`export default function installCompiledArtifactsPlugin() {`,` // Already installed on import above.`,`}`,``,`export async function __ashInstallCompiledArtifactsStep() {`,` "use step";`,` return null;`,`}`,``].join(`
@@ -1,2 +1,2 @@
1
- import{D as e,b as t,t as n,v as r,y as i}from"../../chunks/paths-Dwv0Eash.js";import{d as a,f as o,h as s}from"../../chunks/types-MZUhN0Zy.js";import{createCliTheme as c,renderCliBanner as l,renderCliSection as u}from"../ui/output.js";async function d(e){let t=await f(e);return{application:n(t?.project.appRoot??e),compiledState:t,messaging:{createSessionRoutePath:o,continueSessionRoutePattern:a,streamRoutePattern:s}}}async function f(n){try{return await i({startPath:n})}catch(n){if(n instanceof r)return n.result;if(n instanceof e||n instanceof t)return null;throw n}}function p(e,t){return`${e} ${t}${e===1?``:`s`}`}function m(e,t){return`${`${e} error${e===1?``:`s`}`}, ${`${t} warning${t===1?``:`s`}`}`}function h(e){switch(e){case`ready`:return`success`;case`failed`:return`danger`;default:return`warning`}}async function g(e,t){let n=await d(t),r=n.compiledState,i=n.application,a=c(),o=[{label:`App Root`,value:i.appRoot}],s=[{label:`Workflow Build`,value:i.workflowBuildDir},{label:`Output`,value:i.outputDir}],f=[];r===null?o.push({label:`Compile`,tone:`warning`,value:`unavailable`}):(o.push({label:`Agent Root`,value:r.project.agentRoot},{label:`Layout`,value:r.project.layout},{label:`Compile`,tone:h(r.metadata.status),value:r.metadata.status},{label:`Diagnostics`,tone:r.metadata.discovery.summary.errors>0?`danger`:r.metadata.discovery.summary.warnings>0?`warning`:`success`,value:m(r.metadata.discovery.summary.errors,r.metadata.discovery.summary.warnings)},{label:`Instructions`,value:r.manifest.instructions?.logicalPath??`none`},{label:`Skills`,value:p(r.manifest.skills.length,`skill`)}),s.unshift({label:`Compiled Manifest`,value:r.paths.compiledManifestPath},{label:`Discovery Manifest`,value:r.paths.discoveryManifestPath},{label:`Diagnostics`,value:r.paths.diagnosticsPath},{label:`Module Map`,value:r.paths.moduleMapPath},{label:`Metadata`,value:r.paths.compileMetadataPath}),f.push(r.manifest.instructions===void 0?{label:`Instructions`,value:`No instructions prompt discovered.`}:{label:`Instructions`,value:r.manifest.instructions.logicalPath})),e.log([l(a,{subtitle:`Resolved application paths and the active message contract.`,title:`Ash Info`}),``,u(a,{rows:o,title:`Application`}),``,u(a,{rows:s,title:`Artifacts`}),...r===null?[]:[``,u(a,{rows:f,title:`Instructions`})],``,u(a,{rows:[{label:`Workflow ID`,value:i.workflowId},{label:`Source Dir`,value:i.workflowSourceDir},{label:`Create`,tone:`info`,value:`POST ${n.messaging.createSessionRoutePath}`},{label:`Continue`,tone:`info`,value:`POST ${n.messaging.continueSessionRoutePattern}`},{label:`Stream`,tone:`info`,value:`GET ${n.messaging.streamRoutePattern}`}],title:`Messaging`})].join(`
1
+ import{D as e,b as t,t as n,v as r,y as i}from"../../chunks/paths-BBleOGpa.js";import{d as a,f as o,h as s}from"../../chunks/types-MZUhN0Zy.js";import{createCliTheme as c,renderCliBanner as l,renderCliSection as u}from"../ui/output.js";async function d(e){let t=await f(e);return{application:n(t?.project.appRoot??e),compiledState:t,messaging:{createSessionRoutePath:o,continueSessionRoutePattern:a,streamRoutePattern:s}}}async function f(n){try{return await i({startPath:n})}catch(n){if(n instanceof r)return n.result;if(n instanceof e||n instanceof t)return null;throw n}}function p(e,t){return`${e} ${t}${e===1?``:`s`}`}function m(e,t){return`${`${e} error${e===1?``:`s`}`}, ${`${t} warning${t===1?``:`s`}`}`}function h(e){switch(e){case`ready`:return`success`;case`failed`:return`danger`;default:return`warning`}}async function g(e,t){let n=await d(t),r=n.compiledState,i=n.application,a=c(),o=[{label:`App Root`,value:i.appRoot}],s=[{label:`Workflow Build`,value:i.workflowBuildDir},{label:`Output`,value:i.outputDir}],f=[];r===null?o.push({label:`Compile`,tone:`warning`,value:`unavailable`}):(o.push({label:`Agent Root`,value:r.project.agentRoot},{label:`Layout`,value:r.project.layout},{label:`Compile`,tone:h(r.metadata.status),value:r.metadata.status},{label:`Diagnostics`,tone:r.metadata.discovery.summary.errors>0?`danger`:r.metadata.discovery.summary.warnings>0?`warning`:`success`,value:m(r.metadata.discovery.summary.errors,r.metadata.discovery.summary.warnings)},{label:`Instructions`,value:r.manifest.instructions?.logicalPath??`none`},{label:`Skills`,value:p(r.manifest.skills.length,`skill`)}),s.unshift({label:`Compiled Manifest`,value:r.paths.compiledManifestPath},{label:`Discovery Manifest`,value:r.paths.discoveryManifestPath},{label:`Diagnostics`,value:r.paths.diagnosticsPath},{label:`Module Map`,value:r.paths.moduleMapPath},{label:`Metadata`,value:r.paths.compileMetadataPath}),f.push(r.manifest.instructions===void 0?{label:`Instructions`,value:`No instructions prompt discovered.`}:{label:`Instructions`,value:r.manifest.instructions.logicalPath})),e.log([l(a,{subtitle:`Resolved application paths and the active message contract.`,title:`Ash Info`}),``,u(a,{rows:o,title:`Application`}),``,u(a,{rows:s,title:`Artifacts`}),...r===null?[]:[``,u(a,{rows:f,title:`Instructions`})],``,u(a,{rows:[{label:`Workflow ID`,value:i.workflowId},{label:`Source Dir`,value:i.workflowSourceDir},{label:`Create`,tone:`info`,value:`POST ${n.messaging.createSessionRoutePath}`},{label:`Continue`,tone:`info`,value:`POST ${n.messaging.continueSessionRoutePattern}`},{label:`Stream`,tone:`info`,value:`GET ${n.messaging.streamRoutePattern}`}],title:`Messaging`})].join(`
2
2
  `))}export{g as printApplicationInfo};
@@ -1,3 +1,3 @@
1
- import{t as e}from"../chunks/package-DmsQgn4v.js";import{createCliTheme as t,renderCliTaggedLine as n}from"./ui/output.js";import{i as r,n as i,r as a,t as o}from"../chunks/url-BVRhVE2O.js";import{resolve as s}from"node:path";async function c(){return(await import(`../chunks/host-DkTSR6YJ.js`).then(e=>e.t)).buildHost}async function l(){return(await import(`./commands/info.js`)).printApplicationInfo}async function u(){return(await import(`./dev/repl.js`)).runDevelopmentRepl}async function d(){return(await import(`../evals/cli/eval.js`)).runEvalCommand}async function f(){return(await import(`../chunks/host-DkTSR6YJ.js`).then(e=>e.t)).startHost}function p(e=process.cwd()){return s(e)}function m(e){return`Ash (v${e})`}function h(e){return e.name()===`info`||e.name()===`dev`}async function g(e){await new Promise((t,n)=>{let r=!1,i=()=>{process.off(`SIGINT`,a),process.off(`SIGTERM`,a)},a=()=>{r||(r=!0,i(),e.close().then(t,n))};process.once(`SIGINT`,a),process.once(`SIGTERM`,a)})}function _(e){if(!/^-?\d+$/.test(e))throw new r(`Expected a numeric port, received "${e}".`);let t=Number(e);if(!Number.isInteger(t))throw new r(`Expected a numeric port, received "${e}".`);if(t<0||t>65535)throw new r(`Expected a port between 0 and 65535, received "${e}".`);return t}function v(){return!!(process.stdin.isTTY&&process.stdout.isTTY)}function y(e){let t=e[1];return e[0]!==`dev`||e.length!==2||t===void 0||t.startsWith(`-`)?[...e]:[`dev`,`--url`,t]}function b(e){if(e.url){if(e.host!==void 0)throw new r(`The --host option cannot be used with --url.`);if(e.port!==void 0)throw new r(`The --port option cannot be used with --url.`);if(e.repl===!1)throw new r(`The --no-repl option cannot be used with --url.`);return e.url}}function x(r,a){let s=p(),y=e().version,x=new i,S=t();return x.name(`ash`).description(`Build and run an Ash application.`).version(y).showHelpAfterError().exitOverride().hook(`preAction`,(e,t)=>{h(t)&&r.log(m(y))}).configureOutput({writeErr:e=>{r.error(e.trimEnd())},writeOut:e=>{r.log(e.trimEnd())}}),x.command(`build`).description(`Build the current Ash application.`).action(async()=>{let{loadDevelopmentEnvironmentFiles:e}=await import(`./dev/environment.js`);e(s);let t=await(a.buildHost??await c())(s);r.log(n(S,{message:`built output at ${t}`,tag:`build`,tone:`success`}))}),x.command(`dev`).description(`Start the Ash development server or connect the REPL to an existing URL.`).option(`--host <host>`,`Host interface to bind`).option(`--no-repl`,`Start the server without the interactive REPL`).option(`--port <port>`,`Port to listen on (defaults to $PORT, then 3000)`,_).option(`--schedules`,`Run scheduled tasks during development (off by default)`).option(`-u, --url <url>`,`Connect the REPL to an existing server URL`,o).addHelpText(`after`,`
1
+ import{t as e}from"../chunks/package-DmsQgn4v.js";import{createCliTheme as t,renderCliTaggedLine as n}from"./ui/output.js";import{i as r,n as i,r as a,t as o}from"../chunks/url-BVRhVE2O.js";import{resolve as s}from"node:path";async function c(){return(await import(`../chunks/host-e2GUqnTr.js`).then(e=>e.t)).buildHost}async function l(){return(await import(`./commands/info.js`)).printApplicationInfo}async function u(){return(await import(`./dev/repl.js`)).runDevelopmentRepl}async function d(){return(await import(`../evals/cli/eval.js`)).runEvalCommand}async function f(){return(await import(`../chunks/host-e2GUqnTr.js`).then(e=>e.t)).startHost}function p(e=process.cwd()){return s(e)}function m(e){return`Ash (v${e})`}function h(e){return e.name()===`info`||e.name()===`dev`}async function g(e){await new Promise((t,n)=>{let r=!1,i=()=>{process.off(`SIGINT`,a),process.off(`SIGTERM`,a)},a=()=>{r||(r=!0,i(),e.close().then(t,n))};process.once(`SIGINT`,a),process.once(`SIGTERM`,a)})}function _(e){if(!/^-?\d+$/.test(e))throw new r(`Expected a numeric port, received "${e}".`);let t=Number(e);if(!Number.isInteger(t))throw new r(`Expected a numeric port, received "${e}".`);if(t<0||t>65535)throw new r(`Expected a port between 0 and 65535, received "${e}".`);return t}function v(){return!!(process.stdin.isTTY&&process.stdout.isTTY)}function y(e){let t=e[1];return e[0]!==`dev`||e.length!==2||t===void 0||t.startsWith(`-`)?[...e]:[`dev`,`--url`,t]}function b(e){if(e.url){if(e.host!==void 0)throw new r(`The --host option cannot be used with --url.`);if(e.port!==void 0)throw new r(`The --port option cannot be used with --url.`);if(e.repl===!1)throw new r(`The --no-repl option cannot be used with --url.`);return e.url}}function x(r,a){let s=p(),y=e().version,x=new i,S=t();return x.name(`ash`).description(`Build and run an Ash application.`).version(y).showHelpAfterError().exitOverride().hook(`preAction`,(e,t)=>{h(t)&&r.log(m(y))}).configureOutput({writeErr:e=>{r.error(e.trimEnd())},writeOut:e=>{r.log(e.trimEnd())}}),x.command(`build`).description(`Build the current Ash application.`).action(async()=>{let{loadDevelopmentEnvironmentFiles:e}=await import(`./dev/environment.js`);e(s);let t=await(a.buildHost??await c())(s);r.log(n(S,{message:`built output at ${t}`,tag:`build`,tone:`success`}))}),x.command(`dev`).description(`Start the Ash development server or connect the REPL to an existing URL.`).option(`--host <host>`,`Host interface to bind`).option(`--no-repl`,`Start the server without the interactive REPL`).option(`--port <port>`,`Port to listen on (defaults to $PORT, then 3000)`,_).option(`--schedules`,`Run scheduled tasks during development (off by default)`).option(`-u, --url <url>`,`Connect the REPL to an existing server URL`,o).addHelpText(`after`,`
2
2
  You can also pass a bare URL as the only argument, for example: ash dev https://example.com
3
3
  `).action(async e=>{let t=b(e),{loadDevelopmentEnvironmentFiles:i}=await import(`./dev/environment.js`);if(i(s),t){if(r.log(n(S,{message:`REPL connecting to ${t}`,tag:`dev`,tone:`info`})),!v()){r.log(n(S,{message:`Interactive REPL disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`}));return}r.log(``),await(a.runDevelopmentRepl??await u())({serverUrl:t});return}let o=await(a.startHost??await f())(s,{host:e.host,port:e.port,schedules:e.schedules===!0}),c=!1,l=async()=>{c||(c=!0,await o.close())};try{if(r.log(n(S,{message:`server listening at ${o.url}`,tag:`dev`,tone:`success`})),e.repl===!1)return await g({close:l});if(!v())return r.log(n(S,{message:`Interactive REPL disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`})),await g({close:l});r.log(``),await(a.runDevelopmentRepl??await u())({serverUrl:o.url})}finally{await l()}}),x.command(`info`).description(`Print resolved application information.`).action(async()=>{await(a.printApplicationInfo??await l())(r,s)}),x.command(`eval`).description(`Run eval suites against an Ash agent.`).option(`--suite <id...>`,`Suite IDs to run (repeatable)`).option(`--all`,`Run all discovered suites`).option(`--url <url>`,`Remote agent URL (skip local host startup)`).option(`--timeout <ms>`,`Per-case timeout in milliseconds`).option(`--max-concurrency <n>`,`Max concurrent case executions per suite`).option(`--json`,`Output results as JSON`).option(`--list-suites`,`List discovered suites and exit`).option(`--skip-report`,`Skip suite-defined reporters (e.g. Braintrust)`).action(async e=>{await(a.runEvalCommand??await d())(e,r)}),x}async function S(e=process.argv.slice(2),t=console,n={}){let r=x(t,n),i=e.length===0?[`info`]:y(e);try{await r.parseAsync(i,{from:`user`})}catch(e){if(e instanceof a){if(e.exitCode===0)return;throw Error(e.message)}throw e}}export{S as runCli};
@@ -20,5 +20,5 @@
20
20
  "zod": "4.4.3",
21
21
  "zod-validation-error": "5.0.0"
22
22
  },
23
- "scriptHash": "643a5ee0bb5b169298d682303a7fae206d573de0d39408d97972b7c54602186b"
23
+ "scriptHash": "1244a77b7b22e52bfa6dbbec96a326a7ae4af318c49f1f947fb3e72ef19f65b9"
24
24
  }
@@ -38,11 +38,16 @@ export interface SandboxUpdateParams {
38
38
  }
39
39
 
40
40
  export interface SandboxCreateOptions {
41
+ env?: Record<string, string> | undefined;
41
42
  name?: string | undefined;
42
43
  networkPolicy?: NetworkPolicy | undefined;
43
44
  persistent?: boolean | undefined;
44
- source?: unknown;
45
+ ports?: number[] | undefined;
46
+ resources?: { vcpus?: number | undefined } | undefined;
47
+ runtime?: string | undefined;
45
48
  signal?: AbortSignal | undefined;
49
+ snapshotExpiration?: number | undefined;
50
+ source?: unknown;
46
51
  tags?: Record<string, string> | undefined;
47
52
  timeout?: number | undefined;
48
53
  }
@@ -100,9 +100,13 @@ export async function dispatchHookLifecycle(input) {
100
100
  }
101
101
  }
102
102
  }
103
+ const channelContext = input.input.modelContext ?? [];
104
+ const mergedModelContext = channelContext.length === 0 ? modelContext : [...channelContext, ...modelContext];
103
105
  return {
104
106
  kind: "proceed",
105
- input: modelContext.length > 0 ? { ...input.input, modelContext } : input.input,
107
+ input: mergedModelContext.length > 0
108
+ ? { ...input.input, modelContext: mergedModelContext }
109
+ : input.input,
106
110
  nextSession: input.session,
107
111
  };
108
112
  }
@@ -1 +1 @@
1
- import{n as e}from"../../chunks/paths-Dwv0Eash.js";import{loadDevelopmentEnvironmentFiles as t}from"../../cli/dev/environment.js";import{a as n,n as r,t as i}from"../../chunks/client-CKsU8Li3.js";import{n as a}from"../../chunks/host-DkTSR6YJ.js";import{discoverAndImportSuites as o,discoverSuiteFiles as s,importSuiteFile as c}from"../runner/discover.js";import{executeSuite as l}from"../runner/execute-suite.js";import{ConsoleReporter as u}from"../runner/reporters/console.js";var d=n();function f(e,t){e.command(`eval`).description(`Run eval suites against an Ash agent.`).option(`--suite <id...>`,`Suite IDs to run (repeatable)`).option(`--all`,`Run all discovered suites`).option(`--url <url>`,`Remote agent URL (skip local host startup)`).option(`--timeout <ms>`,`Per-case timeout in milliseconds`).option(`--max-concurrency <n>`,`Max concurrent case executions per suite`).option(`--json`,`Output results as JSON`).option(`--list-suites`,`List discovered suites and exit`).option(`--skip-report`,`Skip suite-defined reporters (e.g. Braintrust)`).action(async e=>{await p(e,t)})}async function p(n,r){let i=e();if(t(i),n.listSuites){await y(i,r);return}let s=n.suite,c=await o(i,s);if(c.length===0){s&&s.length>0?r.error(`No suites found matching: ${s.join(`, `)}`):r.error(`No eval suites found. Create suite files under evals/ with the *.eval.ts extension.`),process.exitCode=1;return}let u,d;n.url?d={kind:`remote`,url:n.url}:(u=await a(i,{host:`127.0.0.1`,port:0}),d={kind:`local`,url:u.url});let f=m(d);try{let e=[];for(let t of c){let r=_(t,n),a=v(r,{json:n.json===!0,skipReport:n.skipReport===!0}),o=await l({suite:r,target:d,reporters:a,appRoot:i,client:f});e.push(o)}n.json&&r.log(JSON.stringify(e,null,2)),e.some(e=>e.errored>0)&&(process.exitCode=1)}finally{u&&await u.close()}process.exit(process.exitCode??0)}function m(e){if(e.kind===`local`)return new i({host:e.url});let t={},n=process.env.VERCEL_AUTOMATION_BYPASS_SECRET?.trim();return n&&(t[r]=n),new i({auth:h(),headers:Object.keys(t).length>0?t:void 0,host:e.url})}function h(){let e=process.env.ASH_EVAL_AUTH_TOKEN?.trim();return e?{bearer:e}:{bearer:g}}async function g(){try{let e=(await(0,d.getVercelOidcToken)()).trim();if(e.length>0)return e}catch{}return process.env.VERCEL_OIDC_TOKEN?.trim()??``}function _(e,t){let n=t.maxConcurrency?Number.parseInt(t.maxConcurrency,10):void 0,r=t.timeout?Number.parseInt(t.timeout,10):void 0;if(n===void 0&&r===void 0)return e;let i={...e};return n!==void 0&&(i.maxConcurrency=n),r!==void 0&&(i.timeoutMs=r),i}function v(e,t){let n=t.json?[]:[new u];return!t.skipReport&&e.reporters&&n.push(...e.reporters),n}async function y(e,t){let n=await s(e);if(n.length===0){t.log(`No eval suites found.`);return}t.log(`Found ${n.length} eval suite file(s):\n`);for(let r of n){let n=await c(e,r);t.log(` ${n.id}${n.description?` - ${n.description}`:``}`)}}export{f as registerEvalCommand,p as runEvalCommand};
1
+ import{n as e}from"../../chunks/paths-BBleOGpa.js";import{loadDevelopmentEnvironmentFiles as t}from"../../cli/dev/environment.js";import{a as n,n as r,t as i}from"../../chunks/client-CKsU8Li3.js";import{n as a}from"../../chunks/host-e2GUqnTr.js";import{discoverAndImportSuites as o,discoverSuiteFiles as s,importSuiteFile as c}from"../runner/discover.js";import{executeSuite as l}from"../runner/execute-suite.js";import{ConsoleReporter as u}from"../runner/reporters/console.js";var d=n();function f(e,t){e.command(`eval`).description(`Run eval suites against an Ash agent.`).option(`--suite <id...>`,`Suite IDs to run (repeatable)`).option(`--all`,`Run all discovered suites`).option(`--url <url>`,`Remote agent URL (skip local host startup)`).option(`--timeout <ms>`,`Per-case timeout in milliseconds`).option(`--max-concurrency <n>`,`Max concurrent case executions per suite`).option(`--json`,`Output results as JSON`).option(`--list-suites`,`List discovered suites and exit`).option(`--skip-report`,`Skip suite-defined reporters (e.g. Braintrust)`).action(async e=>{await p(e,t)})}async function p(n,r){let i=e();if(t(i),n.listSuites){await y(i,r);return}let s=n.suite,c=await o(i,s);if(c.length===0){s&&s.length>0?r.error(`No suites found matching: ${s.join(`, `)}`):r.error(`No eval suites found. Create suite files under evals/ with the *.eval.ts extension.`),process.exitCode=1;return}let u,d;n.url?d={kind:`remote`,url:n.url}:(u=await a(i,{host:`127.0.0.1`,port:0}),d={kind:`local`,url:u.url});let f=m(d);try{let e=[];for(let t of c){let r=_(t,n),a=v(r,{json:n.json===!0,skipReport:n.skipReport===!0}),o=await l({suite:r,target:d,reporters:a,appRoot:i,client:f});e.push(o)}n.json&&r.log(JSON.stringify(e,null,2)),e.some(e=>e.errored>0)&&(process.exitCode=1)}finally{u&&await u.close()}process.exit(process.exitCode??0)}function m(e){if(e.kind===`local`)return new i({host:e.url});let t={},n=process.env.VERCEL_AUTOMATION_BYPASS_SECRET?.trim();return n&&(t[r]=n),new i({auth:h(),headers:Object.keys(t).length>0?t:void 0,host:e.url})}function h(){let e=process.env.ASH_EVAL_AUTH_TOKEN?.trim();return e?{bearer:e}:{bearer:g}}async function g(){try{let e=(await(0,d.getVercelOidcToken)()).trim();if(e.length>0)return e}catch{}return process.env.VERCEL_OIDC_TOKEN?.trim()??``}function _(e,t){let n=t.maxConcurrency?Number.parseInt(t.maxConcurrency,10):void 0,r=t.timeout?Number.parseInt(t.timeout,10):void 0;if(n===void 0&&r===void 0)return e;let i={...e};return n!==void 0&&(i.maxConcurrency=n),r!==void 0&&(i.timeoutMs=r),i}function v(e,t){let n=t.json?[]:[new u];return!t.skipReport&&e.reporters&&n.push(...e.reporters),n}async function y(e,t){let n=await s(e);if(n.length===0){t.log(`No eval suites found.`);return}t.log(`Found ${n.length} eval suite file(s):\n`);for(let r of n){let n=await c(e,r);t.log(` ${n.id}${n.description?` - ${n.description}`:``}`)}}export{f as registerEvalCommand,p as runEvalCommand};
@@ -1,9 +1,22 @@
1
1
  import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
2
+ import type { LocalSandboxCreateOptions } from "#public/sandbox/local-sandbox.js";
3
+ /**
4
+ * Construction input for {@link createLocalSandboxBackend}. Internal —
5
+ * the public surface is the `localBackend()` factory under
6
+ * `experimental-ash/sandbox`.
7
+ */
8
+ export interface CreateLocalSandboxBackendInput {
9
+ readonly createOptions?: LocalSandboxCreateOptions;
10
+ }
2
11
  /**
3
12
  * Creates the local just-bash-backed sandbox backend.
4
13
  *
5
14
  * The cache directory is derived from the runtime context's `appRoot`
6
15
  * on every `create` call so the backend stays stateless and matches
7
16
  * the framework's per-call dispatch contract.
17
+ *
18
+ * Accepts `createOptions` for parity with other backends; the local
19
+ * backend currently has no consumer-controllable create options so the
20
+ * value is reserved for future widening of {@link LocalSandboxCreateOptions}.
8
21
  */
9
- export declare function createLocalSandboxBackend(): SandboxBackend;
22
+ export declare function createLocalSandboxBackend(_input?: CreateLocalSandboxBackendInput): SandboxBackend;
@@ -13,8 +13,12 @@ const LOCAL_SANDBOX_SNAPSHOT_VERSION = 1;
13
13
  * The cache directory is derived from the runtime context's `appRoot`
14
14
  * on every `create` call so the backend stays stateless and matches
15
15
  * the framework's per-call dispatch contract.
16
+ *
17
+ * Accepts `createOptions` for parity with other backends; the local
18
+ * backend currently has no consumer-controllable create options so the
19
+ * value is reserved for future widening of {@link LocalSandboxCreateOptions}.
16
20
  */
17
- export function createLocalSandboxBackend() {
21
+ export function createLocalSandboxBackend(_input = {}) {
18
22
  return {
19
23
  name: "local",
20
24
  async prewarm(prewarmInput) {
@@ -13,10 +13,16 @@ export type VercelSandboxCreateOptions = Omit<NonNullable<Parameters<typeof SdkS
13
13
  * `experimental-ash/sandbox`.
14
14
  */
15
15
  export interface CreateVercelSandboxBackendInput {
16
+ readonly createOptions?: VercelSandboxCreateOptions;
16
17
  readonly loadSandboxModule?: () => Promise<VercelSandboxModule>;
17
18
  }
18
19
  /**
19
20
  * Creates the Vercel-backed sandbox backend.
21
+ *
22
+ * Any author-supplied `createOptions` are forwarded to the Vercel SDK
23
+ * `Sandbox.create(...)` for every fresh sandbox the framework creates
24
+ * (template at prewarm time, session at first-time session-create). On
25
+ * resume (`Sandbox.get`) no create happens, so they are not re-applied.
20
26
  */
21
27
  export declare function createVercelSandboxBackend(input?: CreateVercelSandboxBackendInput): SandboxBackend<VercelSandboxBootstrapUseOptions, VercelSandboxSessionUseOptions>;
22
28
  export {};
@@ -4,11 +4,17 @@ import { buildSandboxSession } from "#execution/sandbox/session.js";
4
4
  import { streamToBuffer } from "#execution/sandbox/stream-utils.js";
5
5
  /**
6
6
  * Creates the Vercel-backed sandbox backend.
7
+ *
8
+ * Any author-supplied `createOptions` are forwarded to the Vercel SDK
9
+ * `Sandbox.create(...)` for every fresh sandbox the framework creates
10
+ * (template at prewarm time, session at first-time session-create). On
11
+ * resume (`Sandbox.get`) no create happens, so they are not re-applied.
7
12
  */
8
13
  export function createVercelSandboxBackend(input = {}) {
9
14
  const loadSandboxModule = input.loadSandboxModule ?? (async () => await import("#compiled/@vercel/sandbox/index.js"));
10
15
  const createOptions = {
11
16
  timeout: DEFAULT_SANDBOX_TIMEOUT_MS,
17
+ ...input.createOptions,
12
18
  };
13
19
  const prewarmedTemplates = new Map();
14
20
  return {
@@ -150,8 +156,13 @@ async function ensureSession(input) {
150
156
  await ensureVercelSandboxTags(existing, input.tags);
151
157
  return existing;
152
158
  }
159
+ // The Vercel SDK rejects `runtime` when `source` is a snapshot — the
160
+ // runtime is already baked into the snapshot's filesystem. Strip it
161
+ // from the consumer-supplied create options for the session path
162
+ // only; template prewarm still honors `runtime`.
163
+ const { runtime: _runtime, ...sessionCreateOptions } = input.createOptions;
153
164
  const createParams = {
154
- ...input.createOptions,
165
+ ...sessionCreateOptions,
155
166
  name: sandboxName,
156
167
  persistent: true,
157
168
  source: { snapshotId: input.snapshotId, type: "snapshot" },
@@ -0,0 +1,15 @@
1
+ import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
2
+ /**
3
+ * Wraps a backend-producing function in a `SandboxBackend` proxy that
4
+ * invokes the function exactly once, on first access to any of `.name`,
5
+ * `.create`, or `.prewarm`. Subsequent accesses return the same cached
6
+ * underlying backend.
7
+ *
8
+ * Used by `defaultBackend()` for env-conditional selection, and by the
9
+ * authored-definition normalizer when an author passes a callback to
10
+ * `SandboxDefinition.backend` (e.g. `backend: () => vercelBackend({...})`)
11
+ * so the factory runs at first use rather than at module load — while
12
+ * still preserving backend-internal state (such as the Vercel backend's
13
+ * prewarmed-templates map) across every framework call.
14
+ */
15
+ export declare function lazyBackend<BO, SO>(factory: () => SandboxBackend<BO, SO>): SandboxBackend<BO, SO>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Wraps a backend-producing function in a `SandboxBackend` proxy that
3
+ * invokes the function exactly once, on first access to any of `.name`,
4
+ * `.create`, or `.prewarm`. Subsequent accesses return the same cached
5
+ * underlying backend.
6
+ *
7
+ * Used by `defaultBackend()` for env-conditional selection, and by the
8
+ * authored-definition normalizer when an author passes a callback to
9
+ * `SandboxDefinition.backend` (e.g. `backend: () => vercelBackend({...})`)
10
+ * so the factory runs at first use rather than at module load — while
11
+ * still preserving backend-internal state (such as the Vercel backend's
12
+ * prewarmed-templates map) across every framework call.
13
+ */
14
+ export function lazyBackend(factory) {
15
+ let resolved;
16
+ function resolve() {
17
+ if (resolved === undefined) {
18
+ resolved = factory();
19
+ }
20
+ return resolved;
21
+ }
22
+ return {
23
+ get name() {
24
+ return resolve().name;
25
+ },
26
+ create(input) {
27
+ return resolve().create(input);
28
+ },
29
+ async prewarm(input) {
30
+ await resolve().prewarm(input);
31
+ },
32
+ };
33
+ }
@@ -1,4 +1,4 @@
1
- import type { UserContent } from "ai";
1
+ import type { RunInput } from "#channel/types.js";
2
2
  /**
3
3
  * Serializable input for the workflow entrypoint.
4
4
  *
@@ -9,9 +9,7 @@ import type { UserContent } from "ai";
9
9
  * the serialized record.
10
10
  */
11
11
  export interface WorkflowEntryInput {
12
- readonly input: {
13
- readonly message: string | UserContent;
14
- };
12
+ readonly input: RunInput["input"];
15
13
  readonly serializedContext: Record<string, unknown>;
16
14
  }
17
15
  /**
@@ -41,7 +41,7 @@ export async function workflowEntry(input) {
41
41
  capabilities,
42
42
  initialInput: {
43
43
  kind: "deliver",
44
- payloads: [{ message: input.input.message }],
44
+ payloads: [{ message: input.input.message, modelContext: input.input.modelContext }],
45
45
  },
46
46
  mode,
47
47
  serializedContext: input.serializedContext,
@@ -14,6 +14,10 @@ export function coalesceTurnInputs(a, b) {
14
14
  a: a.message,
15
15
  b: b.message,
16
16
  });
17
+ const modelContext = coalesceModelContext({
18
+ a: a.modelContext,
19
+ b: b.modelContext,
20
+ });
17
21
  const result = {};
18
22
  if (inputResponses !== undefined) {
19
23
  result.inputResponses = inputResponses;
@@ -21,6 +25,9 @@ export function coalesceTurnInputs(a, b) {
21
25
  if (message !== undefined) {
22
26
  result.message = message;
23
27
  }
28
+ if (modelContext !== undefined) {
29
+ result.modelContext = modelContext;
30
+ }
24
31
  return result;
25
32
  }
26
33
  /**
@@ -73,6 +80,14 @@ function coalesceInputResponses(input) {
73
80
  }
74
81
  return [...a, ...b];
75
82
  }
83
+ function coalesceModelContext(input) {
84
+ const a = input.a ?? [];
85
+ const b = input.b ?? [];
86
+ if (a.length === 0 && b.length === 0) {
87
+ return undefined;
88
+ }
89
+ return [...a, ...b];
90
+ }
76
91
  /**
77
92
  * Merges two optional turn messages into one.
78
93
  *
@@ -79,13 +79,12 @@ export interface StepInput {
79
79
  readonly inputResponses?: readonly InputResponse[];
80
80
  readonly message?: string | UserContent;
81
81
  /**
82
- * Ephemeral messages contributed by lifecycle hooks (via the
83
- * `modelContext` field on a {@link LifecycleHookResult}), appended to
84
- * the next model call's message list and never persisted to durable
85
- * session history.
86
- *
87
- * Internal to the execution/harness boundary; never produced by
88
- * channels.
82
+ * Ephemeral messages appended to the next model call and never
83
+ * persisted to durable session history. Populated by channels via
84
+ * `SendPayload.modelContext` and by lifecycle hooks via the
85
+ * `modelContext` field on a `LifecycleHookResult`. Channel-provided
86
+ * messages appear first, then session-hook messages, then turn-hook
87
+ * messages.
89
88
  */
90
89
  readonly modelContext?: readonly ModelMessage[];
91
90
  /**
@@ -6,7 +6,7 @@ import { ASH_PACKAGE_NAME } from "#package-name.js";
6
6
  let cachedPackageInfo;
7
7
  // The package build stamps the published version into `dist` so bundled
8
8
  // deployments can still report package metadata without resolving package.json.
9
- const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.18.2";
9
+ const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.19.0";
10
10
  const BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER = "__ASH_PACKAGE_VERSION_PLACEHOLDER__";
11
11
  const WORKFLOW_MODULE_ALIASES = {
12
12
  "workflow/api": "src/compiled/@workflow/core/runtime.js",
@@ -1,9 +1,15 @@
1
+ import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
1
2
  import type { SandboxDefinition } from "#public/definitions/sandbox.js";
2
- export type NormalizedSandboxDefinition = Readonly<SandboxDefinition> & {
3
+ export type NormalizedSandboxDefinition = Readonly<Omit<SandboxDefinition, "backend">> & {
4
+ readonly backend?: SandboxBackend;
3
5
  readonly description?: string;
4
6
  };
5
7
  /**
6
8
  * Normalizes one authored sandbox definition into the canonical internal
7
- * shape.
9
+ * shape. If the author supplied a `backend` callback (e.g.
10
+ * `backend: () => vercelBackend({...})`), it is wrapped via
11
+ * {@link lazyBackend} so downstream consumers always see a plain
12
+ * `SandboxBackend` value — the callback fires exactly once on first
13
+ * access and the resulting backend is memoized.
8
14
  */
9
15
  export declare function normalizeSandboxDefinition(value: unknown, message: string): NormalizedSandboxDefinition;
@@ -1,7 +1,12 @@
1
1
  import { expectFunction, expectObjectRecord, expectOnlyKnownKeys, } from "#internal/authored-module.js";
2
+ import { lazyBackend } from "#execution/sandbox/lazy-backend.js";
2
3
  /**
3
4
  * Normalizes one authored sandbox definition into the canonical internal
4
- * shape.
5
+ * shape. If the author supplied a `backend` callback (e.g.
6
+ * `backend: () => vercelBackend({...})`), it is wrapped via
7
+ * {@link lazyBackend} so downstream consumers always see a plain
8
+ * `SandboxBackend` value — the callback fires exactly once on first
9
+ * access and the resulting backend is memoized.
5
10
  */
6
11
  export function normalizeSandboxDefinition(value, message) {
7
12
  const record = expectObjectRecord(value, message);
@@ -25,7 +30,10 @@ export function normalizeSandboxDefinition(value, message) {
25
30
  return definition;
26
31
  }
27
32
  function expectSandboxBackend(value, message) {
28
- const record = expectObjectRecord(value, `${message} The "backend" field must be a SandboxBackend value (use vercelBackend(), localBackend(), or your own factory).`);
33
+ if (typeof value === "function") {
34
+ return lazyBackend(value);
35
+ }
36
+ const record = expectObjectRecord(value, `${message} The "backend" field must be a SandboxBackend value (use vercelBackend(), localBackend(), or your own factory) or a zero-arg function returning one.`);
29
37
  if (typeof record.name !== "string" || record.name.length === 0) {
30
38
  throw new Error(`${message} The "backend" value must expose a non-empty string "name" identifier.`);
31
39
  }
@@ -11,7 +11,6 @@ export const WORKFLOW_STEP_EXTERNAL_PACKAGES = ["@mongodb-js/zstd", "node-liblzm
11
11
  * Nitro performs the final bundling/tracing pass for hosted output.
12
12
  */
13
13
  export const WORKFLOW_BUILDER_DEFERRED_PACKAGES = ["@chat-adapter/slack", "chat"];
14
- const WORKFLOW_FUNCTION_NODE_OPTIONS = "--experimental-require-module";
15
14
  /**
16
15
  * Builds the environment block every generated Vercel workflow function needs.
17
16
  */
@@ -20,7 +19,6 @@ export function createWorkflowFunctionEnvironment(environment) {
20
19
  if (isRecord(environment)) {
21
20
  Object.assign(nextEnvironment, environment);
22
21
  }
23
- nextEnvironment.NODE_OPTIONS = WORKFLOW_FUNCTION_NODE_OPTIONS;
24
22
  return nextEnvironment;
25
23
  }
26
24
  function isRecord(value) {
@@ -272,7 +272,7 @@ export interface SlackBinding {
272
272
  *
273
273
  * Auto-anchor: when the binding starts without a `threadTs`, the first
274
274
  * `chat.postMessage` adopts its own `ts` as the thread root; the live
275
- * `threadTs` is updated and `onAnchor` fires so the caller can persist
275
+ * `threadTs` is updated and `onThreadTsChanged` fires so the caller can persist
276
276
  * the anchor. Ephemerals and files-only posts do not anchor.
277
277
  */
278
278
  export declare function buildSlackBinding(input: {
@@ -280,31 +280,6 @@ export declare function buildSlackBinding(input: {
280
280
  readonly channelId: string;
281
281
  readonly threadTs: string;
282
282
  readonly teamId: string | undefined;
283
- readonly onAnchor?: (ts: string) => void;
283
+ readonly onThreadTsChanged?: (ts: string) => void;
284
284
  }): SlackBinding;
285
- /**
286
- * Best-effort GFM → Slack mrkdwn converter used only in contexts that
287
- * do not support `markdown_text` (e.g. `files.completeUploadExternal`'s
288
- * `initial_comment` field).
289
- *
290
- * The main `{ markdown }` post path sends `markdown_text` directly
291
- * to `chat.postMessage` and does not go through this converter.
292
- */
293
- export declare function gfmToSlackMrkdwn(input: string): string;
294
- /**
295
- * Best-effort Slack mrkdwn → GFM converter applied to the text of
296
- * every inbound Slack message before the harness sees it.
297
- *
298
- * - `<@U123>` → `@U123`
299
- * - `<#C123|name>` → `#name` (or `#C123` when no name)
300
- * - `<!channel>` etc. → `@channel`
301
- * - `<https://x|label>` → `[label](https://x)`
302
- * - `<https://x>` → `https://x`
303
- * - `*bold*` (paired) → `**bold**`
304
- * - `~strike~` (paired) → `~~strike~~`
305
- *
306
- * Inline `_italic_` and code spans pass through unchanged because both
307
- * formats render them identically.
308
- */
309
- export declare function slackMrkdwnToGfm(input: string): string;
310
285
  export {};
@@ -18,6 +18,7 @@ import { isCardElement } from "#compiled/chat/index.js";
18
18
  import { createLogger } from "#internal/logging.js";
19
19
  import { encodeSlackApiBody } from "#public/channels/slack/api-encoding.js";
20
20
  import { cardToBlocks, cardToFallbackText } from "#public/channels/slack/blocks.js";
21
+ import { gfmToSlackMrkdwn, rewriteBareMentions, slackMrkdwnToGfm, } from "#public/channels/slack/mrkdwn.js";
21
22
  const log = createLogger("slack.api");
22
23
  /**
23
24
  * Builds the Slack channel-local continuation token
@@ -72,18 +73,18 @@ export function createSlackRequester(botToken) {
72
73
  *
73
74
  * Auto-anchor: when the binding starts without a `threadTs`, the first
74
75
  * `chat.postMessage` adopts its own `ts` as the thread root; the live
75
- * `threadTs` is updated and `onAnchor` fires so the caller can persist
76
+ * `threadTs` is updated and `onThreadTsChanged` fires so the caller can persist
76
77
  * the anchor. Ephemerals and files-only posts do not anchor.
77
78
  */
78
79
  export function buildSlackBinding(input) {
79
80
  const request = createSlackRequester(input.botToken);
80
81
  const messages = [];
81
82
  let currentThreadTs = input.threadTs;
82
- function anchorIfUnset(ts) {
83
- if (currentThreadTs.length > 0 || ts.length === 0)
83
+ function handleMessageTs(ts) {
84
+ if (currentThreadTs || ts === currentThreadTs)
84
85
  return;
85
86
  currentThreadTs = ts;
86
- input.onAnchor?.(ts);
87
+ input.onThreadTsChanged?.(ts);
87
88
  }
88
89
  async function uploadFiles(files, options) {
89
90
  if (files.length === 0) {
@@ -168,7 +169,7 @@ export function buildSlackBinding(input) {
168
169
  throw new Error(`Slack chat.postMessage failed: ${response.error ?? "unknown_error"}`);
169
170
  }
170
171
  const id = typeof response.ts === "string" ? response.ts : "";
171
- anchorIfUnset(id);
172
+ handleMessageTs(id);
172
173
  // blocks / card + files: structured message lands first, then upload
173
174
  // files as a follow-up post in the same thread.
174
175
  if (files.length > 0 && hasStructured) {
@@ -313,70 +314,6 @@ function parseThreadMessage(raw, threadRootTs) {
313
314
  raw,
314
315
  };
315
316
  }
316
- const BARE_MENTION_RE = /(?<![<\w])@(\w+)/gu;
317
- function rewriteBareMentions(text) {
318
- return text.replace(BARE_MENTION_RE, "<@$1>");
319
- }
320
- /**
321
- * Best-effort GFM → Slack mrkdwn converter used only in contexts that
322
- * do not support `markdown_text` (e.g. `files.completeUploadExternal`'s
323
- * `initial_comment` field).
324
- *
325
- * The main `{ markdown }` post path sends `markdown_text` directly
326
- * to `chat.postMessage` and does not go through this converter.
327
- */
328
- export function gfmToSlackMrkdwn(input) {
329
- const segments = splitCodeFences(input);
330
- return segments
331
- .map((segment) => (segment.kind === "code" ? segment.text : convertInline(segment.text)))
332
- .join("");
333
- }
334
- /**
335
- * Best-effort Slack mrkdwn → GFM converter applied to the text of
336
- * every inbound Slack message before the harness sees it.
337
- *
338
- * - `<@U123>` → `@U123`
339
- * - `<#C123|name>` → `#name` (or `#C123` when no name)
340
- * - `<!channel>` etc. → `@channel`
341
- * - `<https://x|label>` → `[label](https://x)`
342
- * - `<https://x>` → `https://x`
343
- * - `*bold*` (paired) → `**bold**`
344
- * - `~strike~` (paired) → `~~strike~~`
345
- *
346
- * Inline `_italic_` and code spans pass through unchanged because both
347
- * formats render them identically.
348
- */
349
- export function slackMrkdwnToGfm(input) {
350
- const segments = splitCodeFences(input);
351
- return segments
352
- .map((segment) => (segment.kind === "code" ? segment.text : decodeInline(segment.text)))
353
- .join("");
354
- }
355
- function splitCodeFences(input) {
356
- const segments = [];
357
- const fenceRe = /```[\s\S]*?```|`[^`\n]+`/gu;
358
- let lastIndex = 0;
359
- for (const match of input.matchAll(fenceRe)) {
360
- const start = match.index ?? 0;
361
- if (start > lastIndex) {
362
- segments.push({ kind: "text", text: input.slice(lastIndex, start) });
363
- }
364
- segments.push({ kind: "code", text: match[0] });
365
- lastIndex = start + match[0].length;
366
- }
367
- if (lastIndex < input.length) {
368
- segments.push({ kind: "text", text: input.slice(lastIndex) });
369
- }
370
- return segments;
371
- }
372
- function convertInline(input) {
373
- let out = input;
374
- out = out.replace(/\*\*([^*\n]+)\*\*/gu, "*$1*");
375
- out = out.replace(/__([^_\n]+)__/gu, "*$1*");
376
- out = out.replace(/~~([^~\n]+)~~/gu, "~$1~");
377
- out = out.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/gu, "<$2|$1>");
378
- return out;
379
- }
380
317
  /**
381
318
  * Normalize a {@link FileUpload.data} value (`Buffer | Blob | ArrayBuffer`) to
382
319
  * a contiguous `Buffer` we can both POST and length-prefix without
@@ -394,16 +331,3 @@ async function readFileBytes(data) {
394
331
  }
395
332
  throw new Error("FileUpload.data must be a Buffer, ArrayBuffer, or Blob.");
396
333
  }
397
- function decodeInline(input) {
398
- let out = input;
399
- out = out.replace(/<!(channel|here|everyone)>/gu, "@$1");
400
- out = out.replace(/<@([A-Z0-9]+)\|([^>]+)>/gu, "@$2");
401
- out = out.replace(/<@([A-Z0-9]+)>/gu, "@$1");
402
- out = out.replace(/<#([A-Z0-9]+)\|([^>]+)>/gu, "#$2");
403
- out = out.replace(/<#([A-Z0-9]+)>/gu, "#$1");
404
- out = out.replace(/<(https?:\/\/[^|>\s]+)\|([^>]+)>/gu, "[$2]($1)");
405
- out = out.replace(/<(https?:\/\/[^>\s]+)>/gu, "$1");
406
- out = out.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/gu, "$1**$2**");
407
- out = out.replace(/(^|[^~])~([^~\n]+)~(?!~)/gu, "$1~~$2~~");
408
- return out;
409
- }
@@ -1,7 +1,7 @@
1
1
  import { createLogger, extractErrorId, formatErrorHint } from "#internal/logging.js";
2
2
  import { buildAuthCompletedText, buildAuthEphemeralBlocks, buildAuthRequiredPublicText, formatConnectionDisplayName, } from "#public/channels/slack/connections.js";
3
3
  import { renderInputRequestBlocks } from "#public/channels/slack/hitl.js";
4
- import { truncateTypingStatus } from "#public/channels/slack/limits.js";
4
+ import { truncateMessageText, truncateTypingStatus } from "#public/channels/slack/limits.js";
5
5
  const log = createLogger("slack.defaults");
6
6
  /**
7
7
  * Workspace-scoped projection of the Slack actor that produced
@@ -84,7 +84,7 @@ export function defaultInputRequestedHandler() {
84
84
  return async (data, ctx) => {
85
85
  if (data.requests.length === 0)
86
86
  return;
87
- const promptText = data.requests.map((r) => r.prompt).join("\n");
87
+ const promptText = truncateMessageText(data.requests.map((r) => r.prompt).join("\n"));
88
88
  await ctx.thread.post({
89
89
  blocks: data.requests.flatMap(renderInputRequestBlocks),
90
90
  text: promptText,