experimental-ash 0.8.1 → 0.8.3
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 +46 -0
- package/dist/src/chunks/{dev-authored-source-watcher-BFC_yNcP.js → dev-authored-source-watcher-CDT0dQQf.js} +1 -1
- package/dist/src/chunks/{host-DMccRKcz.js → host-M56X595D.js} +2 -2
- package/dist/src/chunks/{paths-B-aiDznc.js → paths-B-Onq-sx.js} +25 -25
- package/dist/src/chunks/{prewarm-CCbReSNm.js → prewarm-DQzlGOTi.js} +1 -1
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/evals/cli/eval.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-definition/connection.js +10 -0
- package/dist/src/public/channels/slack/index.d.ts +1 -1
- package/dist/src/public/channels/slack/slack.js +8 -8
- package/dist/src/public/channels/slack/slackChannel.d.ts +22 -2
- package/dist/src/public/channels/slack/slackChannel.js +15 -4
- package/dist/src/runtime/connections/types.d.ts +21 -0
- package/dist/src/runtime/connections/validate-authorization.js +27 -2
- 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-B-
|
|
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-B-Onq-sx.js";import{t as m}from"./authored-module-loader-Pt_g8xX2.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-B-
|
|
1
|
+
import{D as e,b as t,t as n,v as r,y as i}from"../../chunks/paths-B-Onq-sx.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-M56X595D.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-M56X595D.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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"../../chunks/paths-B-
|
|
1
|
+
import{n as e}from"../../chunks/paths-B-Onq-sx.js";import{loadDevelopmentEnvironmentFiles as t}from"../../cli/dev/environment.js";import{a as n,n as r,t as i}from"../../chunks/client-BeZ_W7vl.js";import{n as a}from"../../chunks/host-M56X595D.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.8.
|
|
9
|
+
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.8.3";
|
|
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",
|
|
@@ -13,6 +13,16 @@ const KNOWN_AUTHORIZATION_KEYS = [
|
|
|
13
13
|
"getToken",
|
|
14
14
|
"principalType",
|
|
15
15
|
"startAuthorization",
|
|
16
|
+
// Optional metadata marker that auth-provider helpers may attach so
|
|
17
|
+
// downstream tooling (eg. the Ash compiler / Vercel dashboard) can
|
|
18
|
+
// detect Vercel Connect-backed connections without opening the
|
|
19
|
+
// closure state of `getToken`. The runtime never reads it; it
|
|
20
|
+
// survives `normalizeAuthorizationSpec` so consumers can pick it
|
|
21
|
+
// off the normalized auth definition. See
|
|
22
|
+
// `runtime/connections/types.ts#AuthorizationDefinitionBase` for
|
|
23
|
+
// the type and `@vercel/connect/ash`'s `connect()` for the
|
|
24
|
+
// canonical producer.
|
|
25
|
+
"vercelConnect",
|
|
16
26
|
];
|
|
17
27
|
/**
|
|
18
28
|
* Validates one authored MCP client connection module export at build time
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { slack, type SlackOptions } from "#public/channels/slack/slack.js";
|
|
2
|
-
export { slackChannel, type SlackApiHandle, type SlackApiResponse, type SlackChannel, type SlackChannelConfig, type SlackChannelEvents, type SlackChannelCredentials, type SlackChannelState, type SlackContext, type SlackInteractionAction, type SlackReceiveArgs, } from "#public/channels/slack/slackChannel.js";
|
|
2
|
+
export { slackChannel, type SlackApiHandle, type SlackApiResponse, type SlackChannel, type SlackChannelConfig, type SlackChannelEvents, type SlackChannelCredentials, type SlackChannelState, type SlackContext, type SlackInteractionAction, type SlackReceiveArgs, type SlackWebhookVerifier, } from "#public/channels/slack/slackChannel.js";
|
|
3
3
|
export { Actions, Button, Card, CardText, Divider, Fields, Image, LinkButton, Modal, RadioSelect, Section, Select, SelectOption, Table, TextInput, } from "#compiled/chat/index.js";
|
|
4
4
|
export type { AdapterPostableMessage, Attachment, Author, CardElement, FileUpload, Message, PostableMessage, SentMessage, Thread, } from "#compiled/chat/index.js";
|
|
@@ -19,21 +19,21 @@ export function slack(options = {}) {
|
|
|
19
19
|
return { auth: resolveAuth(message) };
|
|
20
20
|
},
|
|
21
21
|
events: {
|
|
22
|
-
"turn.started"(_event, ctx) {
|
|
23
|
-
ctx.thread.startTyping("Thinking...");
|
|
22
|
+
async "turn.started"(_event, ctx) {
|
|
23
|
+
await ctx.thread.startTyping("Thinking...");
|
|
24
24
|
},
|
|
25
|
-
"actions.requested"(event, ctx) {
|
|
25
|
+
async "actions.requested"(event, ctx) {
|
|
26
26
|
const labels = event.actions.map((a) => (a.kind === "tool-call" ? a.toolName : a.kind));
|
|
27
|
-
ctx.thread.startTyping(`Running ${labels.join(", ")}...`);
|
|
27
|
+
await ctx.thread.startTyping(`Running ${labels.join(", ")}...`);
|
|
28
28
|
},
|
|
29
|
-
"message.completed"(event, ctx) {
|
|
29
|
+
async "message.completed"(event, ctx) {
|
|
30
30
|
if (event.finishReason === "tool-calls")
|
|
31
31
|
return;
|
|
32
32
|
if (event.message)
|
|
33
|
-
ctx.thread.post(event.message);
|
|
33
|
+
await ctx.thread.post(event.message);
|
|
34
34
|
},
|
|
35
|
-
"session.failed"(_event, ctx) {
|
|
36
|
-
ctx.thread.post("Something went wrong.");
|
|
35
|
+
async "session.failed"(_event, ctx) {
|
|
36
|
+
await ctx.thread.post("Something went wrong.");
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
39
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type
|
|
1
|
+
import { type SlackBotToken } from "#compiled/@chat-adapter/slack/index.js";
|
|
2
|
+
import { type Message, type SerializedThread, type Thread } from "#compiled/chat/index.js";
|
|
3
3
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
4
4
|
import { type UploadPolicy } from "#public/channels/upload-policy.js";
|
|
5
5
|
import { type Channel } from "#public/definitions/defineChannel.js";
|
|
@@ -28,9 +28,29 @@ export interface SlackChannelState {
|
|
|
28
28
|
serializedThread: SerializedThread | null;
|
|
29
29
|
teamId: string | null;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Verifies an inbound Slack webhook request. Returns when the request
|
|
33
|
+
* is authentic, throws otherwise. Used as an alternative to HMAC
|
|
34
|
+
* verification with `signingSecret` — for example, the Connex SDK
|
|
35
|
+
* supplies a verifier that authenticates Connex-forwarded webhooks
|
|
36
|
+
* with Vercel OIDC instead of Slack's signing secret.
|
|
37
|
+
*/
|
|
38
|
+
export type SlackWebhookVerifier = (request: Request, body: string) => unknown | Promise<unknown>;
|
|
31
39
|
export interface SlackChannelCredentials {
|
|
32
40
|
readonly botToken?: SlackBotToken;
|
|
41
|
+
/**
|
|
42
|
+
* Slack signing secret used to HMAC-verify inbound webhook requests.
|
|
43
|
+
* Falls back to `process.env.SLACK_SIGNING_SECRET` when neither this
|
|
44
|
+
* nor `webhookVerifier` is supplied.
|
|
45
|
+
*/
|
|
33
46
|
readonly signingSecret?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Custom inbound webhook verifier. When supplied, ash skips the
|
|
49
|
+
* `SLACK_SIGNING_SECRET` fallback and delegates verification to this
|
|
50
|
+
* function. Typically populated by integrations (e.g. Connex) that
|
|
51
|
+
* authenticate webhooks out-of-band.
|
|
52
|
+
*/
|
|
53
|
+
readonly webhookVerifier?: SlackWebhookVerifier;
|
|
34
54
|
}
|
|
35
55
|
export interface SlackReceiveArgs {
|
|
36
56
|
readonly channelId: string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createSlackAdapter } from "#compiled/@chat-adapter/slack/index.js";
|
|
2
|
+
import { ThreadImpl, } from "#compiled/chat/index.js";
|
|
1
3
|
import { createLogger } from "#internal/logging.js";
|
|
2
4
|
import { buildSlackTurnMessage, collectSlackFileParts, createSlackFetchFile, } from "#public/channels/slack/attachments.js";
|
|
3
5
|
import { deriveHitlResponse, isHitlAction, renderInputRequestBlocks, } from "#public/channels/slack/hitl.js";
|
|
@@ -74,15 +76,18 @@ function extractSelectedOptionValue(action) {
|
|
|
74
76
|
return typeof selected?.value === "string" ? selected.value : undefined;
|
|
75
77
|
}
|
|
76
78
|
function rebuildSlackContext(state, botToken) {
|
|
77
|
-
const
|
|
79
|
+
const adapter = createSlackAdapter({
|
|
80
|
+
botToken: botToken ?? process.env.SLACK_BOT_TOKEN,
|
|
81
|
+
});
|
|
78
82
|
const thread = state.serializedThread
|
|
79
|
-
?
|
|
80
|
-
: new
|
|
83
|
+
? ThreadImpl.fromJSON(state.serializedThread, adapter)
|
|
84
|
+
: new ThreadImpl({
|
|
81
85
|
adapterName: "slack",
|
|
82
86
|
channelId: "",
|
|
83
87
|
id: "slack::",
|
|
84
88
|
isDM: false,
|
|
85
89
|
});
|
|
90
|
+
thread._adapter = adapter;
|
|
86
91
|
return {
|
|
87
92
|
thread,
|
|
88
93
|
slack: buildSlackApiHandle(thread, botToken, state.teamId ?? undefined),
|
|
@@ -118,7 +123,12 @@ export function slackChannel(config = {}) {
|
|
|
118
123
|
return chatPromise;
|
|
119
124
|
const promise = (async () => {
|
|
120
125
|
const botToken = config.credentials?.botToken ?? process.env.SLACK_BOT_TOKEN;
|
|
121
|
-
const
|
|
126
|
+
const webhookVerifier = config.credentials?.webhookVerifier;
|
|
127
|
+
// Skip the SLACK_SIGNING_SECRET env-var fallback when a webhook
|
|
128
|
+
// verifier is supplied, so integrations like Connex don't need
|
|
129
|
+
// the signing secret set in the environment at all.
|
|
130
|
+
const signingSecret = config.credentials?.signingSecret ??
|
|
131
|
+
(webhookVerifier ? undefined : process.env.SLACK_SIGNING_SECRET);
|
|
122
132
|
if (!botToken) {
|
|
123
133
|
throw new Error("slackChannel requires a bot token. Pass credentials.botToken or set SLACK_BOT_TOKEN.");
|
|
124
134
|
}
|
|
@@ -130,6 +140,7 @@ export function slackChannel(config = {}) {
|
|
|
130
140
|
const slackAdapter = slackModule.createSlackAdapter({
|
|
131
141
|
botToken,
|
|
132
142
|
signingSecret,
|
|
143
|
+
webhookVerifier,
|
|
133
144
|
userName: config.botName,
|
|
134
145
|
});
|
|
135
146
|
const chat = new chatModule.Chat({
|
|
@@ -127,6 +127,27 @@ interface AuthorizationDefinitionBase {
|
|
|
127
127
|
* full design.
|
|
128
128
|
*/
|
|
129
129
|
readonly principalType: "app" | "user";
|
|
130
|
+
/**
|
|
131
|
+
* Optional metadata marker attached by `connect()` from
|
|
132
|
+
* `@vercel/connect/ash` so downstream tooling — eg. a future Ash
|
|
133
|
+
* compiler step that surfaces connector identifiers in build output,
|
|
134
|
+
* or the Vercel dashboard rendering deep links to a connector's
|
|
135
|
+
* settings page — can detect Vercel Connect-backed connections at
|
|
136
|
+
* compile time without inspecting `getToken`'s closure state.
|
|
137
|
+
*
|
|
138
|
+
* The runtime token-fetch path ignores this field; it is purely
|
|
139
|
+
* provider attribution. Authors writing their own `getToken`
|
|
140
|
+
* callbacks (raw bearer tokens, custom callbacks) should leave it
|
|
141
|
+
* unset.
|
|
142
|
+
*
|
|
143
|
+
* `connector` carries whatever value the author passed to
|
|
144
|
+
* `connect()` — UID like `"oauth/mcp-linear-app"` or opaque
|
|
145
|
+
* `"scl_..."`; both forms address the same connector on the Vercel
|
|
146
|
+
* Connect side.
|
|
147
|
+
*/
|
|
148
|
+
readonly vercelConnect?: {
|
|
149
|
+
readonly connector: string;
|
|
150
|
+
};
|
|
130
151
|
}
|
|
131
152
|
/**
|
|
132
153
|
* Non-interactive authorization: the runtime only ever calls
|
|
@@ -67,17 +67,42 @@ export function normalizeAuthorizationSpec(authorization, prefix, fieldName = "a
|
|
|
67
67
|
throw new Error(`${prefix} ${message}`);
|
|
68
68
|
}
|
|
69
69
|
const auth = authorization;
|
|
70
|
+
const vercelConnect = extractVercelConnectMarker(auth.vercelConnect);
|
|
70
71
|
if (auth.startAuthorization !== undefined && auth.completeAuthorization !== undefined) {
|
|
71
|
-
|
|
72
|
+
const interactive = {
|
|
72
73
|
completeAuthorization: auth.completeAuthorization,
|
|
73
74
|
getToken: auth.getToken,
|
|
74
75
|
principalType: "user",
|
|
75
76
|
startAuthorization: auth.startAuthorization,
|
|
76
77
|
};
|
|
78
|
+
return vercelConnect === undefined ? interactive : { ...interactive, vercelConnect };
|
|
77
79
|
}
|
|
78
|
-
|
|
80
|
+
const nonInteractive = {
|
|
79
81
|
getToken: auth.getToken,
|
|
80
82
|
principalType: (auth.principalType ??
|
|
81
83
|
"app"),
|
|
82
84
|
};
|
|
85
|
+
return vercelConnect === undefined ? nonInteractive : { ...nonInteractive, vercelConnect };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Reads the optional `vercelConnect: { connector: string }` marker
|
|
89
|
+
* attached by `@vercel/connect/ash`'s `connect()` helper. Returns the
|
|
90
|
+
* parsed marker when present and well-formed, otherwise `undefined`.
|
|
91
|
+
*
|
|
92
|
+
* The marker is opaque to the runtime — it exists so downstream tooling
|
|
93
|
+
* (eg. the Ash compiler / Vercel dashboard) can attribute the auth
|
|
94
|
+
* back to a Vercel Connect connector without inspecting `getToken`'s
|
|
95
|
+
* closure state. Validation is lenient (a malformed marker is dropped,
|
|
96
|
+
* not thrown) so a misbehaving auth provider can't fail an otherwise-
|
|
97
|
+
* valid connection.
|
|
98
|
+
*/
|
|
99
|
+
function extractVercelConnectMarker(value) {
|
|
100
|
+
if (value === null || typeof value !== "object") {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const connector = value.connector;
|
|
104
|
+
if (typeof connector !== "string" || connector.length === 0) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
return { connector };
|
|
83
108
|
}
|