experimental-ash 0.7.5 → 0.8.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 (79) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +79 -56
  3. package/dist/docs/public/channels/README.md +33 -23
  4. package/dist/docs/public/channels/attachments.md +42 -29
  5. package/dist/src/channel/adapter.d.ts +21 -28
  6. package/dist/src/channel/compiled-channel.d.ts +1 -1
  7. package/dist/src/channel/http.d.ts +29 -0
  8. package/dist/src/channel/http.js +30 -0
  9. package/dist/src/channel/schedule.d.ts +20 -0
  10. package/dist/src/channel/schedule.js +22 -1
  11. package/dist/src/channel/send.d.ts +1 -1
  12. package/dist/src/channel/send.js +22 -1
  13. package/dist/src/channel/types.d.ts +1 -1
  14. package/dist/src/chunks/{client-DBMG7iuf.js → client-BeZ_W7vl.js} +2 -2
  15. package/dist/src/chunks/{dev-authored-source-watcher-BcN7BUDE.js → dev-authored-source-watcher-BFC_yNcP.js} +1 -1
  16. package/dist/src/chunks/host-DMccRKcz.js +22 -0
  17. package/dist/src/chunks/{paths-BYIdCNw9.js → paths-B-aiDznc.js} +26 -26
  18. package/dist/src/chunks/{prewarm-DXhyk7i9.js → prewarm-CCbReSNm.js} +1 -1
  19. package/dist/src/chunks/types-MZUhN0Zy.js +1 -0
  20. package/dist/src/cli/commands/info.js +1 -1
  21. package/dist/src/cli/dev/environment.d.ts +3 -2
  22. package/dist/src/cli/dev/repl.js +1 -1
  23. package/dist/src/cli/run.js +1 -1
  24. package/dist/src/compiled/.vendor-stamp.json +1 -1
  25. package/dist/src/compiled/@vercel/sandbox/index.d.ts +37 -3
  26. package/dist/src/evals/cli/eval.js +1 -1
  27. package/dist/src/execution/sandbox/bindings/local.d.ts +0 -2
  28. package/dist/src/execution/sandbox/bindings/local.js +1 -20
  29. package/dist/src/execution/sandbox/bindings/vercel.d.ts +2 -2
  30. package/dist/src/execution/sandbox/bindings/vercel.js +1 -12
  31. package/dist/src/harness/attachment-staging.js +54 -50
  32. package/dist/src/harness/emission.d.ts +14 -1
  33. package/dist/src/harness/emission.js +15 -2
  34. package/dist/src/harness/tool-loop.js +58 -15
  35. package/dist/src/internal/application/package.js +1 -1
  36. package/dist/src/internal/attachments/url-refs.d.ts +14 -0
  37. package/dist/src/internal/attachments/url-refs.js +20 -0
  38. package/dist/src/internal/nitro/host/configure-nitro-routes.d.ts +0 -1
  39. package/dist/src/internal/nitro/host/configure-nitro-routes.js +24 -17
  40. package/dist/src/internal/nitro/host/create-application-nitro.js +1 -16
  41. package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response.d.ts +87 -0
  42. package/dist/src/internal/nitro/routes/{home-page/build-home-page-response.js → agent-info/build-agent-info-response.js} +6 -6
  43. package/dist/src/internal/nitro/routes/{home-page/load-home-page-data.d.ts → agent-info/load-agent-info-data.d.ts} +8 -8
  44. package/dist/src/internal/nitro/routes/{home-page/load-home-page-data.js → agent-info/load-agent-info-data.js} +7 -8
  45. package/dist/src/internal/nitro/routes/index.d.ts +10 -5
  46. package/dist/src/internal/nitro/routes/index.js +225 -18
  47. package/dist/src/internal/nitro/routes/info.d.ts +14 -0
  48. package/dist/src/internal/nitro/routes/info.js +50 -0
  49. package/dist/src/protocol/routes.d.ts +8 -6
  50. package/dist/src/protocol/routes.js +8 -6
  51. package/dist/src/public/channels/ash.js +1 -6
  52. package/dist/src/public/channels/index.d.ts +1 -1
  53. package/dist/src/public/channels/slack/attachments.d.ts +14 -18
  54. package/dist/src/public/channels/slack/attachments.js +30 -36
  55. package/dist/src/public/channels/slack/index.d.ts +0 -1
  56. package/dist/src/public/channels/slack/slackChannel.js +3 -3
  57. package/dist/src/public/definitions/defineChannel.d.ts +9 -7
  58. package/dist/src/public/definitions/defineChannel.js +5 -11
  59. package/dist/src/public/definitions/sandbox.d.ts +3 -3
  60. package/dist/src/public/sandbox/backends/vercel.d.ts +2 -2
  61. package/dist/src/public/sandbox/index.d.ts +2 -2
  62. package/dist/src/public/sandbox/vercel-sandbox.d.ts +11 -10
  63. package/dist/src/runtime/channels/registry.js +9 -3
  64. package/dist/src/runtime/resolve-channel.js +2 -1
  65. package/dist/src/shared/sandbox-backend.d.ts +4 -4
  66. package/dist/src/shared/sandbox-definition.d.ts +6 -36
  67. package/package.json +1 -1
  68. package/dist/src/chunks/host-33-Sb6vq.js +0 -22
  69. package/dist/src/chunks/types-D9Uv7nU4.js +0 -1
  70. package/dist/src/internal/nitro/host/load-home-page-web-assets.d.ts +0 -12
  71. package/dist/src/internal/nitro/host/load-home-page-web-assets.js +0 -34
  72. package/dist/src/internal/nitro/routes/home-page/build-home-page-response.d.ts +0 -87
  73. package/dist/src/internal/nitro/routes/home.d.ts +0 -6
  74. package/dist/src/internal/nitro/routes/home.js +0 -21
  75. package/dist/src/internal/nitro/routes/web-ui/assets/index-BQa8fbHJ.js +0 -11
  76. package/dist/src/internal/nitro/routes/web-ui/assets/style-Kqb6YxTP.css +0 -2
  77. package/dist/src/internal/nitro/routes/web-ui/index.html +0 -17
  78. package/dist/src/public/sandboxes/vercel-sandbox.d.ts +0 -41
  79. package/dist/src/public/sandboxes/vercel-sandbox.js +0 -1
