experimental-ash 0.16.3 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/dist/docs/internals/schedule.md +37 -26
  3. package/dist/docs/public/channels/README.md +71 -0
  4. package/dist/docs/public/schedules.md +77 -49
  5. package/dist/src/channel/cross-channel-receive.d.ts +61 -0
  6. package/dist/src/channel/cross-channel-receive.js +50 -0
  7. package/dist/src/channel/receive-args.d.ts +17 -0
  8. package/dist/src/channel/receive-args.js +1 -0
  9. package/dist/src/channel/routes.d.ts +9 -0
  10. package/dist/src/channel/schedule.d.ts +45 -32
  11. package/dist/src/channel/schedule.js +56 -50
  12. package/dist/src/chunks/authored-module-loader-XcFLnl49.js +2 -0
  13. package/dist/src/chunks/{dev-authored-source-watcher-Tu9dhx5K.js → dev-authored-source-watcher-CG6kri3T.js} +1 -1
  14. package/dist/src/chunks/{host-C83crl7k.js → host-CIU0NATc.js} +6 -6
  15. package/dist/src/chunks/paths-CvbqpwTh.js +88 -0
  16. package/dist/src/chunks/{prewarm-CdxOi2uE.js → prewarm-C_Vd0JR7.js} +2 -2
  17. package/dist/src/cli/commands/info.js +1 -1
  18. package/dist/src/cli/run.js +1 -1
  19. package/dist/src/compiled/.vendor-stamp.json +1 -1
  20. package/dist/src/compiled/@vercel/sandbox/index.d.ts +8 -1
  21. package/dist/src/compiler/manifest.d.ts +6 -24
  22. package/dist/src/compiler/manifest.js +2 -8
  23. package/dist/src/compiler/normalize-channel.d.ts +0 -8
  24. package/dist/src/compiler/normalize-channel.js +0 -27
  25. package/dist/src/compiler/normalize-manifest.js +2 -10
  26. package/dist/src/compiler/normalize-schedule.d.ts +6 -12
  27. package/dist/src/compiler/normalize-schedule.js +9 -32
  28. package/dist/src/evals/cli/eval.js +1 -1
  29. package/dist/src/evals/runner/discover.js +1 -1
  30. package/dist/src/execution/sandbox/bindings/vercel.d.ts +2 -2
  31. package/dist/src/execution/sandbox/bindings/vercel.js +8 -1
  32. package/dist/src/internal/application/package.js +1 -1
  33. package/dist/src/internal/authored-definition/core.d.ts +3 -2
  34. package/dist/src/internal/authored-definition/core.js +20 -10
  35. package/dist/src/internal/authored-module-loader.d.ts +0 -6
  36. package/dist/src/internal/authored-module-loader.js +11 -72
  37. package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response.js +3 -1
  38. package/dist/src/internal/nitro/routes/channel-dispatch.js +3 -0
  39. package/dist/src/internal/nitro/routes/runtime-stack.d.ts +0 -11
  40. package/dist/src/internal/nitro/routes/runtime-stack.js +0 -25
  41. package/dist/src/internal/nitro/routes/schedule-task.d.ts +3 -3
  42. package/dist/src/internal/nitro/routes/schedule-task.js +41 -11
  43. package/dist/src/public/channels/slack/index.d.ts +1 -1
  44. package/dist/src/public/channels/slack/slackChannel.d.ts +20 -1
  45. package/dist/src/public/channels/slack/slackChannel.js +25 -3
  46. package/dist/src/public/channels/twilio/twilioChannel.d.ts +2 -1
  47. package/dist/src/public/definitions/sandbox.d.ts +3 -3
  48. package/dist/src/public/definitions/schedule.d.ts +47 -50
  49. package/dist/src/public/definitions/schedule.js +10 -25
  50. package/dist/src/public/helpers/markdown.d.ts +6 -6
  51. package/dist/src/public/helpers/markdown.js +8 -8
  52. package/dist/src/public/sandbox/backends/vercel.d.ts +5 -5
  53. package/dist/src/public/sandbox/backends/vercel.js +3 -3
  54. package/dist/src/public/sandbox/index.d.ts +2 -2
  55. package/dist/src/public/sandbox/vercel-sandbox.d.ts +13 -0
  56. package/dist/src/public/schedules/index.d.ts +1 -1
  57. package/dist/src/public/schedules/index.js +1 -1
  58. package/dist/src/runtime/resolve-channel.js +1 -0
  59. package/dist/src/runtime/schedules/resolve-schedule.js +5 -5
  60. package/dist/src/runtime/types.d.ts +15 -10
  61. package/dist/src/shared/sandbox-backend.d.ts +7 -7
  62. package/dist/src/shared/sandbox-definition.d.ts +7 -12
  63. package/package.json +1 -1
  64. package/dist/src/chunks/authored-module-loader-Pt_g8xX2.js +0 -3
  65. package/dist/src/chunks/paths-KCBJzXn2.js +0 -88
@@ -80,11 +80,6 @@ export type CompiledInstructions = z.infer<typeof compiledInstructionsSchema>;
80
80
  * Normalized authored skill preserved in the compiled manifest.
81
81
  */
