experimental-ash 0.7.0 → 0.7.2

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 (41) hide show
  1. package/dist/docs/external-agent-protocol.md +5 -5
  2. package/dist/docs/internals/message-runtime.md +4 -4
  3. package/dist/docs/public/auth-and-route-protection.md +10 -10
  4. package/dist/docs/public/channels/README.md +9 -6
  5. package/dist/docs/public/cli-build-and-debugging.md +1 -2
  6. package/dist/docs/public/getting-started.md +0 -11
  7. package/dist/docs/public/skills.md +2 -2
  8. package/dist/docs/public/typescript-api.md +1 -1
  9. package/dist/src/chunks/{dev-authored-source-watcher-HzOplr1S.js → dev-authored-source-watcher-D3ybKVO9.js} +1 -1
  10. package/dist/src/chunks/{host-Ca8xvEQ1.js → host-Ck0qkepf.js} +2 -2
  11. package/dist/src/chunks/{paths-BiY7uVwD.js → paths-BFX2EgQO.js} +21 -21
  12. package/dist/src/chunks/{prewarm-DiZ_sYLy.js → prewarm-DJtOdukm.js} +1 -1
  13. package/dist/src/cli/commands/info.js +1 -1
  14. package/dist/src/cli/run.d.ts +0 -5
  15. package/dist/src/cli/run.js +2 -2
  16. package/dist/src/evals/cli/eval.js +1 -1
  17. package/dist/src/internal/application/package.js +1 -1
  18. package/dist/src/public/channels/{http.d.ts → ash.d.ts} +2 -2
  19. package/dist/src/public/channels/{http.js → ash.js} +2 -2
  20. package/dist/src/public/channels/index.d.ts +1 -1
  21. package/dist/src/public/channels/slack/attachments.d.ts +35 -0
  22. package/dist/src/public/channels/slack/attachments.js +100 -0
  23. package/dist/src/public/channels/slack/hitl.d.ts +67 -0
  24. package/dist/src/public/channels/slack/hitl.js +101 -0
  25. package/dist/src/public/channels/slack/index.d.ts +1 -0
  26. package/dist/src/public/channels/slack/slackChannel.d.ts +13 -0
  27. package/dist/src/public/channels/slack/slackChannel.js +40 -47
  28. package/dist/src/public/definitions/defineChannel.d.ts +9 -0
  29. package/dist/src/public/definitions/defineChannel.js +10 -8
  30. package/dist/src/runtime/framework-channels/index.d.ts +1 -1
  31. package/dist/src/runtime/framework-channels/index.js +7 -7
  32. package/package.json +5 -5
  33. package/dist/src/cli/commands/init.d.ts +0 -13
  34. package/dist/src/cli/commands/init.js +0 -1
  35. package/dist/src/cli/templates/init-app/.vercelignore +0 -11
  36. package/dist/src/cli/templates/init-app/agent/agent.ts +0 -5
  37. package/dist/src/cli/templates/init-app/agent/instructions.md +0 -3
  38. package/dist/src/cli/templates/init-app/agent/tools/hello.ts +0 -12
  39. package/dist/src/cli/templates/init-app/gitignore +0 -8
  40. package/dist/src/cli/templates/init-app/package.json +0 -18
  41. package/dist/src/cli/templates/init-app/tsconfig.json +0 -16
@@ -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-BiY7uVwD.js";import{t as m}from"./authored-module-loader-CqZSviWm.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-BFX2EgQO.js";import{t as m}from"./authored-module-loader-CqZSviWm.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,logicalPath:e.logicalPath,markdown:e.markdown,name:e.name,sourceId:e.sourceId,sourceKind:e.sourceKind};return e.channel===void 0?t:{...t,channel:e.channel}})}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-BiY7uVwD.js";import{d as a,f as o,g as s}from"../../chunks/types-D9Uv7nU4.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-BFX2EgQO.js";import{d as a,f as o,g as s}from"../../chunks/types-D9Uv7nU4.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};
@@ -8,11 +8,6 @@ interface DevelopmentServerHandle {
8
8
  }
