@plasm_lang/vercel-agent 0.3.82 → 0.3.85

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 (47) hide show
  1. package/README.md +1 -1
  2. package/agent/hooks/trace-log.ts +1 -1
  3. package/agent/instructions.md +1 -1
  4. package/package.json +8 -4
  5. package/scripts/plasm-cli.ts +6 -0
  6. package/src/archive/paths.ts +4 -1
  7. package/src/authoring/slot-loader.ts +57 -4
  8. package/src/cli/build.ts +41 -2
  9. package/src/cli/compile-authored-slots.ts +79 -0
  10. package/src/cli/init-scaffold.ts +5 -6
  11. package/src/cli/init.ts +3 -1
  12. package/src/cli/nitro-dev.ts +23 -62
  13. package/src/gateway-model.ts +17 -6
  14. package/src/index.ts +5 -0
  15. package/src/instrumentation.ts +1 -1
  16. package/src/nitro/agent-summary.ts +133 -0
  17. package/src/nitro/build-application.ts +98 -0
  18. package/src/nitro/build-nitro-output.ts +14 -0
  19. package/src/nitro/copy-vercel-function-assets.ts +31 -0
  20. package/src/nitro/create-plasm-nitro.ts +111 -0
  21. package/src/nitro/patch-vercel-config.ts +37 -0
  22. package/src/nitro/paths.ts +30 -0
  23. package/src/nitro/prepare-host.ts +69 -0
  24. package/src/nitro/vercel-build-output-config.ts +18 -0
  25. package/src/nitro/workflow-directives.ts +43 -0
  26. package/src/nitro/write-nitro-entry.ts +87 -0
  27. package/src/package-version.ts +3 -11
  28. package/src/runtime/agent-runtime.ts +13 -12
  29. package/src/runtime/compaction.ts +1 -1
  30. package/src/server/plasm-handler.ts +22 -2
  31. package/src/telemetry/plasm-spans.ts +3 -3
  32. package/src/tools/descriptions.ts +29 -42
  33. package/src/tools/format.ts +2 -2
  34. package/src/tools/plasm-tools.ts +4 -4
  35. package/src/workflow/world-bootstrap.ts +12 -1
  36. package/templates/mcp-radar/agent/instrumentation.ts +18 -0
  37. package/templates/mcp-radar/lib/proof-store.ts +10 -3
  38. package/templates/mcp-radar/vercel.json +2 -13
  39. package/templates/scaffold/agent/instrumentation.ts +18 -0
  40. package/templates/scaffold/vercel.default.json +2 -2
  41. package/templates/scaffold/vercel.mcp-radar.json +2 -13
  42. package/templates/mcp-radar/api/[[...path]].ts +0 -23
  43. package/templates/mcp-radar/nitro.config.ts +0 -17
  44. package/templates/mcp-radar/routes/[...path].ts +0 -29
  45. package/templates/scaffold/api/[[...path]].ts +0 -23
  46. package/templates/scaffold/nitro.config.ts +0 -17
  47. package/templates/scaffold/routes/[...path].ts +0 -29
@@ -1,14 +1,6 @@
1
- import { readFileSync } from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
1
+ import plasmAgentPackage from "../package.json" with { type: "json" };
4
2
 
5
- let cachedVersion: string | undefined;
6
-
7
- /** Published semver from @plasm_lang/vercel-agent package.json. */
3
+ /** Published semver from @plasm_lang/vercel-agent package.json (inlined when Nitro bundles). */
8
4
  export function frameworkPackageVersion(): string {
9
- if (cachedVersion) return cachedVersion;
10
- const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11
- const raw = readFileSync(path.join(packageRoot, "package.json"), "utf8");
12
- cachedVersion = (JSON.parse(raw) as { version: string }).version;
13
- return cachedVersion;
5
+ return plasmAgentPackage.version;
14
6
  }
@@ -21,6 +21,7 @@ import { createArchiveStore } from "../archive/resolve-backend.js";
21
21
  import type { ProdArchiveStore } from "../archive/prod-archive-store.js";
22
22
  import { computeRunId } from "../archive/run-id.js";
23
23
  import { activeTraceId, plasmSpans } from "../telemetry/plasm-spans.js";
24
+ import { PlasmSpanAttributes } from "../instrumentation.js";
24
25
  import type { AuthoringContext } from "../authoring/context.js";
25
26
  import type { HookRunner } from "../authoring/hook-runner.js";
26
27
  import type { AgentWorkflowWorldDefinition } from "../define-agent.js";
@@ -61,7 +62,7 @@ export interface PlasmPlanInput {
61
62
 
62
63
  export interface PlasmRunInput {
63
64
  logicalSessionRef: string;
64
- planCommitRef: string;
65
+ runRef: string;
65
66
  reasoning?: string;
66
67
  }
67
68
 
@@ -283,7 +284,7 @@ export class AgentRuntime {
283
284
  });
284
285
  await this.sessionManager.update(session);
285
286
 
286
- span.setAttribute("plasm.plan_commit_ref", dry.planCommitRef);
287
+ span.setAttribute(PlasmSpanAttributes.RUN_REF, dry.planCommitRef);
287
288
  if (catalogCgsHash) {
288
289
  span.setAttribute("plasm.catalog_cgs_hash", catalogCgsHash);
289
290
  }