@@ -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.7.5";
9
+ const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.8.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",
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Custom URL scheme for serializing `URL` objects in `FilePart.data`
3
+ * across the queue boundary.
4
+ *
5
+ * Route handlers place `new URL("https://...")` on `FilePart.data`.
6
+ * Before the message crosses the queue, `send()` rewrites these as
7
+ * `ash-url:https://...` strings. After the queue, the staging pipeline
8
+ * reconstitutes them back to `URL` objects so `instanceof URL` checks
9
+ * work reliably.
10
+ */
11
+ export declare const ASH_URL_SCHEME = "ash-url:";
12
+ export declare function serializeUrlFilePart(url: URL): string;
13
+ export declare function isSerializedUrlFilePart(data: unknown): data is string;
14
+ export declare function deserializeUrlFilePart(data: string): URL;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Custom URL scheme for serializing `URL` objects in `FilePart.data`
3
+ * across the queue boundary.
4
+ *
5
+ * Route handlers place `new URL("https://...")` on `FilePart.data`.
6
+ * Before the message crosses the queue, `send()` rewrites these as
7
+ * `ash-url:https://...` strings. After the queue, the staging pipeline
8
+ * reconstitutes them back to `URL` objects so `instanceof URL` checks
9
+ * work reliably.
10
+ */
11
+ export const ASH_URL_SCHEME = "ash-url:";
12
+ export function serializeUrlFilePart(url) {
13
+ return `${ASH_URL_SCHEME}${url.href}`;
14
+ }
15
+ export function isSerializedUrlFilePart(data) {
16
+ return typeof data === "string" && data.startsWith(ASH_URL_SCHEME);
17
+ }
18
+ export function deserializeUrlFilePart(data) {
19
+ return new URL(data.slice(ASH_URL_SCHEME.length));
20
+ }
@@ -5,6 +5,5 @@ import type { NitroBuildSurface, PreparedApplicationHost } from "#internal/nitro
5
5
  * SDK endpoints into one Nitro host instance.
6
6
  */
7
7
  export declare function configureNitroRoutes(nitro: Nitro, preparedHost: PreparedApplicationHost, input: {
8
- homePageHtml: string;
9
8
  surface: NitroBuildSurface;
10
9
  }): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { dirname, join, relative } from "node:path";
3
- import { ASH_HEALTH_ROUTE_PATH, ASH_HOME_ROUTE_PATH } from "#protocol/routes.js";
3
+ import { ASH_HEALTH_ROUTE_PATH, ASH_INFO_ROUTE_PATH } from "#protocol/routes.js";
4
4
  import { normalizeEsmImportSpecifier, stringifyEsmImportSpecifier, } from "#internal/application/import-specifier.js";
5
5
  import { resolvePackageRoot, resolvePackageSourceFilePath, resolveWorkflowModulePath, } from "#internal/application/package.js";
6
6
  import { WorkflowBundleBuilder } from "#internal/workflow-bundle/builder.js";
@@ -118,7 +118,12 @@ function buildWorkflowFileHandlerSource(input) {
118
118
  }
119
119
  /**
120
120
  * Registers a virtual Nitro handler for a framework route that needs
121
- * build-time config values (e.g. homePageHtml, appRoot) baked in.
121
+ * build-time config values (e.g. `appRoot`) baked in.
122
+ *
123
+ * The generated handler is invoked by Nitro with `(event)` and forwards
124
+ * `event.req` as the trailing argument to `${handlerExport}`, so the
125
+ * handler can run request-time auth, header inspection, etc. on top of
126
+ * its baked-in config.
122
127
  */