9
9
  interface CliRuntimeDependencies {
10
10
  buildHost(appRoot: string): Promise<string>;
11
- initializeApplication(input: {
12
- logger: CliLogger;
13
- parentDirectoryPath: string;
14
- targetName: string;
15
- }): Promise<string>;
16
11
  printApplicationInfo(logger: CliLogger, appRoot: string): Promise<void>;
17
12
  runDevelopmentRepl(input: {
18
13
  serverUrl: string;
@@ -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-Ca8xvEQ1.js`).then(e=>e.t)).buildHost}async function l(){return(await import(`./commands/init.js`)).initializeApplication}async function u(){return(await import(`./commands/info.js`)).printApplicationInfo}async function d(){return(await import(`./dev/repl.js`)).runDevelopmentRepl}async function f(){return(await import(`../evals/cli/eval.js`)).runEvalCommand}async function p(){return(await import(`../chunks/host-Ca8xvEQ1.js`).then(e=>e.t)).startHost}function m(e=process.cwd()){return s(e)}function h(e){return`Ash (v${e})`}function g(e){return e.name()===`info`||e.name()===`dev`}async function _(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 v(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 y(){return!!(process.stdin.isTTY&&process.stdout.isTTY)}function b(e){let t=e[1];return e[0]!==`dev`||e.length!==2||t===void 0||t.startsWith(`-`)?[...e]:[`dev`,`--url`,t]}function x(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 S(r,a){let s=m(),b=e().version,S=new i,C=t();return S.name(`ash`).description(`Build and run an Ash application.`).version(b).showHelpAfterError().exitOverride().hook(`preAction`,(e,t)=>{g(t)&&r.log(h(b))}).configureOutput({writeErr:e=>{r.error(e.trimEnd())},writeOut:e=>{r.log(e.trimEnd())}}),S.command(`build`).description(`Build the current Ash application.`).action(async()=>{let e=await(a.buildHost??await c())(s);r.log(n(C,{message:`built output at ${e}`,tag:`build`,tone:`success`}))}),S.command(`init`).description(`Scaffold a new Ash application.`).argument(`<name>`,`Directory name for the new Ash application`).action(async e=>{await(a.initializeApplication??await l())({logger:r,parentDirectoryPath:s,targetName:e})}),S.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)`,v).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-Ck0qkepf.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-Ck0qkepf.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 e=await(a.buildHost??await c())(s);r.log(n(S,{message:`built output at ${e}`,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
- `).action(async e=>{let t=x(e),{loadDevelopmentEnvironmentFiles:i}=await import(`./dev/environment.js`);if(i(s),t){if(r.log(n(C,{message:`REPL connecting to ${t}`,tag:`dev`,tone:`info`})),!y()){r.log(n(C,{message:`Interactive REPL disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`}));return}r.log(``),await(a.runDevelopmentRepl??await d())({serverUrl:t});return}let o=await(a.startHost??await p())(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(C,{message:`server listening at ${o.url}`,tag:`dev`,tone:`success`})),e.repl===!1)return await _({close:l});if(!y())return r.log(n(C,{message:`Interactive REPL disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`})),await _({close:l});r.log(``),await(a.runDevelopmentRepl??await d())({serverUrl:o.url})}finally{await l()}}),S.command(`info`).description(`Print resolved application information.`).action(async()=>{await(a.printApplicationInfo??await u())(r,s)}),S.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 f())(e,r)}),S}async function C(e=process.argv.slice(2),t=console,n={}){let r=S(t,n),i=e.length===0?[`info`]:b(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{C as runCli};
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};
@@ -1 +1 @@
1
- import{n as e}from"../../chunks/paths-BiY7uVwD.js";import{loadDevelopmentEnvironmentFiles as t}from"../../cli/dev/environment.js";import{a as n,n as r,t as i}from"../../chunks/client-DBMG7iuf.js";import{n as a}from"../../chunks/host-Ca8xvEQ1.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-BFX2EgQO.js";import{loadDevelopmentEnvironmentFiles as t}from"../../cli/dev/environment.js";import{a as n,n as r,t as i}from"../../chunks/client-DBMG7iuf.js";import{n as a}from"../../chunks/host-Ck0qkepf.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};
@@ -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.7.0";
9
+ const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.7.2";
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,8 +1,8 @@
1
1
  import { type AuthFn } from "#public/channels/auth.js";
2
2
  import { type UploadPolicy } from "#public/channels/upload-policy.js";
3
3
  import { type Channel } from "#public/definitions/defineChannel.js";
4
- export interface HttpRouteInput {
4
+ export interface AshChannelInput {
5
5
  readonly auth: AuthFn<Request>;
6
6
  readonly uploadPolicy?: Partial<UploadPolicy>;
7
7
  }
8
- export declare function httpChannel(input: HttpRouteInput): Channel;
8
+ export declare function ashChannel(input: AshChannelInput): Channel;
@@ -3,7 +3,7 @@ import { isInputResponse } from "#runtime/input/types.js";
3
3
  import { createUnauthorizedResponse } from "#public/channels/auth.js";
4
4
  import { collectUploadPolicyViolations, formatUploadPolicyViolation, mergeUploadPolicy, } from "#public/channels/upload-policy.js";
5
5
  import { defineChannel, POST, GET } from "#public/definitions/defineChannel.js";
6
- export function httpChannel(input) {
6
+ export function ashChannel(input) {
7
7
  const uploadPolicy = mergeUploadPolicy(input.uploadPolicy);
8
8
  return defineChannel({
9
9
  routes: [
@@ -28,7 +28,7 @@ export function httpChannel(input) {
28
28
  const policyRejection = checkUploadPolicy(body, uploadPolicy);
29
29
  if (policyRejection !== null)
30
30
  return policyRejection;
31
- const token = `http:${crypto.randomUUID()}`;
31
+ const token = `ash:${crypto.randomUUID()}`;
32
32
  const messageText = typeof body.message === "string"
33
33
  ? body.message
34
34
  : body.message
@@ -1 +1 @@
1
- export { defineChannel, POST, GET, PUT, DELETE, type Channel, type ChannelConfig, type ChannelEvents, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, } from "#public/definitions/defineChannel.js";
1
+ export { defineChannel, POST, GET, PUT, DELETE, type AttachmentResolver, type Channel, type ChannelConfig, type ChannelEvents, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, } from "#public/definitions/defineChannel.js";
@@ -0,0 +1,35 @@
1
+ import type { FilePart, UserContent } from "ai";
2
+ import type { SlackBotToken } from "#compiled/@chat-adapter/slack/index.js";
3
+ import type { Message } from "#compiled/chat/index.js";
4
+ import type { AttachmentResolver } from "#channel/adapter.js";
5
+ import type { UploadPolicy } from "#public/channels/upload-policy.js";
6
+ /**
7
+ * Payload an `ash-attachment:` ref carries for Slack file uploads.
8
+ * The Slack resolver reads `params.url` and fetches it with the bot
9
+ * token.
10
+ */
11
+ export interface SlackAttachmentParams {
12
+ readonly url: string;
13
+ }
14
+ /**
15
+ * Emits one {@link FilePart} per supported attachment in the inbound
16
+ * message, with `data` set to an `ash-attachment:` ref URL. Audio,
17
+ * video, url-less, and policy-violating attachments are dropped so a
18
+ * single bad upload never blocks the text portion of the mention.
19
+ */
20
+ export declare function collectSlackFileParts(message: Pick<Message, "attachments">, policy: UploadPolicy): FilePart[];
21
+ /**
22
+ * Combines text + file parts into the {@link UserContent} shape the
23
+ * harness expects. Returns the raw text string when there are no
24
+ * parts (the common path).
25
+ */
26
+ export declare function buildSlackTurnMessage(text: string, fileParts: readonly FilePart[]): string | UserContent;
27
+ /**
28
+ * Slack-channel {@link AttachmentResolver}: fetches the upstream
29
+ * Slack file URL with the bot token and returns the bytes plus any
30
+ * advertised media-type. Staging writes bytes to the sandbox and
31
+ * rewrites the file part as an `ash-sandbox:` ref.
32
+ */
33
+ export declare function createSlackAttachmentResolver(input: {
34
+ readonly botToken?: SlackBotToken;
35
+ }): AttachmentResolver<SlackAttachmentParams>;
@@ -0,0 +1,100 @@
1
+ import { AshAttachmentError } from "#internal/attachments/errors.js";
2
+ import { encodeAttachmentRef } from "#internal/attachments/refs.js";
3
+ import { createLogger } from "#internal/logging.js";
4
+ import { evaluateFilePart, formatUploadPolicyViolation } from "#public/channels/upload-policy.js";
5
+ const log = createLogger("slack.attachments");
6
+ /**
7
+ * Emits one {@link FilePart} per supported attachment in the inbound
8
+ * message, with `data` set to an `ash-attachment:` ref URL. Audio,
9
+ * video, url-less, and policy-violating attachments are dropped so a
10
+ * single bad upload never blocks the text portion of the mention.
11
+ */
12
+ export function collectSlackFileParts(message, policy) {
13
+ const parts = [];
14
+ for (const attachment of message.attachments ?? []) {
15
+ const part = toSlackFilePart(attachment, parts.length);
16
+ if (part === null)
17
+ continue;
18
+ const violation = evaluateFilePart(part, policy);
19
+ if (violation !== null) {
20
+ log.warn(`dropped attachment — ${formatUploadPolicyViolation(violation)}`, {
21
+ name: attachment.name,
22
+ });
23
+ continue;
24
+ }
25
+ parts.push(part);
26
+ }
27
+ return parts;
28
+ }
29
+ function toSlackFilePart(attachment, index) {
30
+ if (attachment.type === "audio" || attachment.type === "video") {
31
+ return null;
32
+ }
33
+ if (!attachment.url) {
34
+ log.warn("dropped attachment — no url available for ref construction", {
35
+ name: attachment.name,
36
+ });
37
+ return null;
38
+ }
39
+ return {
40
+ type: "file",
41
+ mediaType: attachment.mimeType ?? "application/octet-stream",
42
+ filename: attachment.name ?? `attachment-${index}`,
43
+ data: encodeAttachmentRef({
44
+ params: { url: attachment.url },
45
+ size: attachment.size,
46
+ }),
47
+ };
48
+ }
49
+ /**
50
+ * Combines text + file parts into the {@link UserContent} shape the
51
+ * harness expects. Returns the raw text string when there are no
52
+ * parts (the common path).
53
+ */
54
+ export function buildSlackTurnMessage(text, fileParts) {
55
+ if (fileParts.length === 0) {
56
+ return text;
57
+ }
58
+ const trimmed = text.trim();
59
+ if (trimmed.length === 0) {
60
+ return [...fileParts];
61
+ }
62
+ const textPart = { type: "text", text };
63
+ return [textPart, ...fileParts];
64
+ }
65
+ /** Materializes a {@link SlackBotToken} to a string at fetch time
66
+ * so callers can rotate (function-shaped credentials) or read lazily. */
67
+ async function resolveSlackBotToken(token) {
68
+ const source = token ?? process.env.SLACK_BOT_TOKEN;
69
+ if (!source) {
70
+ throw new Error("SLACK_BOT_TOKEN is required to resolve Slack attachment refs.");
71
+ }
72
+ return typeof source === "function" ? await source() : source;
73
+ }
74
+ /**
75
+ * Slack-channel {@link AttachmentResolver}: fetches the upstream
76
+ * Slack file URL with the bot token and returns the bytes plus any
77
+ * advertised media-type. Staging writes bytes to the sandbox and
78
+ * rewrites the file part as an `ash-sandbox:` ref.
79
+ */
80
+ export function createSlackAttachmentResolver(input) {
81
+ return {
82
+ async resolve(ref) {
83
+ const token = await resolveSlackBotToken(input.botToken);
84
+ const response = await fetch(ref.params.url, {
85
+ headers: { authorization: `Bearer ${token}` },
86
+ });
87
+ if (!response.ok) {
88
+ throw new AshAttachmentError({
89
+ adapterKind: "slack",
90
+ kind: "fetch-failed",
91
+ message: `Slack attachment fetch returned HTTP ${response.status} for ${ref.params.url}.`,
92
+ });
93
+ }
94
+ return {
95
+ bytes: Buffer.from(await response.arrayBuffer()),
96
+ mediaType: response.headers.get("content-type") ?? undefined,
97
+ };
98
+ },
99
+ };
100
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Slack HITL widget rendering + click-decode helpers.
3
+ *
4
+ * Wire format: every HITL widget mints `action_id = HITL_ACTION_PREFIX
5
+ * + requestId`. The colon delimiter is not valid inside an AI SDK call
6
+ * id (`requestId = action.callId`), so the decode is unambiguous for
7
+ * any requestId shape — including the `call_…` / `toolu_…` ids that
8
+ * contain `_`.
9
+ *
10
+ * Buttons surface the selected option on `action.value`; radio and
11
+ * static selects surface it on `selected_option.value`. The decoder
12
+ * picks whichever is set so the renderer can pick a widget kind on
13
+ * UX grounds without changing the read path.
14
+ */
15
+ import type { InputRequest } from "#runtime/input/types.js";
16
+ /**
17
+ * Wire-format prefix every framework HITL widget mints onto its
18
+ * `action_id`. Exposed so end-user adapters that render their own
19
+ * interactive widgets can avoid collisions.
20
+ */
21
+ export declare const HITL_ACTION_PREFIX = "ash_input:";
22
+ /**
23
+ * Subset of one Slack interactivity action the HITL decoder reads.
24
+ * Mirrors the relevant fields of `SlackInteractionAction`.
25
+ */
26
+ export interface SlackHitlAction {
27
+ readonly actionId: string;
28
+ /** `value` field on Slack `button` payloads. */
29
+ readonly value?: string;
30
+ /** `selected_option.value` field on radio / static-select payloads. */
31
+ readonly selectedOptionValue?: string;
32
+ }
33
+ /**
34
+ * Resolved HITL response derived from one Slack interactivity action.
35
+ * Matches the `InputResponse` contract minus `text` — freeform answers
36
+ * come back through a different interaction path.
37
+ */
38
+ export interface DerivedHitlResponse {
39
+ readonly requestId: string;
40
+ readonly optionId: string;
41
+ }
42
+ /**
43
+ * Decodes one Slack interactivity action into an HITL response, or
44
+ * returns `null` when the action does not match an HITL widget the
45
+ * framework rendered.
46
+ */
47
+ export declare function deriveHitlResponse(action: SlackHitlAction): DerivedHitlResponse | null;
48
+ /**
49
+ * Returns `true` when the action id was minted by an HITL widget the
50
+ * framework rendered. Used by the channel route to split inbound
51
+ * clicks into the HITL path vs. the user-owned `onInteraction` path.
52
+ */
53
+ export declare function isHitlAction(actionId: string): boolean;
54
+ /**
55
+ * Renders one `InputRequest` as Block Kit blocks:
56
+ *
57
+ * - `display === "select"` with ≤ {@link RADIO_SELECT_OPTION_LIMIT}
58
+ * options → `radio_buttons`. Single-click answer, options stay
59
+ * visible.
60
+ * - `display === "select"` with more options → `static_select`
61
+ * dropdown so the picker stays scrollable.
62
+ * - Anything else with options → buttons. Best for visually distinct
63
+ * choices (approve / deny / cancel).
64
+ *
65
+ * Always emits at least the prompt section.
66
+ */
67
+ export declare function renderInputRequestBlocks(request: InputRequest): unknown[];
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Slack HITL widget rendering + click-decode helpers.
3
+ *
4
+ * Wire format: every HITL widget mints `action_id = HITL_ACTION_PREFIX
5
+ * + requestId`. The colon delimiter is not valid inside an AI SDK call
6
+ * id (`requestId = action.callId`), so the decode is unambiguous for
7
+ * any requestId shape — including the `call_…` / `toolu_…` ids that
8
+ * contain `_`.
9
+ *
10
+ * Buttons surface the selected option on `action.value`; radio and
11
+ * static selects surface it on `selected_option.value`. The decoder
12
+ * picks whichever is set so the renderer can pick a widget kind on
13
+ * UX grounds without changing the read path.
14
+ */
15
+ /**
16
+ * Wire-format prefix every framework HITL widget mints onto its
17
+ * `action_id`. Exposed so end-user adapters that render their own
18
+ * interactive widgets can avoid collisions.
19
+ */
20
+ export const HITL_ACTION_PREFIX = "ash_input:";
21
+ /**
22
+ * Maximum radio-button option count before the renderer falls back to
23
+ * a `static_select` dropdown. Matches Slack's UX guidance (radio
24
+ * groups stay readable up to ~6 items).
25
+ */
26
+ const RADIO_SELECT_OPTION_LIMIT = 6;
27
+ /**
28
+ * Decodes one Slack interactivity action into an HITL response, or
29
+ * returns `null` when the action does not match an HITL widget the
30
+ * framework rendered.
31
+ */
32
+ export function deriveHitlResponse(action) {
33
+ if (!action.actionId.startsWith(HITL_ACTION_PREFIX))
34
+ return null;
35
+ const requestId = action.actionId.slice(HITL_ACTION_PREFIX.length);
36
+ const optionId = action.selectedOptionValue ?? action.value;
37
+ return requestId && optionId ? { optionId, requestId } : null;
38
+ }
39
+ /**
40
+ * Returns `true` when the action id was minted by an HITL widget the
41
+ * framework rendered. Used by the channel route to split inbound
42
+ * clicks into the HITL path vs. the user-owned `onInteraction` path.
43
+ */
44
+ export function isHitlAction(actionId) {
45
+ return actionId.startsWith(HITL_ACTION_PREFIX);
46
+ }
47
+ /**
48
+ * Renders one `InputRequest` as Block Kit blocks:
49
+ *
50
+ * - `display === "select"` with ≤ {@link RADIO_SELECT_OPTION_LIMIT}
51
+ * options → `radio_buttons`. Single-click answer, options stay
52
+ * visible.
53
+ * - `display === "select"` with more options → `static_select`
54
+ * dropdown so the picker stays scrollable.
55
+ * - Anything else with options → buttons. Best for visually distinct
56
+ * choices (approve / deny / cancel).
57
+ *
58
+ * Always emits at least the prompt section.
59
+ */
60
+ export function renderInputRequestBlocks(request) {
61
+ const prompt = { text: { text: request.prompt, type: "mrkdwn" }, type: "section" };
62
+ const actionId = `${HITL_ACTION_PREFIX}${request.requestId}`;
63
+ const options = request.options;
64
+ if (!options || options.length === 0) {
65
+ return [prompt];
66
+ }
67
+ if (request.display === "select") {
68
+ const widget = options.length <= RADIO_SELECT_OPTION_LIMIT
69
+ ? { type: "radio_buttons", action_id: actionId, options: options.map(buildOption) }
70
+ : {
71
+ type: "static_select",
72
+ action_id: actionId,
73
+ options: options.map(buildOption),
74
+ placeholder: { type: "plain_text", text: "Choose an option" },
75
+ };
76
+ return [prompt, { type: "actions", elements: [widget] }];
77
+ }
78
+ return [prompt, { type: "actions", elements: options.map((opt) => buildButton(opt, actionId)) }];
79
+ }
80
+ function buildButton(opt, actionId) {
81
+ const button = {
82
+ action_id: actionId,
83
+ text: { text: opt.label, type: "plain_text" },
84
+ type: "button",
85
+ value: opt.id,
86
+ };
87
+ if (opt.style === "primary" || opt.style === "danger") {
88
+ button.style = opt.style;
89
+ }
90
+ return button;
91
+ }
92
+ function buildOption(opt) {
93
+ const option = {
94
+ text: { text: opt.label, type: "plain_text" },
95
+ value: opt.id,
96
+ };
97
+ if (opt.description && opt.description.length > 0) {
98
+ option.description = { text: opt.description, type: "plain_text" };
99
+ }
100
+ return option;
101
+ }
@@ -1,4 +1,5 @@
1
1
  export { slack, type SlackOptions } from "#public/channels/slack/slack.js";
2
2
  export { slackChannel, type SlackApiHandle, type SlackApiResponse, type SlackChannelConfig, type SlackChannelEvents, type SlackChannelCredentials, type SlackChannelState, type SlackContext, type SlackInteractionAction, type SlackReceiveArgs, } from "#public/channels/slack/slackChannel.js";
3
+ export type { SlackAttachmentParams } from "#public/channels/slack/attachments.js";
3
4
  export { Actions, Button, Card, CardText, Divider, Fields, Image, LinkButton, Modal, RadioSelect, Section, Select, SelectOption, Table, TextInput, } from "#compiled/chat/index.js";
4
5
  export type { AdapterPostableMessage, Attachment, Author, CardElement, FileUpload, Message, PostableMessage, SentMessage, Thread, } from "#compiled/chat/index.js";
@@ -1,6 +1,7 @@
1
1
  import type { SlackBotToken } from "#compiled/@chat-adapter/slack/index.js";
2
2
  import type { Message, SerializedThread, Thread } from "#compiled/chat/index.js";
3
3
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
4
+ import { type UploadPolicy } from "#public/channels/upload-policy.js";
4
5
  import { type Channel } from "#public/definitions/defineChannel.js";
5
6
  type EventData<T extends HandleMessageStreamEvent["type"]> = Extract<HandleMessageStreamEvent, {
6
7
  type: T;
@@ -38,6 +39,11 @@ export interface SlackInteractionAction {
38
39
  readonly actionId: string;
39
40
  readonly value?: string;
40
41
  readonly blockId?: string;
42
+ /**
43
+ * `selected_option.value` for radio / select / external_select
44
+ * widgets. `undefined` for buttons and multi-select widgets.
45
+ */
46
+ readonly selectedOptionValue?: string;
41
47
  /**
42
48
  * `ts` of the Slack message that hosts the clicked component. Required by
43
49
  * handlers that want to update the clicked message in place via
@@ -66,6 +72,13 @@ export interface SlackChannelEvents {
66
72
  export interface SlackChannelConfig {
67
73
  readonly credentials?: SlackChannelCredentials;
68
74
  readonly botName?: string;
75
+ /**
76
+ * Inbound upload policy applied to file attachments before they
77
+ * reach the harness. Violating attachments are dropped with a
78
+ * warning so the text portion of the mention still gets delivered.
79
+ * Defaults to the framework's 25 MB cap with unrestricted media types.
80
+ */
81
+ readonly uploadPolicy?: Partial<UploadPolicy>;
69
82
  run?(ctx: SlackContext, message: Message): SlackRunResult;
70
83
  onInteraction?(action: SlackInteractionAction, ctx: SlackContext): void | Promise<void>;
71
84
  readonly events?: SlackChannelEvents;