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
@@ -1,27 +1,57 @@
1
+ import { expectScheduleRun, ScheduleDispatcher } from "#channel/schedule.js";
2
+ import { createWorkflowRuntime } from "#execution/workflow-runtime.js";
3
+ import { loadResolvedModuleExport } from "#runtime/resolve-helpers.js";
1
4
  import { loadResolvedCompiledScheduleByTaskName } from "#runtime/schedules/resolve-schedule.js";
5
+ import { getCompiledRuntimeAgentBundle } from "#runtime/sessions/compiled-agent-cache.js";
2
6
  import { resolveNitroCompiledArtifactsSource } from "#internal/nitro/routes/runtime-artifacts.js";
3
- import { resolveNitroScheduleDispatcher } from "#internal/nitro/routes/runtime-stack.js";
4
7
  /**
5
8
  * Dispatches one Ash authored schedule via the execution engine.
6
9
  *
7
10
  * Fire-and-forget: the workflow runtime owns terminal completion and
8
- * its own failure observability. The task return value reports only
9
- * that the session was dispatched.
11
+ * its own failure observability. The task return value reports which
12
+ * session ids the handler started so Nitro / dev tools can correlate.
10
13
  */
11
14
  export async function dispatchScheduleTask(taskName, config) {
12
15
  const compiledArtifactsSource = resolveNitroCompiledArtifactsSource(config);
13
16
  const schedule = await loadResolvedCompiledScheduleByTaskName(taskName, {
14
17
  compiledArtifactsSource,
15
18
  });
16
- const dispatcher = resolveNitroScheduleDispatcher(config);
17
- const triggerInput = {
18
- channel: schedule.channel,
19
- markdown: schedule.markdown,
20
- scheduleId: schedule.name,
21
- };
22
- const session = await dispatcher.trigger(triggerInput);
19
+ const bundle = await getCompiledRuntimeAgentBundle({ compiledArtifactsSource });
20
+ const runtime = createWorkflowRuntime({ compiledArtifactsSource });
21
+ const dispatcher = new ScheduleDispatcher({
22
+ runtime,
23
+ channels: bundle.graph.root.channels,
24
+ });
25
+ const dispatchInput = { scheduleId: schedule.name };
26
+ if (schedule.hasRun) {
27
+ dispatchInput.run = await loadScheduleRun(schedule, bundle.moduleMap);
28
+ }
29
+ if (schedule.markdown !== undefined) {
30
+ dispatchInput.markdown = schedule.markdown;
31
+ }
32
+ const result = await dispatcher.trigger(dispatchInput);
33
+ if (result.waitUntilTasks.length > 0) {
34
+ await Promise.allSettled(result.waitUntilTasks);
35
+ }
23
36
  return {
24
37
  scheduleId: schedule.name,
25
- sessionId: session.id,
38
+ sessionIds: result.sessions.map((session) => session.id),
26
39
  };
27
40
  }
41
+ async function loadScheduleRun(schedule, moduleMap) {
42
+ if (schedule.sourceKind !== "module") {
43
+ throw new Error(`Schedule "${schedule.name}" claims hasRun but is not a module-backed schedule.`);
44
+ }
45
+ const moduleSchedule = schedule;
46
+ const exportValue = await loadResolvedModuleExport({
47
+ definition: {
48
+ exportName: moduleSchedule.exportName,
49
+ logicalPath: moduleSchedule.logicalPath,
50
+ sourceId: moduleSchedule.sourceId,
51
+ },
52
+ kindLabel: "schedule",
53
+ moduleMap,
54
+ nodeId: undefined,
55
+ });
56
+ return expectScheduleRun(exportValue, moduleSchedule.logicalPath, moduleSchedule.exportName);
57
+ }
@@ -1,4 +1,4 @@
1
- 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 SlackInteractionAction, type SlackMentionResult, type SlackMentionResultOrPromise, type SlackReceiveArgs, type SlackThread, type SlackWebhookVerifier, } from "#public/channels/slack/slackChannel.js";
1
+ 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 SlackInitialPost, type SlackInteractionAction, type SlackMentionResult, type SlackMentionResultOrPromise, type SlackReceiveArgs, type SlackThread, type SlackWebhookVerifier, } from "#public/channels/slack/slackChannel.js";
2
2
  export type { SlackAttachment, SlackAuthor, SlackInboundContext, SlackMessage, } from "#public/channels/slack/inbound.js";