123
128
  function addFrameworkVirtualHandler(nitro, input) {
124
129
  const virtualId = `#ash-route${input.route}`;
@@ -130,7 +135,7 @@ function addFrameworkVirtualHandler(nitro, input) {
130
135
  });
131
136
  nitro.options.virtual[virtualId] = [
132
137
  `import { ${input.handlerExport} } from ${modulePath};`,
133
- `export default () => ${input.handlerExport}(${input.args});`,
138
+ `export default async (event) => ${input.handlerExport}(${input.args}, event.req);`,
134
139
  ].join("\n");
135
140
  }
136
141
  /**
@@ -183,28 +188,30 @@ export async function configureNitroRoutes(nitro, preparedHost, input) {
183
188
  dev: nitro.options.dev,
184
189
  });
185
190
  if (includesApplicationRoutes(input.surface)) {
186
- // Framework routes that need build-time config are registered as virtual
187
- // handlers so the values are baked in no global runtime config store.
188
- addFrameworkVirtualHandler(nitro, {
189
- args: JSON.stringify({ homePageHtml: input.homePageHtml }),
190
- handlerExport: "handleHomePageRequest",
191
+ // Framework routes that need no build-time config use physical handler
192
+ // files directly. The home page is a fully static, build-time-baked HTML
193
+ // string with no agent metadata, so it does not need to round-trip
194
+ // through the virtual-handler args plumbing.
195
+ registerHandler(nitro, {
196
+ handlerPath: resolvePackageSourceFilePath("src/internal/nitro/routes/index.ts"),
191
197
  method: "GET",
192
- modulePath: resolvePackageSourceFilePath("src/internal/nitro/routes/index.ts"),
193
198
  route: "/",
194
199
  });
195
- addFrameworkVirtualHandler(nitro, {
196
- args: JSON.stringify({ appRoot: artifactsConfig.appRoot }),
197
- handlerExport: "handleHomePageDataRequest",
198
- method: "GET",
199
- modulePath: resolvePackageSourceFilePath("src/internal/nitro/routes/home.ts"),
200
- route: ASH_HOME_ROUTE_PATH,
201
- });
202
- // Framework routes that need no config use physical handler files directly.
203
200
  registerHandler(nitro, {
204
201
  handlerPath: resolvePackageSourceFilePath("src/internal/nitro/routes/health.ts"),
205
202
  method: "GET",
206
203
  route: ASH_HEALTH_ROUTE_PATH,
207
204
  });
205
+ // The agent info endpoint needs `appRoot` baked at build time (used by
206
+ // the disk-source fallback in dev) and runs request-time auth, so it
207
+ // stays a virtual handler.
208
+ addFrameworkVirtualHandler(nitro, {
209
+ args: JSON.stringify({ appRoot: artifactsConfig.appRoot }),
210
+ handlerExport: "handleAgentInfoRequest",
211
+ method: "GET",
212
+ modulePath: resolvePackageSourceFilePath("src/internal/nitro/routes/info.ts"),
213
+ route: ASH_INFO_ROUTE_PATH,
214
+ });
208
215
  registerHandler(nitro, {
209
216
  handlerPath: resolvePackageSourceFilePath("src/internal/nitro/routes/workflow-runs.ts"),
210
217
  method: "GET",
@@ -3,7 +3,6 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { createNitro } from "nitro/builder";
5
5
  import { ASH_PACKAGE_NAME } from "#package-name.js";
6
- import { ASH_HOME_PAGE_UI_ROUTE_PREFIX } from "#protocol/routes.js";
7
6
  import { resolvePackageSourceDirectoryPath, resolvePackageSourceFilePath, resolveWorkflowModulePath, } from "#internal/application/package.js";
8
7
  import { prepareAshVersionedCacheDirectory, writeAshVersionedCacheMetadata, } from "#internal/application/cache-metadata.js";
9
8
  import { resolveNitroBuildDirectory } from "#internal/application/paths.js";
@@ -11,7 +10,6 @@ import { createNitroArtifactsConfig, } from "#internal/nitro/host/artifacts-conf
11
10
  import { createCompiledSandboxBackendPrunePlugin } from "#internal/nitro/host/compiled-sandbox-backend-prune-plugin.js";
12
11
  import { configureNitroRoutes } from "#internal/nitro/host/configure-nitro-routes.js";
13
12
  import { applyAshCronHandlerRoute } from "#internal/nitro/host/cron-handler-route.js";
14
- import { loadHomePageWebAssets, } from "#internal/nitro/host/load-home-page-web-assets.js";
15
13
  import { createNitroBundlerConfig } from "#internal/nitro/host/nitro-bundler-config.js";
16
14
  import { addNitroRoutingImportSpecifierPlugin } from "#internal/nitro/host/nitro-routing-import-specifier-plugin.js";
17
15
  import { registerScheduleTaskHandlers } from "#internal/nitro/host/schedule-task-routes.js";
@@ -371,7 +369,6 @@ function patchWorkflowTransformExcludePath(nitro, workflowBuildDir) {
371
369
  */
