@agent-native/core 0.84.0 → 0.84.2

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 (152) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/docs/content/evals.mdx +34 -12
  4. package/corpus/core/docs/content/locales/ar-SA/evals.mdx +7 -0
  5. package/corpus/core/docs/content/locales/de-DE/evals.mdx +8 -0
  6. package/corpus/core/docs/content/locales/es-ES/evals.mdx +8 -0
  7. package/corpus/core/docs/content/locales/fr-FR/evals.mdx +8 -0
  8. package/corpus/core/docs/content/locales/hi-IN/evals.mdx +8 -0
  9. package/corpus/core/docs/content/locales/ja-JP/evals.mdx +7 -0
  10. package/corpus/core/docs/content/locales/ko-KR/evals.mdx +7 -0
  11. package/corpus/core/docs/content/locales/pt-BR/evals.mdx +8 -0
  12. package/corpus/core/docs/content/locales/zh-CN/evals.mdx +7 -0
  13. package/corpus/core/docs/content/locales/zh-TW/evals.mdx +7 -0
  14. package/corpus/core/docs/design/durable-agent-runs.md +34 -35
  15. package/corpus/core/package.json +1 -1
  16. package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
  17. package/corpus/core/src/agent/run-manager.ts +8 -7
  18. package/corpus/core/src/cli/design-connect.ts +175 -0
  19. package/corpus/core/src/cli/skills.ts +5 -7
  20. package/corpus/core/src/client/AgentPanel.tsx +8 -0
  21. package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
  22. package/corpus/core/src/client/agent-chat-adapter.ts +45 -15
  23. package/corpus/core/src/client/sse-event-processor.ts +31 -4
  24. package/corpus/core/src/eval/define-eval.ts +4 -1
  25. package/corpus/core/src/eval/report.ts +23 -8
  26. package/corpus/core/src/eval/runner.ts +41 -6
  27. package/corpus/core/src/eval/types.ts +11 -0
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  29. package/corpus/templates/content/AGENTS.md +9 -2
  30. package/corpus/templates/content/actions/_database-row-batch.ts +230 -0
  31. package/corpus/templates/content/actions/_database-utils.ts +17 -9
  32. package/corpus/templates/content/actions/_notion-action-utils.ts +26 -0
  33. package/corpus/templates/content/actions/connect-notion-status.ts +13 -4
  34. package/corpus/templates/content/actions/create-and-link-notion-page.ts +23 -0
  35. package/corpus/templates/content/actions/delete-database-items.ts +55 -0
  36. package/corpus/templates/content/actions/delete-document.ts +4 -4
  37. package/corpus/templates/content/actions/disconnect-notion.ts +16 -0
  38. package/corpus/templates/content/actions/duplicate-database-item.ts +1 -1
  39. package/corpus/templates/content/actions/duplicate-database-items.ts +191 -0
  40. package/corpus/templates/content/actions/link-notion-page.ts +10 -7
  41. package/corpus/templates/content/actions/list-notion-links.ts +3 -4
  42. package/corpus/templates/content/actions/pull-notion-page.ts +7 -9
  43. package/corpus/templates/content/actions/push-notion-page.ts +7 -9
  44. package/corpus/templates/content/actions/refresh-notion-sync-status.ts +24 -0
  45. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +23 -0
  46. package/corpus/templates/content/actions/search-notion-pages.ts +67 -0
  47. package/corpus/templates/content/actions/unlink-notion-page.ts +23 -0
  48. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +27 -34
  49. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +16 -13
  50. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +10 -10
  51. package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +1 -1
  52. package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +18 -9
  53. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +27 -34
  54. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +10 -4
  55. package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +32 -17
  56. package/corpus/templates/content/app/hooks/use-content-database.ts +35 -0
  57. package/corpus/templates/content/app/hooks/use-notion.ts +108 -99
  58. package/corpus/templates/content/package.json +3 -0
  59. package/corpus/templates/content/parity/README.md +69 -0
  60. package/corpus/templates/content/parity/eval-scenarios.ts +118 -0
  61. package/corpus/templates/content/parity/exceptions.allowlist.ts +17 -0
  62. package/corpus/templates/content/parity/matrix.md +28 -0
  63. package/corpus/templates/content/parity/matrix.ts +606 -0
  64. package/corpus/templates/content/parity/matrix.types.ts +42 -0
  65. package/corpus/templates/content/parity/parity-evals.eval.ts +4 -0
  66. package/corpus/templates/content/parity/render-matrix.ts +90 -0
  67. package/corpus/templates/content/parity/scenario-to-eval.ts +57 -0
  68. package/corpus/templates/content/shared/api.ts +11 -0
  69. package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
  70. package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
  71. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
  72. package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
  73. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
  74. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
  75. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
  76. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
  77. package/corpus/templates/design/app/components/design/types.ts +13 -0
  78. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  79. package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
  80. package/corpus/templates/design/app/i18n-data.ts +245 -0
  81. package/corpus/templates/design/app/pages/DesignEditor.tsx +762 -153
  82. package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
  83. package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
  84. package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
  85. package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
  86. package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
  87. package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
  88. package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
  89. package/corpus/templates/design/server/plugins/db.ts +10 -0
  90. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  91. package/dist/agent/engine/builder-engine.js +61 -10
  92. package/dist/agent/engine/builder-engine.js.map +1 -1
  93. package/dist/agent/run-manager.d.ts +8 -7
  94. package/dist/agent/run-manager.d.ts.map +1 -1
  95. package/dist/agent/run-manager.js +8 -7
  96. package/dist/agent/run-manager.js.map +1 -1
  97. package/dist/cli/design-connect.d.ts +2 -0
  98. package/dist/cli/design-connect.d.ts.map +1 -1
  99. package/dist/cli/design-connect.js +140 -0
  100. package/dist/cli/design-connect.js.map +1 -1
  101. package/dist/cli/skills.d.ts.map +1 -1
  102. package/dist/cli/skills.js +5 -7
  103. package/dist/cli/skills.js.map +1 -1
  104. package/dist/client/AgentPanel.d.ts +5 -1
  105. package/dist/client/AgentPanel.d.ts.map +1 -1
  106. package/dist/client/AgentPanel.js +4 -4
  107. package/dist/client/AgentPanel.js.map +1 -1
  108. package/dist/client/MultiTabAssistantChat.d.ts +3 -1
  109. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  110. package/dist/client/MultiTabAssistantChat.js +2 -2
  111. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  112. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  113. package/dist/client/agent-chat-adapter.js +37 -14
  114. package/dist/client/agent-chat-adapter.js.map +1 -1
  115. package/dist/client/sse-event-processor.d.ts +14 -1
  116. package/dist/client/sse-event-processor.d.ts.map +1 -1
  117. package/dist/client/sse-event-processor.js +21 -5
  118. package/dist/client/sse-event-processor.js.map +1 -1
  119. package/dist/collab/routes.d.ts +1 -1
  120. package/dist/eval/define-eval.d.ts.map +1 -1
  121. package/dist/eval/define-eval.js +2 -1
  122. package/dist/eval/define-eval.js.map +1 -1
  123. package/dist/eval/report.d.ts.map +1 -1
  124. package/dist/eval/report.js +19 -4
  125. package/dist/eval/report.js.map +1 -1
  126. package/dist/eval/runner.d.ts.map +1 -1
  127. package/dist/eval/runner.js +38 -6
  128. package/dist/eval/runner.js.map +1 -1
  129. package/dist/eval/types.d.ts +11 -0
  130. package/dist/eval/types.d.ts.map +1 -1
  131. package/dist/eval/types.js.map +1 -1
  132. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  133. package/dist/observability/routes.d.ts +2 -2
  134. package/dist/progress/routes.d.ts +1 -1
  135. package/dist/resources/handlers.d.ts +3 -3
  136. package/dist/server/transcribe-voice.d.ts +1 -1
  137. package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  138. package/docs/content/evals.mdx +34 -12
  139. package/docs/content/locales/ar-SA/evals.mdx +7 -0
  140. package/docs/content/locales/de-DE/evals.mdx +8 -0
  141. package/docs/content/locales/es-ES/evals.mdx +8 -0
  142. package/docs/content/locales/fr-FR/evals.mdx +8 -0
  143. package/docs/content/locales/hi-IN/evals.mdx +8 -0
  144. package/docs/content/locales/ja-JP/evals.mdx +7 -0
  145. package/docs/content/locales/ko-KR/evals.mdx +7 -0
  146. package/docs/content/locales/pt-BR/evals.mdx +8 -0
  147. package/docs/content/locales/zh-CN/evals.mdx +7 -0
  148. package/docs/content/locales/zh-TW/evals.mdx +7 -0
  149. package/docs/design/durable-agent-runs.md +34 -35
  150. package/package.json +1 -1
  151. package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  152. package/corpus/templates/content/server/routes/api/notion/disconnect.post.ts +0 -12
