experimental-ash 0.20.1 → 0.22.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 (66) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/docs/public/channels/README.md +3 -1
  3. package/dist/docs/public/hooks.md +76 -6
  4. package/dist/docs/public/skills.md +6 -1
  5. package/dist/docs/public/tools.md +4 -0
  6. package/dist/src/chunks/{dev-authored-source-watcher-BR7XYOW0.js → dev-authored-source-watcher-DKDaaPea.js} +1 -1
  7. package/dist/src/chunks/{host-Dxf7CGaE.js → host-Btr4S69C.js} +7 -7
  8. package/dist/src/chunks/paths-DZTgjrW-.js +88 -0
  9. package/dist/src/chunks/{prewarm-vAT2QBqc.js → prewarm-BELT37PI.js} +1 -1
  10. package/dist/src/cli/commands/info.js +1 -1
  11. package/dist/src/cli/run.js +1 -1
  12. package/dist/src/compiled/.vendor-stamp.json +3 -3
  13. package/dist/src/compiled/@ai-sdk/anthropic/_provider-utils.d.ts +15 -0
  14. package/dist/src/compiled/@ai-sdk/anthropic/index.d.ts +1259 -6
  15. package/dist/src/compiled/@ai-sdk/google/_provider-utils.d.ts +11 -0
  16. package/dist/src/compiled/@ai-sdk/google/index.d.ts +561 -6
  17. package/dist/src/compiled/@ai-sdk/google/index.js +4 -4
  18. package/dist/src/compiled/@ai-sdk/google/package.json +1 -1
  19. package/dist/src/compiled/@ai-sdk/openai/_provider-utils.d.ts +15 -0
  20. package/dist/src/compiled/@ai-sdk/openai/index.d.ts +1255 -6
  21. package/dist/src/compiled/@ai-sdk/openai/index.js +1 -1
  22. package/dist/src/compiled/@ai-sdk/openai/package.json +1 -1
  23. package/dist/src/compiler/normalize-skill.js +5 -1
  24. package/dist/src/compiler/workspace-resources.js +12 -5
  25. package/dist/src/context/hook-lifecycle.d.ts +8 -8
  26. package/dist/src/context/hook-lifecycle.js +88 -36
  27. package/dist/src/evals/cli/eval.js +1 -1
  28. package/dist/src/execution/skills/instructions.d.ts +8 -4
  29. package/dist/src/execution/skills/instructions.js +3 -2
  30. package/dist/src/harness/attachment-staging.js +5 -1
  31. package/dist/src/harness/tool-loop.js +11 -3
  32. package/dist/src/internal/application/package.js +1 -1
  33. package/dist/src/internal/authored-definition/core.js +16 -1
  34. package/dist/src/internal/nitro/routes/workflow-route-helpers.d.ts +4 -4
  35. package/dist/src/internal/nitro/routes/workflow-route-helpers.js +6 -5
  36. package/dist/src/internal/nitro/routes/workflow-run-events.d.ts +1 -1
  37. package/dist/src/internal/nitro/routes/workflow-run-events.js +1 -1
  38. package/dist/src/internal/nitro/routes/workflow-run-steps.d.ts +1 -1
  39. package/dist/src/internal/nitro/routes/workflow-run-steps.js +1 -1
  40. package/dist/src/internal/nitro/routes/workflow-run.d.ts +1 -1
  41. package/dist/src/internal/nitro/routes/workflow-run.js +1 -1
  42. package/dist/src/internal/nitro/routes/workflow-runs.d.ts +1 -1
  43. package/dist/src/internal/nitro/routes/workflow-runs.js +1 -1
  44. package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +2 -0
  45. package/dist/src/public/definitions/hook.d.ts +11 -3
  46. package/dist/src/public/definitions/skill.d.ts +4 -3
  47. package/dist/src/public/definitions/skill.js +1 -1
  48. package/dist/src/public/next/index.d.ts +74 -0
  49. package/dist/src/public/next/index.js +150 -0
  50. package/dist/src/public/next/server.d.ts +6 -0
  51. package/dist/src/public/next/server.js +348 -0
  52. package/dist/src/public/next/vercel-json.d.ts +8 -0
  53. package/dist/src/public/next/vercel-json.js +76 -0
  54. package/dist/src/public/skills/index.d.ts +1 -1
  55. package/dist/src/public/skills/index.js +1 -1
  56. package/dist/src/public/tool-result-narrowing.d.ts +68 -0
  57. package/dist/src/public/tool-result-narrowing.js +58 -0
  58. package/dist/src/public/tools/index.d.ts +1 -0
  59. package/dist/src/public/tools/index.js +1 -0
  60. package/dist/src/runtime/resolve-connection.js +2 -0
  61. package/dist/src/runtime/resolve-tool.js +2 -0
  62. package/dist/src/shared/skill-definition.d.ts +20 -2
  63. package/dist/src/shared/skill-package.d.ts +43 -0
  64. package/dist/src/shared/skill-package.js +100 -0
  65. package/package.json +13 -3
  66. package/dist/src/chunks/paths-BcM3el4Q.js +0 -88