@@ -310,7 +311,7 @@ export class AgentRuntime {
310
311
  });
311
312
 
312
313
  await this.emitHook("plan:commit", {
313
- planCommitRef: dry.planCommitRef,
314
+ runRef: dry.planCommitRef,
314
315
  program: input.program,
315
316
  logicalSessionRef: session.logicalSessionRef,
316
317
  });
@@ -325,14 +326,14 @@ export class AgentRuntime {
325
326
  void input.reasoning;
326
327
  const entryId = session.seeds[0]?.api;
327
328
  const catalogCgsHash = entryId ? this.catalogHashForEntry(entryId) : undefined;
328
- const planCommit = session.planCommits.find((pc) => pc.ref === input.planCommitRef);
329
+ const planCommit = session.planCommits.find((pc) => pc.ref === input.runRef);
329
330
 
330
331
  return plasmSpans.liveRun(
331
332
  {
332
333
  intent: session.intent,
333
334
  logicalSessionRef: session.logicalSessionRef,
334
335
  sessionId: session.logicalSessionId,
335
- planCommitRef: input.planCommitRef,
336
+ runRef: input.runRef,
336
337
  entryId,
337
338
  catalogCgsHash,
338
339
  },
@@ -340,8 +341,8 @@ export class AgentRuntime {
340
341
  const started = Date.now();
341
342
  const result =
342
343
  this.hostTransport && typeof this.engine.runPlanLive === "function"
343
- ? await this.engine.runPlanLive(input.planCommitRef, this.hostTransport)
344
- : await this.engine.runPlan(input.planCommitRef);
344
+ ? await this.engine.runPlanLive(input.runRef, this.hostTransport)
345
+ : await this.engine.runPlan(input.runRef);
345
346
  const program = planCommit?.program ?? "";
346
347
  const runMeta = parseRunMeta(result.metaJson);
347
348
  const requestFingerprints = collectRequestFingerprints(runMeta);
@@ -349,7 +350,7 @@ export class AgentRuntime {
349
350
  catalogCgsHash && program
350
351
  ? computeRunId({
351
352
  catalogCgsHash,
352
- planCommitRef: input.planCommitRef,
353
+ planCommitRef: input.runRef,
353
354
  program,
354
355
  entryId,
355
356
  requestFingerprints,
@@ -357,7 +358,7 @@ export class AgentRuntime {
357
358
  : `pr${createHash("sha256").update(randomUUID()).digest("hex")}`;
358
359
 
359
360
  span.setAttribute("plasm.run_id", runId);
360
- span.setAttribute("plasm.plan_commit_ref", input.planCommitRef);
361
+ span.setAttribute(PlasmSpanAttributes.RUN_REF, input.runRef);
361
362
  if (catalogCgsHash) {
362
363
  span.setAttribute("plasm.catalog_cgs_hash", catalogCgsHash);
363
364
  }
@@ -365,7 +366,7 @@ export class AgentRuntime {
365
366
  if (this.archive) {
366
367
  await this.archive.writeRunSnapshot({
367
368
  run_id: runId,
368
- plan_commit_ref: input.planCommitRef,
369
+ plan_commit_ref: input.runRef,
369
370
  catalog_cgs_hash: catalogCgsHash ?? "unknown",
370
371
  entry_id: entryId,
371
372
  logical_session_ref: session.logicalSessionRef,
@@ -386,7 +387,7 @@ export class AgentRuntime {
386
387
  await this.recordToolTrace("plasm", "plasm.live_run", started, {
387
388
  intent: session.intent,
388
389
  logical_session_ref: session.logicalSessionRef,
389
- plan_commit_ref: input.planCommitRef,
390
+ plan_commit_ref: input.runRef,
390
391
  run_id: runId,
391
392
  catalog_cgs_hash: catalogCgsHash,
392
393
  ok: result.ok,
@@ -394,7 +395,7 @@ export class AgentRuntime {
394
395
  });
395
396
 
396
397
  await this.emitHook("run:complete", {
397
- planCommitRef: input.planCommitRef,
398
+ runRef: input.runRef,
398
399
  runId,
399
400
  ok: result.ok,
400
401
  logicalSessionRef: session.logicalSessionRef,
@@ -66,7 +66,7 @@ export async function maybeCompactMessages(
66
66
  const summary = await generateText({
67
67
  model: summaryModel,
68
68
  system:
69
- "Summarize the prior agent conversation for continuation. Preserve goals, catalog picks, logical_session_ref, plan_commit_ref, and unresolved tasks. Be concise.",
69
+ "Summarize the prior agent conversation for continuation. Preserve goals, catalog picks, logical_session_ref, run_ref, and unresolved tasks. Be concise.",
70
70
  prompt: transcript,
71
71
  temperature: 0,
72
72
  });
@@ -6,6 +6,7 @@ import {
6
6
  createAgentFromDefinition,
7
7
  resolveAgentDefinition,
8
8
  } from "../define-agent.js";
9
+ import { readBuildManifest } from "../cli/build.js";
9
10
  import { createAuthoringContext, type AuthoringContext } from "../authoring/context.js";
10
11
  import { tryHandleChannelRoute } from "../authoring/channel-dispatch.js";
11
12
  import {
@@ -75,9 +76,24 @@ export function normalizePlasmPathname(url: string | undefined): string {
75
76
  return pathname;
76
77
  }
77
78
 
79
+ /** Resolve request path on Vercel (rewrite-to-/api preserves public path in req.url). */
80
+ function vercelIncomingUrl(req: IncomingMessage): string {
81
+ for (const key of [
82
+ "x-vercel-original-url",
83
+ "x-middleware-request-url",
84
+ "x-forwarded-uri",
85
+ ] as const) {
86
+ const raw = req.headers[key];
87
+ if (typeof raw === "string" && raw.length > 0) {
88
+ return raw.startsWith("/") ? raw : new URL(raw, "http://localhost").pathname;
89
+ }
90
+ }
91
+ return req.url ?? "/";
92
+ }
93
+
78
94
  export function rewriteRequestPath(req: IncomingMessage): IncomingMessage {
79
- const pathname = normalizePlasmPathname(req.url);
80
- const original = req.url ?? "/";
95
+ const original = vercelIncomingUrl(req);
96
+ const pathname = normalizePlasmPathname(original);
81
97
  const query = original.includes("?") ? original.slice(original.indexOf("?")) : "";
82
98
  const proxy = Object.create(req) as IncomingMessage;
83
99
  Object.defineProperty(proxy, "url", {
@@ -235,9 +251,13 @@ export async function createPlasmApp(options: PlasmAppOptions): Promise<PlasmApp
235
251
 
236
252
  const refreshSlots = async (): Promise<LoadedProjectSlots> => {
237
253
  const currentDiscovery = discovery ?? (await refreshDiscovery());
254
+ const buildManifest = await readBuildManifest(agentRoot);
238
255
  loadedSlots = await loadAuthoredSlots({
239
256
  discovery: currentDiscovery,
240
257
  importCacheBust,
258
+ agentRoot,
259
+ projectRoot,
260
+ compiledSlots: buildManifest?.compiledSlots,
241
261
  });
242
262
  return loadedSlots;
243
263
  };
@@ -8,7 +8,7 @@ export interface PlasmSpanContext {
8
8
  logicalSessionRef?: string;
9
9
  catalogCgsHash?: string;
10
10
  entryId?: string;
11
- planCommitRef?: string;
11
+ runRef?: string;
12
12
  runId?: string;
13
13
  toolName?: string;
14
14
  transportHost?: string;
@@ -26,8 +26,8 @@ function applyPlasmAttributes(span: Span, ctx: PlasmSpanContext): void {
26
26
  if (ctx.catalogCgsHash) {
27
27
  span.setAttribute(PlasmSpanAttributes.CATALOG_CGS_HASH, ctx.catalogCgsHash);
28
28
  }
29
- if (ctx.planCommitRef) {
30
- span.setAttribute(PlasmSpanAttributes.PLAN_COMMIT_REF, ctx.planCommitRef);
29
+ if (ctx.runRef) {
30
+ span.setAttribute(PlasmSpanAttributes.RUN_REF, ctx.runRef);
31
31
  }
32
32
  if (ctx.runId) span.setAttribute(PlasmSpanAttributes.RUN_ID, ctx.runId);
33
33
  if (ctx.entryId) span.setAttribute(PlasmSpanAttributes.ENTRY_ID, ctx.entryId);
@@ -17,7 +17,7 @@ Returns **\`logical_session_ref\`** + fenced teaching TSV. TSV is the active sym
17
17
 
18
18
  **\`_meta.plasm\`:** \`logical_session_ref\`, \`continuity\`, \`domain_revision\`, optional **\`relations\`**.`;
19
19
 
20
- export const PLASM_TOOL_DESCRIPTION = `**Plan Plasm** (dry-run): **\`logical_session_ref\`** + **\`program\`**. Returns reviewable plan topology and executable **\`plan_commit_ref\`** (\`pcN\`). Pass that token to **\`plasm_run\`**; do **not** echo the program.
20
+ export const PLASM_TOOL_DESCRIPTION = `**Plan Plasm** (dry-run): **\`logical_session_ref\`** + **\`program\`**. Returns reviewable plan topology and executable **\`run_ref\`** (\`pcN\`). Pass that token to **\`plasm_run\`**; do **not** echo the program.
21
21
 
22
22
  **\`program\` is Plasm source text, not JSON data.** Write one raw expression (e.g. \`e3(p15="electric").r2[p4,p5]\`) or multiline bindings with final roots. If a plan merely echoes an object/array/string literal, that is a literal no-op; rewrite as Plasm source.
23
23
 
@@ -29,66 +29,53 @@ Output:
29
29
 
30
30
  TSV table semantics:
31
31
  - Header: \`plasm_expr<TAB>Meaning\`; one tab per row. Left = syntax/metadata; right = guidance only (never copy \`Meaning\`).
32
- - Executable rows start with \`e#\`/Entity; metadata-only \`p#\`/\`v#\` rows are never roots.
32
+ - Executable rows start with \`e#\`; metadata-only \`p#\`/\`r#\`/\`v#\` rows are never roots.
33
33
 
34
34
  Symbol and fill rules:
35
35
  - \`e#\` entity; \`m#\` method; \`p#\` field/param/filter; \`r#\` relation nav; \`v#\` value-domain metadata only.
36
- - Relation hops: \`.wire\` or \`.r#\` on row producers — never bare \`p#\` after \`.\` (\`p#\` in \`e#{…}\` is filter/param).
36
+ - Relation hops: \`.r#\` (or the relation wire) on row producers — never a bare filter \`p#\` after \`.\` (\`p#\` in \`e#{…}\` is filter/param).
37
37
  - Never write \`v#\` in code; use \`p#\` keys and read \`v#\` rows for allowed values.
38
- - \`e#(p#)\` identity rows: substitute real wire ids for \`<id>\`, \`<value>\`, \`<receiver>\`, \`elem\`.
39
- - Remove \`..\` ellipses or replace with real \`p#=\` assignments before output.
40
- - Optional keys (\`opt:\` in \`Meaning\`): add only as keyed assignments with real values.
41
- - Projection rows ending \`[p#,…]\` teach a valid field set. Reuse that suffix only on another expression returning the same entity or list type.
38
+ - **Session handles** (\`logical_session_ref\`, \`run_ref\` / \`pcN\`): MCP tokens only copy verbatim; not program syntax.
39
+ - Replace teaching placeholders (\`$\`, \`<id>\`, \`<val>\`) and exemplar ids (\`pikachu\`, \`example-name\`, …) with real values before output. Never emit \`$\` from a teaching row.
40
+ - Get-head: **parens = identity** (\`e_type(electric)\`; bare word = string identity when the slot is a name/string field), **braces = query/filter** (\`e#{p#=…}\`). Keyed \`e#(p#=<id>)\` only when that \`p#\` is the identity field.
41
+ - **Quote scalars only in \`{…}\` predicates** (\`e#{p#="EVA"}\`); identity ids in \`e#(…)\` need no quotes. EntityRef slots use \`e#(id)\`, not bare scalars.
42
+ - Remove \`..\` ellipses; add \`opt:\` keys (from \`Meaning\`) only as keyed assignments with real values.
43
+ - Projection \`[p#,…]\` suffixes: reuse only on expressions returning the same entity/list type.
42
44
 
43
45
  Core surface:
44
46
  - Program shape: one \`plasm_expr\`, or \`label = …\` bindings then comma-separated final roots (no \`return\`).
45
- - Postfix on row producers: \`.limit(N)\` | \`.page_size(N)\` | \`.sort(p#, desc)\` / \`.sort(p#,dir)\` | \`.filter{…}\` | \`.filter(…)\` | \`.aggregate(specs)\` | \`.group_by(p#)\` then \`.aggregate(specs)\` | \`.group_by(p#, specs)\` (comma sugar) | \`.dedupe(p#[, ])\` | \`.distinct(p#[, …])\` | \`.distinct()\` | \`.singleton()\` | \`[p#,…]\`. Row postfix fields use teaching \`rows:\` \`p#\` symbols; wire field names are sugar.
46
- - Large list reads return the first **25 rows** in \`plasm_run\`; continue with \`page(l_<token>_pgN)\` from the tool result not \`resources/read\`.
47
- - Run gate: pass \`pcN\` from a prior dry-run to \`plasm_run\`; MCP \`plasm_run\` does not accept program continuations.
48
- - Copy teaching TSV left cells; substitute placeholders; compose via bindings.
49
- - Search when exposed: \`e#~$\` or \`e#~"text"\` (bare \`e#~\` is invalid); optional scoped filters \`e#~"text"[{p#=…}]\`.
50
- - Field projection suffix: \`[p#,…]\`.
47
+ - Postfix/\`[p#]\`/\`[fields]\`: copy exact forms from the TSV left column (\`.limit\` \`.sort\` \`.filter{…}\` \`.group_by\` \`.aggregate\` …).
48
+ - Large list reads return the first **25 rows** in \`plasm_run\`; further pages copy the handle from the prior result's "more pages" line into **\`run_ref\`** on the next **\`plasm_run\`** (no second **\`plasm\`** call).
49
+ - \`page(...)\` is HTTP-execute program syntax only not an MCP tool argument or \`plasm\` program.
50
+ - Search when exposed: \`e#~$\` or \`e#~"text"\` (bare \`e#~\` is a parse error); optional scoped filters \`e#~"text"[{p#=…}]\`.
51
51
 
52
52
  Composition rules:
53
53
  - One binding per line; final roots last (preferred). Single-line bindings coerced; default return is first binding.
54
- - Postfix/\`[fields]\` chain on any row-producing binding or expression.
55
- - Row text: \`label = source <<TAG\` \`TAG\` (equals required never \`label source <<TAG\`); optional column list: \`label = source[p#,…] <<TAG\`. Minijinja \`rows\` = that source's projected rows only — not \`\${}\`.
56
- - Pass \`binding.content\` to string params; compose with \`\${report.content}\` in later heredocs/strings (never bare \`\${}\` in prose; \`$$\` escapes \`$\`).
57
- - Minijinja \`{{ }}\` / \`{% %}\` only inside row-to-text heredoc bodies — not in capability heredocs that use \`\${binding.path}\`.
58
- - Do not use \`report.content\` as a final root or relation receiver.
54
+ - Row text: \`label = source <<TAG\` … \`TAG\` (equals required); optional \`label = source[p#,…] <<TAG\`. Minijinja \`{{ }}\` / \`{% %}\` over \`rows\` only inside the heredoc body — not \`\${}\`.
55
+ - Pass \`binding.content\` to string params; compose with \`\${report.content}\` in later heredocs/strings (\`$$\` escapes \`$\`). Do not use \`report.content\` as a final root or relation receiver.
59
56
  - Action/create roots: return the action row or follow with \`e#(p#=…)\` get — not \`created.p#\` as a program root.
60
- - Relation embed fan-out (\`Type.pokemon\` / \`.r#\`): prefer explicit Gets until embed path is warm; \`.limit\` on relation hops may need hydrate when graph targets are missing.
61
- - Heredoc: \`<<TAG\` + newline; first trimmed \`TAG\` line closes; pick a tag absent from the body.
62
- - Examples: array argument \`p#=[e#(<id>), …]\`. Quoted strings use only \`\\"\` and \`\\\\\` escapes.
63
- - Worked row-compute program (shape only — substitute \`e#\` / \`p#\` from your table below):
57
+ - Heredoc: \`<<TAG\` + newline; first trimmed \`TAG\` line closes; pick a tag absent from the body. \`markdown\`/\`html\`/\`document\`/\`json_text\`/\`blob\` values use \`<<TAG\` \`TAG\` only.
58
+ - Worked shape (substitute symbols from your TSV):
64
59
  items = e_source
65
- filtered = items.filter{p_field>=300} # prefer bind; bare \`e_source.filter{…}\` list-alls first
66
- sorted = filtered.sort(p_field, desc)
67
- limited = sorted.limit(10)
68
- limited[p_a,p_field]
69
- - Worked federated relation + row compute (shape only — copy \`e#\` / \`.r#\` from your table's left column):
70
- parent = e2(p_key=<val>)
71
- children = parent.rN
72
- limited = children.limit(5)
73
- limited[p_a,p_b]
74
- - \`markdown\`/\`html\`/\`document\`/\`json_text\`/\`blob\` values (per \`Meaning\`): \`<<TAG\` … \`TAG\` only; e.g. \`e2.mN(..., p#=<<TXT\` + newline body + \`TXT\` newline \`)\`.
60
+ filtered = items.filter{p_field>=300}
61
+ sorted = filtered.sort(p_field, desc).limit(10)
62
+ sorted[p_a,p_field]
75
63
 
76
64
  Common pitfalls:
77
- - \`[fields]\`/\`.group_by\`/\`.sort\`/\`.dedupe\`/row \`.filter\` use \`rows:\` fields only not \`inputs:\`/\`opt:\` filter keys.
78
- - \`e#{…}\` filters at HTTP; \`binding.filter{…}\` filters materialized rows. Prefer \`label = e#\` then filter; bare \`e#.filter{}\` list-alls first.
79
- - Primary group/agg: \`group_by(p_key).aggregate(n=count)\`; bare \`group_by(p_key)\` \`group_by(p_key, count=count)\`; multi-key comma sugar: \`group_by(k1, k2, n=count)\`.
80
- - \`=>\` only for derive \`{ k: _.field }\` or \`for_each\` updates — not relation reads (\`labels = issues.r#\`).
81
- - Homograph: relation fanout via \`.r#\`/wire; \`labels = issues.p#\` forgiven when binding name matches.
82
- - Never emit \`$\` from teaching rows; substitute real ids and \`e#~"text"\` search terms.
65
+ - Prefer \`label = e#\` then \`label.filter{…}\`; bare \`e#.filter{…}\` list-alls first. Row \`.filter{…}\` needs a materialized list.
66
+ - \`=>\` only for derive \`{ k: _.field }\` or write effects (\`source => e#.m#()\`) not relation reads (use \`child = source.r#\`).
67
+ - Federated writes: one created-row return; discover anchors live never hardcode workspace ids.
68
+ - **Symbols only:** use this session's \`e#\` / \`m#\` / \`r#\` / \`p#\` from the teaching TSV — not catalog type names, method verbs, or API labels. Unambiguous single-catalog wire names may parse, but always prefer taught \`e#\`/\`m#\`.
83
69
  - Search operand: \`e#~$\` or \`e#~"text"\` required — bare \`e#~\` is a parse error.
84
70
  - Federated sessions: when the same wire entity/method/relation/field name appears in multiple catalogs, bare wire tokens are compile errors — copy the session \`e#\` / \`m#\` / \`r#\` / \`p#\` from the teaching row for that catalog. Never write \`entry_id:Entity\` or \`catalog.Entity\` in programs.
85
- - Cross-catalog stitch (shape only — substitute symbols from your TSV): pokeapi \`Type\` get → row-to-text bindings → proof \`ShareLink\` document param with \`\${binding.content}\` inside \`<<TAG\` … \`TAG\`.
86
71
  - Search-only entities (no query): no \`e#{}\` list-all — scoped \`e#{p#=…}\` and/or \`e#~"text"\`.`;
87
72
 
88
- export const PLASM_RUN_TOOL_DESCRIPTION = `**Run Plasm** (live): **\`logical_session_ref\`** + **\`plan_commit_ref\`** (\`pcN\`) from a prior **\`plasm\`** dry-run. Real HTTP/API; may return **\`resource_link\`** / \`_meta.plasm\` snapshots.
73
+ export const PLASM_RUN_TOOL_DESCRIPTION = `**Run Plasm** (live): **\`logical_session_ref\`** + **\`run_ref\`** — a \`pcN\` plan commit from **\`plasm\`**, or the page handle from a prior result's "more pages" line. Real HTTP/API; may return **\`resource_link\`** snapshots.
89
74
 
90
- **Review gate:** \`plasm_run\` executes exactly the reviewed plan stored under the **\`plan_commit_ref\`**. If the token is missing, expired, or from another plan, call **\`plasm\`** again.
75
+ **Review gate:** first live run executes the reviewed plan for the \`pcN\` **\`run_ref\`** from **\`plasm\`**. If the token is missing, expired, or from another plan, call **\`plasm\`** again.
76
+
77
+ **Paging:** when a result says \`more pages — call plasm_run with run_ref: "…"\`, copy that handle into **\`run_ref\`** on the next **\`plasm_run\`** (same **\`logical_session_ref\`**; no second **\`plasm\`** call). \`page(...)\` is HTTP-execute program syntax only — not an MCP tool argument.
91
78
 
92
79
  **Live execute:** server spawns one async operation and awaits terminal rows in the tool response. Progress uses standard \`notifications/plasm/op\` on the registered handle.
93
80
 
94
- **Live results:** \`## {return_label} ({n} rows)\` + capped TSV (25 rows inline); multi-return programs use \`# Results\` with \`### {label} ({n} rows)\` per root. When more rows exist, continue with \`page(l_<token>_pgN)\` from the tool result or \`_meta.plasm.paging\`. Snapshot URI lines point at full JSON via MCP **\`resources/read\`** when needed. Run Explorer is supplemental.`;
81
+ **Live results:** \`## {return_label} ({n} rows)\` + capped TSV (25 rows inline); multi-return programs use \`# Results\` with \`### {label} ({n} rows)\` per root. ≤25 rows inline \` \`\`tsv \`\`; 26–499 capped TSV + snapshot URI (\`resources/read\` when the host supports it); 500+ preview + snapshot, no inline TSV — narrow the program or page.`;
@@ -13,8 +13,8 @@ export function formatPlasmContextMarkdown(
13
13
  return `\`${logicalSessionRef}\`\n\n\`\`\`tsv\n${delta}\n\`\`\`\n`;
14
14
  }
15
15
 
16
- export function formatPlasmDryRunMarkdown(summary: string, planCommitRef: string): string {
17
- return `\`\`\`text\n${summary.trim()}\n\`\`\`\n\n**Run:** pass \`plan_commit_ref\`: \`${planCommitRef}\` to **\`plasm_run\`**. Do not echo the program.`;
16
+ export function formatPlasmDryRunMarkdown(summary: string, runRef: string): string {
17
+ return `\`\`\`text\n${summary.trim()}\n\`\`\`\n\n**Run:** pass \`run_ref\`: \`${runRef}\` to **\`plasm_run\`**. Do not echo the program.`;
18
18
  }
19
19
 
20
20
  export function formatPlasmRunMarkdown(
@@ -62,9 +62,9 @@ const plasmRunInputSchema = z.object({
62
62
  logical_session_ref: z
63
63
  .string()
64
64
  .describe("Same logical_session_ref returned by plasm_context"),
65
- plan_commit_ref: z
65
+ run_ref: z
66
66
  .string()
67
- .describe("Executable plan token (pcN) from a prior plasm dry-run"),
67
+ .describe("pcN from plasm dry-run, or page handle from a prior plasm_run more-pages line"),
68
68
  reasoning: z
69
69
  .string()
70
70
  .optional()
@@ -100,10 +100,10 @@ export function createPlasmTools(runtime: AgentRuntime): ToolSet {
100
100
  plasm_run: tool({
101
101
  description: PLASM_RUN_TOOL_DESCRIPTION,
102
102
  inputSchema: toolInput(plasmRunInputSchema),
103
- execute: async ({ logical_session_ref, plan_commit_ref, reasoning }) =>
103
+ execute: async ({ logical_session_ref, run_ref, reasoning }) =>
104
104
  runtime.plasmRun({
105
105
  logicalSessionRef: logical_session_ref,
106
- planCommitRef: plan_commit_ref,
106
+ runRef: run_ref,
107
107
  reasoning,
108
108
  }),
109
109
  }),
@@ -19,7 +19,9 @@ export function resolveWorkflowWorldType(
19
19
 
20
20
  /**
21
21
  * Bootstrap Workflow SDK world from agent definition.
22
- * - `vercel`: platform-managed world on deploy (no-op here)
22
+ * - `vercel`: Vercel World is selected automatically by the Workflow SDK when
23
+ * `VERCEL_DEPLOYMENT_ID` is set; durable session routes require `/.well-known/workflow/`
24
+ * (eve emits these via Nitro + @workflow/nitro when sources contain `use workflow` / `use step`).
23
25
  * - `postgres`: `@workflow/world-postgres` long-lived worker
24
26
  * - `local`: `@workflow/world-local` for dev
25
27
  */
@@ -28,6 +30,15 @@ export async function bootstrapWorkflowWorld(
28
30
  ): Promise<WorkflowWorldType> {
29
31
  const worldType = resolveWorkflowWorldType(definition);
30
32
  if (worldType === "vercel") {
33
+ if (process.env.VERCEL_DEPLOYMENT_ID?.trim()) {
34
+ try {
35
+ const { getWorld } = await import("workflow/runtime");
36
+ await getWorld().start?.();
37
+ } catch (err) {
38
+ if (process.env.PLASM_WORKFLOW_STRICT === "1") throw err;
39
+ console.warn("[plasm:workflow] vercel world start skipped:", err);
40
+ }
41
+ }
31
42
  return worldType;
32
43
  }
33
44
 
@@ -0,0 +1,18 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { registerOTel } from "@vercel/otel";
5
+ import { OpenTelemetry } from "@ai-sdk/otel";
6
+ import { registerTelemetryIntegration } from "ai";
7
+
8
+ const agentRoot = path.dirname(fileURLToPath(import.meta.url));
9
+ const serviceName =
10
+ process.env.PLASM_AGENT_NAME?.trim() || path.basename(path.dirname(agentRoot));
11
+
12
+ /** Eve-shaped OTEL bootstrap — optional export to Braintrust/Datadog via registerOTel. */
13
+ export function register(): void {
14
+ registerOTel({ serviceName });
15
+ registerTelemetryIntegration(new OpenTelemetry());
16
+ }
17
+
18
+ register();
@@ -257,9 +257,16 @@ export function tavilyConfigured(): boolean {
257
257
  }
258
258
 
259
259
  export function gatewayConfigured(): boolean {
260
- return Boolean(
260
+ if (
261
261
  process.env.AI_GATEWAY_API_KEY?.trim() ||
262
- process.env.AI_API_GATEWAY_KEY?.trim() ||
263
- process.env.AI_GATEWAY_KEY?.trim(),
262
+ process.env.AI_API_GATEWAY_KEY?.trim() ||
263
+ process.env.AI_GATEWAY_KEY?.trim()
264
+ ) {
265
+ return true;
266
+ }
267
+ return (
268
+ process.env.VERCEL === "1" ||
269
+ Boolean(process.env.VERCEL_DEPLOYMENT_ID?.trim()) ||
270
+ Boolean(process.env.VERCEL_ENV?.trim())
264
271
  );
265
272
  }
@@ -1,15 +1,4 @@
1
1
  {
2
- "buildCommand": "npx tsx scripts/vercel-build.ts",
3
- "rewrites": [{ "source": "/(.*)", "destination": "/api/$1" }],
4
- "crons": [
5
- {
6
- "path": "/internal/cron/mcp-radar-scan",
7
- "schedule": "0 */6 * * *"
8
- }
9
- ],
10
- "functions": {
11
- "api/**": {
12
- "maxDuration": 300
13
- }
14
- }
2
+ "installCommand": "npm ci --omit=dev",
3
+ "buildCommand": "plasm-agent build"
15
4
  }
@@ -0,0 +1,18 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { registerOTel } from "@vercel/otel";
5
+ import { OpenTelemetry } from "@ai-sdk/otel";
6
+ import { registerTelemetryIntegration } from "ai";
7
+
8
+ const agentRoot = path.dirname(fileURLToPath(import.meta.url));
9
+ const serviceName =
10
+ process.env.PLASM_AGENT_NAME?.trim() || path.basename(path.dirname(agentRoot));
11
+
12
+ /** Eve-shaped OTEL bootstrap — optional export to Braintrust/Datadog via registerOTel. */
13
+ export function register(): void {
14
+ registerOTel({ serviceName });
15
+ registerTelemetryIntegration(new OpenTelemetry());
16
+ }
17
+
18
+ register();
@@ -1,4 +1,4 @@
1
1
  {
2
- "buildCommand": "plasm-agent build",
3
- "rewrites": [{ "source": "/(.*)", "destination": "/api/$1" }]
2
+ "installCommand": "npm ci --omit=dev",
3
+ "buildCommand": "plasm-agent build"
4
4
  }
@@ -1,15 +1,4 @@
1
1
  {
2
- "buildCommand": "plasm-agent build",
3
- "rewrites": [{ "source": "/(.*)", "destination": "/api/$1" }],
4
- "crons": [
5
- {
6
- "path": "/internal/cron/mcp-radar-scan",
7
- "schedule": "0 */6 * * *"
8
- }
9
- ],
10
- "functions": {
11
- "api/**": {
12
- "maxDuration": 300
13
- }
14
- }
2
+ "installCommand": "npm ci --omit=dev",
3
+ "buildCommand": "plasm-agent build"
15
4
  }
@@ -1,23 +0,0 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
- import agentDefinition from "../agent/agent.js";
5
- import { createPlasmApp, vercelPlasmHandler } from "@plasm_lang/vercel-agent";
6
-
7
- const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
8
- const agentRoot = path.join(packageRoot, "agent");
9
-
10
- let app: Awaited<ReturnType<typeof createPlasmApp>> | undefined;
11
-
12
- export default async function handler(
13
- req: import("node:http").IncomingMessage,
14
- res: import("node:http").ServerResponse,
15
- ): Promise<void> {
16
- app ??= await createPlasmApp({
17
- agentRoot,
18
- definition: agentDefinition,
19
- mode: "prod",
20
- sessions: false,
21
- });
22
- await vercelPlasmHandler(app)(req, res);
23
- }
@@ -1,17 +0,0 @@
1
- import { defineNitroConfig } from "nitropack/config";
2
-
3
- export default defineNitroConfig({
4
- compatibilityDate: "2026-06-26",
5
- srcDir: ".",
6
- ignore: ["api/**"],
7
- devServer: {
8
- port: Number(process.env.PORT ?? 3000),
9
- host: process.env.HOST ?? "127.0.0.1",
10
- },
11
- typescript: {
12
- strict: false,
13
- },
14
- externals: {
15
- inline: ["@plasm_lang/engine"],
16
- },
17
- });
@@ -1,29 +0,0 @@
1
- import path from "node:path";
2
-
3
- import { fromNodeMiddleware } from "h3";
4
-
5
- import agentDefinition from "../agent/agent.js";
6
- import { createPlasmApp, vercelPlasmHandler } from "@plasm_lang/vercel-agent";
7
-
8
- const agentRoot = path.join(process.cwd(), "agent");
9
-
10
- let appPromise: ReturnType<typeof createPlasmApp> | undefined;
11
-
12
- async function plasmApp() {
13
- appPromise ??= createPlasmApp({
14
- agentRoot,
15
- definition: agentDefinition,
16
- mode: "prod",
17
- sessions: false,
18
- });
19
- return appPromise;
20
- }
21
-
22
- export default fromNodeMiddleware(async (req, res) => {
23
- const app = await plasmApp();
24
- await new Promise<void>((resolve, reject) => {
25
- res.once("finish", () => resolve());
26
- res.once("error", reject);
27
- void vercelPlasmHandler(app)(req, res).catch(reject);
28
- });
29
- });
@@ -1,23 +0,0 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
- import agentDefinition from "../agent/agent.js";
5
- import { createPlasmApp, vercelPlasmHandler } from "@plasm_lang/vercel-agent/server";
6
-
7
- const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
8
- const agentRoot = path.join(packageRoot, "agent");
9
-
10
- let app: Awaited<ReturnType<typeof createPlasmApp>> | undefined;
11
-
12
- export default async function handler(
13
- req: import("node:http").IncomingMessage,
14
- res: import("node:http").ServerResponse,
15
- ): Promise<void> {
16
- app ??= await createPlasmApp({
17
- agentRoot,
18
- definition: agentDefinition,
19
- mode: "prod",
20
- sessions: false,
21
- });
22
- await vercelPlasmHandler(app)(req, res);
23
- }
@@ -1,17 +0,0 @@
1
- import { defineNitroConfig } from "nitropack/config";
2
-
3
- export default defineNitroConfig({
4
- compatibilityDate: "2026-06-26",
5
- srcDir: ".",
6
- ignore: ["api/**"],
7
- devServer: {
8
- port: Number(process.env.PORT ?? 3000),
9
- host: process.env.HOST ?? "127.0.0.1",
10
- },
11
- typescript: {
12
- strict: false,
13
- },
14
- externals: {
15
- inline: ["@plasm_lang/engine"],
16
- },
17
- });
@@ -1,29 +0,0 @@
1
- import path from "node:path";
2
-
3
- import { fromNodeMiddleware } from "h3";
4
-
5
- import agentDefinition from "../agent/agent.js";
6
- import { createPlasmApp, vercelPlasmHandler } from "@plasm_lang/vercel-agent/server";
7
-
8
- const agentRoot = path.join(process.cwd(), "agent");
9
-
10
- let appPromise: ReturnType<typeof createPlasmApp> | undefined;
11
-
12
- async function plasmApp() {
13
- appPromise ??= createPlasmApp({
14
- agentRoot,
15
- definition: agentDefinition,
16
- mode: "prod",
17
- sessions: false,
18
- });
19
- return appPromise;
20
- }
21
-
22
- export default fromNodeMiddleware(async (req, res) => {
23
- const app = await plasmApp();
24
- await new Promise<void>((resolve, reject) => {
25
- res.once("finish", () => resolve());
26
- res.once("error", reject);
27
- void vercelPlasmHandler(app)(req, res).catch(reject);
28
- });
29
- });