@agent-native/core 0.52.0 → 0.53.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 (212) hide show
  1. package/README.md +41 -95
  2. package/blueprints/action/crud.md +98 -0
  3. package/blueprints/channel/discord.md +74 -0
  4. package/blueprints/provider/stripe.md +87 -0
  5. package/blueprints/sandbox/docker.md +78 -0
  6. package/dist/action.d.ts +24 -0
  7. package/dist/action.d.ts.map +1 -1
  8. package/dist/action.js +4 -0
  9. package/dist/action.js.map +1 -1
  10. package/dist/agent/observational-memory/compactor.d.ts +43 -0
  11. package/dist/agent/observational-memory/compactor.d.ts.map +1 -0
  12. package/dist/agent/observational-memory/compactor.js +50 -0
  13. package/dist/agent/observational-memory/compactor.js.map +1 -0
  14. package/dist/agent/observational-memory/config.d.ts +37 -0
  15. package/dist/agent/observational-memory/config.d.ts.map +1 -0
  16. package/dist/agent/observational-memory/config.js +48 -0
  17. package/dist/agent/observational-memory/config.js.map +1 -0
  18. package/dist/agent/observational-memory/index.d.ts +26 -0
  19. package/dist/agent/observational-memory/index.d.ts.map +1 -0
  20. package/dist/agent/observational-memory/index.js +25 -0
  21. package/dist/agent/observational-memory/index.js.map +1 -0
  22. package/dist/agent/observational-memory/internal-run.d.ts +37 -0
  23. package/dist/agent/observational-memory/internal-run.d.ts.map +1 -0
  24. package/dist/agent/observational-memory/internal-run.js +59 -0
  25. package/dist/agent/observational-memory/internal-run.js.map +1 -0
  26. package/dist/agent/observational-memory/message-text.d.ts +13 -0
  27. package/dist/agent/observational-memory/message-text.d.ts.map +1 -0
  28. package/dist/agent/observational-memory/message-text.js +46 -0
  29. package/dist/agent/observational-memory/message-text.js.map +1 -0
  30. package/dist/agent/observational-memory/migrations.d.ts +13 -0
  31. package/dist/agent/observational-memory/migrations.d.ts.map +1 -0
  32. package/dist/agent/observational-memory/migrations.js +43 -0
  33. package/dist/agent/observational-memory/migrations.js.map +1 -0
  34. package/dist/agent/observational-memory/observer.d.ts +37 -0
  35. package/dist/agent/observational-memory/observer.d.ts.map +1 -0
  36. package/dist/agent/observational-memory/observer.js +82 -0
  37. package/dist/agent/observational-memory/observer.js.map +1 -0
  38. package/dist/agent/observational-memory/plugin.d.ts +16 -0
  39. package/dist/agent/observational-memory/plugin.d.ts.map +1 -0
  40. package/dist/agent/observational-memory/plugin.js +26 -0
  41. package/dist/agent/observational-memory/plugin.js.map +1 -0
  42. package/dist/agent/observational-memory/prompts.d.ts +27 -0
  43. package/dist/agent/observational-memory/prompts.d.ts.map +1 -0
  44. package/dist/agent/observational-memory/prompts.js +42 -0
  45. package/dist/agent/observational-memory/prompts.js.map +1 -0
  46. package/dist/agent/observational-memory/read.d.ts +47 -0
  47. package/dist/agent/observational-memory/read.d.ts.map +1 -0
  48. package/dist/agent/observational-memory/read.js +99 -0
  49. package/dist/agent/observational-memory/read.js.map +1 -0
  50. package/dist/agent/observational-memory/reflector.d.ts +31 -0
  51. package/dist/agent/observational-memory/reflector.d.ts.map +1 -0
  52. package/dist/agent/observational-memory/reflector.js +76 -0
  53. package/dist/agent/observational-memory/reflector.js.map +1 -0
  54. package/dist/agent/observational-memory/schema.d.ts +267 -0
  55. package/dist/agent/observational-memory/schema.d.ts.map +1 -0
  56. package/dist/agent/observational-memory/schema.js +48 -0
  57. package/dist/agent/observational-memory/schema.js.map +1 -0
  58. package/dist/agent/observational-memory/store.d.ts +52 -0
  59. package/dist/agent/observational-memory/store.d.ts.map +1 -0
  60. package/dist/agent/observational-memory/store.js +197 -0
  61. package/dist/agent/observational-memory/store.js.map +1 -0
  62. package/dist/agent/observational-memory/types.d.ts +61 -0
  63. package/dist/agent/observational-memory/types.d.ts.map +1 -0
  64. package/dist/agent/observational-memory/types.js +9 -0
  65. package/dist/agent/observational-memory/types.js.map +1 -0
  66. package/dist/agent/production-agent.d.ts +15 -0
  67. package/dist/agent/production-agent.d.ts.map +1 -1
  68. package/dist/agent/production-agent.js +240 -1
  69. package/dist/agent/production-agent.js.map +1 -1
  70. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  71. package/dist/agent/run-loop-with-resume.js +49 -0
  72. package/dist/agent/run-loop-with-resume.js.map +1 -1
  73. package/dist/agent/run-store.d.ts +17 -0
  74. package/dist/agent/run-store.d.ts.map +1 -1
  75. package/dist/agent/run-store.js +55 -0
  76. package/dist/agent/run-store.js.map +1 -1
  77. package/dist/agent/runtime-context.d.ts +30 -0
  78. package/dist/agent/runtime-context.d.ts.map +1 -1
  79. package/dist/agent/runtime-context.js +54 -1
  80. package/dist/agent/runtime-context.js.map +1 -1
  81. package/dist/agent/tool-call-journal.d.ts +101 -0
  82. package/dist/agent/tool-call-journal.d.ts.map +1 -0
  83. package/dist/agent/tool-call-journal.js +214 -0
  84. package/dist/agent/tool-call-journal.js.map +1 -0
  85. package/dist/agent/types.d.ts +24 -0
  86. package/dist/agent/types.d.ts.map +1 -1
  87. package/dist/agent/types.js.map +1 -1
  88. package/dist/cli/add.d.ts +109 -0
  89. package/dist/cli/add.d.ts.map +1 -0
  90. package/dist/cli/add.js +352 -0
  91. package/dist/cli/add.js.map +1 -0
  92. package/dist/cli/connect.d.ts +2 -2
  93. package/dist/cli/connect.d.ts.map +1 -1
  94. package/dist/cli/connect.js +92 -24
  95. package/dist/cli/connect.js.map +1 -1
  96. package/dist/cli/eval.d.ts +17 -0
  97. package/dist/cli/eval.d.ts.map +1 -0
  98. package/dist/cli/eval.js +121 -0
  99. package/dist/cli/eval.js.map +1 -0
  100. package/dist/cli/index.js +44 -3
  101. package/dist/cli/index.js.map +1 -1
  102. package/dist/cli/mcp.d.ts.map +1 -1
  103. package/dist/cli/mcp.js +11 -5
  104. package/dist/cli/mcp.js.map +1 -1
  105. package/dist/cli/plan-local.d.ts +66 -5
  106. package/dist/cli/plan-local.d.ts.map +1 -1
  107. package/dist/cli/plan-local.js +495 -19
  108. package/dist/cli/plan-local.js.map +1 -1
  109. package/dist/cli/skills.d.ts +2 -2
  110. package/dist/cli/skills.d.ts.map +1 -1
  111. package/dist/cli/skills.js +70 -59
  112. package/dist/cli/skills.js.map +1 -1
  113. package/dist/client/AssistantChat.d.ts.map +1 -1
  114. package/dist/client/AssistantChat.js +118 -92
  115. package/dist/client/AssistantChat.js.map +1 -1
  116. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  117. package/dist/client/agent-chat-adapter.js +16 -0
  118. package/dist/client/agent-chat-adapter.js.map +1 -1
  119. package/dist/client/chat/tool-call-display.d.ts +20 -1
  120. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  121. package/dist/client/chat/tool-call-display.js +32 -7
  122. package/dist/client/chat/tool-call-display.js.map +1 -1
  123. package/dist/client/sse-event-processor.d.ts +13 -0
  124. package/dist/client/sse-event-processor.d.ts.map +1 -1
  125. package/dist/client/sse-event-processor.js +21 -0
  126. package/dist/client/sse-event-processor.js.map +1 -1
  127. package/dist/db/client.d.ts +4 -2
  128. package/dist/db/client.d.ts.map +1 -1
  129. package/dist/db/client.js +6 -4
  130. package/dist/db/client.js.map +1 -1
  131. package/dist/deploy/route-discovery.d.ts.map +1 -1
  132. package/dist/deploy/route-discovery.js +1 -0
  133. package/dist/deploy/route-discovery.js.map +1 -1
  134. package/dist/eval/agent-runner.d.ts +63 -0
  135. package/dist/eval/agent-runner.d.ts.map +1 -0
  136. package/dist/eval/agent-runner.js +142 -0
  137. package/dist/eval/agent-runner.js.map +1 -0
  138. package/dist/eval/define-eval.d.ts +29 -0
  139. package/dist/eval/define-eval.d.ts.map +1 -0
  140. package/dist/eval/define-eval.js +43 -0
  141. package/dist/eval/define-eval.js.map +1 -0
  142. package/dist/eval/index.d.ts +18 -0
  143. package/dist/eval/index.d.ts.map +1 -0
  144. package/dist/eval/index.js +17 -0
  145. package/dist/eval/index.js.map +1 -0
  146. package/dist/eval/report.d.ts +8 -0
  147. package/dist/eval/report.d.ts.map +1 -0
  148. package/dist/eval/report.js +44 -0
  149. package/dist/eval/report.js.map +1 -0
  150. package/dist/eval/runner.d.ts +67 -0
  151. package/dist/eval/runner.d.ts.map +1 -0
  152. package/dist/eval/runner.js +256 -0
  153. package/dist/eval/runner.js.map +1 -0
  154. package/dist/eval/scorer.d.ts +83 -0
  155. package/dist/eval/scorer.d.ts.map +1 -0
  156. package/dist/eval/scorer.js +195 -0
  157. package/dist/eval/scorer.js.map +1 -0
  158. package/dist/eval/types.d.ts +162 -0
  159. package/dist/eval/types.d.ts.map +1 -0
  160. package/dist/eval/types.js +20 -0
  161. package/dist/eval/types.js.map +1 -0
  162. package/dist/observability/traces.d.ts.map +1 -1
  163. package/dist/observability/traces.js +100 -1
  164. package/dist/observability/traces.js.map +1 -1
  165. package/dist/observability/tracing.d.ts +73 -0
  166. package/dist/observability/tracing.d.ts.map +1 -0
  167. package/dist/observability/tracing.js +126 -0
  168. package/dist/observability/tracing.js.map +1 -0
  169. package/dist/onboarding/default-steps.d.ts.map +1 -1
  170. package/dist/onboarding/default-steps.js +4 -1
  171. package/dist/onboarding/default-steps.js.map +1 -1
  172. package/dist/provider-api/actions/query-staged-dataset.d.ts +1 -1
  173. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  174. package/dist/scripts/agent-engines/list-agent-engines.js +10 -3
  175. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  176. package/dist/server/action-discovery.d.ts.map +1 -1
  177. package/dist/server/action-discovery.js +4 -0
  178. package/dist/server/action-discovery.js.map +1 -1
  179. package/dist/server/agent-chat-plugin.d.ts +9 -0
  180. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  181. package/dist/server/agent-chat-plugin.js +118 -110
  182. package/dist/server/agent-chat-plugin.js.map +1 -1
  183. package/dist/server/agent-teams.d.ts +62 -0
  184. package/dist/server/agent-teams.d.ts.map +1 -1
  185. package/dist/server/agent-teams.js +99 -2
  186. package/dist/server/agent-teams.js.map +1 -1
  187. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  188. package/dist/server/core-routes-plugin.js +7 -4
  189. package/dist/server/core-routes-plugin.js.map +1 -1
  190. package/dist/server/credential-provider.d.ts.map +1 -1
  191. package/dist/server/credential-provider.js +2 -0
  192. package/dist/server/credential-provider.js.map +1 -1
  193. package/dist/server/framework-request-handler.d.ts.map +1 -1
  194. package/dist/server/framework-request-handler.js +33 -1
  195. package/dist/server/framework-request-handler.js.map +1 -1
  196. package/dist/server/index.d.ts +1 -0
  197. package/dist/server/index.d.ts.map +1 -1
  198. package/dist/server/index.js +1 -0
  199. package/dist/server/index.js.map +1 -1
  200. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +10 -0
  201. package/dist/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +20 -0
  202. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  203. package/docs/content/agent-teams.md +32 -0
  204. package/docs/content/blueprint-installer.md +73 -0
  205. package/docs/content/evals.md +141 -0
  206. package/docs/content/pr-visual-recap.md +7 -4
  207. package/docs/content/sandbox-adapters.md +134 -0
  208. package/docs/content/template-plan.md +20 -8
  209. package/package.json +5 -1
  210. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +10 -0
  211. package/src/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +20 -0
  212. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