3
3
  export type { SlackPostInput, SlackPostedMessage, SlackThreadMessage, SlackUploadFilesOptions, SlackUploadFilesResult, } from "#public/channels/slack/api.js";
4
4
  export { cardToBlocks, cardToFallbackText, type BlockKitBlock, } from "#public/channels/slack/blocks.js";
@@ -1,4 +1,6 @@
1
+ import type { TypedReceiveRoute } from "#channel/receive-args.js";
1
2
  import type { SessionAuthContext } from "#channel/types.js";
3
+ import type { CardElement } from "#compiled/chat/index.js";
2
4
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
3
5
  import { type SlackBotToken, type SlackHandle, type SlackThread } from "#public/channels/slack/api.js";
4
6
  import { type SlackMessage } from "#public/channels/slack/inbound.js";
@@ -94,6 +96,23 @@ export interface SlackChannelCredentials {
94
96
  export interface SlackReceiveArgs {
95
97
  readonly channelId: string;
96
98
  readonly threadTs?: string;
99
+ /**
100
+ * Optional message posted into the Slack channel before the agent
101
+ * runs. The post becomes the thread root and the agent's first turn
102
+ * lands threaded under it. Useful for cross-channel handoffs where
103
+ * the caller wants a visible "this is why we're here" anchor before
104
+ * the model speaks. Mutually exclusive with {@link threadTs}.
105
+ */
106
+ readonly initialPost?: SlackInitialPost;
107
+ }
108
+ /**
109
+ * Pre-agent post issued by `slackChannel().receive` when the caller
110
+ * provides `args.initialPost`. The shape mirrors `ctx.thread.post`'s
111
+ * card variant so the same `Card({...})` construction can be reused.
112
+ */
113
+ export interface SlackInitialPost {
114
+ readonly card: CardElement;
115
+ readonly fallbackText?: string;
97
116
  }
98
117
  export interface SlackInteractionAction {
99
118
  readonly actionId: string;
@@ -260,7 +279,7 @@ export interface SlackChannelConfig {
260
279
  * default-export a `slackChannel(...)` call under `declaration: true`
261
280
  * without TypeScript falling back to an internal path for `Channel`.
262
281
  */
263
- export interface SlackChannel extends Channel<SlackChannelState> {
282
+ export interface SlackChannel extends Channel<SlackChannelState>, TypedReceiveRoute<SlackReceiveArgs> {
264
283
  }
265
284
  /**
266
285
  * Slack channel factory. Wires up the Slack webhook route, mention
@@ -72,11 +72,33 @@ export function slackChannel(config = {}) {
72
72
  }),
73
73
  ],
74
74
  async receive(input, { send }) {
75
- const channelId = input.args.channelId;
76
- if (!channelId) {
75
+ const receiveArgs = input.args;
76
+ const channelId = receiveArgs.channelId;
77
+ if (!channelId || typeof channelId !== "string") {
77
78
  throw new Error("slackChannel().receive requires args.channelId.");
78
79
  }
79
- const threadTs = typeof input.args.threadTs === "string" ? input.args.threadTs : "";
80
+ const requestedThreadTs = typeof receiveArgs.threadTs === "string" ? receiveArgs.threadTs : "";
81
+ const initialPost = receiveArgs.initialPost;
82
+ if (initialPost && requestedThreadTs.length > 0) {
83
+ throw new Error("slackChannel().receive: `threadTs` and `initialPost` are mutually exclusive.");
84
+ }
85
+ let threadTs = requestedThreadTs;
86
+ if (initialPost) {
87
+ const { thread } = buildSlackBinding({
88
+ botToken: config.credentials?.botToken,
89
+ channelId,
90
+ threadTs: "",
91
+ teamId: undefined,
92
+ });
93
+ const postInput = {
94
+ card: initialPost.card,
95
+ };
96
+ if (initialPost.fallbackText !== undefined) {
97
+ postInput.fallbackText = initialPost.fallbackText;
98
+ }
99
+ const posted = await thread.post(postInput);
100
+ threadTs = posted.id;
101
+ }
80
102
  return send(input.message, {
81
103
  auth: input.auth,
82
104
  continuationToken: encodeSlackContinuationToken(channelId, threadTs),
@@ -1,3 +1,4 @@
1
+ import type { TypedReceiveRoute } from "#channel/receive-args.js";
1
2
  import type { SessionAuthContext } from "#channel/types.js";
2
3
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
3
4
  import { type TwilioApiOptions, type TwilioApiResponse, type TwilioCredentials } from "#public/channels/twilio/api.js";
@@ -172,7 +173,7 @@ export interface TwilioSendMessageOptions {
172
173
  readonly statusCallbackUrl?: string;
173
174
  }
174
175
  /** Concrete return type of {@link twilioChannel}. */
175
- export interface TwilioChannel extends Channel<TwilioChannelState> {
176
+ export interface TwilioChannel extends Channel<TwilioChannelState>, TypedReceiveRoute<TwilioReceiveArgs> {
176
177
  }
177
178
  /** Twilio channel factory for SMS and speech-transcribed inbound calls. */
178
179
  export declare function twilioChannel(config: TwilioChannelConfig): TwilioChannel;
@@ -2,9 +2,9 @@ import type { Optional } from "#shared/optional.js";
2
2
  import type { SandboxSession } from "#shared/sandbox-session.js";
3
3
  import type { SandboxDefinition as SharedSandboxDefinition } from "#shared/sandbox-definition.js";
4
4
  export type { SandboxCommandOptions, SandboxCommandResult, SandboxReadTextFileOptions, SandboxSession, SandboxWriteTextFileOptions, } from "#shared/sandbox-session.js";
5
- export type { SandboxBootstrapUseOptions, SandboxBootstrapUseFn, SandboxSessionUseFn, SandboxBootstrapContext, SandboxSessionContext, } from "#shared/sandbox-definition.js";
6
- export type SandboxDefinition<S extends SandboxSession = SandboxSession, O = Record<string, never>> = Optional<SharedSandboxDefinition<S, O>, "backend">;
5
+ export type { SandboxBootstrapUseFn, SandboxSessionUseFn, SandboxBootstrapContext, SandboxSessionContext, } from "#shared/sandbox-definition.js";
6
+ export type SandboxDefinition<S extends SandboxSession = SandboxSession, BO = Record<string, never>, SO = Record<string, never>> = Optional<SharedSandboxDefinition<S, BO, SO>, "backend">;
7
7
  /**
8
8
  * Defines a sandbox configuration.
9
9
  */
10
- export declare function defineSandbox<S extends SandboxSession = SandboxSession, O = Record<string, never>>(definition: SandboxDefinition<S, O>): SandboxDefinition<S, O>;
10
+ export declare function defineSandbox<S extends SandboxSession = SandboxSession, BO = Record<string, never>, SO = Record<string, never>>(definition: SandboxDefinition<S, BO, SO>): SandboxDefinition<S, BO, SO>;
@@ -1,77 +1,75 @@
1
- declare const receiveArgsMarker: unique symbol;
1
+ import type { CrossChannelReceiveFn } from "#channel/cross-channel-receive.js";
2
+ import type { SessionAuthContext } from "#channel/types.js";
3
+ export type { InferReceiveArgs, TypedReceiveRoute } from "#channel/receive-args.js";
2
4
  /**
3
- * A route that declares its receive args type. Channel route factories
4
- * return this so {@link receive} can infer the
5
- * correct args type from the channel import.
6
- *
7
- * Accepts both old Route-style and new CompiledChannel values.
5
+ * Arguments handed to {@link ScheduleDefinition.run}. Tight subset of
6
+ * `RouteHandlerArgs` schedules can hand work off to a channel via
7
+ * {@link receive} and extend the task lifetime via {@link waitUntil}.
8
+ * No `send` because schedules have no current channel.
8
9
  */
9
- export interface TypedReceiveRoute<TArgs = Record<string, unknown>> {
10
- readonly [receiveArgsMarker]?: TArgs;
10
+ export interface ScheduleHandlerArgs {
11
+ /**
12
+ * Starts a session on another channel — same contract as a route
13
+ * handler's `args.receive(channel, ...)`.
14
+ */
15
+ readonly receive: CrossChannelReceiveFn;
16
+ /**
17
+ * Extends the cron task's lifetime past handler return so background
18
+ * work (the parked workflow session, in-flight fetches, etc.) is
19
+ * awaited before the Nitro task settles.
20
+ */
21
+ readonly waitUntil: (task: Promise<unknown>) => void;
22
+ /**
23
+ * Pre-built APP auth context. Pass this to `receive(channel, { auth })`
24
+ * for schedules that run on behalf of the agent itself.
25
+ */
26
+ readonly appAuth: SessionAuthContext;
11
27
  }
12
28
  /**
13
- * Extracts the receive args type from a channel value.
14
- */
15
- type InferReceiveArgs<TChannel> = TChannel extends TypedReceiveRoute<infer TArgs> ? TArgs : Record<string, unknown>;
16
- /**
17
- * A channel reference with typed args, produced by {@link receive}.
18
- * Carries the route/channel object for identity resolution at compile time
19
- * and the args for the compiled manifest.
29
+ * Handler invoked when the cron fires.
20
30
  */
21
- export interface ChannelReceiveRef<TArgs = Record<string, unknown>> {
22
- readonly __route: any;
23
- readonly args: TArgs;
24
- }
31
+ export type ScheduleRunHandler = (args: ScheduleHandlerArgs) => Promise<void> | void;
25
32
  /**
26
- * Creates a typed channel reference for a schedule. Import the channel
27
- * module and pass it with the args — TypeScript infers the correct
28
- * args type from the channel's route type.
29
- *
30
- * Accepts both old Route-style channels and new CompiledChannel values.
33
+ * Public definition for a schedule authored in TypeScript.
31
34
  *
32
- * @example
33
- * ```ts
34
- * import slack from "../../channels/slack.js";
35
+ * Provide exactly one of `markdown` or `run`:
35
36
  *
36
- * export default defineSchedule({
37
- * cron: "0 9 * * 1-5",
38
- * channel: receive(slack, { channelId: "C0123ABC" }),
39
- * });
40
- * ```
41
- */
42
- export declare function receive<TChannel extends TypedReceiveRoute<any> | {
43
- readonly __kind: any;
44
- }>(channel: TChannel, args: InferReceiveArgs<TChannel>): ChannelReceiveRef<InferReceiveArgs<TChannel>>;
45
- /**
46
- * Public definition for a schedule authored in TypeScript.
37
+ * - `markdown` — fire-and-forget agent invocation. The framework runs
38
+ * the agent on the prompt and discards the output. Equivalent to the
39
+ * markdown form (`<name>.md`).
40
+ * - `run` — full handler. Receives `{ receive, waitUntil, appAuth }`
41
+ * and decides what to do.
47
42
  *
48
43
  * Identity is derived from the file path under `agent/schedules/`;
49
- * authored definitions do not carry a `name` field. The optional
50
- * `channel` delivers the agent's output to a typed channel target;
51
- * when omitted, the agent runs and the output is discarded (the
52
- * agent is still free to call tools, log, or hit backends).
44
+ * authored definitions do not carry a `name` field.
53
45
  */
54
46
  export interface ScheduleDefinition {
55
- cron: string;
56
- markdown: string;
57
- channel?: ChannelReceiveRef<any>;
47
+ readonly cron: string;
48
+ readonly markdown?: string;
49
+ readonly run?: ScheduleRunHandler;
58
50
  }
59
51
  /**
60
52
  * Defines a schedule in TypeScript. Export as the default from
61
53
  * `agent/schedules/<name>.ts`.
62
54
  *
63
- * @example
55
+ * @example Hand off to Slack:
64
56
  * ```ts
57
+ * import { defineSchedule } from "experimental-ash/schedules";
65
58
  * import slack from "../channels/slack.js";
66
59
  *
67
60
  * export default defineSchedule({
68
61
  * cron: "0 9 * * 1-5",
69
- * markdown: "Post the daily standup summary.",
70
- * channel: receive(slack, { channelId: "C0123ABC" }),
62
+ * async run({ receive, waitUntil, appAuth }) {
63
+ * waitUntil(receive(slack, {
64
+ * message: "Post the daily standup summary.",
65
+ * args: { channelId: "C0123ABC" },
66
+ * auth: appAuth,
67
+ * }));
68
+ * },
71
69
  * });
72
70
  * ```
73
71
  *
74
- * @example Without a channel — fire-and-forget:
72
+ * @example Fire-and-forget:
75
73
  * ```ts
76
74
  * export default defineSchedule({
77
75
  * cron: "* / 5 * * * *",
@@ -80,4 +78,3 @@ export interface ScheduleDefinition {
80
78
  * ```
81
79
  */
82
80
  export declare function defineSchedule<TSchedule extends ScheduleDefinition>(definition: TSchedule): TSchedule;
83
- export {};
@@ -1,40 +1,25 @@
1
- /**
2
- * Creates a typed channel reference for a schedule. Import the channel
3
- * module and pass it with the args — TypeScript infers the correct
4
- * args type from the channel's route type.
5
- *
6
- * Accepts both old Route-style channels and new CompiledChannel values.
7
- *
8
- * @example
9
- * ```ts
10
- * import slack from "../../channels/slack.js";
11
- *
12
- * export default defineSchedule({
13
- * cron: "0 9 * * 1-5",
14
- * channel: receive(slack, { channelId: "C0123ABC" }),
15
- * });
16
- * ```
17
- */
18
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
- export function receive(channel, args) {
20
- return { __route: channel, args };
21
- }
22
1
  /**
23
2
  * Defines a schedule in TypeScript. Export as the default from
24
3
  * `agent/schedules/<name>.ts`.
25
4
  *
26
- * @example
5
+ * @example Hand off to Slack:
27
6
  * ```ts
7
+ * import { defineSchedule } from "experimental-ash/schedules";
28
8
  * import slack from "../channels/slack.js";
29
9
  *
30
10
  * export default defineSchedule({
31
11
  * cron: "0 9 * * 1-5",
32
- * markdown: "Post the daily standup summary.",
33
- * channel: receive(slack, { channelId: "C0123ABC" }),
12
+ * async run({ receive, waitUntil, appAuth }) {
13
+ * waitUntil(receive(slack, {
14
+ * message: "Post the daily standup summary.",
15
+ * args: { channelId: "C0123ABC" },
16
+ * auth: appAuth,
17
+ * }));
18
+ * },
34
19
  * });
35
20
  * ```
36
21
  *
37
- * @example Without a channel — fire-and-forget:
22
+ * @example Fire-and-forget:
38
23
  * ```ts
39
24
  * export default defineSchedule({
40
25
  * cron: "* / 5 * * * *",
@@ -55,12 +55,12 @@ export interface LowerSkillMarkdownInput {
55
55
  */
56
56
  /**
57
57
  * Lowers an authored schedule markdown file into the shared public
58
- * definition shape. The frontmatter must contain `cron`; no other keys
59
- * are accepted (markdown-form schedules cannot reference a channel —
60
- * use the `.ts` form for that). The body becomes the schedule's
61
- * `markdown` (the prompt delivered when the schedule fires). Schedule
62
- * identity is path-derived, so the lowered definition never carries a
63
- * `name`.
58
+ * definition shape. The frontmatter must contain `cron`. The body
59
+ * becomes the schedule's `markdown` (the fire-and-forget prompt the
60
+ * agent runs when the cron fires). Markdown-form schedules cannot
61
+ * declare a `run` handler use the `.ts` form for handler-based
62
+ * schedules. Schedule identity is path-derived, so the lowered
63
+ * definition never carries a `name`.
64
64
  */
65
65
  export declare function lowerScheduleMarkdown(source: string): ScheduleDefinition;
66
66
  export declare function lowerSkillMarkdown(source: string, input?: LowerSkillMarkdownInput): SkillDefinition;
@@ -56,20 +56,20 @@ export function lowerInstructionsMarkdown(markdown) {
56
56
  */
57
57
  /**
58
58
  * Lowers an authored schedule markdown file into the shared public
59
- * definition shape. The frontmatter must contain `cron`; no other keys
60
- * are accepted (markdown-form schedules cannot reference a channel —
61
- * use the `.ts` form for that). The body becomes the schedule's
62
- * `markdown` (the prompt delivered when the schedule fires). Schedule
63
- * identity is path-derived, so the lowered definition never carries a
64
- * `name`.
59
+ * definition shape. The frontmatter must contain `cron`. The body
60
+ * becomes the schedule's `markdown` (the fire-and-forget prompt the
61
+ * agent runs when the cron fires). Markdown-form schedules cannot
62
+ * declare a `run` handler use the `.ts` form for handler-based
63
+ * schedules. Schedule identity is path-derived, so the lowered
64
+ * definition never carries a `name`.
65
65
  */
66
66
  export function lowerScheduleMarkdown(source) {
67
67
  const document = parseMarkdownDocument(source);
68
68
  if (!document.hasFrontmatter) {
69
69
  throw new Error('Schedule markdown must start with YAML frontmatter declaring "cron".');
70
70
  }
71
- if ("channel" in document.frontmatter) {
72
- throw new Error('Markdown-form schedules do not support the "channel" frontmatter key. Use a TypeScript schedule (`<name>.ts`) to deliver to a channel.');
71
+ if ("run" in document.frontmatter) {
72
+ throw new Error('Markdown-form schedules do not support the "run" frontmatter key. Use a TypeScript schedule (`<name>.ts`) to author a handler.');
73
73
  }
74
74
  const rawDefinition = {
75
75
  ...document.frontmatter,
@@ -1,11 +1,11 @@
1
1
  import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
2
- import type { VercelSandbox, VercelSandboxSessionUseOptions } from "#public/sandbox/vercel-sandbox.js";
2
+ import type { VercelSandbox, VercelSandboxBootstrapUseOptions, VercelSandboxSessionUseOptions } from "#public/sandbox/vercel-sandbox.js";
3
3
  /**
4
4
  * Constructs the built-in Vercel sandbox backend.
5
5
  *
6
- * Creation-time config (runtime, ports, env) lives in `bootstrap`'s
7
- * `create()` call; session-level config (networkPolicy, resources,
8
- * timeout) lives in `onSession`'s `create()` call or
6
+ * `bootstrap({ use })` applies its options to the template via
7
+ * `sandbox.update(...)`; those settings persist into the snapshot.
8
+ * `onSession({ use })` applies its options to the live session via
9
9
  * `VercelSandbox.update()`.
10
10
  */
11
- export declare function vercelBackend(): SandboxBackend<VercelSandbox, VercelSandboxSessionUseOptions>;
11
+ export declare function vercelBackend(): SandboxBackend<VercelSandbox, VercelSandboxBootstrapUseOptions, VercelSandboxSessionUseOptions>;
@@ -2,9 +2,9 @@ import { createVercelSandboxBackend } from "#execution/sandbox/bindings/vercel.j
2
2
  /**
3
3
  * Constructs the built-in Vercel sandbox backend.
4
4
  *
5
- * Creation-time config (runtime, ports, env) lives in `bootstrap`'s
6
- * `create()` call; session-level config (networkPolicy, resources,
7
- * timeout) lives in `onSession`'s `create()` call or
5
+ * `bootstrap({ use })` applies its options to the template via
6
+ * `sandbox.update(...)`; those settings persist into the snapshot.
7
+ * `onSession({ use })` applies its options to the live session via
8
8
  * `VercelSandbox.update()`.
9
9
  */
10
10
  export function vercelBackend() {
@@ -3,10 +3,10 @@
3
3
  * `agent/sandbox/sandbox.ts` when paired with a `workspace/` folder).
4
4
  */
5
5
  export { getSandbox } from "#context/accessors.js";
6
- export { defineSandbox, type SandboxBootstrapContext, type SandboxBootstrapUseFn, type SandboxBootstrapUseOptions, type SandboxCommandOptions, type SandboxCommandResult, type SandboxDefinition, type SandboxReadTextFileOptions, type SandboxSession, type SandboxSessionContext, type SandboxSessionUseFn, type SandboxWriteTextFileOptions, } from "#public/definitions/sandbox.js";
6
+ export { defineSandbox, type SandboxBootstrapContext, type SandboxBootstrapUseFn, type SandboxCommandOptions, type SandboxCommandResult, type SandboxDefinition, type SandboxReadTextFileOptions, type SandboxSession, type SandboxSessionContext, type SandboxSessionUseFn, type SandboxWriteTextFileOptions, } from "#public/definitions/sandbox.js";
7
7
  export type { SandboxBackend, SandboxBackendCreateInput, SandboxBackendHandle, SandboxBackendPrewarmInput, SandboxBackendRuntimeContext, SandboxBackendSessionState, SandboxSeedFile, } from "#public/definitions/sandbox-backend.js";
8
8
  export { SandboxTemplateNotProvisionedError } from "#public/definitions/sandbox-backend.js";
9
9
  export { defaultBackend } from "#public/sandbox/backends/default.js";
10
10
  export { localBackend } from "#public/sandbox/backends/local.js";
11
11
  export { vercelBackend } from "#public/sandbox/backends/vercel.js";
12
- export type { VercelSandbox, VercelSandboxSessionUseOptions, } from "#public/sandbox/vercel-sandbox.js";
12
+ export type { VercelSandbox, VercelSandboxBootstrapUseOptions, VercelSandboxSessionUseOptions, } from "#public/sandbox/vercel-sandbox.js";
@@ -1,5 +1,18 @@
1
1
  import type { SandboxUpdateParams } from "#compiled/@vercel/sandbox/index.js";
2
2
  import type { SandboxSession } from "#public/definitions/sandbox.js";
3
+ /**
4
+ * Options accepted by the Vercel backend's `bootstrap({ use })` hook.
5
+ * Aliases the Vercel SDK's `SandboxUpdateParams` because bootstrap
6
+ * applies its options to the template via `sandbox.update(...)` after
7
+ * `Sandbox.create()` and before the snapshot is captured. The Vercel
8
+ * SDK persists `update`-d settings on the sandbox so they survive into
9
+ * the snapshot, which becomes the seed for every later session.
10
+ *
11
+ * Today this is the same shape as
12
+ * {@link VercelSandboxSessionUseOptions}; both are exposed as separate
13
+ * named aliases so future divergence is non-breaking.
14
+ */
15
+ export type VercelSandboxBootstrapUseOptions = SandboxUpdateParams;
3
16
  /**
4
17
  * Options accepted by the Vercel backend's `onSession({ use })` hook
5
18
  * and `VercelSandbox.update()`. Aliases the Vercel SDK's
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Schedule authoring helpers for `agent/schedules/*` files.
3
3
  */
4
- export { type ChannelReceiveRef, defineSchedule, receive, type ScheduleDefinition, type TypedReceiveRoute, } from "#public/definitions/schedule.js";
4
+ export { defineSchedule, type ScheduleDefinition, type ScheduleHandlerArgs, type ScheduleRunHandler, type TypedReceiveRoute, } from "#public/definitions/schedule.js";
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Schedule authoring helpers for `agent/schedules/*` files.
3
3
  */
4
- export { defineSchedule, receive, } from "#public/definitions/schedule.js";
4
+ export { defineSchedule, } from "#public/definitions/schedule.js";
@@ -45,6 +45,7 @@ export async function resolveChannelDefinition(definition, moduleMap, nodeId) {
45
45
  },
46
46
  handler: matchedRoute?.handler,
47
47
  receive: channelDefinition.receive,
48
+ definition: channelDefinition,
48
49
  adapter,
49
50
  ...sourceRef,
50
51
  };
@@ -19,18 +19,18 @@ export class ResolveScheduleError extends Error {
19
19
  */
20
20
  export async function resolveSchedules(input) {
21
21
  return [...input.manifest.schedules].map((schedule) => {
22
- const resolved = {
22
+ const base = {
23
23
  cron: schedule.cron,
24
+ hasRun: schedule.hasRun,
24
25
  logicalPath: schedule.logicalPath,
25
- markdown: schedule.markdown,
26
26
  name: schedule.name,
27
27
  sourceId: schedule.sourceId,
28
28
  sourceKind: schedule.sourceKind,
29
29
  };
30
- if (schedule.channel !== undefined) {
31
- return { ...resolved, channel: schedule.channel };
30
+ if (schedule.markdown !== undefined) {
31
+ return { ...base, markdown: schedule.markdown };
32
32
  }
33
- return resolved;
33
+ return base;
34
34
  });
35
35
  }
36
36
  /**
@@ -43,20 +43,17 @@ export type ResolvedSkillDefinition = Readonly<InternalSkillDefinition & (Omit<M
43
43
  /**
44
44
  * Runtime-owned authored schedule definition resolved from compiler artifacts.
45
45
  *
46
- * `channel` is optional schedules without a channel run the agent on a
47
- * cron and discard the output. The agent is still free to call tools,
48
- * write to backends, and log. `sourceKind` reflects whether the schedule
49
- * was authored as a TypeScript module or as a markdown file with
50
- * frontmatter.
46
+ * A schedule has exactly one of `markdown` (fire-and-forget agent run)
47
+ * or `hasRun: true` (authored handler). For the handler form the
48
+ * runtime loads the schedule's module and invokes `definition.run` with
49
+ * a {@link ScheduleHandlerArgs}-shaped argument; for the markdown form
50
+ * the dispatcher synthesizes a channel-less SCHEDULE_ADAPTER run.
51
51
  */
52
52
  export type ResolvedSchedule = Readonly<SourceRef & {
53
- readonly channel?: {
54
- readonly name: string;
55
- readonly args: Readonly<Record<string, unknown>>;
56
- };
57
53
  readonly cron: string;
58
54
  readonly name: string;
59
- readonly markdown: string;
55
+ readonly markdown?: string;
56
+ readonly hasRun: boolean;
60
57
  readonly sourceKind: "markdown" | "module";
61
58
  } & (Omit<MarkdownSourceRef<undefined>, "definition"> | ModuleSourceRef)>;
62
59
  /**
@@ -187,6 +184,14 @@ export interface ResolvedChannelDefinition extends ResolvedModuleSourceRef {
187
184
  * accepting a different shape.
188
185
  */
189
186
  readonly receive?: CompiledChannel["receive"];
187
+ /**
188
+ * Reference to the authored {@link CompiledChannel} value the channel
189
+ * module exported. Preserved so callers of `args.receive(channel, …)`
190
+ * can identify a target by the same imported reference. `undefined`
191
+ * for framework-internal channels constructed without going through
192
+ * `defineChannel`.
193
+ */
194
+ readonly definition?: CompiledChannel;
190
195
  /**
191
196
  * New-style route handler from CompiledChannel. When present, the
192
197
  * dispatch layer uses this instead of `fetch`.
@@ -7,9 +7,9 @@ import type { SandboxSession } from "#shared/sandbox-session.js";
7
7
  * runtime orchestrator can persist reconnect metadata and release
8
8
  * resources.
9
9
  */
10
- export interface SandboxBackendHandle<S extends SandboxSession = SandboxSession, O = Record<string, never>> {
10
+ export interface SandboxBackendHandle<S extends SandboxSession = SandboxSession, SO = Record<string, never>> {
11
11
  readonly session: SandboxSession;
12
- readonly useSessionFn: SandboxSessionUseFn<S, O>;
12
+ readonly useSessionFn: SandboxSessionUseFn<S, SO>;
13
13
  captureState(): Promise<SandboxBackendSessionState>;
14
14
  dispose(): Promise<void>;
15
15
  }
@@ -79,9 +79,9 @@ export interface SandboxBackendCreateInput {
79
79
  * `bootstrap` hook and `seedFiles`, then `backend.create(...)` opens
80
80
  * durable sessions from that snapshot.
81
81
  */
82
- export interface SandboxBackendPrewarmInput {
82
+ export interface SandboxBackendPrewarmInput<BO = Record<string, never>> {
83
83
  readonly templateKey: string;
84
- readonly bootstrap?: (input: SandboxBootstrapContext) => void | Promise<void>;
84
+ readonly bootstrap?: (input: SandboxBootstrapContext<BO>) => void | Promise<void>;
85
85
  readonly runtimeContext: SandboxBackendRuntimeContext;
86
86
  readonly seedFiles: ReadonlyArray<SandboxSeedFile>;
87
87
  }
@@ -97,7 +97,7 @@ export interface SandboxBackendPrewarmInput {
97
97
  * Each backend owns the full template-then-session lifecycle internally;
98
98
  * callers only need a single `create` call.
99
99
  */
100
- export interface SandboxBackend<S extends SandboxSession = SandboxSession, O = Record<string, never>> {
100
+ export interface SandboxBackend<S extends SandboxSession = SandboxSession, BO = Record<string, never>, SO = Record<string, never>> {
101
101
  /**
102
102
  * Stable identifier for this backend implementation.
103
103
  *
@@ -113,12 +113,12 @@ export interface SandboxBackend<S extends SandboxSession = SandboxSession, O = R
113
113
  * {@link SandboxTemplateNotProvisionedError} when the requested
114
114
  * template is missing.
115
115
  */
116
- create(input: SandboxBackendCreateInput): Promise<SandboxBackendHandle<S, O>>;
116
+ create(input: SandboxBackendCreateInput): Promise<SandboxBackendHandle<S, SO>>;
117
117
  /**
118
118
  * Build-time prewarm hook. Ash invokes this for every authored
119
119
  * sandbox in the compiled graph before serving traffic so the backend
120
120
  * can capture a reusable template snapshot. Idempotent against an
121
121
  * existing snapshot keyed by `templateKey`.
122
122
  */
123
- prewarm(input: SandboxBackendPrewarmInput): Promise<void>;
123
+ prewarm(input: SandboxBackendPrewarmInput<BO>): Promise<void>;
124
124
  }