372
370
  export async function createApplicationNitro(preparedHost, dev, options = {}) {
373
371
  const surface = options.surface ?? "all";
374
- const homePageWebAssets = await loadHomePageWebAssets();
375
372
  // In dev mode, schedules are skipped unless explicitly enabled via
376
373
  // `ash dev --schedules`. Production builds always register schedules.
377
374
  const schedulesAllowed = !dev || options.schedules === true;
@@ -406,9 +403,7 @@ export async function createApplicationNitro(preparedHost, dev, options = {}) {
406
403
  },
407
404
  preset,
408
405
  plugins: nitroPlugins,
409
- publicAssets: includesApplicationSurface(surface)
410
- ? createHomePagePublicAssetEntries(homePageWebAssets)
411
- : [],
406
+ publicAssets: [],
412
407
  scanDirs: includesWorkflowStepRegistrations(surface)
413
408
  ? [resolvePackageSourceDirectoryPath("src/execution")]
414
409
  : undefined,
@@ -487,7 +482,6 @@ export async function createApplicationNitro(preparedHost, dev, options = {}) {
487
482
  });
488
483
  }
489
484
  await configureNitroRoutes(nitro, preparedHost, {
490
- homePageHtml: homePageWebAssets.homePageHtml,
491
485
  surface,
492
486
  });
493
487
  if (includesWorkflowStepRegistrations(surface)) {
@@ -495,12 +489,3 @@ export async function createApplicationNitro(preparedHost, dev, options = {}) {
495
489
  }
496
490
  return nitro;
497
491
  }
498
- function createHomePagePublicAssetEntries(homePageWebAssets) {
499
- return [
500
- {
501
- baseURL: ASH_HOME_PAGE_UI_ROUTE_PREFIX,
502
- dir: homePageWebAssets.assetsDirectoryPath,
503
- maxAge: 60 * 60 * 24 * 365,
504
- },
505
- ];
506
- }
@@ -0,0 +1,87 @@
1
+ import type { AgentInfoData } from "#internal/nitro/routes/agent-info/load-agent-info-data.js";
2
+ /**
3
+ * One summary count shown in the agent info overview.
4
+ */
5
+ export interface AgentInfoSummaryCount {
6
+ readonly label: string;
7
+ readonly value: number;
8
+ }
9
+ /**
10
+ * One source-backed row in the agent info tables.
11
+ */
12
+ export interface AgentInfoSourceRow {
13
+ readonly details: string;
14
+ readonly logicalPath: string;
15
+ readonly name: string;
16
+ readonly sourceKind: string;
17
+ }
18
+ /**
19
+ * One schedule row in the agent info schedule table.
20
+ */
21
+ export interface AgentInfoScheduleRow {
22
+ readonly cron: string;
23
+ readonly logicalPath: string;
24
+ readonly name: string;
25
+ readonly preview: string;
26
+ }
27
+ /**
28
+ * One subagent row in the agent info subagent table.
29
+ */
30
+ export interface AgentInfoSubagentRow {
31
+ readonly details: string;
32
+ readonly kind: "Local";
33
+ readonly logicalPath: string;
34
+ readonly name: string;
35
+ }
36
+ /**
37
+ * One endpoint row in the agent info endpoint table.
38
+ */
39
+ export interface AgentInfoEndpointRow {
40
+ readonly method: "GET" | "POST";
41
+ readonly name: string;
42
+ readonly notes: string;
43
+ readonly path: string;
44
+ }
45
+ /**
46
+ * Runtime overview section returned by the agent info route.
47
+ */
48
+ export interface AgentInfoOverview {
49
+ readonly configMetadata: Readonly<Record<string, string>>;
50
+ readonly configSource: string;
51
+ readonly discoveryErrors: number;
52
+ readonly discoveryWarnings: number;
53
+ readonly modelId: string;
54
+ readonly subagentCount: number;
55
+ readonly instructionsPromptSource: string;
56
+ }
57
+ /**
58
+ * Instructions prompt section returned by the agent info route.
59
+ */
60
+ export interface AgentInfoInstructionsPrompt {
61
+ readonly logicalPath: string;
62
+ readonly markdownPreview: string;
63
+ }
64
+ /**
65
+ * Full response payload returned by `GET /ash/v1/info`.
66
+ */
67
+ export interface AgentInfoResponse {
68
+ readonly agentId: string;
69
+ readonly endpointRows: readonly AgentInfoEndpointRow[];
70
+ readonly heroSummary: string;
71
+ readonly messageRequestExample: string;
72
+ readonly overview: AgentInfoOverview;
73
+ readonly pageDescription: string;
74
+ readonly pageTitle: string;
75
+ readonly sandbox: AgentInfoSourceRow | null;
76
+ readonly schedules: readonly AgentInfoScheduleRow[];
77
+ readonly skills: readonly AgentInfoSourceRow[];
78
+ readonly subagents: readonly AgentInfoSubagentRow[];
79
+ readonly summaryCounts: readonly AgentInfoSummaryCount[];
80
+ readonly instructions: AgentInfoSourceRow | null;
81
+ readonly instructionsPrompt: AgentInfoInstructionsPrompt;
82
+ readonly tools: readonly AgentInfoSourceRow[];
83
+ }
84
+ /**
85
+ * Builds the JSON payload served by `GET /ash/v1/info`.
86
+ */
87
+ export declare function buildAgentInfoResponse(data: AgentInfoData): AgentInfoResponse;
@@ -1,4 +1,4 @@
1
- import { ASH_CONTINUE_SESSION_ROUTE_PATTERN, ASH_CREATE_SESSION_ROUTE_PATH, ASH_HEALTH_ROUTE_PATH, ASH_HOME_ROUTE_PATH, ASH_MESSAGE_STREAM_ROUTE_PATTERN, } from "#protocol/routes.js";
1
+ import { ASH_CONTINUE_SESSION_ROUTE_PATTERN, ASH_CREATE_SESSION_ROUTE_PATH, ASH_HEALTH_ROUTE_PATH, ASH_INFO_ROUTE_PATH, ASH_MESSAGE_STREAM_ROUTE_PATTERN, } from "#protocol/routes.js";
2
2
  function pluralize(count, noun) {
3
3
  return `${count} ${noun}${count === 1 ? "" : "s"}`;
4
4
  }