@@ -2,17 +2,29 @@
2
2
  * Plan helper commands.
3
3
  *
4
4
  * The `plan local` commands are intentionally separate from the Plan app
5
- * actions. They do not call MCP, HTTP, SQLite, or the Plan template runtime;
6
- * they only read and write local files so privacy-focused users have an
7
- * auditable no-DB path. The top-level `plan blocks` command is a schema-only,
8
- * no-auth helper for fetching the public block catalog before authoring local
9
- * MDX; it never sends plan content.
5
+ * actions. They do not call MCP, hosted write actions, SQLite, or hosted
6
+ * storage; they only read local files or serve them from a localhost bridge so
7
+ * privacy-focused users have an auditable no-DB path. The top-level
8
+ * `plan blocks` command is a schema-only, no-auth helper for fetching the
9
+ * public block catalog before authoring local MDX; it never sends plan content.
10
10
  */
11
11
  import fs from "node:fs";
12
+ import crypto from "node:crypto";
13
+ import http from "node:http";
12
14
  import path from "node:path";
13
15
  import { spawnSync } from "node:child_process";
14
16
  import { pathToFileURL } from "node:url";
15
17
  import { DEFAULT_PLAN_APP_URL, defaultPlanBlocksOut, fetchPlanBlockCatalog, normalizePlanBlockFormat, } from "./plan-blocks.js";