@@ -1,3 +1,4 @@
1
+ import { spawn } from "node:child_process";
1
2
  import crypto from "node:crypto";
2
3
  import fsSync from "node:fs";
3
4
  import fs from "node:fs/promises";
@@ -44,6 +45,7 @@ export interface DesignConnectArgs {
44
45
  json: boolean;
45
46
  once: boolean;
46
47
  dryRun: boolean;
48
+ daemon: boolean;
47
49
  help: boolean;
48
50
  }
49
51
 
@@ -115,6 +117,7 @@ export function parseDesignConnectArgs(argv: string[]): DesignConnectArgs {
115
117
  json: false,
116
118
  once: false,
117
119
  dryRun: false,
120
+ daemon: false,
118
121
  help: false,
119
122
  };
120
123
 
@@ -155,6 +158,8 @@ export function parseDesignConnectArgs(argv: string[]): DesignConnectArgs {
155
158
  } else if (arg === "--dry-run") {
156
159
  parsed.dryRun = true;
157
160
  parsed.once = true;
161
+ } else if (arg === "--daemon") {
162
+ parsed.daemon = true;
158
163
  } else if (arg.startsWith("-")) {
159
164
  throw new Error(`Unknown option: ${arg}`);
160
165
  } else {
@@ -165,6 +170,11 @@ export function parseDesignConnectArgs(argv: string[]): DesignConnectArgs {
165
170
  if (!Number.isInteger(parsed.port) || parsed.port <= 0) {
166
171
  throw new Error("--port must be a positive integer");
167
172
  }
173
+ if (parsed.daemon && (parsed.json || parsed.once || parsed.dryRun)) {
174
+ throw new Error(
175
+ "--daemon cannot be combined with --json, --once, or --dry-run",
176
+ );
177
+ }
168
178
  parsed.root = path.resolve(parsed.root);
169
179
  parsed.url = parsed.url ? normalizeHttpUrl(parsed.url) : undefined;
170
180
  return parsed;
@@ -298,6 +308,86 @@ async function probeDevServer(url: string): Promise<boolean> {
298
308
  }
299
309
  }
300
310
 
311
+ async function waitForBridgeHealth(
312
+ bridgeUrl: string,
313
+ timeoutMs = 5_000,
314
+ ): Promise<boolean> {
315
+ const healthUrl = new URL("/health", bridgeUrl).toString();
316
+ const deadline = Date.now() + timeoutMs;
317
+ while (Date.now() < deadline) {
318
+ const controller = new AbortController();
319
+ const timeout = setTimeout(() => controller.abort(), 400);
320
+ try {
321
+ const response = await fetch(healthUrl, {
322
+ method: "GET",
323
+ signal: controller.signal,
324
+ });
325
+ if (response.ok) return true;
326
+ } catch {
327
+ // Keep polling until the detached process finishes binding the port.
328
+ } finally {
329
+ clearTimeout(timeout);
330
+ }
331
+ await new Promise((resolve) => setTimeout(resolve, 150));
332
+ }
333
+ return false;
334
+ }
335
+
336
+ function isDesignConnectManifest(
337
+ value: unknown,
338
+ ): value is DesignConnectManifest {
339
+ if (!value || typeof value !== "object") return false;
340
+ const manifest = value as Partial<DesignConnectManifest>;
341
+ return (
342
+ manifest.version === 1 &&
343
+ manifest.source === "agent-native-design-connect" &&
344
+ manifest.sourceType === "localhost" &&
345
+ manifest.localOnly === true &&
346
+ typeof manifest.devServerUrl === "string" &&
347
+ typeof manifest.bridgeUrl === "string" &&
348
+ typeof manifest.rootPath === "string"
349
+ );
350
+ }
351
+
352
+ async function fetchRunningBridgeManifest(
353
+ bridgeUrl: string,
354
+ ): Promise<DesignConnectManifest | null> {
355
+ const manifestUrl = new URL("/manifest.json", bridgeUrl).toString();
356
+ const controller = new AbortController();
357
+ const timeout = setTimeout(() => controller.abort(), 800);
358
+ try {
359
+ const response = await fetch(manifestUrl, {
360
+ method: "GET",
361
+ signal: controller.signal,
362
+ });
363
+ if (!response.ok) return null;
364
+ const body = (await response.json()) as unknown;
365
+ return isDesignConnectManifest(body) ? body : null;
366
+ } catch {
367
+ return null;
368
+ } finally {
369
+ clearTimeout(timeout);
370
+ }
371
+ }
372
+
373
+ export function designConnectManifestsTargetSameApp(
374
+ running: Pick<DesignConnectManifest, "devServerUrl" | "rootPath">,
375
+ requested: Pick<DesignConnectManifest, "devServerUrl" | "rootPath">,
376
+ ): boolean {
377
+ let runningUrl = running.devServerUrl;
378
+ let requestedUrl = requested.devServerUrl;
379
+ try {
380
+ runningUrl = normalizeHttpUrl(runningUrl);
381
+ requestedUrl = normalizeHttpUrl(requestedUrl);
382
+ } catch {
383
+ // Fall through to direct string comparison for malformed legacy manifests.
384
+ }
385
+ return (
386
+ runningUrl === requestedUrl &&
387
+ path.resolve(running.rootPath) === path.resolve(requested.rootPath)
388
+ );
389
+ }
390
+
301
391
  async function resolveDevServerUrl(url?: string): Promise<string> {
302
392
  if (url) return normalizeHttpUrl(url);
303
393
  for (const candidate of DEFAULT_DEV_SERVER_CANDIDATES) {
@@ -942,6 +1032,7 @@ Options:
942
1032
  --route-manifest <path> Non-destructive route manifest output path
943
1033
  --app-url <url> Deployed design app URL for self-registration
944
1034
  (also reads AGENT_NATIVE_URL / DESIGN_APP_URL env)
1035
+ --daemon Start the bridge detached, wait for /health, then exit
945
1036
  --json Print the manifest JSON and exit
946
1037
  --once Prepare/scaffold the manifest and exit
947
1038
  --dry-run Print what would be exposed without writing files
@@ -966,6 +1057,87 @@ Element provenance (resolveNodeToFile):
966
1057
  Cross-origin localhost iframes cannot be read regardless of attributes (CSP).`);
967
1058
  }
968
1059
 
1060
+ function removeDaemonFlag(argv: string[]): string[] {
1061
+ return argv.filter((arg) => arg !== "--daemon");
1062
+ }
1063
+
1064
+ function resolveCurrentCliInvocation(argv: string[]): {
1065
+ command: string;
1066
+ args: string[];
1067
+ } {
1068
+ const suffixLength = argv.length + 1; // leading "design" command + runDesign argv
1069
+ const prefixEnd = Math.max(1, process.argv.length - suffixLength);
1070
+ const cliPrefix = process.argv.slice(1, prefixEnd);
1071
+ const entry = cliPrefix[0] ?? process.argv[1];
1072
+ if (!entry) {
1073
+ throw new Error("Could not resolve current CLI entrypoint for --daemon");
1074
+ }
1075
+ if (entry.endsWith(".ts") || entry.endsWith(".tsx")) {
1076
+ return {
1077
+ command: "tsx",
1078
+ args: [...cliPrefix, "design", ...removeDaemonFlag(argv)],
1079
+ };
1080
+ }
1081
+ return {
1082
+ command: process.execPath,
1083
+ args: [...cliPrefix, "design", ...removeDaemonFlag(argv)],
1084
+ };
1085
+ }
1086
+
1087
+ async function startDetachedDesignBridge(
1088
+ argv: string[],
1089
+ manifest: DesignConnectManifest,
1090
+ ): Promise<number> {
1091
+ if (await waitForBridgeHealth(manifest.bridgeUrl, 800)) {
1092
+ const runningManifest = await fetchRunningBridgeManifest(
1093
+ manifest.bridgeUrl,
1094
+ );
1095
+ if (
1096
+ runningManifest &&
1097
+ designConnectManifestsTargetSameApp(runningManifest, manifest)
1098
+ ) {
1099
+ console.error(
1100
+ `Design localhost bridge already running at ${manifest.bridgeUrl}`,
1101
+ );
1102
+ console.log(JSON.stringify(runningManifest, null, 2));
1103
+ return 0;
1104
+ }
1105
+
1106
+ console.error(
1107
+ [
1108
+ `Design localhost bridge already running at ${manifest.bridgeUrl} for a different app.`,
1109
+ runningManifest
1110
+ ? `Running app: ${runningManifest.devServerUrl} (${runningManifest.rootPath})`
1111
+ : "Running bridge did not expose a compatible manifest.",
1112
+ `Requested app: ${manifest.devServerUrl} (${manifest.rootPath})`,
1113
+ "Stop the existing bridge or choose a different --port.",
1114
+ ].join("\n"),
1115
+ );
1116
+ return 1;
1117
+ }
1118
+
1119
+ const invocation = resolveCurrentCliInvocation(argv);
1120
+ const child = spawn(invocation.command, invocation.args, {
1121
+ cwd: process.cwd(),
1122
+ detached: true,
1123
+ env: process.env,
1124
+ stdio: "ignore",
1125
+ shell: process.platform === "win32",
1126
+ });
1127
+ child.unref();
1128
+
1129
+ if (await waitForBridgeHealth(manifest.bridgeUrl)) {
1130
+ console.error(`Design localhost bridge running at ${manifest.bridgeUrl}`);
1131
+ console.log(JSON.stringify(manifest, null, 2));
1132
+ return 0;
1133
+ }
1134
+
1135
+ console.error(
1136
+ `Timed out waiting for detached Design bridge at ${manifest.bridgeUrl}`,
1137
+ );
1138
+ return 1;
1139
+ }
1140
+
969
1141
  export async function runDesign(argv: string[]) {
970
1142
  const subcommand = argv[0];
971
1143
  if (subcommand !== "connect") {
@@ -988,6 +1160,9 @@ export async function runDesign(argv: string[]) {
988
1160
  }
989
1161
 
990
1162
  const manifest = await prepareDesignConnectManifest(parsed);
1163
+ if (parsed.daemon) {
1164
+ return startDetachedDesignBridge(argv, manifest);
1165
+ }
991
1166
  if (parsed.json || parsed.once || parsed.dryRun) {
992
1167
  console.log(JSON.stringify(manifest, null, 2));
993
1168
  return 0;
@@ -464,18 +464,16 @@ iframe-backed screens on the infinite canvas.
464
464
  From the target app repo, make sure its dev server is running, then run:
465
465
 
466
466
  \`\`\`bash
467
- npx @agent-native/core@latest design connect --url http://localhost:5173 --root .
467
+ npx @agent-native/core@latest design connect --url http://localhost:5173 --root . --daemon
468
468
  \`\`\`
469
469
 
470
- Use the app's real port. The command starts a local bridge on
471
- \`http://127.0.0.1:7331\` by default and exposes \`/manifest.json\`,
472
- \`/routes.json\`, and \`/health\`.
470
+ Use the app's real port. The command starts a detached local bridge on
471
+ \`http://127.0.0.1:7331\` by default, waits for \`/health\`, prints the
472
+ manifest JSON, and keeps the bridge alive after the agent command exits.
473
473
 
474
- For one-shot agent setup, ask for JSON and keep the long-running bridge open in
475
- a second terminal if the user needs live updates:
474
+ For a manual health/manifest check:
476
475
 
477
476
  \`\`\`bash
478
- npx @agent-native/core@latest design connect --url http://localhost:5173 --root .
479
477
  curl http://127.0.0.1:7331/manifest.json
480
478
  \`\`\`
481
479
 
@@ -548,6 +548,8 @@ export interface AgentPanelProps extends Omit<
548
548
  * see the `Layout` files for each template.
549
549
  */
550
550
  scope?: import("./use-chat-threads.js").ChatThreadScope | null;
551
+ /** Show the compact scope chip above the composer. Default: true. */
552
+ showScopeBadge?: MultiTabAssistantChatProps["showScopeBadge"];
551
553
  /** Stable browser tab id used for tab-scoped app-state context. */
552
554
  browserTabId?: string;
553
555
  /** Keep chat thread selection in URL state. */
@@ -673,6 +675,7 @@ function AgentPanelInner({
673
675
  storageKey,
674
676
  restoreActiveThread = true,
675
677
  scope,
678
+ showScopeBadge,
676
679
  browserTabId,
677
680
  threadUrlSync,
678
681
  chatNotice,
@@ -1710,6 +1713,7 @@ function AgentPanelInner({
1710
1713
  storageKey={storageKey}
1711
1714
  restoreActiveThread={restoreActiveThread}
1712
1715
  scope={scope}
1716
+ showScopeBadge={showScopeBadge}
1713
1717
  browserTabId={browserTabId}
1714
1718
  threadUrlSync={threadUrlSync}
1715
1719
  />
@@ -2393,6 +2397,8 @@ export interface AgentSidebarProps {
2393
2397
  * Templates compute this from the active route (see template layouts).
2394
2398
  */
2395
2399
  scope?: import("./use-chat-threads.js").ChatThreadScope | null;
2400
+ /** Show the compact scope chip above the composer. Default: true. */
2401
+ showScopeBadge?: MultiTabAssistantChatProps["showScopeBadge"];
2396
2402
  /** Stable browser tab id used for tab-scoped app-state context. */
2397
2403
  browserTabId?: string;
2398
2404
  /** Keep chat thread selection in URL state. */
@@ -2422,6 +2428,7 @@ export function AgentSidebar({
2422
2428
  openOnChatRunning = false,
2423
2429
  onFullscreenRequest,
2424
2430
  scope,
2431
+ showScopeBadge,
2425
2432
  browserTabId,
2426
2433
  threadUrlSync,
2427
2434
  }: AgentSidebarProps) {
@@ -2955,6 +2962,7 @@ export function AgentSidebar({
2955
2962
  }
2956
2963
  storageKey={storageKey}
2957
2964
  scope={scope}
2965
+ showScopeBadge={showScopeBadge}
2958
2966
  browserTabId={browserTabId}
2959
2967
  threadUrlSync={threadUrlSync}
2960
2968
  />
@@ -929,6 +929,8 @@ export type MultiTabAssistantChatProps = Omit<
929
929
  * the composer.
930
930
  */
931
931
  scope?: ChatThreadScope | null;
932
+ /** Show the compact scope chip above the composer. Default: true. */
933
+ showScopeBadge?: boolean;
932
934
  };
933
935
 
934
936
  export function MultiTabAssistantChat({
@@ -942,6 +944,7 @@ export function MultiTabAssistantChat({
942
944
  browserTabId,
943
945
  threadUrlSync = false,
944
946
  scope = null,
947
+ showScopeBadge = true,
945
948
  ...props
946
949
  }: MultiTabAssistantChatProps) {
947
950
  const {
@@ -2625,7 +2628,7 @@ export function MultiTabAssistantChat({
2625
2628
  ? props.dynamicSuggestions
2626
2629
  : false;
2627
2630
  const scopeComposerSlot =
2628
- tabId === activeThreadId && !contentHidden ? (
2631
+ showScopeBadge && tabId === activeThreadId && !contentHidden ? (
2629
2632
  tabScope && activeThreadId ? (
2630
2633
  <ScopeBadge
2631
2634
  scope={tabScope}
@@ -17,6 +17,7 @@ import { formatChatErrorText, normalizeChatError } from "./error-format.js";
17
17
  import {
18
18
  AgentAutoContinueSignal,
19
19
  type AgentActivityTrailEntry,
20
+ type AgentAutoContinueErrorInfo,
20
21
  type ContentPart,
21
22
  readSSEStream,
22
23
  settleInterruptedToolCalls,
@@ -1353,6 +1354,7 @@ export function createAgentChatAdapter(
1353
1354
  const structuredContinuationFragments: AgentChatStructuredMessage[] = [];
1354
1355
  let visibleContinuationPrefix: ContentPart[] = [];
1355
1356
  let lastAutoContinueReason: string | null = null;
1357
+ let lastRecoverableRunError: AgentAutoContinueErrorInfo | null = null;
1356
1358
  const attemptedRunIds: string[] = [];
1357
1359
  let authRecoveryAttempted = false;
1358
1360
  let continuationToolCallCounter = 0;
@@ -1364,6 +1366,12 @@ export function createAgentChatAdapter(
1364
1366
  lastAutoContinueReason
1365
1367
  ? `last_auto_continue_reason: ${lastAutoContinueReason}`
1366
1368
  : "",
1369
+ lastRecoverableRunError?.errorCode
1370
+ ? `last_recoverable_error_code: ${lastRecoverableRunError.errorCode}`
1371
+ : "",
1372
+ lastRecoverableRunError?.message
1373
+ ? `last_recoverable_error: ${lastRecoverableRunError.message}`
1374
+ : "",
1367
1375
  `stale_run_continuations: ${staleRunContinuationAttempts}`,
1368
1376
  `stalled_transient_continuations: ${stalledTransientContinuationAttempts}`,
1369
1377
  `empty_transient_continuations: ${emptyTransientContinuationAttempts}`,
@@ -1677,6 +1685,9 @@ export function createAgentChatAdapter(
1677
1685
  signal: AgentAutoContinueSignal,
1678
1686
  ): { ok: boolean; resetVisibleContent: boolean } => {
1679
1687
  lastAutoContinueReason = signal.reason;
1688
+ if (signal.errorInfo) {
1689
+ lastRecoverableRunError = signal.errorInfo;
1690
+ }
1680
1691
  const isTransient = signal.reason !== "loop_limit";
1681
1692
  const visibleContent = visibleContentForContinuation();
1682
1693
  const currentPartialHistory =
@@ -1686,14 +1697,12 @@ export function createAgentChatAdapter(
1686
1697
  // whitespace-only output cannot keep the run alive indefinitely.
1687
1698
  const madeContentProgress = hasContinuationProgress(visibleContent);
1688
1699
  // An action was streamed but has not returned yet (a tool_start with
1689
- // no tool_done), or the activity trail shows the server was working
1690
- // on a tool. A run_timeout that fires in this window means the agent
1691
- // was actively making progress the server's foldAssistantTurn
1692
- // persisted the in-flight call — so it must NOT count against the
1693
- // stalled/empty continuation budgets.
1694
- const hasInFlightTool =
1695
- hasInFlightToolCall(visibleContent) ||
1696
- Boolean(lastActivityTool(signal.activityTrail));
1700
+ // no tool_done). This is durable enough to survive continuation: the
1701
+ // server already emitted a real tool call. A tool-scoped activity
1702
+ // card ("Preparing generate-design") is useful UI, but it happens
1703
+ // before tool_start; treating it as progress caused silent retry
1704
+ // loops when the LLM timed out while assembling a large tool input.
1705
+ const hasInFlightTool = hasInFlightToolCall(visibleContent);
1697
1706
  // Either real output or an actively-running tool counts as progress
1698
1707
  // for the stalled/empty caps.
1699
1708
  const madeProgress = madeContentProgress || hasInFlightTool;
@@ -2174,15 +2183,28 @@ export function createAgentChatAdapter(
2174
2183
  }
2175
2184
  const continuation = prepareAutoContinuation(err);
2176
2185
  if (!continuation.ok) {
2177
- const message = exhaustedRecoveryMessage(err.reason);
2186
+ const preservedError =
2187
+ err.errorInfo ?? lastRecoverableRunError ?? null;
2188
+ const message =
2189
+ preservedError?.message ??
2190
+ exhaustedRecoveryMessage(err.reason);
2191
+ const details = [
2192
+ preservedError?.details,
2193
+ connectionRecoveryDetails(),
2194
+ ]
2195
+ .filter(Boolean)
2196
+ .join("\n\n");
2197
+ const errorCode =
2198
+ preservedError?.errorCode ?? "connection_error";
2178
2199
  captureChatClientError(err, "auto-continuation-exhausted", {
2179
2200
  autoContinueReason: err.reason,
2201
+ ...(errorCode ? { errorCode } : {}),
2180
2202
  });
2181
2203
  const runError = {
2182
2204
  message,
2183
- details: connectionRecoveryDetails(),
2184
- errorCode: "connection_error",
2185
- recoverable: true,
2205
+ ...(details ? { details } : {}),
2206
+ errorCode,
2207
+ recoverable: preservedError?.recoverable ?? true,
2186
2208
  ...(runId ? { runId } : {}),
2187
2209
  };
2188
2210
  if (typeof window !== "undefined") {
@@ -2192,10 +2214,16 @@ export function createAgentChatAdapter(
2192
2214
  }),
2193
2215
  );
2194
2216
  }
2195
- settleInterruptedToolCalls(content);
2217
+ settleInterruptedToolCalls(content, undefined, {
2218
+ includeActivity: true,
2219
+ });
2196
2220
  content.push({
2197
2221
  type: "text",
2198
- text: `Something went wrong: ${message}`,
2222
+ text: formatChatErrorText(
2223
+ message,
2224
+ preservedError?.upgradeUrl,
2225
+ errorCode,
2226
+ ),
2199
2227
  });
2200
2228
  yield {
2201
2229
  content: [...content],
@@ -2343,7 +2371,9 @@ export function createAgentChatAdapter(
2343
2371
  }),
2344
2372
  );
2345
2373
  }
2346
- settleInterruptedToolCalls(content);
2374
+ settleInterruptedToolCalls(content, undefined, {
2375
+ includeActivity: true,
2376
+ });
2347
2377
  content.push({
2348
2378
  type: "text",
2349
2379
  text: `Something went wrong: ${message}`,
@@ -80,21 +80,34 @@ export type AgentAutoContinueReason =
80
80
 
81
81
  export type AgentActivityTrailEntry = { label: string; tool?: string };
82
82
 
83
+ export interface AgentAutoContinueErrorInfo {
84
+ message: string;
85
+ details?: string;
86
+ errorCode?: string;
87
+ recoverable?: boolean;
88
+ upgradeUrl?: string;
89
+ }
90
+
83
91
  const INTERRUPTED_TOOL_RESULT =
84
92
  "Interrupted before this tool returned a result.";
93
+ const INTERRUPTED_ACTIVITY_RESULT = "Stopped before this action started.";
85
94
 
86
95
  export function settleInterruptedToolCalls(
87
96
  content: ContentPart[],
88
97
  result = INTERRUPTED_TOOL_RESULT,
98
+ options?: { includeActivity?: boolean; activityResult?: string },
89
99
  ): boolean {
90
100
  let changed = false;
91
101
  for (const part of content) {
92
102
  if (
93
103
  part.type === "tool-call" &&
94
104
  part.result === undefined &&
95
- part.activity !== true
105
+ (part.activity !== true || options?.includeActivity === true)
96
106
  ) {
97
- part.result = result;
107
+ part.result =
108
+ part.activity === true
109
+ ? (options?.activityResult ?? INTERRUPTED_ACTIVITY_RESULT)
110
+ : result;
98
111
  changed = true;
99
112
  }
100
113
  }
@@ -105,17 +118,20 @@ export class AgentAutoContinueSignal extends Error {
105
118
  readonly reason: AgentAutoContinueReason;
106
119
  readonly maxIterations?: number;
107
120
  readonly activityTrail: AgentActivityTrailEntry[];
121
+ readonly errorInfo?: AgentAutoContinueErrorInfo;
108
122
 
109
123
  constructor(options: {
110
124
  reason: AgentAutoContinueReason;
111
125
  maxIterations?: number;
112
126
  activityTrail?: AgentActivityTrailEntry[];
127
+ errorInfo?: AgentAutoContinueErrorInfo;
113
128
  }) {
114
129
  super(`Agent run needs automatic continuation: ${options.reason}`);
115
130
  this.name = "AgentAutoContinueSignal";
116
131
  this.reason = options.reason;
117
132
  this.maxIterations = options.maxIterations;
118
133
  this.activityTrail = options.activityTrail ?? [];
134
+ this.errorInfo = options.errorInfo;
119
135
  }
120
136
  }
121
137
 
@@ -342,6 +358,7 @@ export function processEvent(
342
358
  autoContinue?: {
343
359
  reason: AgentAutoContinueReason;
344
360
  maxIterations?: number;
361
+ errorInfo?: AgentAutoContinueErrorInfo;
345
362
  };
346
363
  } {
347
364
  if (ev.type === "clear") {
@@ -598,7 +615,7 @@ export function processEvent(
598
615
  }),
599
616
  );
600
617
  }
601
- settleInterruptedToolCalls(content);
618
+ settleInterruptedToolCalls(content, undefined, { includeActivity: true });
602
619
  content.push({
603
620
  type: "text",
604
621
  text: formatChatErrorText(errMsg, undefined, errorCode),
@@ -665,6 +682,7 @@ export function processEvent(
665
682
  (ev.errorCode === "run_timeout" && ev.recoverable) ||
666
683
  isAutoRecoverableError(ev, errMsg)
667
684
  ) {
685
+ const normalized = normalizeChatError(errMsg, ev.errorCode);
668
686
  return {
669
687
  action: "auto_continue",
670
688
  autoContinue: {
@@ -676,6 +694,15 @@ export function processEvent(
676
694
  errMsg.toLowerCase().includes("timeout")
677
695
  ? "run_timeout"
678
696
  : "stream_ended",
697
+ errorInfo: {
698
+ message: normalized.message,
699
+ ...(ev.details || normalized.details
700
+ ? { details: ev.details ?? normalized.details }
701
+ : {}),
702
+ ...(ev.errorCode ? { errorCode: ev.errorCode } : {}),
703
+ recoverable: ev.recoverable ?? true,
704
+ ...(ev.upgradeUrl ? { upgradeUrl: ev.upgradeUrl } : {}),
705
+ },
679
706
  },
680
707
  };
681
708
  }
@@ -700,7 +727,7 @@ export function processEvent(
700
727
  }),
701
728
  );
702
729
  }
703
- settleInterruptedToolCalls(content);
730
+ settleInterruptedToolCalls(content, undefined, { includeActivity: true });
704
731
  content.push({
705
732
  type: "text",
706
733
  text: formatChatErrorText(errMsg, ev.upgradeUrl, ev.errorCode),
@@ -35,7 +35,10 @@ export function defineEval(spec: Eval): Eval {
35
35
  if (!spec.input || typeof spec.input.prompt !== "string") {
36
36
  throw new Error(`defineEval("${spec.name}"): \`input.prompt\` is required`);
37
37
  }
38
- if (!Array.isArray(spec.scorers) || spec.scorers.length === 0) {
38
+ if (
39
+ (!Array.isArray(spec.scorers) || spec.scorers.length === 0) &&
40
+ !spec.skipReason
41
+ ) {
39
42
  throw new Error(
40
43
  `defineEval("${spec.name}"): at least one scorer is required`,
41
44
  );
@@ -27,13 +27,19 @@ export function formatReport(report: EvalRunReport): string {
27
27
  lines.push(" ─────");
28
28
 
29
29
  for (const row of report.results) {
30
- const mark = row.passed ? "✓" : "✗";
30
+ const mark = row.status === "skipped" ? "-" : row.passed ? "✓" : "✗";
31
31
  lines.push("");
32
- lines.push(
33
- ` ${mark} ${row.eval} (avg ${pct(row.avgScore)}, threshold ${pct(
34
- row.threshold,
35
- )})`,
36
- );
32
+ if (row.status === "skipped") {
33
+ lines.push(` ${mark} ${row.eval} (skipped)`);
34
+ lines.push(` reason: ${row.skipReason ?? "No reason provided"}`);
35
+ continue;
36
+ } else {
37
+ lines.push(
38
+ ` ${mark} ${row.eval} (avg ${pct(row.avgScore)}, threshold ${pct(
39
+ row.threshold,
40
+ )})`,
41
+ );
42
+ }
37
43
  if (row.error) {
38
44
  lines.push(` ⚠ run error: ${row.error}`);
39
45
  }
@@ -50,9 +56,18 @@ export function formatReport(report: EvalRunReport): string {
50
56
 
51
57
  lines.push("");
52
58
  lines.push(" ─────");
53
- const verdict = report.failed === 0 ? "PASS" : "FAIL";
59
+ const skipped = report.skipped ?? 0;
60
+ const executedTotal = report.total - skipped;
61
+ const executedPassed = report.passed - skipped;
62
+ const verdict =
63
+ executedTotal === 0 && skipped > 0
64
+ ? "SKIPPED"
65
+ : report.failed === 0
66
+ ? "PASS"
67
+ : "FAIL";
54
68
  lines.push(
55
- ` ${verdict}: ${report.passed}/${report.total} evals passed` +
69
+ ` ${verdict}: ${executedPassed}/${executedTotal} evals passed` +
70
+ (skipped > 0 ? `, ${skipped} skipped` : "") +
56
71
  (report.failed > 0 ? `, ${report.failed} below threshold` : ""),
57
72
  );
58
73
  lines.push("");