82
82
  export type CompiledSkillDefinition = InternalSkillDefinition & (Omit<MarkdownSourceRef<undefined>, "definition"> | ModuleSourceRef | SkillPackageSourceRef);
83
- /**
84
- * Compiled channel target reference produced by `receive(channel, args)`
85
- * on the authored schedule definition.
86
- */
87
- export type CompiledScheduleChannelRef = z.infer<typeof compiledScheduleChannelRefSchema>;
88
83
  /**
89
84
  * Normalized authored schedule preserved in the compiled manifest.
90
85
  */
@@ -162,19 +157,12 @@ declare const compiledInstructionsSchema: z.ZodObject<{
162
157
  sourceId: z.ZodString;
163
158
  sourceKind: z.ZodUnion<readonly [z.ZodLiteral<"markdown">, z.ZodLiteral<"module">]>;
164
159
  }, z.core.$strict>;
165
- declare const compiledScheduleChannelRefSchema: z.ZodObject<{
166
- name: z.ZodString;
167
- args: z.ZodRecord<z.ZodString, z.ZodUnknown>;
168
- }, z.core.$strict>;
169
160
  declare const compiledScheduleDefinitionSchema: z.ZodObject<{
170
- channel: z.ZodOptional<z.ZodObject<{
171
- name: z.ZodString;
172
- args: z.ZodRecord<z.ZodString, z.ZodUnknown>;
173
- }, z.core.$strict>>;
174
161
  cron: z.ZodString;
162
+ hasRun: z.ZodBoolean;
175
163
  name: z.ZodString;
176
164
  logicalPath: z.ZodString;
177
- markdown: z.ZodString;
165
+ markdown: z.ZodOptional<z.ZodString>;
178
166
  sourceId: z.ZodString;
179
167
  sourceKind: z.ZodUnion<readonly [z.ZodLiteral<"markdown">, z.ZodLiteral<"module">]>;
180
168
  }, z.core.$strict>;
@@ -247,14 +235,11 @@ export declare const compiledAgentNodeManifestSchema: z.ZodObject<{
247
235
  sourcePath: z.ZodString;
248
236
  }, z.core.$strict>>;
249
237
  schedules: z.ZodArray<z.ZodObject<{
250
- channel: z.ZodOptional<z.ZodObject<{
251
- name: z.ZodString;
252
- args: z.ZodRecord<z.ZodString, z.ZodUnknown>;
253
- }, z.core.$strict>>;
254
238
  cron: z.ZodString;
239
+ hasRun: z.ZodBoolean;
255
240
  name: z.ZodString;
256
241
  logicalPath: z.ZodString;
257
- markdown: z.ZodString;
242
+ markdown: z.ZodOptional<z.ZodString>;
258
243
  sourceId: z.ZodString;
259
244
  sourceKind: z.ZodUnion<readonly [z.ZodLiteral<"markdown">, z.ZodLiteral<"module">]>;
260
245
  }, z.core.$strict>>;
@@ -321,14 +306,11 @@ export declare const compiledAgentManifestSchema: z.ZodObject<{
321
306
  sourcePath: z.ZodString;
322
307
  }, z.core.$strict>>;
323
308
  schedules: z.ZodArray<z.ZodObject<{
324
- channel: z.ZodOptional<z.ZodObject<{
325
- name: z.ZodString;
326
- args: z.ZodRecord<z.ZodString, z.ZodUnknown>;
327
- }, z.core.$strict>>;
328
309
  cron: z.ZodString;
310
+ hasRun: z.ZodBoolean;
329
311
  name: z.ZodString;
330
312
  logicalPath: z.ZodString;
331
- markdown: z.ZodString;
313
+ markdown: z.ZodOptional<z.ZodString>;
332
314
  sourceId: z.ZodString;
333
315
  sourceKind: z.ZodUnion<readonly [z.ZodLiteral<"markdown">, z.ZodLiteral<"module">]>;
334
316
  }, z.core.$strict>>;
@@ -127,19 +127,13 @@ const compiledSkillSourceSchema = z.discriminatedUnion("sourceKind", [
127
127
  })
128
128
  .strict(),
129
129
  ]);
130
- const compiledScheduleChannelRefSchema = z
131
- .object({
132
- name: z.string(),
133
- args: z.record(z.string(), z.unknown()),
134
- })
135
- .strict();
136
130
  const compiledScheduleDefinitionSchema = z
137
131
  .object({
138
- channel: compiledScheduleChannelRefSchema.optional(),
139
132
  cron: z.string(),
133
+ hasRun: z.boolean(),
140
134
  name: z.string(),
141
135
  logicalPath: z.string(),
142
- markdown: z.string(),
136
+ markdown: z.string().optional(),
143
137
  sourceId: z.string(),
144
138
  sourceKind: z.union([z.literal("markdown"), z.literal("module")]),
145
139
  })
@@ -1,6 +1,5 @@
1
1
  import type { ChannelSourceRef } from "#discover/manifest.js";
2
2
  import type { CompiledChannelEntry } from "#compiler/manifest.js";
