experimental-ash 0.18.3 → 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.
- package/CHANGELOG.md +7 -0
- package/dist/docs/internals/context.md +7 -0
- package/dist/docs/public/channels/README.md +5 -0
- package/dist/docs/public/channels/slack.md +58 -4
- package/dist/docs/public/hooks.md +4 -2
- package/dist/docs/public/sandbox.md +71 -49
- package/dist/docs/public/typescript-api.md +6 -1
- package/dist/src/channel/adapter.js +12 -2
- package/dist/src/channel/routes.d.ts +9 -1
- package/dist/src/channel/send.js +3 -3
- package/dist/src/channel/types.d.ts +3 -1
- package/dist/src/chunks/{dev-authored-source-watcher-j7YWh2Gx.js → dev-authored-source-watcher-L3_pagDa.js} +1 -1
- package/dist/src/chunks/{host-C19hLVqS.js → host-e2GUqnTr.js} +2 -2
- package/dist/src/chunks/{paths-Dwv0Eash.js → paths-BBleOGpa.js} +25 -25
- package/dist/src/chunks/{prewarm-CQYfka30.js → prewarm-DEymC5M-.js} +1 -1
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +1 -1
- package/dist/src/compiled/@vercel/sandbox/index.d.ts +6 -1
- package/dist/src/context/hook-lifecycle.js +5 -1
- package/dist/src/evals/cli/eval.js +1 -1
- package/dist/src/execution/sandbox/bindings/local.d.ts +14 -1
- package/dist/src/execution/sandbox/bindings/local.js +5 -1
- package/dist/src/execution/sandbox/bindings/vercel.d.ts +6 -0
- package/dist/src/execution/sandbox/bindings/vercel.js +12 -1
- package/dist/src/execution/sandbox/lazy-backend.d.ts +15 -0
- package/dist/src/execution/sandbox/lazy-backend.js +33 -0
- package/dist/src/execution/workflow-entry.d.ts +2 -4
- package/dist/src/execution/workflow-entry.js +1 -1
- package/dist/src/harness/messages.js +15 -0
- package/dist/src/harness/types.d.ts +6 -7
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-definition/sandbox.d.ts +8 -2
- package/dist/src/internal/authored-definition/sandbox.js +10 -2
- package/dist/src/public/channels/slack/index.d.ts +3 -0
- package/dist/src/public/channels/slack/index.js +2 -0
- package/dist/src/public/channels/slack/slackChannel.d.ts +12 -4
- package/dist/src/public/channels/slack/slackChannel.js +4 -1
- package/dist/src/public/channels/slack/thread.d.ts +26 -0
- package/dist/src/public/channels/slack/thread.js +45 -0
- package/dist/src/public/sandbox/backends/default.d.ts +16 -1
- package/dist/src/public/sandbox/backends/default.js +7 -19
- package/dist/src/public/sandbox/backends/local.d.ts +7 -4
- package/dist/src/public/sandbox/backends/local.js +7 -5
- package/dist/src/public/sandbox/backends/vercel.d.ts +9 -3
- package/dist/src/public/sandbox/backends/vercel.js +9 -3
- package/dist/src/public/sandbox/index.d.ts +2 -1
- package/dist/src/public/sandbox/local-sandbox.d.ts +7 -0
- package/dist/src/public/sandbox/local-sandbox.js +1 -0
- package/dist/src/public/sandbox/vercel-sandbox.d.ts +13 -1
- package/dist/src/runtime/resolve-sandbox.js +5 -1
- package/dist/src/runtime/types.d.ts +10 -1
- package/dist/src/shared/sandbox-definition.d.ts +16 -1
- 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-
|
|
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-
|
|
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};
|
package/dist/src/cli/run.js
CHANGED
|
@@ -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-
|
|
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};
|
|
@@ -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
|
-
|
|
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:
|
|
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-
|
|
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
|
-
...
|
|
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 {
|
|
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
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
export type { ModelMessage } from "ai";
|
|
1
2
|
export { slackChannel, type SlackApiResponse, type SlackBotToken, type SlackChannel, type SlackChannelConfig, type SlackChannelCredentials, type SlackChannelEvents, type SlackChannelState, type SlackContext, type SlackEventContext, type SlackHandle, type SlackInboundResult, type SlackInboundResultOrPromise, type SlackInitialMessage, type SlackInteractionAction, type SlackMentionResult, type SlackMentionResultOrPromise, type SlackReceiveArgs, type SlackThread, type SlackWebhookVerifier, } from "#public/channels/slack/slackChannel.js";
|
|
2
3
|
export type { SlackAttachment, SlackAuthor, SlackInboundContext, SlackMessage, } from "#public/channels/slack/inbound.js";
|
|
3
4
|
export { slackContinuationToken, type SlackPostInput, type SlackPostedMessage, type SlackThreadMessage, type SlackUploadFilesOptions, type SlackUploadFilesResult, } from "#public/channels/slack/api.js";
|
|
5
|
+
export { defaultSlackAuth } from "#public/channels/slack/defaults.js";
|
|
6
|
+
export { loadThreadContextMessages, type LoadThreadContextMessagesOptions, type ThreadContextSince, } from "#public/channels/slack/thread.js";
|
|
4
7
|
export { cardToBlocks, cardToFallbackText, type BlockKitBlock, } from "#public/channels/slack/blocks.js";
|
|
5
8
|
/**
|
|
6
9
|
* Card builders and element types re-exported from the vendored chat
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { slackChannel, } from "#public/channels/slack/slackChannel.js";
|
|
2
2
|
export { slackContinuationToken, } from "#public/channels/slack/api.js";
|
|
3
|
+
export { defaultSlackAuth } from "#public/channels/slack/defaults.js";
|
|
4
|
+
export { loadThreadContextMessages, } from "#public/channels/slack/thread.js";
|
|
3
5
|
export { cardToBlocks, cardToFallbackText, } from "#public/channels/slack/blocks.js";
|
|
4
6
|
/**
|
|
5
7
|
* Card builders and element types re-exported from the vendored chat
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ModelMessage } from "ai";
|
|
1
2
|
import type { TypedReceiveRoute } from "#channel/receive-args.js";
|
|
2
3
|
import type { SessionHandle } from "#channel/session.js";
|
|
3
4
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
@@ -158,12 +159,19 @@ export interface SlackInteractionUser {
|
|
|
158
159
|
readonly name?: string;
|
|
159
160
|
}
|
|
160
161
|
/**
|
|
161
|
-
* Result of an `onAppMention` or `onDirectMessage` callback. Return
|
|
162
|
-
*
|
|
163
|
-
*
|
|
162
|
+
* Result of an `onAppMention` or `onDirectMessage` callback. Return an
|
|
163
|
+
* object (auth may be `null`) to dispatch a turn, or `null` to silently
|
|
164
|
+
* drop the inbound message.
|
|
165
|
+
*
|
|
166
|
+
* `modelContext` mirrors lifecycle hook results: it contributes
|
|
167
|
+
* ephemeral messages to the next model call only, without writing them
|
|
168
|
+
* to durable session history. Use `role: "system"` for Slack thread
|
|
169
|
+
* background context so it lands before the user's mention; see
|
|
170
|
+
* `docs/public/channels/slack.md`.
|
|
164
171
|
*/
|
|
165
172
|
export type SlackMentionResult = {
|
|
166
|
-
auth: SessionAuthContext | null;
|
|
173
|
+
readonly auth: SessionAuthContext | null;
|
|
174
|
+
readonly modelContext?: readonly ModelMessage[];
|
|
167
175
|
} | null;
|
|
168
176
|
export type SlackMentionResultOrPromise = SlackMentionResult | Promise<SlackMentionResult>;
|
|
169
177
|
/**
|
|
@@ -230,7 +230,10 @@ async function dispatchInboundMessage(input) {
|
|
|
230
230
|
? prependSlackContext(baseMessage, inboundContext)
|
|
231
231
|
: baseMessage;
|
|
232
232
|
try {
|
|
233
|
-
await input.send(
|
|
233
|
+
await input.send({
|
|
234
|
+
message: turnMessage,
|
|
235
|
+
modelContext: result.modelContext,
|
|
236
|
+
}, {
|
|
234
237
|
auth: result.auth,
|
|
235
238
|
continuationToken: slackContinuationToken(message.channelId, message.threadTs),
|
|
236
239
|
state: {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SlackThread, SlackThreadMessage } from "#public/channels/slack/api.js";
|
|
2
|
+
export type ThreadContextSince = "thread-root" | "last-agent-reply" | ((message: SlackThreadMessage) => boolean);
|
|
3
|
+
export interface LoadThreadContextMessagesOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Boundary for returned context messages. Defaults to `"thread-root"`.
|
|
6
|
+
*
|
|
7
|
+
* Use `"last-agent-reply"` to include only user/thread messages
|
|
8
|
+
* since the last agent-authored Slack reply. Pass a predicate
|
|
9
|
+
* function for custom boundaries, such as "since the last message
|
|
10
|
+
* that mentioned a particular user".
|
|
11
|
+
*/
|
|
12
|
+
readonly since?: ThreadContextSince;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Loads messages that are useful as background context for the current
|
|
16
|
+
* Slack thread turn.
|
|
17
|
+
*
|
|
18
|
+
* Returns an empty array when `message` is the thread root. For thread
|
|
19
|
+
* replies, refreshes the bound Slack thread and returns its recent
|
|
20
|
+
* messages before the triggering message, filtered by {@link options}.
|
|
21
|
+
* Formatting and model-message role choice stay with the caller.
|
|
22
|
+
*/
|
|
23
|
+
export declare function loadThreadContextMessages(thread: Pick<SlackThread, "recentMessages" | "refresh">, message: {
|
|
24
|
+
readonly threadTs: string;
|
|
25
|
+
readonly ts: string;
|
|
26
|
+
}, options?: LoadThreadContextMessagesOptions): Promise<SlackThreadMessage[]>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads messages that are useful as background context for the current
|
|
3
|
+
* Slack thread turn.
|
|
4
|
+
*
|
|
5
|
+
* Returns an empty array when `message` is the thread root. For thread
|
|
6
|
+
* replies, refreshes the bound Slack thread and returns its recent
|
|
7
|
+
* messages before the triggering message, filtered by {@link options}.
|
|
8
|
+
* Formatting and model-message role choice stay with the caller.
|
|
9
|
+
*/
|
|
10
|
+
export async function loadThreadContextMessages(thread, message, options = {}) {
|
|
11
|
+
if (isThreadRootMessage(message)) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
await thread.refresh();
|
|
15
|
+
const currentIndex = thread.recentMessages.findIndex((entry) => entry.ts === message.ts);
|
|
16
|
+
const candidateMessages = currentIndex === -1 ? thread.recentMessages : thread.recentMessages.slice(0, currentIndex);
|
|
17
|
+
const priorMessages = candidateMessages.filter((entry) => entry.threadTs === message.threadTs && entry.ts !== message.ts);
|
|
18
|
+
return applySinceBoundary(priorMessages, options.since);
|
|
19
|
+
}
|
|
20
|
+
function isThreadRootMessage(message) {
|
|
21
|
+
return message.threadTs === message.ts;
|
|
22
|
+
}
|
|
23
|
+
function findLastIndex(items, predicate) {
|
|
24
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
25
|
+
if (predicate(items[index])) {
|
|
26
|
+
return index;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return -1;
|
|
30
|
+
}
|
|
31
|
+
function applySinceBoundary(messages, since) {
|
|
32
|
+
const boundary = since ?? "thread-root";
|
|
33
|
+
if (typeof boundary === "function") {
|
|
34
|
+
const lastMatchingIndex = findLastIndex(messages, boundary);
|
|
35
|
+
return messages.slice(lastMatchingIndex + 1);
|
|
36
|
+
}
|
|
37
|
+
switch (boundary) {
|
|
38
|
+
case "thread-root":
|
|
39
|
+
return [...messages];
|
|
40
|
+
case "last-agent-reply": {
|
|
41
|
+
const lastAgentReplyIndex = findLastIndex(messages, (entry) => entry.isMe);
|
|
42
|
+
return messages.slice(lastAgentReplyIndex + 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
|
|
2
|
+
import type { LocalSandboxCreateOptions } from "#public/sandbox/local-sandbox.js";
|
|
3
|
+
import type { VercelSandboxCreateOptions } from "#public/sandbox/vercel-sandbox.js";
|
|
4
|
+
/**
|
|
5
|
+
* Input to {@link defaultBackend}: a separate options bag per inner
|
|
6
|
+
* backend. The framework picks one bag at runtime based on `process.env.VERCEL`
|
|
7
|
+
* and passes it to the chosen factory; the other is ignored.
|
|
8
|
+
*/
|
|
9
|
+
export interface DefaultBackendOptions {
|
|
10
|
+
readonly local?: LocalSandboxCreateOptions;
|
|
11
|
+
readonly vercel?: VercelSandboxCreateOptions;
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* Constructs an env-aware sandbox backend that delegates to
|
|
4
15
|
* {@link vercelBackend} on hosted Vercel (where `process.env.VERCEL`
|
|
5
16
|
* is truthy) and to {@link localBackend} everywhere else.
|
|
17
|
+
*
|
|
18
|
+
* Optionally accepts a keyed options bag (`{ local, vercel }`) so each
|
|
19
|
+
* inner backend receives its own typed create options without forcing
|
|
20
|
+
* authors to pin to one backend up front.
|
|
6
21
|
*/
|
|
7
|
-
export declare function defaultBackend(): SandboxBackend;
|
|
22
|
+
export declare function defaultBackend(opts?: DefaultBackendOptions): SandboxBackend;
|
|
@@ -1,27 +1,15 @@
|
|
|
1
|
+
import { lazyBackend } from "#execution/sandbox/lazy-backend.js";
|
|
1
2
|
import { localBackend } from "#public/sandbox/backends/local.js";
|
|
2
3
|
import { vercelBackend } from "#public/sandbox/backends/vercel.js";
|
|
3
4
|
/**
|
|
4
5
|
* Constructs an env-aware sandbox backend that delegates to
|
|
5
6
|
* {@link vercelBackend} on hosted Vercel (where `process.env.VERCEL`
|
|
6
7
|
* is truthy) and to {@link localBackend} everywhere else.
|
|
8
|
+
*
|
|
9
|
+
* Optionally accepts a keyed options bag (`{ local, vercel }`) so each
|
|
10
|
+
* inner backend receives its own typed create options without forcing
|
|
11
|
+
* authors to pin to one backend up front.
|
|
7
12
|
*/
|
|
8
|
-
export function defaultBackend() {
|
|
9
|
-
|
|
10
|
-
function resolve() {
|
|
11
|
-
if (resolved === undefined) {
|
|
12
|
-
resolved = process.env.VERCEL ? vercelBackend() : localBackend();
|
|
13
|
-
}
|
|
14
|
-
return resolved;
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
get name() {
|
|
18
|
-
return resolve().name;
|
|
19
|
-
},
|
|
20
|
-
create(input) {
|
|
21
|
-
return resolve().create(input);
|
|
22
|
-
},
|
|
23
|
-
async prewarm(input) {
|
|
24
|
-
await resolve().prewarm(input);
|
|
25
|
-
},
|
|
26
|
-
};
|
|
13
|
+
export function defaultBackend(opts) {
|
|
14
|
+
return lazyBackend(() => process.env.VERCEL ? vercelBackend(opts?.vercel) : localBackend(opts?.local));
|
|
27
15
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
|
|
2
|
+
import type { LocalSandboxCreateOptions } from "#public/sandbox/local-sandbox.js";
|
|
2
3
|
/**
|
|
3
4
|
* Constructs the built-in local sandbox backend.
|
|
4
5
|
*
|
|
@@ -7,8 +8,10 @@ import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
|
|
|
7
8
|
* under the application root. It is the default backend on developer
|
|
8
9
|
* machines (`pnpm ash dev`).
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Accepts an `opts` parameter for parity with other backends, but
|
|
12
|
+
* `LocalSandboxCreateOptions` is empty today — `just-bash` does not
|
|
13
|
+
* currently expose any configuration worth surfacing. New options would
|
|
14
|
+
* be added by widening `LocalSandboxCreateOptions` and routing them
|
|
15
|
+
* into the binding without changing this signature.
|
|
13
16
|
*/
|
|
14
|
-
export declare function localBackend(): SandboxBackend;
|
|
17
|
+
export declare function localBackend(opts?: LocalSandboxCreateOptions): SandboxBackend;
|