18
+ const LOCAL_PLAN_ASSET_MAX_SINGLE_BYTES = 2 * 1024 * 1024;
19
+ const LOCAL_PLAN_ASSET_MAX_TOTAL_BYTES = 10 * 1024 * 1024;
20
+ const LOCAL_PLAN_ASSET_EXTENSIONS = new Set([
21
+ "png",
22
+ "jpg",
23
+ "jpeg",
24
+ "gif",
25
+ "webp",
26
+ "svg",
27
+ ]);
16
28
  function parseArgs(argv) {
17
29
  const out = {};
18
30
  for (let i = 0; i < argv.length; i += 1) {
@@ -70,12 +82,23 @@ function defaultLocalPlanAppUrl() {
70
82
  process.env.PLAN_BASE_URL ||
71
83
  "http://localhost:8096");
72
84
  }
85
+ function defaultLocalPlanBridgeAppUrl() {
86
+ return (process.env.PLAN_LOCAL_BRIDGE_APP_URL ||
87
+ process.env.PLAN_BASE_URL ||
88
+ DEFAULT_PLAN_APP_URL);
89
+ }
73
90
  function normalizeAppUrl(value) {
74
91
  return (value || defaultLocalPlanAppUrl()).replace(/\/+$/, "");
75
92
  }
93
+ function normalizeBridgeAppUrl(value) {
94
+ return (value || defaultLocalPlanBridgeAppUrl()).replace(/\/+$/, "");
95
+ }
76
96
  function localPlanPreviewUrl(dir, appUrl) {
77
97
  return `${normalizeAppUrl(appUrl)}/local-plans/${encodeURIComponent(path.basename(path.resolve(dir)))}`;
78
98
  }
99
+ function localPlanBridgePageUrl(input) {
100
+ return `${normalizeBridgeAppUrl(input.appUrl)}/local-plans/${encodeURIComponent(path.basename(path.resolve(input.dir)))}?bridge=${encodeURIComponent(input.bridgeUrl)}`;
101
+ }
79
102
  function openLocalUrl(url) {
80
103
  const platform = process.platform;
81
104
  const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
@@ -241,6 +264,33 @@ function renderMarkdownish(source) {
241
264
  flushList();
242
265
  return html.join("\n");
243
266
  }
267
+ function readLocalPlanAssets(dir) {
268
+ const assetsDir = path.join(dir, "assets");
269
+ if (!fs.existsSync(assetsDir))
270
+ return undefined;
271
+ const assets = {};
272
+ let totalBytes = 0;
273
+ for (const entry of fs.readdirSync(assetsDir, { withFileTypes: true })) {
274
+ if (!entry.isFile())
275
+ continue;
276
+ const filename = path.basename(entry.name);
277
+ if (!filename || filename !== entry.name)
278
+ continue;
279
+ const ext = filename.split(".").pop()?.toLowerCase() ?? "";
280
+ if (!LOCAL_PLAN_ASSET_EXTENSIONS.has(ext))
281
+ continue;
282
+ const abs = path.join(assetsDir, filename);
283
+ const bytes = fs.readFileSync(abs);
284
+ if (bytes.byteLength > LOCAL_PLAN_ASSET_MAX_SINGLE_BYTES)
285
+ continue;
286
+ if (totalBytes + bytes.byteLength > LOCAL_PLAN_ASSET_MAX_TOTAL_BYTES) {
287
+ continue;
288
+ }
289
+ totalBytes += bytes.byteLength;
290
+ assets[filename] = bytes.toString("base64");
291
+ }
292
+ return Object.keys(assets).length > 0 ? assets : undefined;
293
+ }
244
294
  export function readLocalPlanFiles(dir) {
245
295
  const resolved = path.resolve(dir);
246
296
  const planPath = path.join(resolved, "plan.mdx");
@@ -257,10 +307,244 @@ export function readLocalPlanFiles(dir) {
257
307
  canvasMdx: readOptional("canvas.mdx"),
258
308
  prototypeMdx: readOptional("prototype.mdx"),
259
309
  stateJson: readOptional(".plan-state.json"),
310
+ assets: readLocalPlanAssets(resolved),
311
+ };
312
+ }
313
+ function localPlanMdxFolder(files) {
314
+ return {
315
+ "plan.mdx": files.planMdx,
316
+ ...(files.canvasMdx ? { "canvas.mdx": files.canvasMdx } : {}),
317
+ ...(files.prototypeMdx ? { "prototype.mdx": files.prototypeMdx } : {}),
318
+ ...(files.stateJson ? { ".plan-state.json": files.stateJson } : {}),
319
+ ...(files.assets ? { "assets/": files.assets } : {}),
260
320
  };
261
321
  }
322
+ function localPlanFileList(files) {
323
+ return [
324
+ "plan.mdx",
325
+ ...(files.canvasMdx ? ["canvas.mdx"] : []),
326
+ ...(files.prototypeMdx ? ["prototype.mdx"] : []),
327
+ ...(files.stateJson ? [".plan-state.json"] : []),
328
+ ...Object.keys(files.assets ?? {}).map((filename) => `assets/${filename}`),
329
+ ];
330
+ }
331
+ function localPlanSourceEntries(files) {
332
+ return [
333
+ { file: "plan.mdx", source: files.planMdx },
334
+ ...(files.canvasMdx
335
+ ? [{ file: "canvas.mdx", source: files.canvasMdx }]
336
+ : []),
337
+ ...(files.prototypeMdx
338
+ ? [{ file: "prototype.mdx", source: files.prototypeMdx }]
339
+ : []),
340
+ ];
341
+ }
342
+ function lineNumberAt(source, index) {
343
+ let line = 1;
344
+ for (let i = 0; i < index; i += 1) {
345
+ if (source.charCodeAt(i) === 10)
346
+ line += 1;
347
+ }
348
+ return line;
349
+ }
350
+ function maskFencedCode(source) {
351
+ return source.replace(/(^|\n)(```|~~~)[\s\S]*?(\n\2[^\n]*(?=\n|$))/g, (match) => match.replace(/[^\n]/g, " "));
352
+ }
353
+ function findJsxOpeningTagEnd(source, start) {
354
+ let quote = null;
355
+ let braceDepth = 0;
356
+ for (let i = start; i < source.length; i += 1) {
357
+ const char = source[i];
358
+ if (quote) {
359
+ if (char === "\\" && i + 1 < source.length) {
360
+ i += 1;
361
+ continue;
362
+ }
363
+ if (char === quote)
364
+ quote = null;
365
+ continue;
366
+ }
367
+ if (char === '"' || char === "'" || char === "`") {
368
+ quote = char;
369
+ continue;
370
+ }
371
+ if (char === "{") {
372
+ braceDepth += 1;
373
+ continue;
374
+ }
375
+ if (char === "}") {
376
+ braceDepth = Math.max(0, braceDepth - 1);
377
+ continue;
378
+ }
379
+ if (char === ">" && braceDepth === 0)
380
+ return i;
381
+ }
382
+ return -1;
383
+ }
384
+ function addValidationIssue(issues, file, source, index, message) {
385
+ issues.push({ file, line: lineNumberAt(source, index), message });
386
+ }
387
+ const ENTITY_RE = /&(?:[a-z][a-z0-9]+|#[0-9]+|#x[0-9a-f]+);/gi;
388
+ const HTML_TEXT_ATTR_RE = /\b(?:aria-label|alt|placeholder|title|value)=\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/gi;
389
+ const WIREFRAME_TEXT_ATTR_RE = /\b(?:text|value|label|placeholder|title|note|due)=\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/gi;
390
+ function normalizeVisibleText(value) {
391
+ return value
392
+ .replace(/&nbsp;|&#160;|&#x0*a0;/gi, " ")
393
+ .replace(ENTITY_RE, "x")
394
+ .replace(/\s+/g, " ")
395
+ .trim();
396
+ }
397
+ function meaningfulTextLength(value) {
398
+ return normalizeVisibleText(value ?? "").length;
399
+ }
400
+ function htmlMeaningfulTextLength(html) {
401
+ let length = 0;
402
+ for (const match of html.matchAll(HTML_TEXT_ATTR_RE)) {
403
+ length += meaningfulTextLength(match[1] ?? match[2] ?? match[3]);
404
+ }
405
+ const visibleText = html
406
+ .replace(/<!--[\s\S]*?-->/g, " ")
407
+ .replace(/<script\b[\s\S]*?<\/script>/gi, " ")
408
+ .replace(/<style\b[\s\S]*?<\/style>/gi, " ")
409
+ .replace(/<[^>]+>/g, " ");
410
+ length += meaningfulTextLength(visibleText);
411
+ return length;
412
+ }
413
+ function hasSkeletonGeometry(html) {
414
+ return (/<(?:div|span|section|main|article|ul|li)\b/i.test(html) &&
415
+ /\b(?:height|width|background|border|padding|wf-card|wf-box|wf-pill|wf-chip)\b/i.test(html));
416
+ }
417
+ function stringAttributeValues(source, name) {
418
+ const values = [];
419
+ const re = new RegExp(`\\b${name}\\s*=\\s*(?:\\{\\s*\`([\\s\\S]*?)\`\\s*\\}|\\{\\s*"([^"]*)"\\s*\\}|\\{\\s*'([^']*)'\\s*\\}|"([^"]*)"|'([^']*)')`, "g");
420
+ for (const match of source.matchAll(re)) {
421
+ const value = match[1] ?? match[2] ?? match[3] ?? match[4] ?? match[5];
422
+ if (value !== undefined)
423
+ values.push(value);
424
+ }
425
+ return values;
426
+ }
427
+ function hasUnparsedAttributeExpression(source, name) {
428
+ return new RegExp(`\\b${name}\\s*=\\s*\\{`).test(source);
429
+ }
430
+ function hasMeaningfulWireframeHtml(screenOpening) {
431
+ const htmlValues = stringAttributeValues(screenOpening, "html");
432
+ if (htmlValues.length === 0) {
433
+ return hasUnparsedAttributeExpression(screenOpening, "html") ? null : false;
434
+ }
435
+ return htmlValues.some((html) => htmlMeaningfulTextLength(html) >= 2 || hasSkeletonGeometry(html));
436
+ }
437
+ function hasMeaningfulKitScreen(screenSource) {
438
+ for (const match of screenSource.matchAll(WIREFRAME_TEXT_ATTR_RE)) {
439
+ if (meaningfulTextLength(match[1] ?? match[2] ?? match[3]) >= 2) {
440
+ return true;
441
+ }
442
+ }
443
+ if (/\bitems\s*=\s*\{[\s\S]*?\blabel\s*:/i.test(screenSource))
444
+ return true;
445
+ if (/\brows\s*=\s*\{[\s\S]*?\b[klv]\s*:/i.test(screenSource))
446
+ return true;
447
+ return false;
448
+ }
449
+ function hasMeaningfulWireframeScreen(blockSource) {
450
+ const screenMatch = /<Screen\b/.exec(blockSource);
451
+ if (!screenMatch)
452
+ return false;
453
+ const screenStart = screenMatch.index;
454
+ const screenOpeningEnd = findJsxOpeningTagEnd(blockSource, screenStart);
455
+ if (screenOpeningEnd < 0)
456
+ return false;
457
+ const screenOpening = blockSource.slice(screenStart, screenOpeningEnd + 1);
458
+ const htmlMeaningful = hasMeaningfulWireframeHtml(screenOpening);
459
+ if (htmlMeaningful === true)
460
+ return true;
461
+ const selfClosing = /\/\s*>$/.test(screenOpening);
462
+ const closeIndex = selfClosing
463
+ ? -1
464
+ : blockSource.indexOf("</Screen>", screenOpeningEnd + 1);
465
+ const screenSource = closeIndex >= 0
466
+ ? blockSource.slice(screenStart, closeIndex + "</Screen>".length)
467
+ : screenOpening;
468
+ if (hasMeaningfulKitScreen(screenSource))
469
+ return true;
470
+ return htmlMeaningful === null ? null : false;
471
+ }
472
+ function lintWireframeBlocks(file, source, issues) {
473
+ const scanSource = maskFencedCode(source);
474
+ const re = /<WireframeBlock\b/g;
475
+ let match;
476
+ while ((match = re.exec(scanSource))) {
477
+ const start = match.index;
478
+ const openingEnd = findJsxOpeningTagEnd(scanSource, start);
479
+ if (openingEnd < 0) {
480
+ addValidationIssue(issues, file, source, start, "WireframeBlock opening tag is not closed.");
481
+ continue;
482
+ }
483
+ const opening = scanSource.slice(start, openingEnd + 1);
484
+ const unsupportedAttr = opening.match(/\b(data|screens|screen|elements)\s*=/);
485
+ if (unsupportedAttr) {
486
+ addValidationIssue(issues, file, source, start, `WireframeBlock uses unsupported "${unsupportedAttr[1]}" prop. Put content inside a <Screen> child instead.`);
487
+ }
488
+ const selfClosing = /\/\s*>$/.test(opening);
489
+ const closeTag = "</WireframeBlock>";
490
+ const closeIndex = selfClosing
491
+ ? -1
492
+ : scanSource.indexOf(closeTag, openingEnd + 1);
493
+ const blockSource = selfClosing
494
+ ? opening
495
+ : closeIndex >= 0
496
+ ? scanSource.slice(start, closeIndex + closeTag.length)
497
+ : scanSource.slice(start, openingEnd + 1);
498
+ if (!selfClosing && closeIndex < 0) {
499
+ addValidationIssue(issues, file, source, start, "WireframeBlock must have a closing </WireframeBlock> tag.");
500
+ }
501
+ if (selfClosing || !/<Screen\b/.test(blockSource)) {
502
+ addValidationIssue(issues, file, source, start, 'WireframeBlock must wrap a <Screen> child; self-closing wireframes render empty. Use <WireframeBlock><Screen surface="browser">...</Screen></WireframeBlock>.');
503
+ continue;
504
+ }
505
+ const meaningfulScreen = hasMeaningfulWireframeScreen(blockSource);
506
+ if (meaningfulScreen === false) {
507
+ addValidationIssue(issues, file, source, start, 'WireframeBlock contains an empty <Screen>; local previews render blank wireframes. Add visible html text/controls or kit nodes such as <Title text="Checkout" /> and <Btn label="Pay" />.');
508
+ }
509
+ }
510
+ }
511
+ function lintColumnsBlocks(file, source, issues) {
512
+ const scanSource = maskFencedCode(source);
513
+ const re = /<Columns\b/g;
514
+ let match;
515
+ while ((match = re.exec(scanSource))) {
516
+ const start = match.index;
517
+ const openingEnd = findJsxOpeningTagEnd(scanSource, start);
518
+ if (openingEnd < 0)
519
+ continue;
520
+ const opening = scanSource.slice(start, openingEnd + 1);
521
+ if (/\bcolumns\s*=/.test(opening)) {
522
+ addValidationIssue(issues, file, source, start, 'Columns must use <Column> children, not a columns= prop. Use <Columns><Column label="Before">...</Column><Column label="After">...</Column></Columns>.');
523
+ }
524
+ }
525
+ }
526
+ export function validateLocalPlanFiles(files) {
527
+ const issues = [];
528
+ for (const entry of localPlanSourceEntries(files)) {
529
+ lintWireframeBlocks(entry.file, entry.source, issues);
530
+ lintColumnsBlocks(entry.file, entry.source, issues);
531
+ }
532
+ return issues;
533
+ }
534
+ export function assertLocalPlanFilesValid(files) {
535
+ const issues = validateLocalPlanFiles(files);
536
+ if (issues.length === 0)
537
+ return;
538
+ const details = issues
539
+ .slice(0, 8)
540
+ .map((issue) => `${issue.file}:${issue.line} ${issue.message}`)
541
+ .join("\n");
542
+ const overflow = issues.length > 8 ? `\n...plus ${issues.length - 8} more issues` : "";
543
+ throw new Error(`Local plan source validation failed:\n${details}${overflow}\nRun \`npx @agent-native/core@latest plan blocks --out plan-blocks.md\` and update the MDX to the documented block shapes.`);
544
+ }
262
545
  export function buildLocalPlanPreviewHtml(input) {
263
546
  const files = readLocalPlanFiles(input.dir);
547
+ assertLocalPlanFilesValid(files);
264
548
  const parsed = stripFrontmatter(files.planMdx);
265
549
  const title = input.title ||
266
550
  parsed.frontmatter.title ||
@@ -358,7 +642,9 @@ export function buildLocalPlanPreviewHtml(input) {
358
642
  }
359
643
  export function writeLocalPlanPreview(input) {
360
644
  const dir = path.resolve(input.dir);
361
- const parsed = stripFrontmatter(readLocalPlanFiles(dir).planMdx);
645
+ const files = readLocalPlanFiles(dir);
646
+ assertLocalPlanFilesValid(files);
647
+ const parsed = stripFrontmatter(files.planMdx);
362
648
  const kind = input.kind || normalizeKind(parsed.frontmatter.kind);
363
649
  const title = input.title ||
364
650
  parsed.frontmatter.title ||
@@ -369,12 +655,6 @@ export function writeLocalPlanPreview(input) {
369
655
  fs.mkdirSync(path.dirname(out), { recursive: true });
370
656
  fs.writeFileSync(out, buildLocalPlanPreviewHtml({ ...input, dir, kind }));
371
657
  }
372
- const files = [
373
- "plan.mdx",
374
- "canvas.mdx",
375
- "prototype.mdx",
376
- ".plan-state.json",
377
- ].filter((file) => fs.existsSync(path.join(dir, file)));
378
658
  const result = {
379
659
  ok: true,
380
660
  dir,
@@ -382,7 +662,7 @@ export function writeLocalPlanPreview(input) {
382
662
  url: out ? pathToFileURL(out).href : localPlanPreviewUrl(dir, input.appUrl),
383
663
  title,
384
664
  kind,
385
- files,
665
+ files: localPlanFileList(files),
386
666
  };
387
667
  if (!input.open)
388
668
  return result;
@@ -394,6 +674,160 @@ export function writeLocalPlanPreview(input) {
394
674
  ...(openResult.error ? { openError: openResult.error } : {}),
395
675
  };
396
676
  }
677
+ function buildLocalPlanBridgePayload(input) {
678
+ const dir = path.resolve(input.dir);
679
+ const files = readLocalPlanFiles(dir);
680
+ assertLocalPlanFilesValid(files);
681
+ const parsed = stripFrontmatter(files.planMdx);
682
+ const kind = input.kind || normalizeKind(parsed.frontmatter.kind);
683
+ const title = input.title ||
684
+ parsed.frontmatter.title ||
685
+ firstHeading(parsed.body) ||
686
+ path.basename(dir);
687
+ const brief = input.brief || parsed.frontmatter.brief || "";
688
+ return {
689
+ ok: true,
690
+ version: 1,
691
+ source: "agent-native-local-bridge",
692
+ localOnly: true,
693
+ slug: path.basename(dir),
694
+ dir,
695
+ title,
696
+ brief,
697
+ kind,
698
+ updatedAt: latestLocalPlanMtime(dir, files),
699
+ files: localPlanFileList(files),
700
+ mdx: localPlanMdxFolder(files),
701
+ };
702
+ }
703
+ function latestLocalPlanMtime(dir, files) {
704
+ const candidates = [
705
+ path.join(dir, "plan.mdx"),
706
+ ...(files.canvasMdx ? [path.join(dir, "canvas.mdx")] : []),
707
+ ...(files.prototypeMdx ? [path.join(dir, "prototype.mdx")] : []),
708
+ ...(files.stateJson ? [path.join(dir, ".plan-state.json")] : []),
709
+ ...Object.keys(files.assets ?? {}).map((filename) => path.join(dir, "assets", filename)),
710
+ ];
711
+ let latest = 0;
712
+ for (const file of candidates) {
713
+ try {
714
+ latest = Math.max(latest, fs.statSync(file).mtimeMs);
715
+ }
716
+ catch {
717
+ // Ignore files deleted between the read and stat passes.
718
+ }
719
+ }
720
+ return new Date(latest || Date.now()).toISOString();
721
+ }
722
+ function sendBridgeJson(res, status, payload) {
723
+ res.writeHead(status, {
724
+ "access-control-allow-origin": "*",
725
+ "access-control-allow-methods": "GET, OPTIONS",
726
+ "access-control-allow-headers": "content-type",
727
+ "cache-control": "no-store",
728
+ "content-type": "application/json; charset=utf-8",
729
+ "x-agent-native-local-bridge": "1",
730
+ });
731
+ res.end(`${JSON.stringify(payload)}\n`);
732
+ }
733
+ function bridgeRequestUrl(req) {
734
+ return new URL(req.url || "/", "http://127.0.0.1");
735
+ }
736
+ function bridgeHostForUrl(host) {
737
+ if (host === "0.0.0.0" || host === "::")
738
+ return "127.0.0.1";
739
+ return host;
740
+ }
741
+ export async function startLocalPlanBridge(input) {
742
+ const dir = path.resolve(input.dir);
743
+ const initialPayload = buildLocalPlanBridgePayload({
744
+ dir,
745
+ kind: input.kind,
746
+ title: input.title,
747
+ brief: input.brief,
748
+ });
749
+ const token = input.token || crypto.randomBytes(24).toString("base64url");
750
+ const host = input.host || "127.0.0.1";
751
+ const appUrl = normalizeBridgeAppUrl(input.appUrl);
752
+ const server = http.createServer((req, res) => {
753
+ if (req.method === "OPTIONS") {
754
+ sendBridgeJson(res, 204, "");
755
+ return;
756
+ }
757
+ if (req.method !== "GET") {
758
+ sendBridgeJson(res, 405, { ok: false, error: "Method not allowed." });
759
+ return;
760
+ }
761
+ const url = bridgeRequestUrl(req);
762
+ if (url.pathname !== "/local-plan.json") {
763
+ sendBridgeJson(res, 404, { ok: false, error: "Not found." });
764
+ return;
765
+ }
766
+ if (url.searchParams.get("token") !== token) {
767
+ sendBridgeJson(res, 403, { ok: false, error: "Invalid bridge token." });
768
+ return;
769
+ }
770
+ try {
771
+ sendBridgeJson(res, 200, buildLocalPlanBridgePayload({
772
+ dir,
773
+ kind: input.kind,
774
+ title: input.title,
775
+ brief: input.brief,
776
+ }));
777
+ }
778
+ catch (error) {
779
+ sendBridgeJson(res, 500, {
780
+ ok: false,
781
+ error: error instanceof Error ? error.message : String(error),
782
+ });
783
+ }
784
+ });
785
+ await new Promise((resolve, reject) => {
786
+ const onError = (error) => {
787
+ server.off("listening", onListening);
788
+ reject(error);
789
+ };
790
+ const onListening = () => {
791
+ server.off("error", onError);
792
+ resolve();
793
+ };
794
+ server.once("error", onError);
795
+ server.once("listening", onListening);
796
+ server.listen(input.port ?? 0, host);
797
+ });
798
+ const address = server.address();
799
+ if (!address || typeof address === "string") {
800
+ server.close();
801
+ throw new Error("Local plan bridge did not bind to a TCP port.");
802
+ }
803
+ const bridgeUrl = `http://${bridgeHostForUrl(host)}:${address.port}/local-plan.json?token=${encodeURIComponent(token)}`;
804
+ const url = localPlanBridgePageUrl({ dir, bridgeUrl, appUrl });
805
+ const openResult = input.open
806
+ ? (input.openUrl || openLocalUrl)(url)
807
+ : undefined;
808
+ return {
809
+ server,
810
+ result: {
811
+ ok: true,
812
+ dir,
813
+ url,
814
+ bridgeUrl,
815
+ appUrl,
816
+ title: initialPayload.title,
817
+ kind: initialPayload.kind,
818
+ files: initialPayload.files,
819
+ host,
820
+ port: address.port,
821
+ ...(openResult
822
+ ? {
823
+ opened: openResult.ok,
824
+ openCommand: openResult.command,
825
+ ...(openResult.error ? { openError: openResult.error } : {}),
826
+ }
827
+ : {}),
828
+ },
829
+ };
830
+ }
397
831
  function writeLocalPlanSkeleton(input) {
398
832
  const dir = path.resolve(input.dir || path.join(defaultPlansDir(), localPlanFolderName(input.title)));
399
833
  const planPath = path.join(dir, "plan.mdx");
@@ -421,9 +855,9 @@ function writeLocalPlanSkeleton(input) {
421
855
  "## Review Surface",
422
856
  "",
423
857
  "Author the structured plan or recap here. You can add Agent-Native Plan MDX",
424
- "blocks such as `<WireframeBlock />`, `<Diagram />`, `<TabsBlock />`,",
425
- "`<FileTree />`, or `<Diff />`; the local preview will show the source",
426
- "without publishing it to the Plan app.",
858
+ 'blocks such as `<WireframeBlock><Screen surface="browser">...</Screen></WireframeBlock>`,',
859
+ "`<Diagram />`, `<TabsBlock />`, `<FileTree />`, or `<Diff />`; the local",
860
+ "preview will show the source without publishing it to the Plan app.",
427
861
  "",
428
862
  ].join("\n");
429
863
  fs.writeFileSync(planPath, mdx, "utf-8");
@@ -448,10 +882,12 @@ function runInit(args) {
448
882
  function runCheck(args) {
449
883
  const dir = stringArg(args, "dir");
450
884
  const files = readLocalPlanFiles(dir);
885
+ assertLocalPlanFilesValid(files);
451
886
  const parsed = stripFrontmatter(files.planMdx);
452
887
  const result = {
453
888
  ok: true,
454
889
  noDb: true,
890
+ validation: "passed",
455
891
  dir: files.dir,
456
892
  title: parsed.frontmatter.title || firstHeading(parsed.body),
457
893
  kind: normalizeKind(parsed.frontmatter.kind),
@@ -466,6 +902,12 @@ function runCheck(args) {
466
902
  ...(files.stateJson
467
903
  ? { ".plan-state.json": Buffer.byteLength(files.stateJson) }
468
904
  : {}),
905
+ ...(files.assets
906
+ ? Object.fromEntries(Object.entries(files.assets).map(([filename, base64]) => [
907
+ `assets/${filename}`,
908
+ Buffer.byteLength(base64, "base64"),
909
+ ]))
910
+ : {}),
469
911
  },
470
912
  };
471
913
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
@@ -484,6 +926,34 @@ function runPreview(args) {
484
926
  });
485
927
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
486
928
  }
929
+ async function runServe(args) {
930
+ const portValue = optionalArg(args, "port");
931
+ const port = portValue ? Number(portValue) : undefined;
932
+ if (portValue && (!Number.isInteger(port) || port < 0 || port > 65535)) {
933
+ throw new Error("--port must be an integer between 0 and 65535.");
934
+ }
935
+ const bridge = await startLocalPlanBridge({
936
+ dir: stringArg(args, "dir"),
937
+ appUrl: optionalArg(args, "app-url"),
938
+ title: optionalArg(args, "title"),
939
+ brief: optionalArg(args, "brief"),
940
+ host: optionalArg(args, "host"),
941
+ port,
942
+ open: boolArg(args, "open"),
943
+ kind: optionalArg(args, "kind")
944
+ ? normalizeKind(optionalArg(args, "kind"))
945
+ : undefined,
946
+ });
947
+ process.stdout.write(`${JSON.stringify(bridge.result, null, 2)}\n`);
948
+ process.stderr.write(`Local Plan bridge running at ${bridge.result.bridgeUrl}\nPress Ctrl+C to stop.\n`);
949
+ await new Promise((resolve) => {
950
+ const stop = () => {
951
+ bridge.server.close(() => resolve());
952
+ };
953
+ process.once("SIGINT", stop);
954
+ process.once("SIGTERM", stop);
955
+ });
956
+ }
487
957
  async function runBlocks(args) {
488
958
  const format = normalizePlanBlockFormat(optionalArg(args, "format"));
489
959
  const appUrl = optionalArg(args, "app-url") ||
@@ -536,6 +1006,7 @@ Usage:
536
1006
  agent-native plan blocks [--format reference|schema] [--app-url <url>] [--out <file>] [--json]
537
1007
  agent-native plan local init --title <title> [--brief <text>] [--kind plan|recap] [--dir <folder>] [--force]
538
1008
  agent-native plan local check --dir <folder>
1009
+ agent-native plan local serve --dir <folder> [--app-url <url>] [--kind plan|recap] [--open] [--port <port>]
539
1010
  agent-native plan local preview --dir <folder> [--app-url <url>] [--kind plan|recap] [--open] [--out preview.html]
540
1011
 
541
1012
  The blocks command fetches the no-auth, read-only get-plan-blocks catalog from
@@ -552,10 +1023,12 @@ write actions, hosted storage, or SQLite.
552
1023
  Common flow:
553
1024
  agent-native plan blocks --out plan-blocks.md
554
1025
  agent-native plan local init --title "Checkout review" --kind plan
555
- agent-native plan local preview --dir plans/checkout-review --open
1026
+ agent-native plan local serve --dir plans/checkout-review --open
556
1027
 
557
- \`plan local preview\` opens the local Plan app route by default. Pass
558
- \`--app-url\` when your local Plan app is on a non-default port. \`--out\` is a
1028
+ \`plan local serve\` starts a tiny localhost bridge and opens the hosted Plan UI
1029
+ against that local-only source. The hosted app fetches the MDX from localhost in
1030
+ the browser; it does not write plan content to the hosted database. Use
1031
+ \`plan local preview\` for a local Plan dev server route. \`preview --out\` is a
559
1032
  legacy/debug escape hatch that writes a standalone static HTML file.
560
1033
  `;
561
1034
  export async function runPlan(argv) {
@@ -592,6 +1065,9 @@ export async function runPlan(argv) {
592
1065
  case "preview":
593
1066
  runPreview(args);
594
1067
  return;
1068
+ case "serve":
1069
+ await runServe(args);
1070
+ return;
595
1071
  case "help":
596
1072
  case "--help":
597
1073
  case "-h":