3
- import type { ChannelRouteIdentityMap } from "#compiler/normalize-schedule.js";
4
3
  /**
5
4
  * Compiles one authored channel module into the normalized channel
6
5
  * entries stored on the compiled agent manifest.
@@ -15,10 +14,3 @@ import type { ChannelRouteIdentityMap } from "#compiler/normalize-schedule.js";
15
14
  * the filesystem path; the URL path comes from the route's `path` field.
16
15
  */
17
16
  export declare function compileChannelDefinition(agentRoot: string, source: ChannelSourceRef): Promise<CompiledChannelEntry | readonly CompiledChannelEntry[]>;
18
- /**
19
- * Loads all channel modules and builds a map from route object identity
20
- * to channel name. Schedule modules import channels, so by the time a
21
- * schedule is compiled the channel module is already in Node's module
22
- * cache — the route object the schedule sees is the same instance.
23
- */
24
- export declare function buildChannelRouteIdentityMap(agentRoot: string, channels: readonly ChannelSourceRef[]): Promise<ChannelRouteIdentityMap>;
@@ -1,7 +1,5 @@
1
- import { join, resolve } from "node:path";
2
1
  import { stripLogicalPathExtension } from "#discover/filesystem.js";
3
2
  import { normalizeChannelDefinition } from "#internal/authored-definition/channel.js";
4
- import { registerChannelModuleInCache } from "#internal/authored-module-loader.js";
5
3
  import { isDisabledRouteSentinel } from "#public/definitions/channel.js";
6
4
  import { loadModuleBackedDefinition } from "#compiler/normalize-helpers.js";
7
5
  /**
@@ -51,28 +49,3 @@ function extractAdapterKind(adapter) {
51
49
  const kind = adapter.kind;
52
50
  return typeof kind === "string" && kind.length > 0 ? kind : undefined;
53
51
  }
54
- /**
55
- * Loads all channel modules and builds a map from route object identity
56
- * to channel name. Schedule modules import channels, so by the time a
57
- * schedule is compiled the channel module is already in Node's module
58
- * cache — the route object the schedule sees is the same instance.
59
- */
60
- export async function buildChannelRouteIdentityMap(agentRoot, channels) {
61
- const map = new Map();
62
- for (const source of channels) {
63
- const rawValue = await loadModuleBackedDefinition({
64
- agentRoot,
65
- kind: "channel",
66
- source,
67
- });
68
- if (isDisabledRouteSentinel(rawValue))
69
- continue;
70
- const channelName = stripLogicalPathExtension(source.logicalPath).replace(/^channels\//, "");
71
- if (typeof rawValue === "object" && rawValue !== null) {
72
- map.set(rawValue, channelName);
73
- const absolutePath = resolve(join(agentRoot, source.logicalPath));
74
- registerChannelModuleInCache(absolutePath, rawValue);
75
- }
76
- }
77
- return map;
78
- }
@@ -7,7 +7,6 @@ import { compileHookEntry } from "#compiler/normalize-hook.js";
7
7
  import { compileSandboxDefinition } from "#compiler/normalize-sandbox.js";
8
8
  import { compileInstructions } from "#compiler/normalize-instructions.js";
9
9
  import { compileScheduleDefinition } from "#compiler/normalize-schedule.js";
10
- import { buildChannelRouteIdentityMap } from "#compiler/normalize-channel.js";
11
10
  import { compileSkillSource } from "#compiler/normalize-skill.js";
12
11
  import { compileSubagentGraph } from "#compiler/normalize-subagent.js";
13
12
  import { compileToolEntry } from "#compiler/normalize-tool.js";
@@ -46,14 +45,7 @@ async function compileAgentNodeManifest(manifest, context) {
46
45
  disabledFrameworkTools.push(entry.name);
47
46
  }
48
47
  }
49
- // Channels must be loaded before schedules because schedule modules
50
- // import channel modules to get typed receive args. Loading channels
51
- // first populates Node's module cache so the schedule sees the same
52
- // route object instance the identity map was built from.
53
- const [compiledChannelResults, channelRouteIdentityMap] = await Promise.all([
54
- Promise.all(manifest.channels.map((channelSource) => compileChannelDefinition(manifest.agentRoot, channelSource))),
55
- buildChannelRouteIdentityMap(manifest.agentRoot, manifest.channels),
56
- ]);
48
+ const compiledChannelResults = await Promise.all(manifest.channels.map((channelSource) => compileChannelDefinition(manifest.agentRoot, channelSource)));
57
49
  // compileChannelDefinition returns a single entry for old-style routes
58
50
  // or an array of entries for CompiledChannel (one per route). Flatten.
59
51
  const compiledChannels = compiledChannelResults.flat();
@@ -75,7 +67,7 @@ async function compileAgentNodeManifest(manifest, context) {
75
67
  sourceId: workspace.sourceId,
76
68
  sourcePath: workspace.sourcePath,
77
69
  })),
78
- schedules: await Promise.all(manifest.schedules.map((scheduleSource) => compileScheduleDefinition(manifest.agentRoot, scheduleSource, channelRouteIdentityMap))),
70
+ schedules: await Promise.all(manifest.schedules.map((scheduleSource) => compileScheduleDefinition(manifest.agentRoot, scheduleSource))),
79
71
  skills: await Promise.all(manifest.skills.map((skillSource) => compileSkillSource(manifest.agentRoot, skillSource))),
80
72
  instructions: manifest.instructions === undefined