@@ -90,9 +90,9 @@ function renderEndpointRows() {
90
90
  },
91
91
  {
92
92
  method: "GET",
93
- name: "Home data",
94
- notes: "Inspection JSON consumed by the static home page.",
95
- path: ASH_HOME_ROUTE_PATH,
93
+ name: "Agent info",
94
+ notes: "Inspection JSON for the deployed agent. Requires Vercel OIDC auth on Vercel.",
95
+ path: ASH_INFO_ROUTE_PATH,
96
96
  },
97
97
  {
98
98
  method: "POST",
@@ -115,9 +115,9 @@ function renderEndpointRows() {
115
115
  ];
116
116
  }
117
117
  /**
118
- * Builds the JSON payload served to Ash's static Nitro home page.
118
+ * Builds the JSON payload served by `GET /ash/v1/info`.
119
119
  */
120
- export function buildHomePageResponse(data) {
120
+ export function buildAgentInfoResponse(data) {
121
121
  const instructions = data.agent.instructions;
122
122
  const requestExample = `POST ${ASH_CREATE_SESSION_ROUTE_PATH}
123
123
  content-type: application/json
@@ -2,25 +2,25 @@ import type { CompiledAgentManifest, CompiledSubagentNode } from "#compiler/mani
2
2
  import { type RuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";
3
3
  import type { ResolvedAgent, ResolvedSandboxDefinition, ResolvedSchedule, ResolvedSkillDefinition, ResolvedInstructions } from "#runtime/types.js";
4
4
  /**
5
- * Runtime data needed to build the package-owned Nitro homepage.
5
+ * Runtime data needed to build the package-owned `GET /ash/v1/info`
6
+ * inspection JSON.
6
7
  */
7
- export interface HomePageData {
8
+ export interface AgentInfoData {
8
9
  readonly agent: ResolvedAgent;
9
10
  readonly manifest: CompiledAgentManifest;
10
11
  readonly schedules: readonly ResolvedSchedule[];
11
12
  }
12
13
  /**
13
- * Loads the resolved runtime data displayed on Ash's package-owned Nitro
14
- * homepage.
14
+ * Loads the resolved runtime data projected by `GET /ash/v1/info`.
15
15
  */
16
- export declare function loadHomePageData(input: {
16
+ export declare function loadAgentInfoData(input: {
17
17
  readonly compiledArtifactsSource: RuntimeCompiledArtifactsSource;
18
- }): Promise<HomePageData>;
18
+ }): Promise<AgentInfoData>;
19
19
  /**
20
20
  * Resolves the explicit runtime artifact source used by the package-owned
21
- * Nitro home page.
21
+ * `GET /ash/v1/info` handler.
22
22
  */
23
- export declare function resolveHomePageCompiledArtifactsSource(input?: {
23
+ export declare function resolveAgentInfoCompiledArtifactsSource(input?: {
24
24
  readonly appRoot?: string;
25
25
  }): RuntimeCompiledArtifactsSource;
26
26
  export type { CompiledAgentManifest, CompiledSubagentNode, ResolvedSandboxDefinition, ResolvedSchedule, ResolvedSkillDefinition, ResolvedInstructions, };
@@ -5,26 +5,25 @@ import { loadCompiledModuleMap } from "#runtime/loaders/module-map.js";
5
5
  import { resolveAgent } from "#runtime/resolve-agent.js";
6
6
  import { resolveSchedules } from "#runtime/schedules/resolve-schedule.js";
7
7
  /**
8
- * Loads the resolved runtime data displayed on Ash's package-owned Nitro
9
- * homepage.
8
+ * Loads the resolved runtime data projected by `GET /ash/v1/info`.
10
9
  */
11
- export async function loadHomePageData(input) {
12
- return await loadHomePageDataFromArtifacts(input.compiledArtifactsSource);
10
+ export async function loadAgentInfoData(input) {
11
+ return await loadAgentInfoDataFromArtifacts(input.compiledArtifactsSource);
13
12
  }
14
13
  /**
15
14
  * Resolves the explicit runtime artifact source used by the package-owned
16
- * Nitro home page.
15
+ * `GET /ash/v1/info` handler.
17
16
  */
18
- export function resolveHomePageCompiledArtifactsSource(input = {}) {
17
+ export function resolveAgentInfoCompiledArtifactsSource(input = {}) {
19
18
  if (readBundledCompiledArtifacts() !== null) {
20
19
  return createBundledRuntimeCompiledArtifactsSource();
21
20
  }
22
21
  if (input.appRoot !== undefined) {
23
22
  return createDiskRuntimeCompiledArtifactsSource(input.appRoot);
24
23
  }
25
- throw new Error("Ash home page runtime data requires bundled artifacts or an app root.");
24
+ throw new Error("Ash agent info runtime data requires bundled artifacts or an app root.");
26
25
  }
27
- async function loadHomePageDataFromArtifacts(compiledArtifactsSource) {
26
+ async function loadAgentInfoDataFromArtifacts(compiledArtifactsSource) {
28
27
  const [manifest, moduleMap] = await Promise.all([
29
28
  loadCompiledManifest({
30
29
  compiledArtifactsSource,
@@ -1,7 +1,12 @@
1
+ import type { H3Event } from "nitro";
1
2
  /**
2
- * Builds the package-owned home page response for one resolved application.
3
+ * Builds the barebones home page response for one request. Exposed
4
+ * for tests so callers can supply a real {@link Request}; production
5
+ * traffic flows through the Nitro {@link H3Event} default export.
3
6
  */
4
- export declare function handleHomePageRequest(input: {
5
- agentInfoOnlyMode?: boolean;
6
- homePageHtml: string;
7
- }): Promise<Response>;
7
+ export declare function buildHomePageResponse(request: Request): Response;
8
+ /**
9
+ * Nitro route handler for `GET /`. Adapts the Nitro event shape into
10
+ * {@link buildHomePageResponse}.
11
+ */
12
+ export default function handleHomePageRequest(event: H3Event): Response;
@@ -1,31 +1,238 @@
1
- function resolveAgentInfoOnlyMode(configuredAgentInfoOnlyMode) {
2
- if (typeof configuredAgentInfoOnlyMode === "boolean") {
3
- return configuredAgentInfoOnlyMode;
1
+ /**
2
+ * Public docs URL surfaced from the barebones home page. Kept in source
3
+ * so the deployment output is a fully static, build-time-baked HTML
4
+ * response that performs no runtime resolution.
5
+ */
6
+ const ASH_DOCS_URL = "https://ash.labs.vercel.dev/";
7
+ const DEPLOYMENT_URL_PLACEHOLDER = "{{DEPLOYMENT_URL}}";
8
+ /**
9
+ * Barebones HTML served at `GET /`.
10
+ *
11
+ * Reveals no information about the deployed agent — no name, no model,
12
+ * no instructions, no list of skills or schedules, no API endpoint paths.
13
+ * This is intentional: the root URL of a deployment is reachable by
14
+ * anyone on the public internet, and the deployment must not advertise
15
+ * its agent's configuration to unauthenticated callers. Inspection JSON
16
+ * (model id, instructions preview, skills, etc.) lives behind default
17
+ * Vercel OIDC auth on `/ash/v1/info`.
18
+ *
19
+ * The page also loads zero external assets — no fonts, no scripts, no
20
+ * images, no analytics beacons — so it cannot leak the deployment's
21
+ * origin to a third party simply by being visited.
22
+ *
23
+ * `{{DEPLOYMENT_URL}}` is the only request-time substitution: the page
24
+ * echoes the visitor's own request origin back into the `$ ash dev …`
25
+ * hint so they can copy-paste it without typing the URL by hand. We
26
+ * don't read any other request state.
27
+ */
28
+ const HOME_PAGE_HTML_TEMPLATE = `<!doctype html>
29
+ <html lang="en">
30
+ <head>
31
+ <meta charset="utf-8">
32
+ <meta name="viewport" content="width=device-width,initial-scale=1">
33
+ <meta name="robots" content="noindex">
34
+ <meta name="referrer" content="no-referrer">
35
+ <title>ash</title>
36
+ <style>
37
+ :root {
38
+ color-scheme: light dark;
39
+ --bg: #fff;
40
+ --fg: #0a0a0a;
41
+ --muted: #6b6b6b;
42
+ --faint: #999;
43
+ --border: rgba(0, 0, 0, 0.09);
44
+ --surface: rgba(0, 0, 0, 0.025);
45
+ --accent: #00c46a;
46
+ --accent-glow: rgba(0, 196, 106, 0.18);
47
+ --button-bg: #0a0a0a;
48
+ --button-fg: #fff;
49
+ }
50
+ @media (prefers-color-scheme: dark) {
51
+ :root {
52
+ --bg: #000;
53
+ --fg: #ededed;
54
+ --muted: #8f8f8f;
55
+ --faint: #666;
56
+ --border: rgba(255, 255, 255, 0.1);
57
+ --surface: rgba(255, 255, 255, 0.035);
58
+ --accent: #46d4a4;
59
+ --accent-glow: rgba(70, 212, 164, 0.22);
60
+ --button-bg: #fff;
61
+ --button-fg: #000;
4
62
  }
5
- return process.env.VERCEL === "1";
63
+ }
64
+ * { box-sizing: border-box; }
65
+ html, body { height: 100%; margin: 0; }
66
+ body {
67
+ background: var(--bg);
68
+ color: var(--fg);
69
+ font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
70
+ Roboto, "Helvetica Neue", Arial, sans-serif;
71
+ font-feature-settings: "cv11", "ss01";
72
+ font-size: 15px;
73
+ line-height: 1.55;
74
+ -webkit-font-smoothing: antialiased;
75
+ -moz-osx-font-smoothing: grayscale;
76
+ text-rendering: optimizeLegibility;
77
+ display: grid;
78
+ place-items: center;
79
+ padding: 2rem;
80
+ }
81
+ .mono {
82
+ font-family: ui-monospace, "SF Mono", "Menlo", "JetBrains Mono",
83
+ "Cascadia Code", Consolas, "Liberation Mono", monospace;
84
+ font-feature-settings: "zero", "ss01";
85
+ }
86
+ main {
87
+ width: 100%;
88
+ max-width: 32rem;
89
+ text-align: center;
90
+ }
91
+ .status {
92
+ display: inline-flex;
93
+ align-items: center;
94
+ gap: 0.5rem;
95
+ padding: 0.3125rem 0.75rem;
96
+ background: var(--surface);
97
+ border: 1px solid var(--border);
98
+ border-radius: 9999px;
99
+ font-size: 0.75rem;
100
+ color: var(--muted);
101
+ margin: 0 0 2rem;
102
+ }
103
+ .status-dot {
104
+ width: 6px;
105
+ height: 6px;
106
+ border-radius: 50%;
107
+ background: var(--accent);
108
+ box-shadow: 0 0 0 3px var(--accent-glow);
109
+ flex-shrink: 0;
110
+ }
111
+ h1 {
112
+ margin: 0 0 0.875rem;
113
+ font-size: clamp(2.5rem, 9vw, 3.25rem);
114
+ font-weight: 500;
115
+ letter-spacing: -0.05em;
116
+ line-height: 1;
117
+ }
118
+ .lede {
119
+ margin: 0 0 1.5rem;
120
+ color: var(--muted);
121
+ font-size: 0.9375rem;
122
+ }
123
+ .lede a {
124
+ color: var(--fg);
125
+ text-decoration: underline;
126
+ text-decoration-thickness: 1px;
127
+ text-underline-offset: 3px;
128
+ text-decoration-color: var(--border);
129
+ transition: text-decoration-color 0.15s ease;
130
+ white-space: nowrap;
131
+ }
132
+ .lede a:hover { text-decoration-color: var(--fg); }
133
+ .lede-arrow {
134
+ display: inline-block;
135
+ margin-left: 0.125rem;
136
+ transition: transform 0.15s ease;
137
+ }
138
+ .lede a:hover .lede-arrow { transform: translateX(2px); }
139
+ .terminal {
140
+ display: inline-flex;
141
+ align-items: center;
142
+ gap: 0.625rem;
143
+ width: 100%;
144
+ max-width: 28rem;
145
+ padding: 0.75rem 1rem;
146
+ background: var(--surface);
147
+ border: 1px solid var(--border);
148
+ border-radius: 0.5rem;
149
+ text-align: left;
150
+ font-size: 0.8125rem;
151
+ margin: 0 auto;
152
+ overflow-x: auto;
153
+ white-space: nowrap;
154
+ }
155
+ .terminal-prompt {
156
+ color: var(--faint);
157
+ user-select: none;
158
+ flex-shrink: 0;
159
+ }
160
+ .terminal-cmd { color: var(--fg); }
161
+ </style>
162
+ </head>
163
+ <body>
164
+ <main>
165
+ <span class="status mono">
166
+ <span class="status-dot" aria-hidden="true"></span>
167
+ running
168
+ </span>
169
+ <h1 class="mono">ash</h1>
170
+ <p class="lede">The agent is up and accepting messages. <a href="${ASH_DOCS_URL}">Read the docs<span class="lede-arrow" aria-hidden="true">&nbsp;&rarr;</span></a></p>
171
+ <div class="terminal mono" role="group" aria-label="Send a message from your terminal">
172
+ <span class="terminal-prompt" aria-hidden="true">$</span>
173
+ <span class="terminal-cmd">ash dev ${DEPLOYMENT_URL_PLACEHOLDER}</span>
174
+ </div>
175
+ </main>
176
+ </body>
177
+ </html>
178
+ `;
179
+ function escapeHtml(value) {
180
+ return value
181
+ .replaceAll("&", "&amp;")
182
+ .replaceAll("<", "&lt;")
183
+ .replaceAll(">", "&gt;")
184
+ .replaceAll('"', "&quot;")
185
+ .replaceAll("'", "&#39;");
6
186
  }
7
- function injectHomePageRuntimeFlags(input) {
8
- const runtimeFlagsScript = `<script>globalThis.__ASH_UI_AGENT_INFO_ONLY_MODE__=${input.agentInfoOnlyMode ? "true" : "false"};</script>`;
9
- if (input.homePageHtml.includes("</head>")) {
10
- return input.homePageHtml.replace("</head>", `${runtimeFlagsScript}</head>`);
187
+ function pickFirstForwardedValue(value) {
188
+ if (value === null) {
189
+ return undefined;
11
190
  }
12
- if (input.homePageHtml.includes("</body>")) {
13
- return input.homePageHtml.replace("</body>", `${runtimeFlagsScript}</body>`);
191
+ const first = value.split(",")[0]?.trim();
192
+ if (first === undefined || first.length === 0) {
193
+ return undefined;
14
194
  }
15
- return `${runtimeFlagsScript}${input.homePageHtml}`;
195
+ return first;
16
196
  }
17
197
  /**
18
- * Builds the package-owned home page response for one resolved application.
198
+ * Resolves the public origin a visitor is using to reach the deployment.
199
+ *
200
+ * Prefers the `x-forwarded-host` / `x-forwarded-proto` headers set by
201
+ * Vercel's edge so the rendered URL matches the address the visitor
202
+ * actually typed (including custom domains), then falls back to the
203
+ * `host` header, then to `request.url` for local `ash dev` runs that
204
+ * skip the proxy chain. Comma-separated forwarded values are split and
205
+ * the first hop is used — that is the public-facing entry, the rest are
206
+ * internal forwarder hostnames.
19
207
  */
20
- export async function handleHomePageRequest(input) {
21
- const homePageHtml = injectHomePageRuntimeFlags({
22
- agentInfoOnlyMode: resolveAgentInfoOnlyMode(input.agentInfoOnlyMode),
23
- homePageHtml: input.homePageHtml,
24
- });
25
- return new Response(homePageHtml, {
208
+ function resolveDeploymentUrl(request) {
209
+ const headers = request.headers;
210
+ const requestUrl = new URL(request.url);
211
+ const forwardedHost = pickFirstForwardedValue(headers.get("x-forwarded-host"));
212
+ const forwardedProto = pickFirstForwardedValue(headers.get("x-forwarded-proto"));
213
+ const host = forwardedHost ?? headers.get("host") ?? requestUrl.host;
214
+ const proto = forwardedProto ?? requestUrl.protocol.replace(/:$/, "");
215
+ return `${proto}://${host}`;
216
+ }
217
+ /**
218
+ * Builds the barebones home page response for one request. Exposed
219
+ * for tests so callers can supply a real {@link Request}; production
220
+ * traffic flows through the Nitro {@link H3Event} default export.
221
+ */
222
+ export function buildHomePageResponse(request) {
223
+ const deploymentUrl = resolveDeploymentUrl(request);
224
+ const html = HOME_PAGE_HTML_TEMPLATE.replace(DEPLOYMENT_URL_PLACEHOLDER, escapeHtml(deploymentUrl));
225
+ return new Response(html, {
26
226
  headers: {
27
227
  "cache-control": "no-store",
28
228
  "content-type": "text/html; charset=utf-8",
29
229
  },
30
230
  });
31
231
  }
232
+ /**
233
+ * Nitro route handler for `GET /`. Adapts the Nitro event shape into
234
+ * {@link buildHomePageResponse}.
235
+ */
236
+ export default function handleHomePageRequest(event) {
237
+ return buildHomePageResponse(event.req);
238
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Builds the package-owned JSON inspection response for the current
3
+ * agent (model id, instructions preview, skills, schedules, subagents,
4
+ * tools, sandbox, endpoint catalog).
5
+ *
6
+ * The route is gated by {@link resolveAgentInfoAuth} so the deployed URL
7
+ * does not expose agent metadata to anonymous callers. The same-project
8
+ * bypass on `vercelOidc()` keeps the route reachable to in-deployment
9
+ * callers (`apps/nextjs-ash`-style consumers) without any additional
10
+ * setup.
11
+ */
12
+ export declare function handleAgentInfoRequest(input: {
13
+ appRoot?: string;
14
+ }, request: Request): Promise<Response>;