@@ -2,8 +2,10 @@ import { getAdapterKind } from "../channel/adapter.js";
2
2
  import { emitRecoverableFailedTurn, emitTurnPreamble, getHarnessEmissionState, setHarnessEmissionState, } from "../harness/emission.js";
3
3
  import { createLogger } from "../internal/logging.js";
4
4
  import { toErrorMessage } from "../shared/errors.js";
5
+ import { normalizeSkillPackage, writeSkillPackageToSandbox, } from "../shared/skill-package.js";
6
+ import { formatAvailableSkillsSection } from "#execution/skills/instructions.js";
5
7
  import { contextStorage } from "./container.js";
6
- import { BundleKey, ChannelKey, ContinuationTokenKey, SessionIdKey, SessionPreparedKey, } from "./keys.js";
8
+ import { BundleKey, ChannelKey, ContinuationTokenKey, SandboxKey, SessionIdKey, SessionPreparedKey, } from "./keys.js";
7
9
  const log = createLogger("hooks.lifecycle");
8
10
  /**
9
11
  * Runs the per-turn hook lifecycle inside the active ALS scope and
@@ -20,11 +22,12 @@ const log = createLogger("hooks.lifecycle");
20
22
  *
21
23
  * 2. `lifecycle.turn` — runs once per fresh delivery (the caller
22
24
  * gates dispatch with `isHarnessBetweenTurns(session)`). Each
23
- * hook may return `{ modelContext }`; contributions are
24
- * concatenated in registry order. A thrown hook is caught here,
25
- * the recoverable `turn.failed` cascade is emitted, and the
26
- * dispatcher returns `kind: "turn-failed"` so the caller can park
27
- * the session.
25
+ * hook may return `{ modelContext, skills }`; model-context
26
+ * contributions are concatenated in registry order and skills are
27
+ * materialized into the live sandbox before the model call. A
28
+ * thrown hook is caught here, the recoverable `turn.failed`
29
+ * cascade is emitted, and the dispatcher returns
30
+ * `kind: "turn-failed"` so the caller can park the session.
28
31
  *
29
32
  * Errors:
30
33
  *
@@ -48,56 +51,63 @@ export async function dispatchHookLifecycle(input) {
48
51
  turn: { sequence, turnId },
49
52
  };
50
53
  const modelContext = [];
54
+ let nextSession = input.session;
51
55
  // ---------- lifecycle.session ----------
52
56
  if (registry.session.length > 0 && ctx.get(SessionPreparedKey) !== true) {
53
57
  // Set the flag BEFORE running the chain. A thrown session hook is
54
58
  // a terminal session failure; the next turn must not retry.
55
59
  ctx.set(SessionPreparedKey, true);
60
+ const stageSkills = [];
56
61
  for (const entry of registry.session) {
57
62
  const result = (await entry.handler(lifecycleInput, hookCtx));
58
63
  if (result?.modelContext !== undefined && result.modelContext.length > 0) {
59
64
  modelContext.push(...result.modelContext);
60
65
  }
66
+ stageSkills.push(...normalizeLifecycleSkillResults(ctx, result));
61
67
  }
68
+ nextSession = await materializeLifecycleSkills(ctx, nextSession, stageSkills);
62
69
  }
63
70
  // ---------- lifecycle.turn ----------
64
71
  let preambleEmitted = false;
65
72
  let preambleState = emissionState;
66
- for (const entry of registry.turn) {
67
- try {
73
+ try {
74
+ const stageSkills = [];
75
+ for (const entry of registry.turn) {
68
76
  const result = (await entry.handler(lifecycleInput, hookCtx));
69
77
  if (result?.modelContext !== undefined && result.modelContext.length > 0) {
70
78
  modelContext.push(...result.modelContext);
71
79
  }
80
+ stageSkills.push(...normalizeLifecycleSkillResults(ctx, result));
72
81
  }
73
- catch (error) {
74
- const message = toErrorMessage(error);
75
- try {
76
- if (!preambleEmitted) {
77
- preambleState = await emitTurnPreamble(emit, { message: input.input.message }, emissionState, input.runtimeIdentity);
78
- preambleEmitted = true;
79
- }
80
- const failedState = await emitRecoverableFailedTurn(emit, preambleState, {
81
- code: "HOOK_TURN_FAILED",
82
- message,
83
- });
84
- return {
85
- kind: "turn-failed",
86
- message,
87
- nextSession: setHarnessEmissionState(input.session, failedState),
88
- };
89
- }
90
- catch (cascadeError) {
91
- // An event hook subscribed to one of the failure-cascade
92
- // events (eg. `events["turn.failed"]`) threw while we were
93
- // emitting the recoverable failure. Re-throwing escalates to
94
- // a terminal `session.failed` via the runtime's outer error
95
- // path. Document this as the bounded second-order behavior:
96
- // when both a `lifecycle.turn` and a `turn.failed` event hook
97
- // throw, the session ends.
98
- log.error(`Event hook threw while emitting the turn.failed cascade for hook "${entry.slug}" — escalating to session.failed.`, { error: toErrorMessage(cascadeError) });
99
- throw cascadeError;
82
+ nextSession = await materializeLifecycleSkills(ctx, nextSession, stageSkills);
83
+ }
84
+ catch (error) {
85
+ const message = toErrorMessage(error);
86
+ try {
87
+ if (!preambleEmitted) {
88
+ preambleState = await emitTurnPreamble(emit, { message: input.input.message }, emissionState, input.runtimeIdentity);
89
+ preambleEmitted = true;
100
90
  }
91
+ const failedState = await emitRecoverableFailedTurn(emit, preambleState, {
92
+ code: "HOOK_TURN_FAILED",
93
+ message,
94
+ });
95
+ return {
96
+ kind: "turn-failed",
97
+ message,
98
+ nextSession: setHarnessEmissionState(nextSession, failedState),
99
+ };
100
+ }
101
+ catch (cascadeError) {
102
+ // An event hook subscribed to one of the failure-cascade events
103
+ // (eg. `events["turn.failed"]`) threw while we were emitting the
104
+ // recoverable failure. Re-throwing escalates to a terminal
105
+ // `session.failed` via the runtime's outer error path. Document
106
+ // this as the bounded second-order behavior: when both a
107
+ // `lifecycle.turn` and a `turn.failed` event hook throw, the
108
+ // session ends.
109
+ log.error("Event hook threw while emitting the turn.failed cascade for a lifecycle.turn failure — escalating to session.failed.", { error: toErrorMessage(cascadeError) });
110
+ throw cascadeError;
101
111
  }
102
112
  }
103
113
  const channelContext = input.input.modelContext ?? [];
@@ -107,7 +117,49 @@ export async function dispatchHookLifecycle(input) {
107
117
  input: mergedModelContext.length > 0
108
118
  ? { ...input.input, modelContext: mergedModelContext }
109
119
  : input.input,
110
- nextSession: input.session,
120
+ nextSession,
121
+ };
122
+ }
123
+ function normalizeLifecycleSkillResults(ctx, result) {
124
+ if (result?.skills === undefined || result.skills.length === 0) {
125
+ return [];
126
+ }
127
+ const authoredSkillNames = new Set(ctx.require(BundleKey).resolvedAgent.skills.map((skill) => skill.name));
128
+ const dynamicSkills = new Map();
129
+ for (const skill of result.skills) {
130
+ const normalized = normalizeSkillPackage(skill);
131
+ if (authoredSkillNames.has(normalized.name)) {
132
+ throw new Error(`Hook-contributed skill "${normalized.name}" conflicts with an authored skill.`);
133
+ }
134
+ dynamicSkills.set(normalized.name, normalized);
135
+ }
136
+ return [...dynamicSkills.values()];
137
+ }
138
+ async function materializeLifecycleSkills(ctx, session, skills) {
139
+ if (skills.length === 0) {
140
+ return session;
141
+ }
142
+ const dedupedSkills = new Map(skills.map((skill) => [skill.name, skill]));
143
+ const sandbox = await ctx.require(SandboxKey).get();
144
+ if (sandbox === null) {
145
+ throw new Error("Dynamic skills require a sandbox for the current agent.");
146
+ }
147
+ for (const skill of dedupedSkills.values()) {
148
+ await writeSkillPackageToSandbox({ sandbox, skill });
149
+ }
150
+ const announcement = formatAvailableSkillsSection([...dedupedSkills.values()]);
151
+ if (announcement === null) {
152
+ return session;
153
+ }
154
+ return {
155
+ ...session,
156
+ history: [
157
+ ...session.history,
158
+ {
159
+ role: "system",
160
+ content: announcement,
161
+ },
162
+ ],
111
163
  };
112
164
  }
113
165
  /**
@@ -1 +1 @@
1
- import{n as e}from"../../chunks/paths-BcM3el4Q.js";import{loadDevelopmentEnvironmentFiles as t}from"../../cli/dev/environment.js";import{a as n,n as r,t as i}from"../../chunks/client-CKsU8Li3.js";import{n as a}from"../../chunks/host-Dxf7CGaE.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-DZTgjrW-.js";import{loadDevelopmentEnvironmentFiles as t}from"../../cli/dev/environment.js";import{a as n,n as r,t as i}from"../../chunks/client-CKsU8Li3.js";import{n as a}from"../../chunks/host-Btr4S69C.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,4 +1,7 @@
1
- import type { ResolvedSkillDefinition } from "#runtime/types.js";
1
+ export interface AvailableSkillDescription {
2
+ readonly description: string;
3
+ readonly name: string;
4
+ }
2
5
  /**
3
6
  * Formats the "Available skills" system prompt section.
4
7
  *
@@ -7,7 +10,8 @@ import type { ResolvedSkillDefinition } from "#runtime/types.js";
7
10
  * has them from the `load_skill` tool result. This keeps the system
8
11
  * prompt identical across the entire session, preserving prompt caching.
9
12
  *
10
- * Called at graph resolution time so the section is part of the turn
11
- * agent's static instructions rather than injected per-step.
13
+ * Authored skills call this at graph resolution time so the section is
14
+ * part of the turn agent's static instructions. Hook-contributed skills
15
+ * reuse the same formatter for durable history announcements.
12
16
  */