81
73
  ? undefined
@@ -1,20 +1,14 @@
1
1
  import type { ScheduleSourceRef } from "#discover/manifest.js";
2
2
  import type { CompiledScheduleDefinition } from "#compiler/manifest.js";
3
- /**
4
- * Identity map from route objects to channel names. Built by the
5
- * manifest compiler after loading channel modules so schedule modules
6
- * (which import channels) can resolve channel names at compile time.
7
- */
8
- export type ChannelRouteIdentityMap = Map<object, string>;
9
3
  /**
10
4
  * Compiles one authored schedule into the normalized shape consumed by
11
5
  * the runtime scheduler.
12
6
  *
13
- * Schedules are single files: `schedules/<name>.{ts,md}`. The TS form
14
- * may declare an optional `channel` (a route object resolved through the
15
- * identity map); the markdown form never declares a channel. The
16
- * schedule name is derived from the relative file path under
17
- * `schedules/` minus the extension
7
+ * Schedules are single files: `schedules/<name>.{ts,md}`. The markdown
8
+ * form always produces a fire-and-forget schedule (`markdown` body, no
9
+ * `run`). The TypeScript form may declare either `markdown` or `run`
10
+ * (exactly one). The schedule name is derived from the relative file
11
+ * path under `schedules/` minus the extension
18
12
  * (`schedules/billing/invoice-sweep.ts` → `"billing/invoice-sweep"`).
19
13
  */
20
- export declare function compileScheduleDefinition(agentRoot: string, source: ScheduleSourceRef, channelIdentityMap: ChannelRouteIdentityMap): Promise<CompiledScheduleDefinition>;
14
+ export declare function compileScheduleDefinition(agentRoot: string, source: ScheduleSourceRef): Promise<CompiledScheduleDefinition>;
@@ -5,14 +5,14 @@ import { loadModuleBackedDefinition } from "#compiler/normalize-helpers.js";
5
5
  * Compiles one authored schedule into the normalized shape consumed by
6
6
  * the runtime scheduler.
7
7
  *
8
- * Schedules are single files: `schedules/<name>.{ts,md}`. The TS form
9
- * may declare an optional `channel` (a route object resolved through the
10
- * identity map); the markdown form never declares a channel. The
11
- * schedule name is derived from the relative file path under
12
- * `schedules/` minus the extension
8
+ * Schedules are single files: `schedules/<name>.{ts,md}`. The markdown
9
+ * form always produces a fire-and-forget schedule (`markdown` body, no
10
+ * `run`). The TypeScript form may declare either `markdown` or `run`
11
+ * (exactly one). The schedule name is derived from the relative file
12
+ * path under `schedules/` minus the extension
13
13
  * (`schedules/billing/invoice-sweep.ts` → `"billing/invoice-sweep"`).
14
14
  */
15
- export async function compileScheduleDefinition(agentRoot, source, channelIdentityMap) {
15
+ export async function compileScheduleDefinition(agentRoot, source) {
16
16
  const definition = source.sourceKind === "markdown"
17
17
  ? normalizeScheduleDefinition(source.definition, `Expected the compiled schedule definition at "${source.logicalPath}" to match the public Ash shape.`)
18
18
  : normalizeScheduleDefinition(await loadModuleBackedDefinition({
@@ -22,40 +22,17 @@ export async function compileScheduleDefinition(agentRoot, source, channelIdenti
22
22
  }), `Expected the schedule export "${source.exportName ?? "default"}" from "${source.logicalPath}" to match the public Ash shape.`);
23
23
  const compiled = {
24
24
  cron: definition.cron,
25
+ hasRun: definition.run !== undefined,
25
26
  logicalPath: source.logicalPath,
26
- markdown: definition.markdown.trim(),
27
27
  name: deriveScheduleName(source.logicalPath),
28
28
  sourceId: source.sourceId,
29
29
  sourceKind: source.sourceKind,
30
30
  };
31
- if (definition.channel !== undefined) {
32
- compiled.channel = resolveScheduleChannel({
33
- channel: definition.channel,
34
- channelIdentityMap,
35
- logicalPath: source.logicalPath,
36
- });
31
+ if (definition.markdown !== undefined) {
32
+ return { ...compiled, markdown: definition.markdown.trim() };
37
33
  }
38
34
  return compiled;
39
35
  }
40
- function resolveScheduleChannel(input) {
41
- const channelRef = input.channel;
42
- const routeObject = channelRef.__route ?? channelRef;
43
- const channelArgs = channelRef.args ?? {};
44
- // Resolve the channel name:
45
- // 1. Route object identity via __route — the channel-identity
46
- // plugin ensures the route object matches the identity map.
47
- // 2. Plain { name, args } fallback for test fixtures.
48
- let channelName = input.channelIdentityMap.get(routeObject);
49
- if (!channelName && typeof channelRef.name === "string") {
50
- channelName = channelRef.name;
51
- }
52
- if (!channelName) {
53
- throw new Error(`Schedule at "${input.logicalPath}" references a channel that was not ` +
54
- `discovered in agent/channels/. Import a channel module or pass ` +
55
- `{ name: "<channel-name>", args: { ... } } as the channel field.`);
56
- }
57
- return { name: channelName, args: channelArgs };
58
- }
59
36
  function deriveScheduleName(logicalPath) {
60
37
  return stripLogicalPathExtension(logicalPath).replace(/^schedules\//, "");
61
38
  }
@@ -1 +1 @@
1
- import{n as e}from"../../chunks/paths-KCBJzXn2.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-C83crl7k.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-CvbqpwTh.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-CIU0NATc.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 +1 @@
1
- import{t as e}from"../../chunks/authored-module-loader-Pt_g8xX2.js";import{join as t,relative as n}from"node:path";import{readdir as r}from"node:fs/promises";const i=`.eval.ts`;async function a(e){let r=t(e,`evals`),i=[];try{await u(r,i)}catch(e){if(d(e))return[];throw e}return i.sort((e,t)=>n(r,e).localeCompare(n(r,t))),i}function o(e,r){let a=n(t(e,`evals`),r).split(/[\\/]/u).join(`/`);return a.endsWith(i)?a.slice(0,-8):a}async function s(t,n){let r=(await e(n)).default;if(!l(r))throw Error(`Suite file "${n}" does not export a valid AshEvalSuite as its default export. Use defineEvalSuite() to create the suite.`);return{...r,id:o(t,n)}}async function c(e,t){let n=await a(e);if(n.length===0)return[];let r=[];for(let i of n){let n=await s(e,i);(t===void 0||t.length===0||t.includes(n.id))&&r.push(n)}return r}function l(e){return typeof e==`object`&&!!e&&`_tag`in e&&e._tag===`AshEvalSuite`}async function u(e,n){let a=await r(e,{withFileTypes:!0});for(let r of a){let a=t(e,r.name);r.isDirectory()?await u(a,n):r.isFile()&&r.name.endsWith(i)&&n.push(a)}}function d(e){return typeof e==`object`&&!!e&&`code`in e&&e.code===`ENOENT`}export{o as deriveSuiteId,c as discoverAndImportSuites,a as discoverSuiteFiles,s as importSuiteFile};
1
+ import{t as e}from"../../chunks/authored-module-loader-XcFLnl49.js";import{join as t,relative as n}from"node:path";import{readdir as r}from"node:fs/promises";const i=`.eval.ts`;async function a(e){let r=t(e,`evals`),i=[];try{await u(r,i)}catch(e){if(d(e))return[];throw e}return i.sort((e,t)=>n(r,e).localeCompare(n(r,t))),i}function o(e,r){let a=n(t(e,`evals`),r).split(/[\\/]/u).join(`/`);return a.endsWith(i)?a.slice(0,-8):a}async function s(t,n){let r=(await e(n)).default;if(!l(r))throw Error(`Suite file "${n}" does not export a valid AshEvalSuite as its default export. Use defineEvalSuite() to create the suite.`);return{...r,id:o(t,n)}}async function c(e,t){let n=await a(e);if(n.length===0)return[];let r=[];for(let i of n){let n=await s(e,i);(t===void 0||t.length===0||t.includes(n.id))&&r.push(n)}return r}function l(e){return typeof e==`object`&&!!e&&`_tag`in e&&e._tag===`AshEvalSuite`}async function u(e,n){let a=await r(e,{withFileTypes:!0});for(let r of a){let a=t(e,r.name);r.isDirectory()?await u(a,n):r.isFile()&&r.name.endsWith(i)&&n.push(a)}}function d(e){return typeof e==`object`&&!!e&&`code`in e&&e.code===`ENOENT`}export{o as deriveSuiteId,c as discoverAndImportSuites,a as discoverSuiteFiles,s as importSuiteFile};
@@ -1,7 +1,7 @@
1
1
  import type * as VercelSandboxSdk from "#compiled/@vercel/sandbox/index.js";
2
2
  import type { Sandbox as SdkSandbox } from "#compiled/@vercel/sandbox/index.js";
3
3
  import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
4
- import type { VercelSandbox, VercelSandboxSessionUseOptions } from "#public/sandbox/vercel-sandbox.js";
4
+ import type { VercelSandbox, VercelSandboxBootstrapUseOptions, VercelSandboxSessionUseOptions } from "#public/sandbox/vercel-sandbox.js";
5
5
  type VercelSandboxModule = typeof VercelSandboxSdk;
6
6
  /**
7
7
  * User-controllable subset of `Sandbox.create` parameters.
@@ -18,5 +18,5 @@ export interface CreateVercelSandboxBackendInput {
18
18
  /**
19
19
  * Creates the Vercel-backed sandbox backend.
20
20
  */
21
- export declare function createVercelSandboxBackend(input?: CreateVercelSandboxBackendInput): SandboxBackend<VercelSandbox, VercelSandboxSessionUseOptions>;
21
+ export declare function createVercelSandboxBackend(input?: CreateVercelSandboxBackendInput): SandboxBackend<VercelSandbox, VercelSandboxBootstrapUseOptions, VercelSandboxSessionUseOptions>;
22
22
  export {};
@@ -119,7 +119,14 @@ async function ensureTemplate(input) {
119
119
  await ensureSandboxWorkingDirectory(sandbox, input.createOptions);
120
120
  const templateSession = buildSandboxSession(createVercelInternalSandboxSession(sandbox, input.templateKey));
121
121
  if (input.bootstrap !== undefined) {
122
- await input.bootstrap({ use: async () => templateSession });
122
+ await input.bootstrap({
123
+ use: async (options) => {
124
+ if (options !== undefined) {
125
+ await sandbox.update(options);
126
+ }
127
+ return templateSession;
128
+ },
129
+ });
123
130
  }
124
131
  for (const file of input.seedFiles) {
125
132
  if (typeof file.content === "string") {
@@ -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.16.3";
9
+ const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.18.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",
@@ -36,8 +36,9 @@ export declare function normalizeSkillDefinition(value: unknown, message: string
36
36
  * shape.
37
37
  *
38
38
  * Authored `name` fields are rejected — schedule identity is derived from
39
- * the file path under `agent/schedules/`. The `channel` field is optional;
40
- * a schedule without a channel runs the agent and discards its output.
39
+ * the file path under `agent/schedules/`. Exactly one of `markdown` (the
40
+ * fire-and-forget agent prompt) or `run` (the cron handler function)
41
+ * must be provided.
41
42
  */
42
43
  export declare function normalizeScheduleDefinition(value: unknown, message: string): ScheduleDefinition;
43
44
  export {};
@@ -1,4 +1,4 @@
1
- import { expectObjectRecord, expectOnlyKnownKeys, expectProviderOptions, expectString, getOptionalStringRecordProperty, } from "#internal/authored-module.js";
1
+ import { expectFunction, expectObjectRecord, expectOnlyKnownKeys, expectProviderOptions, expectString, getOptionalStringRecordProperty, } from "#internal/authored-module.js";
2
2
  /**
3
3
  * Normalizes one authored agent definition into the canonical internal shape.
4
4
  *
@@ -138,18 +138,28 @@ export function normalizeSkillDefinition(value, message) {
138
138
  * shape.
139
139
  *
140
140
  * Authored `name` fields are rejected — schedule identity is derived from
141
- * the file path under `agent/schedules/`. The `channel` field is optional;
142
- * a schedule without a channel runs the agent and discards its output.
141
+ * the file path under `agent/schedules/`. Exactly one of `markdown` (the
142
+ * fire-and-forget agent prompt) or `run` (the cron handler function)
143
+ * must be provided.
143
144
  */
144
145
  export function normalizeScheduleDefinition(value, message) {
145
146
  const record = expectObjectRecord(value, message);
146
- expectOnlyKnownKeys(record, ["channel", "cron", "markdown"], message);
147
- const definition = {
148
- cron: expectString(record.cron, message),
149
- markdown: expectString(record.markdown, message),
150
- };
151
- if (record.channel !== undefined && record.channel !== null) {
152
- definition.channel = record.channel;
147
+ expectOnlyKnownKeys(record, ["cron", "markdown", "run"], message);
148
+ const cron = expectString(record.cron, message);
149
+ const hasMarkdown = record.markdown !== undefined;
150
+ const hasRun = record.run !== undefined;
151
+ if (hasMarkdown && hasRun) {
152
+ throw new Error(`${message} Pass either "markdown" (fire-and-forget) or "run" (handler) — not both.`);
153
+ }
154
+ if (!hasMarkdown && !hasRun) {
155
+ throw new Error(`${message} Must provide either "markdown" (fire-and-forget) or "run" (handler).`);
156
+ }
157
+ const definition = { cron };
158
+ if (hasMarkdown) {
159
+ definition.markdown = expectString(record.markdown, message);
160
+ }
161
+ else {
162
+ definition.run = expectFunction(record.run, message);
153
163
  }
154
164
  return definition;
155
165
  }
@@ -1,9 +1,3 @@
1
- /**
2
- * Registers a loaded channel module in the global cache so Rolldown-
3
- * bundled schedule modules that import the same channel get the exact
4
- * same route object instance.
5
- */
6
- export declare function registerChannelModuleInCache(absolutePath: string, moduleDefault: unknown): void;
7
1
  /**
8
2
  * Loads one authored module namespace from disk during compile-time
9
3
  * discovery. Concurrent loads of the same `modulePath` share a single
@@ -17,42 +17,22 @@ const RESOLVE_EXTENSIONS = [
17
17
  ".cjs",
18
18
  ".json",
19
19
  ];
20
- const CHANNEL_MODULE_CACHE_KEY = "__ashChannelModuleCache__";
21
- const CACHED_CHANNEL_PREFIX = "ash-cached-channel:";
22
- /**
23
- * Registers a loaded channel module in the global cache so Rolldown-
24
- * bundled schedule modules that import the same channel get the exact
25
- * same route object instance.
26
- */
27
- export function registerChannelModuleInCache(absolutePath, moduleDefault) {
28
- const cache = (globalThis[CHANNEL_MODULE_CACHE_KEY] ??= new Map());
29
- cache.set(resolve(absolutePath), moduleDefault);
30
- }
31
- function getChannelModuleCache() {
32
- return globalThis[CHANNEL_MODULE_CACHE_KEY];
33
- }
34
20
  /**
35
21
  * In-flight load deduplication map keyed by the absolute module path.
36
22
  *
37
- * The compiler walks every authored slot concurrently
38
- * (`compileChannelDefinition` and `buildChannelRouteIdentityMap` both
39
- * load the same channel module via `Promise.all`), so the same module
40
- * path is frequently loaded twice in parallel. Without dedup, both
41
- * callers race the bundler write/import pipeline against the
42
- * same `node_modules/.cache/.../<hash>.mjs` file: one call's
43
- * `writeFile` can truncate the bundle while another's `import()` is
44
- * still resolving it, surfacing as intermittent
23
+ * The compiler walks every authored slot concurrently via `Promise.all`,
24
+ * so the same module path is frequently loaded twice in parallel.
25
+ * Without dedup, both callers race the bundler write/import pipeline
26
+ * against the same `node_modules/.cache/.../<hash>.mjs` file: one
27
+ * call's `writeFile` can truncate the bundle while another's `import()`
28
+ * is still resolving it, surfacing as intermittent
45
29
  * "Expected … to match the public Ash shape" failures during
46
30
  * compilation.
47
31
  *
48
32
  * The map only holds in-flight promises; once a load settles the entry
49
- * is cleared so subsequent compiles (e.g. a dev-server reload after
50
- * the author edits a file) re-run the bundle pipeline against the
51
- * fresh source. Node's ESM cache then dedupes by content-hashed URL
52
- * for unchanged files. The companion "skip write when the cache file
53
- * already exists" check inside {@link loadBundledAuthoredModule}
54
- * eliminates the write/read race even when two non-concurrent compile
55
- * passes overlap on the same hashed bundle path.
33
+ * is cleared so subsequent compiles re-run the bundle pipeline against
34
+ * the fresh source. Node's ESM cache then dedupes by content-hashed
35
+ * URL for unchanged files.
56
36
  */
57
37
  const inFlightModuleLoads = new Map();
58
38
  /**
@@ -94,52 +74,15 @@ function createFileImportSpecifier(modulePath) {
94
74
  return normalizedPath;
95
75
  }
96
76
  async function loadBundledAuthoredModule(modulePath) {
97
- const channelCache = getChannelModuleCache();
98
77
  const packageRoot = resolveAuthoredPackageRoot(modulePath);
99
78
  const tsconfigPath = join(packageRoot, "tsconfig.json");
100
79
  const tsconfigPathAliasPlugin = createTsconfigPathAliasPlugin({
101
80
  tsconfigPath,
102
81
  });
103
- const channelIdentityPlugin = channelCache && channelCache.size > 0
104
- ? {
105
- name: "ash-channel-identity",
106
- async resolveId(source, importer, options) {
107
- if (!/channels[/\\]/.test(source) || options.kind !== "import-statement") {
108
- return undefined;
109
- }
110
- const resolved = await this.resolve(source, importer, {
111
- kind: options.kind,
112
- skipSelf: true,
113
- });
114
- if (resolved === null || typeof resolved.id !== "string") {
115
- return undefined;
116
- }
117
- const resolvedPath = resolve(resolved.id);
118
- if (!channelCache.has(resolvedPath)) {
119
- return undefined;
120
- }
121
- return { id: `${CACHED_CHANNEL_PREFIX}${resolvedPath}` };
122
- },
123
- load(id) {
124
- if (!id.startsWith(CACHED_CHANNEL_PREFIX)) {
125
- return undefined;
126
- }
127
- const cachedPath = id.slice(CACHED_CHANNEL_PREFIX.length);
128
- return {
129
- code: [
130
- `const cache = globalThis["${CHANNEL_MODULE_CACHE_KEY}"];`,
131
- `export default cache.get(${JSON.stringify(cachedPath)});`,
132
- ].join("\n"),
133
- moduleType: "js",
134
- };
135
- },
136
- }
137
- : null;
138
82
  const plugins = [
139
83
  createNodeEsmCompatBannerPlugin({ includeRequire: true }),
140
84
  createPackageBoundaryPlugin(packageRoot),
141
85
  tsconfigPathAliasPlugin,
142
- channelIdentityPlugin,
143
86
  ].filter((plugin) => plugin !== null);
144
87
  const result = await buildWithNitroRolldown({
145
88
  cwd: packageRoot,
@@ -190,11 +133,7 @@ function createPackageBoundaryPlugin(packageRoot) {
190
133
  id: source,
191
134
  };
192
135
  }
193
- const importerPath = importer === undefined ||
194
- importer.startsWith("\0") ||
195
- importer.startsWith(CACHED_CHANNEL_PREFIX)
196
- ? undefined
197
- : resolve(importer);
136
+ const importerPath = importer === undefined || importer.startsWith("\0") ? undefined : resolve(importer);
198
137
  // Keep package imports authored directly by the app external by
199
138
  // default, but let symlinked/file workspace packages compile as
200
139
  // source. Those packages often export `.ts` files and rely on the
@@ -316,7 +255,7 @@ function isPackageImport(source) {
316
255
  if (source.startsWith("@/")) {
317
256
  return false;
318
257
  }
319
- return !source.startsWith(CACHED_CHANNEL_PREFIX);
258
+ return true;
320
259
  }
321
260
  function isAshFrameworkImport(source) {
322
261
  return source === "experimental-ash" || source.startsWith("experimental-ash/");
@@ -65,7 +65,9 @@ function renderScheduleRows(schedules) {
65
65
  cron: schedule.cron,
66
66
  logicalPath: schedule.logicalPath,
67
67
  name: schedule.name,
68
- preview: truncateCopy(schedule.markdown.trim(), 220),
68
+ preview: schedule.markdown
69
+ ? truncateCopy(schedule.markdown.trim(), 220)
70
+ : "(handler-based schedule)",
69
71
  }));
70
72
  }
71
73
  function renderSubagentRows(manifest) {
@@ -1,3 +1,4 @@
1
+ import { createCrossChannelReceiveFn } from "#channel/cross-channel-receive.js";
1
2
  import { createSendFn } from "#channel/send.js";
2
3
  import { createGetSessionFn } from "#channel/session.js";
3
4
  import { resolveNitroChannelRuntimeBundle } from "#internal/nitro/routes/runtime-stack.js";
@@ -40,9 +41,11 @@ export async function dispatchChannelRequest(event, routeKey, config) {
40
41
  const adapter = matchedChannel.adapter ?? { kind: "channel" };
41
42
  const send = createSendFn(bundle.runtime, adapter, matchedChannel.name);
42
43
  const getSession = createGetSessionFn(bundle.runtime);
44
+ const receive = createCrossChannelReceiveFn(bundle.runtime, bundle.channels);
43
45
  const args = {
44
46
  send,
45
47
  getSession,
48
+ receive,
46
49
  params,
47
50
  waitUntil,
48
51
  requestIp,
@@ -1,4 +1,3 @@
1
- import { ScheduleDispatcher } from "#channel/schedule.js";
2
1
  import type { Runtime } from "#channel/types.js";
3
2
  import type { ResolvedChannelDefinition } from "#runtime/types.js";
4
3
  import { type NitroArtifactsConfig } from "#internal/nitro/routes/runtime-artifacts.js";
@@ -25,13 +24,3 @@ export interface NitroChannelRuntimeBundle {
25
24
  * compiled bundle on each request.
26
25
  */
27
26
  export declare function resolveNitroChannelRuntimeBundle(config: NitroArtifactsConfig): Promise<NitroChannelRuntimeBundle>;
28
- /**
29
- * Builds and returns the schedule channel backed by the workflow runtime.
30
- *
31
- * Stateless — each cron trigger starts a fresh workflow with a unique
32
- * continuation token. Schedule channels remain a separate primitive
33
- * because they are triggered by a Nitro task scheduler rather than an
34
- * inbound HTTP request and do not fit the per-request `Channel.fetch`
35
- * shape.
36
- */
37
- export declare function resolveNitroScheduleDispatcher(config: NitroArtifactsConfig): ScheduleDispatcher;
@@ -1,4 +1,3 @@
1
- import { ScheduleDispatcher } from "#channel/schedule.js";
2
1
  import { createWorkflowRuntime } from "#execution/workflow-runtime.js";
3
2
  import { getCompiledRuntimeAgentBundle } from "#runtime/sessions/compiled-agent-cache.js";
4
3
  import { resolveNitroCompiledArtifactsSource, } from "#internal/nitro/routes/runtime-artifacts.js";
@@ -22,27 +21,3 @@ export async function resolveNitroChannelRuntimeBundle(config) {
22
21
  runtime,
23
22
  };
24
23
  }
25
- /**
26
- * Builds and returns the schedule channel backed by the workflow runtime.
27
- *
28
- * Stateless — each cron trigger starts a fresh workflow with a unique
29
- * continuation token. Schedule channels remain a separate primitive
30
- * because they are triggered by a Nitro task scheduler rather than an
31
- * inbound HTTP request and do not fit the per-request `Channel.fetch`
32
- * shape.
33
- */
34
- export function resolveNitroScheduleDispatcher(config) {
35
- const compiledArtifactsSource = resolveNitroCompiledArtifactsSource(config);
36
- const runtime = createWorkflowRuntime({ compiledArtifactsSource });
37
- return new ScheduleDispatcher({
38
- runtime,
39
- async resolveChannel(channelName) {
40
- const bundle = await resolveNitroChannelRuntimeBundle(config);
41
- const channel = bundle.channels.find((ch) => ch.name === channelName);
42
- if (!channel) {
43
- throw new Error(`Channel "${channelName}" not found in agent/channels/.`);
44
- }
45
- return { receive: channel.receive, adapter: channel.adapter };
46
- },
47
- });
48
- }
@@ -3,10 +3,10 @@ import type { NitroArtifactsConfig } from "#internal/nitro/routes/runtime-artifa
3
3
  * Dispatches one Ash authored schedule via the execution engine.
4
4
  *
5
5
  * Fire-and-forget: the workflow runtime owns terminal completion and
6
- * its own failure observability. The task return value reports only
7
- * that the session was dispatched.
6
+ * its own failure observability. The task return value reports which
7
+ * session ids the handler started so Nitro / dev tools can correlate.
8
8
  */
9
9
  export declare function dispatchScheduleTask(taskName: string, config: NitroArtifactsConfig): Promise<{
10
10
  scheduleId: string;
11
- sessionId: string;
11
+ sessionIds: readonly string[];
12
12
  }>;