13
- export declare function formatAvailableSkillsSection(skills: readonly ResolvedSkillDefinition[]): string | null;
17
+ export declare function formatAvailableSkillsSection(skills: readonly AvailableSkillDescription[]): string | null;
@@ -7,8 +7,9 @@ import { WORKSPACE_ROOT } from "#runtime/workspace/types.js";
7
7
  * has them from the `load_skill` tool result. This keeps the system
8
8
  * prompt identical across the entire session, preserving prompt caching.
9
9
  *
10
- * Called at graph resolution time so the section is part of the turn
11
- * agent's static instructions rather than injected per-step.
10
+ * Authored skills call this at graph resolution time so the section is
11
+ * part of the turn agent's static instructions. Hook-contributed skills
12
+ * reuse the same formatter for durable history announcements.
12
13
  */
13
14
  export function formatAvailableSkillsSection(skills) {
14
15
  if (skills.length === 0) {
@@ -84,7 +84,11 @@ export async function stageAttachmentsToSandbox(message) {
84
84
  const adapter = container.get(ChannelKey);
85
85
  const adapterCtx = adapter
86
86
  ? buildAdapterContext(adapter, container)
87
- : { ctx: container, state: {}, session: buildSessionHandle(container) };
87
+ : {
88
+ ctx: container,
89
+ state: {},
90
+ session: buildSessionHandle(container),
91
+ };
88
92
  return stageAttachmentsForAdapter(message, sandbox, adapterCtx);
89
93
  }
90
94
  /**
@@ -299,10 +299,18 @@ export function createToolLoopHarness(config) {
299
299
  // bloating every future step boundary.
300
300
  const hydratedMessages = await hydrateSandboxAttachments(messages);
301
301
  const ephemeralContext = stepInput.input?.modelContext;
302
- // AI SDK rejects role:"system" in `messages` — route those entries
303
- // to `instructions` instead.
302
+ // AI SDK rejects role:"system" in `messages` — route durable history
303
+ // and ephemeral context system entries to `instructions` instead.
304
304
  const systemMessages = [];
305
305
  const nonSystemMessages = [];
306
+ for (const entry of hydratedMessages) {
307
+ if (entry.role === "system") {
308
+ systemMessages.push(entry);
309
+ }
310
+ else {
311
+ nonSystemMessages.push(entry);
312
+ }
313
+ }
306
314
  if (ephemeralContext !== undefined) {
307
315
  for (const entry of ephemeralContext) {
308
316
  if (entry.role === "system") {
@@ -334,7 +342,7 @@ export function createToolLoopHarness(config) {
334
342
  tools: effectiveTools,
335
343
  };
336
344
  const agent = new ToolLoopAgent(agentSettings);
337
- const modelMessages = nonSystemMessages.length > 0 ? [...hydratedMessages, ...nonSystemMessages] : hydratedMessages;
345
+ const modelMessages = nonSystemMessages;
338
346
  let result;
339
347
  const executeModelCall = async () => {
340
348
  if (emit) {
@@ -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.20.1";
9
+ const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.22.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",
@@ -118,7 +118,7 @@ export function normalizeInstructionsDefinition(value, message) {
118
118
  */
119
119
  export function normalizeSkillDefinition(value, message) {
120
120
  const record = expectObjectRecord(value, message);
121
- expectOnlyKnownKeys(record, ["description", "license", "markdown", "metadata"], message);
121
+ expectOnlyKnownKeys(record, ["description", "files", "license", "markdown", "metadata"], message);
122
122
  const definition = {
123
123
  description: expectString(record.description, message),
124
124
  markdown: expectString(record.markdown, message),
@@ -131,8 +131,23 @@ export function normalizeSkillDefinition(value, message) {
131
131
  if (metadata !== undefined) {
132
132
  definition.metadata = metadata;
133
133
  }
134
+ if (record.files !== undefined) {
135
+ definition.files = normalizeSkillFiles(record.files, message);
136
+ }
134
137
  return definition;
135
138
  }
139
+ function normalizeSkillFiles(value, message) {
140
+ const files = expectObjectRecord(value, message);
141
+ const normalized = {};
142
+ for (const [filePath, content] of Object.entries(files)) {
143
+ if (typeof content === "string" || content instanceof Uint8Array) {
144
+ normalized[filePath] = content;
145
+ continue;
146
+ }
147
+ throw new Error(`${message} Expected skill file "${filePath}" to be a string or Uint8Array.`);
148
+ }
149
+ return normalized;
150
+ }
136
151
  /**
137
152
  * Normalizes one authored schedule definition into the canonical internal
138
153
  * shape.
@@ -1,8 +1,8 @@
1
1
  import type { H3Event } from "nitro";
2
- export declare const WORKFLOW_RUNS_API_ROUTE_PATH = "/api/runs";
3
- export declare const WORKFLOW_RUN_ROUTE_PATTERN = "/api/runs/:runId";
4
- export declare const WORKFLOW_RUN_STEPS_ROUTE_PATTERN = "/api/runs/:runId/steps";
5
- export declare const WORKFLOW_RUN_EVENTS_ROUTE_PATTERN = "/api/runs/:runId/events";
2
+ export declare const WORKFLOW_RUNS_API_ROUTE_PATH = "/ash/v1/runs";
3
+ export declare const WORKFLOW_RUN_ROUTE_PATTERN = "/ash/v1/runs/:runId";
4
+ export declare const WORKFLOW_RUN_STEPS_ROUTE_PATTERN = "/ash/v1/runs/:runId/steps";
5
+ export declare const WORKFLOW_RUN_EVENTS_ROUTE_PATTERN = "/ash/v1/runs/:runId/events";
6
6
  declare const WORKFLOW_RUN_STATUSES: readonly ["pending", "running", "completed", "failed", "cancelled"];
7
7
  export type WorkflowRunStatus = (typeof WORKFLOW_RUN_STATUSES)[number];
8
8
  export interface PaginationQuery {
@@ -1,7 +1,8 @@
1
- export const WORKFLOW_RUNS_API_ROUTE_PATH = "/api/runs";
2
- export const WORKFLOW_RUN_ROUTE_PATTERN = "/api/runs/:runId";
3
- export const WORKFLOW_RUN_STEPS_ROUTE_PATTERN = "/api/runs/:runId/steps";
4
- export const WORKFLOW_RUN_EVENTS_ROUTE_PATTERN = "/api/runs/:runId/events";
1
+ import { ASH_ROUTE_PREFIX } from "#protocol/routes.js";
2
+ export const WORKFLOW_RUNS_API_ROUTE_PATH = `${ASH_ROUTE_PREFIX}/runs`;
3
+ export const WORKFLOW_RUN_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/runs/:runId`;
4
+ export const WORKFLOW_RUN_STEPS_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/runs/:runId/steps`;
5
+ export const WORKFLOW_RUN_EVENTS_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/runs/:runId/events`;
5
6
  const MAX_PAGE_LIMIT = 1000;
6
7
  const WORKFLOW_RUN_STATUSES = ["pending", "running", "completed", "failed", "cancelled"];
7
8
  function createWorkflowApiResponse(payload, status) {
@@ -119,7 +120,7 @@ export function resolveRunIdFromNitroEvent(event) {
119
120
  return routeRunId;
120
121
  }
121
122
  const path = new URL(event.req.url).pathname;
122
- const pathMatch = path.match(/^\/api\/runs\/([^/]+)(?:\/.*)?$/);
123
+ const pathMatch = path.match(/^\/ash\/v1\/runs\/([^/]+)(?:\/.*)?$/);
123
124
  if (pathMatch === null) {
124
125
  return null;
125
126
  }
@@ -7,7 +7,7 @@ export declare function handleWorkflowRunEventsRequest(input: {
7
7
  readonly runId: string;
8
8
  }): Promise<Response>;
9
9
  /**
10
- * Nitro route for `GET /api/runs/:runId/events`.
10
+ * Nitro route for `GET /ash/v1/runs/:runId/events`.
11
11
  */
12
12
  declare const _default: (event: H3Event) => Promise<Response>;
13
13
  export default _default;
@@ -27,7 +27,7 @@ export async function handleWorkflowRunEventsRequest(input) {
27
27
  }
28
28
  }
29
29
  /**
30
- * Nitro route for `GET /api/runs/:runId/events`.
30
+ * Nitro route for `GET /ash/v1/runs/:runId/events`.
31
31
  */
32
32
  export default async (event) => {
33
33
  const runId = resolveRunIdFromNitroEvent(event);
@@ -7,7 +7,7 @@ export declare function handleWorkflowRunStepsRequest(input: {
7
7
  readonly runId: string;
8
8
  }): Promise<Response>;
9
9
  /**
10
- * Nitro route for `GET /api/runs/:runId/steps`.
10
+ * Nitro route for `GET /ash/v1/runs/:runId/steps`.
11
11
  */
12
12
  declare const _default: (event: H3Event) => Promise<Response>;
13
13
  export default _default;
@@ -27,7 +27,7 @@ export async function handleWorkflowRunStepsRequest(input) {
27
27
  }
28
28
  }
29
29
  /**
30
- * Nitro route for `GET /api/runs/:runId/steps`.
30
+ * Nitro route for `GET /ash/v1/runs/:runId/steps`.
31
31
  */
32
32
  export default async (event) => {
33
33
  const runId = resolveRunIdFromNitroEvent(event);
@@ -6,7 +6,7 @@ export declare function handleWorkflowRunRequest(input: {
6
6
  runId: string;
7
7
  }): Promise<Response>;
8
8
  /**
9
- * Nitro route for `GET /api/runs/:runId`.
9
+ * Nitro route for `GET /ash/v1/runs/:runId`.
10
10
  */
11
11
  declare const _default: (event: H3Event) => Promise<Response>;
12
12
  export default _default;
@@ -21,7 +21,7 @@ export async function handleWorkflowRunRequest(input) {
21
21
  }
22
22
  }
23
23
  /**
24
- * Nitro route for `GET /api/runs/:runId`.
24
+ * Nitro route for `GET /ash/v1/runs/:runId`.
25
25
  */
26
26
  export default async (event) => {
27
27
  const runId = resolveRunIdFromNitroEvent(event);
@@ -4,7 +4,7 @@ import type { H3Event } from "nitro";
4
4
  */
5
5
  export declare function handleWorkflowRunsRequest(request: Request): Promise<Response>;
6
6
  /**
7
- * Nitro route for `GET /api/runs`.
7
+ * Nitro route for `GET /ash/v1/runs`.
8
8
  */
9
9
  declare const _default: (event: H3Event) => Promise<Response>;
10
10
  export default _default;
@@ -21,7 +21,7 @@ export async function handleWorkflowRunsRequest(request) {
21
21
  }
22
22
  }
23
23
  /**
24
- * Nitro route for `GET /api/runs`.
24
+ * Nitro route for `GET /ash/v1/runs`.
25
25
  */
26
26
  export default async (event) => {
27
27
  return await handleWorkflowRunsRequest(event.req);
@@ -11,6 +11,7 @@ export const WORKFLOW_STEP_EXTERNAL_PACKAGES = ["@mongodb-js/zstd", "node-liblzm
11
11
  * Nitro performs the final bundling/tracing pass for hosted output.
12
12
  */
13
13
  export const WORKFLOW_BUILDER_DEFERRED_PACKAGES = ["@chat-adapter/slack", "chat"];
14
+ const WORKFLOW_FUNCTION_NODE_OPTIONS = "--experimental-require-module";
14
15
  /**
15
16
  * Builds the environment block every generated Vercel workflow function needs.
16
17
  */
@@ -19,6 +20,7 @@ export function createWorkflowFunctionEnvironment(environment) {
19
20
  if (isRecord(environment)) {
20
21
  Object.assign(nextEnvironment, environment);
21
22
  }
23
+ nextEnvironment.NODE_OPTIONS = WORKFLOW_FUNCTION_NODE_OPTIONS;
22
24
  return nextEnvironment;
23
25
  }
24
26
  function isRecord(value) {
@@ -1,6 +1,7 @@
1
1
  import type { ModelMessage } from "ai";
2
2
  import type { ContextAccessor } from "../../context/key.js";
3
3
  import type { HandleMessageStreamEvent } from "../../protocol/message.js";
4
+ import type { NamedSkillDefinition } from "./skill.js";
4
5
  /**
5
6
  * Context passed to every hook handler.
6
7
  *
@@ -45,15 +46,22 @@ export interface LifecycleHookInput {
45
46
  * `lifecycle.turn`).
46
47
  *
47
48
  * `modelContext` messages are appended to the next model call's message
48
- * list and never written to durable session history. Each lifecycle key
49
- * runs in registry order; contributions are concatenated session-then-
50
- * turn before the harness's first model call.
49
+ * list and never written to durable session history. `skills` are
50
+ * materialized into the live sandbox and announced through durable
51
+ * system history. Each lifecycle key runs in registry order;
52
+ * contributions are concatenated session-then-turn before the harness's
53
+ * first model call.
51
54
  *
52
55
  * Returning `void` (or `undefined`) means "no opinion" — the turn
53
56
  * proceeds with no additional model context from this hook.
54
57
  */
55
58
  export interface LifecycleHookResult {
56
59
  readonly modelContext?: readonly ModelMessage[];
60
+ /**
61
+ * Skill packages to materialize into the live durable sandbox before the
62
+ * next model call. Authored skill names cannot be overwritten.
63
+ */
64
+ readonly skills?: readonly NamedSkillDefinition[];
57
65
  }
58
66
  /**
59
67
  * Lifecycle hook signature shared by `lifecycle.session` and
@@ -1,4 +1,5 @@
1
- import type { InternalSkillDefinition } from "#shared/skill-definition.js";
1
+ import type { NamedSkillDefinition, SkillFileContent, SkillPackageDefinition } from "#shared/skill-definition.js";
2
+ export type { NamedSkillDefinition, SkillFileContent, SkillPackageDefinition };
2
3
  /**
3
4
  * Public definition for skill instructions lowered from authored markdown
4
5
  * or authored directly in TypeScript via {@link defineSkill}.
@@ -6,9 +7,9 @@ import type { InternalSkillDefinition } from "#shared/skill-definition.js";
6
7
  * Identity is derived from the file path under `agent/skills/`; authored
7
8
  * definitions do not carry a `name` field.
8
9
  */
9
- export type SkillDefinition = Omit<InternalSkillDefinition, "name">;
10
+ export type SkillDefinition = SkillPackageDefinition;
10
11
  /**
11
12
  * Defines a skill in TypeScript using the same shape discovery produces from
12
- * markdown.
13
+ * markdown, with optional package-relative sibling files.
13
14
  */
14
15
  export declare function defineSkill<TSkill extends SkillDefinition>(definition: TSkill): TSkill;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Defines a skill in TypeScript using the same shape discovery produces from
3
- * markdown.
3
+ * markdown, with optional package-relative sibling files.
4
4
  */
5
5
  export function defineSkill(definition) {
6
6
  return definition;
@@ -0,0 +1,74 @@
1
+ import type { NextConfig } from "next";
2
+ /**
3
+ * Private route namespace used when a Vercel deployment hosts Ash as a
4
+ * separate experimental service behind the Next.js app.
5
+ */
6
+ export declare const ASH_NEXT_SERVICE_PREFIX = "/_ash_internal/ash";
7
+ type ArrayElement<T> = T extends readonly (infer TElement)[] ? TElement : never;
8
+ type NextRewrites = Awaited<ReturnType<NonNullable<NextConfig["rewrites"]>>>;
9
+ /**
10
+ * Next.js rewrite rule used by {@link withAsh}.
11
+ */
12
+ export type AshNextRewriteRule = ArrayElement<NextRewrites>;
13
+ /**
14
+ * Values accepted by Next.js `rewrites`.
15
+ */
16
+ export type AshNextRewrites = NextRewrites;
17
+ /**
18
+ * Sectioned Next.js rewrite rules.
19
+ */
20
+ export type AshNextRewriteSections = Extract<NextRewrites, {
21
+ readonly afterFiles?: AshNextRewriteRule[];
22
+ readonly beforeFiles?: AshNextRewriteRule[];
23
+ readonly fallback?: AshNextRewriteRule[];
24
+ }>;
25
+ /**
26
+ * Next.js config object accepted by {@link withAsh}.
27
+ */
28
+ export type AshNextConfig = NextConfig;
29
+ /**
30
+ * Minimal structural shape for a Next.js config function.
31
+ */
32
+ export type AshNextConfigFunction<TConfig extends AshNextConfig = AshNextConfig> = (phase: string, context: {
33
+ readonly defaultConfig: TConfig;
34
+ }) => TConfig | Promise<TConfig>;
35
+ /**
36
+ * Next.js config input accepted by {@link withAsh}.
37
+ */
38
+ export type AshNextConfigInput<TConfig extends AshNextConfig = AshNextConfig> = AshNextConfigFunction<TConfig> | TConfig;
39
+ /**
40
+ * Options for {@link withAsh}.
41
+ */
42
+ export interface WithAshOptions {
43
+ /**
44
+ * Path to the Ash application root, relative to `process.cwd()` unless
45
+ * absolute. Defaults to the Next.js app root.
46
+ */
47
+ readonly ashRoot?: string;
48
+ /**
49
+ * Set to `false` to skip creating or updating `vercel.json`.
50
+ *
51
+ * By default `withAsh` ensures `vercel.json` contains `experimentalServices`
52
+ * for the Next.js app and Ash app.
53
+ */
54
+ readonly configureVercelJson?: boolean;
55
+ /**
56
+ * Private Vercel service prefix for the Ash deployment. This must match the
57
+ * Ash service's `routePrefix` in `vercel.json`.
58
+ */
59
+ readonly servicePrefix?: string;
60
+ }
61
+ /**
62
+ * Wraps a Next.js config so same-origin Ash endpoints proxy to a separate Ash
63
+ * service.
64
+ *
65
+ * In development, `withAsh` starts `ash dev --no-repl --port 0` for the Ash
66
+ * app and rewrites Ash protocol endpoints to that local URL. In production on
67
+ * Vercel, it rewrites to the private Ash service prefix configured in
68
+ * `vercel.json` using the deployment origin from `VERCEL_URL`. Outside Vercel
69
+ * production, it serves an existing `.output/server/index.mjs` Ash build on a
70
+ * stable local port when present; otherwise set `ASH_NEXT_PRODUCTION_ORIGIN` to
71
+ * the origin that serves the Ash service namespace.
72
+ */
73
+ export declare function withAsh<TConfig extends AshNextConfig>(configOrFunction: AshNextConfigInput<TConfig>, options?: WithAshOptions): AshNextConfigFunction<TConfig>;
74
+ export {};