@agent-native/core 0.187.0 → 0.187.1-nightly-20260925003003

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 (158) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +4 -3
  3. package/corpus/templates/analytics/changelog/2026-09-24-analytics-sidebar-footer-aligns-consistently.md +6 -0
  4. package/corpus/templates/analytics/changelog/2026-09-24-dashboard-emails-match-stacked-area-totals.md +6 -0
  5. package/corpus/templates/analytics/server/lib/report-chart-svg.ts +47 -20
  6. package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +1 -1
  7. package/corpus/templates/calendar/changelog/2026-09-24-guest-inputs-wrap-beneath-existing-guests-so-the-active-inpu.md +6 -0
  8. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +8 -3
  9. package/corpus/templates/clips/app/lib/recording-link.ts +21 -0
  10. package/corpus/templates/clips/app/routes/record.tsx +27 -8
  11. package/corpus/templates/clips/changelog/2026-09-24-the-camera-bubble-stays-visible-while-you-reposition-it-befo.md +6 -0
  12. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +7 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +63 -55
  14. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +251 -74
  15. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +66 -56
  16. package/corpus/templates/design/app/components/design/EditPanel.tsx +19 -0
  17. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +59 -9
  18. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +4 -0
  19. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +119 -0
  20. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +48 -51
  21. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +1 -0
  22. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +298 -187
  23. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
  24. package/corpus/templates/design/app/components/design/inspector/index.ts +4 -0
  25. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -2
  26. package/corpus/templates/design/app/i18n/en-US.ts +6 -0
  27. package/corpus/templates/design/app/pages/Present.tsx +101 -87
  28. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +75 -14
  29. package/corpus/templates/design/app/pages/design-editor/commands/README.md +1 -0
  30. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +69 -56
  31. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +16 -0
  32. package/corpus/templates/design/app/pages/design-editor/commands/scale-selection.ts +17 -2
  33. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +23 -30
  34. package/corpus/templates/design/app/pages/design-editor/commands/swap-fill-stroke.ts +105 -0
  35. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +21 -0
  36. package/corpus/templates/design/app/pages/present-review-state.ts +7 -1
  37. package/corpus/templates/design/changelog/2026-09-24-a-vector-s-stroke-row-shows-position-beside-weight-instead-o.md +6 -0
  38. package/corpus/templates/design/changelog/2026-09-24-css-margins-can-be-adjusted-on-the-canvas-and-in-the-layout-.md +6 -0
  39. package/corpus/templates/design/changelog/2026-09-24-design-reviews-show-the-real-canvas-beside-its-conversation.md +6 -0
  40. package/corpus/templates/design/changelog/2026-09-24-double-clicking-text-or-a-vector-on-the-board-starts-editing.md +6 -0
  41. package/corpus/templates/design/changelog/2026-09-24-interact-opens-a-focused-responsive-preview-without-reloadin.md +6 -0
  42. package/corpus/templates/design/changelog/2026-09-24-keep-gradient-values-visible-and-limit-canvas-paints-to-supp.md +6 -0
  43. package/corpus/templates/design/changelog/2026-09-24-open-pen-paths-no-longer-paint-a-filled-triangle-after-shift.md +6 -0
  44. package/corpus/templates/design/changelog/2026-09-24-re-clicking-the-active-responsive-preview-returns-to-the-can.md +6 -0
  45. package/corpus/templates/design/changelog/2026-09-24-resized-vectors-can-be-point-edited-and-the-k-scale-tool-sho.md +6 -0
  46. package/corpus/templates/design/changelog/2026-09-24-while-editing-text-click-to-place-the-caret-and-double-click.md +6 -0
  47. package/corpus/templates/design/e2e/helpers.ts +19 -2
  48. package/corpus/templates/mail/app/components/email/ComposeModal.tsx +1 -1
  49. package/corpus/templates/mail/changelog/2026-09-24-gmail-push-ack.md +6 -0
  50. package/corpus/templates/mail/changelog/2026-09-24-the-composer-keeps-its-taller-layout-in-a-narrower-window.md +6 -0
  51. package/corpus/templates/mail/server/db/schema.ts +26 -1
  52. package/corpus/templates/mail/server/lib/inbox-store.ts +68 -23
  53. package/corpus/templates/mail/server/lib/inbox-sync.ts +43 -17
  54. package/corpus/templates/mail/server/plugins/db.ts +19 -0
  55. package/corpus/templates/mail/server/routes/api/gmail/push.post.ts +4 -15
  56. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +45 -30
  57. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +18 -2
  58. package/corpus/templates/slides/actions/export-html.ts +28 -17
  59. package/corpus/templates/slides/app/hooks/use-deck-design-system.ts +27 -15
  60. package/corpus/templates/slides/app/pages/DeckEditor.tsx +1 -1
  61. package/corpus/templates/slides/changelog/2026-09-23-decks-with-no-design-system-now-follow-a-theme-chosen-for-th.md +6 -0
  62. package/corpus/templates/slides/shared/slide-background.ts +3 -1
  63. package/dist/agent/engine/builder-engine.js +11 -0
  64. package/dist/agent/engine/types.d.ts +1 -0
  65. package/dist/agent/production-agent.d.ts +2 -0
  66. package/dist/agent/production-agent.js +32 -4
  67. package/dist/agent/run-loop-with-resume.js +6 -0
  68. package/dist/cli/index.js +4 -4
  69. package/dist/cli/process.d.ts +10 -1
  70. package/dist/cli/process.js +66 -0
  71. package/dist/cli/workspace-dev.js +2 -0
  72. package/dist/client/agent-chat.js +13 -14
  73. package/dist/client/error-format.js +4 -4
  74. package/dist/client/observability/ObservabilityDashboard.js +95 -65
  75. package/dist/client/observability/OutputPreview.d.ts +2 -1
  76. package/dist/client/observability/OutputPreview.js +64 -7
  77. package/dist/client/observability/useObservability.d.ts +2 -1
  78. package/dist/client/observability/useObservability.js +5 -1
  79. package/dist/client/settings/UsageSection.js +105 -17
  80. package/dist/collab/routes.d.ts +1 -1
  81. package/dist/collab/struct-routes.d.ts +1 -1
  82. package/dist/db/client.d.ts +44 -0
  83. package/dist/db/client.js +62 -3
  84. package/dist/db/create-get-db.js +23 -1
  85. package/dist/db/embedded-runtime.d.ts +21 -0
  86. package/dist/db/embedded-runtime.js +27 -0
  87. package/dist/db/migrations.js +2 -0
  88. package/dist/db/server-runtime.d.ts +22 -0
  89. package/dist/db/server-runtime.js +26 -0
  90. package/dist/deploy/build.d.ts +29 -0
  91. package/dist/deploy/build.js +204 -121
  92. package/dist/feature-flags/index.d.ts +1 -1
  93. package/dist/feature-flags/index.js +1 -1
  94. package/dist/feature-flags/registry.d.ts +1 -0
  95. package/dist/feature-flags/registry.js +5 -0
  96. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  97. package/dist/index.d.ts +1 -1
  98. package/dist/index.js +1 -1
  99. package/dist/integrations/webhook-handler.js +4 -1
  100. package/dist/jobs/background-automation-runner.js +6 -1
  101. package/dist/localization/core-messages/ar-SA.js +9 -1
  102. package/dist/localization/core-messages/de-DE.js +9 -1
  103. package/dist/localization/core-messages/en-US.d.ts +9 -1
  104. package/dist/localization/core-messages/en-US.js +9 -1
  105. package/dist/localization/core-messages/es-ES.js +9 -1
  106. package/dist/localization/core-messages/fr-FR.js +9 -1
  107. package/dist/localization/core-messages/hi-IN.js +9 -1
  108. package/dist/localization/core-messages/ja-JP.js +9 -1
  109. package/dist/localization/core-messages/ko-KR.js +9 -1
  110. package/dist/localization/core-messages/pt-BR.js +9 -1
  111. package/dist/localization/core-messages/supplemental/ar-SA.js +1 -0
  112. package/dist/localization/core-messages/supplemental/de-DE.js +1 -0
  113. package/dist/localization/core-messages/supplemental/en-US.js +1 -0
  114. package/dist/localization/core-messages/supplemental/es-ES.js +1 -0
  115. package/dist/localization/core-messages/supplemental/fr-FR.js +1 -0
  116. package/dist/localization/core-messages/supplemental/hi-IN.js +1 -0
  117. package/dist/localization/core-messages/supplemental/ja-JP.js +1 -0
  118. package/dist/localization/core-messages/supplemental/ko-KR.js +1 -0
  119. package/dist/localization/core-messages/supplemental/pt-BR.js +1 -0
  120. package/dist/localization/core-messages/supplemental/zh-CN.js +1 -0
  121. package/dist/localization/core-messages/supplemental/zh-TW.js +1 -0
  122. package/dist/localization/core-messages/zh-CN.js +9 -1
  123. package/dist/localization/core-messages/zh-TW.js +9 -1
  124. package/dist/localization/core-messages.d.ts +9 -1
  125. package/dist/localization/mcp-settings-messages.d.ts +1 -0
  126. package/dist/mcp/connect-route.js +45 -8
  127. package/dist/mcp/connect-store.d.ts +7 -5
  128. package/dist/mcp/connect-store.js +30 -39
  129. package/dist/notifications/routes.d.ts +3 -3
  130. package/dist/observability/actions/get-observability-review-detail.d.ts +7 -0
  131. package/dist/observability/actions/get-observability-review-detail.js +25 -0
  132. package/dist/observability/reviews.d.ts +9 -1
  133. package/dist/observability/reviews.js +20 -0
  134. package/dist/observability/routes.d.ts +3 -3
  135. package/dist/observability/types.d.ts +8 -0
  136. package/dist/org/handlers.d.ts +1 -1
  137. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  138. package/dist/provider-api/actions/provider-api.d.ts +9 -9
  139. package/dist/scripts/db/query.js +6 -1
  140. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  141. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  142. package/dist/server/agent-teams.js +4 -1
  143. package/dist/server/auth.js +60 -20
  144. package/dist/server/better-auth-instance.js +7 -1
  145. package/dist/server/core-routes-plugin.d.ts +1 -1
  146. package/dist/server/core-routes-plugin.js +2 -0
  147. package/dist/server/embedded.js +6 -0
  148. package/dist/server/framework-request-handler.js +6 -0
  149. package/dist/server/realtime-token.d.ts +1 -1
  150. package/dist/usage/actions/get-usage-metrics.js +4 -1
  151. package/dist/usage/metrics-store.d.ts +19 -0
  152. package/dist/usage/metrics-store.js +106 -14
  153. package/dist/usage/store.d.ts +7 -2
  154. package/dist/usage/store.js +25 -4
  155. package/dist/vite/action-types-plugin.js +4 -0
  156. package/dist/vite/client.d.ts +6 -1
  157. package/dist/vite/client.js +40 -1
  158. package/package.json +4 -3
@@ -3735,6 +3735,7 @@ export async function runAgentLoop(opts) {
3735
3735
  outputTokens: 0,
3736
3736
  cacheReadTokens: 0,
3737
3737
  cacheWriteTokens: 0,
3738
+ engineName: opts.engine.name,
3738
3739
  model,
3739
3740
  };
3740
3741
  const maxIterations = normalizeMaxIterations(opts.maxIterations, getDefaultMaxIterations());
@@ -4313,12 +4314,21 @@ export async function runAgentLoop(opts) {
4313
4314
  outputTokens: event.outputTokens,
4314
4315
  cacheReadTokens: event.cacheReadTokens ?? 0,
4315
4316
  cacheWriteTokens: event.cacheWriteTokens ?? 0,
4317
+ engineName: opts.engine.name,
4316
4318
  model,
4319
+ ...(event.builderCreditsUsed !== undefined
4320
+ ? { builderCreditsUsed: event.builderCreditsUsed }
4321
+ : {}),
4317
4322
  };
4318
4323
  usage.inputTokens += eventUsage.inputTokens;
4319
4324
  usage.outputTokens += eventUsage.outputTokens;
4320
4325
  usage.cacheReadTokens += eventUsage.cacheReadTokens;
4321
4326
  usage.cacheWriteTokens += eventUsage.cacheWriteTokens;
4327
+ if (eventUsage.builderCreditsUsed !== undefined) {
4328
+ usage.builderCreditsUsed =
4329
+ (usage.builderCreditsUsed ?? 0) +
4330
+ eventUsage.builderCreditsUsed;
4331
+ }
4322
4332
  usage.usageReported = true;
4323
4333
  opts.onUsage?.(eventUsage);
4324
4334
  }
@@ -6188,6 +6198,7 @@ export async function runAgentLoopWithMainChatInternalContinuations(opts) {
6188
6198
  outputTokens: 0,
6189
6199
  cacheReadTokens: 0,
6190
6200
  cacheWriteTokens: 0,
6201
+ engineName: opts.engine.name,
6191
6202
  model: opts.model,
6192
6203
  };
6193
6204
  const addUsage = (next) => {
@@ -6195,6 +6206,11 @@ export async function runAgentLoopWithMainChatInternalContinuations(opts) {
6195
6206
  usage.outputTokens += next.outputTokens;
6196
6207
  usage.cacheReadTokens += next.cacheReadTokens;
6197
6208
  usage.cacheWriteTokens += next.cacheWriteTokens;
6209
+ if (next.builderCreditsUsed !== undefined) {
6210
+ usage.builderCreditsUsed =
6211
+ (usage.builderCreditsUsed ?? 0) + next.builderCreditsUsed;
6212
+ }
6213
+ usage.engineName = next.engineName ?? usage.engineName;
6198
6214
  usage.model = next.model;
6199
6215
  if (typeof next.llmCalls === "number") {
6200
6216
  usage.llmCalls = (usage.llmCalls ?? 0) + next.llmCalls;
@@ -7541,7 +7557,7 @@ export function createProductionAgentHandler(options) {
7541
7557
  availableRequestActions = filterActionsByAllowedNames(availableRequestActions, filterHostedHarnessToolNames(Object.keys(availableRequestActions)));
7542
7558
  }
7543
7559
  let surfacedRequestActions = availableRequestActions;
7544
- let useDefaultRequestActionSurface = !options.resolveActionSurface;
7560
+ let shouldFilterInitialRequestTools = !options.resolveActionSurface;
7545
7561
  if (options.resolveActionSurface) {
7546
7562
  const persistedSurface = isBackgroundWorker
7547
7563
  ? readPersistedActionSurface(body, "__resolvedActionSurface")
@@ -7573,13 +7589,14 @@ export function createProductionAgentHandler(options) {
7573
7589
  availableActionNames: Object.keys(availableRequestActions),
7574
7590
  });
7575
7591
  const normalizedSurface = normalizeAgentActionSurfaceResolution(surface);
7592
+ shouldFilterInitialRequestTools =
7593
+ normalizedSurface.mode === "default" || !normalizedSurface.actionScope;
7576
7594
  if (requestedActionScope &&
7577
7595
  (normalizedSurface.mode === "default" || !normalizedSurface.actionScope)) {
7578
7596
  throw new Error("resolveActionSurface must return actionScope for a scoped request");
7579
7597
  }
7580
7598
  const runCtx = ensureRequestRunContext();
7581
7599
  if (normalizedSurface.mode === "default") {
7582
- useDefaultRequestActionSurface = true;
7583
7600
  if (runCtx) {
7584
7601
  delete runCtx.allowedActionNames;
7585
7602
  delete runCtx.actionScope;
@@ -8113,7 +8130,7 @@ export function createProductionAgentHandler(options) {
8113
8130
  ? createPlanModeActionRegistry(surfacedRequestActions)
8114
8131
  : surfacedRequestActions;
8115
8132
  const availableRequestTools = getEngineTools(requestActions);
8116
- const initialRequestTools = useDefaultRequestActionSurface
8133
+ const initialRequestTools = shouldFilterInitialRequestTools
8117
8134
  ? filterInitialEngineTools(availableRequestTools, options.initialToolNames)
8118
8135
  : availableRequestTools;
8119
8136
  const curatedRequestTools = requestMode === "plan"
@@ -9074,6 +9091,8 @@ export function createProductionAgentHandler(options) {
9074
9091
  outputTokens: subUsage.outputTokens,
9075
9092
  cacheReadTokens: subUsage.cacheReadTokens,
9076
9093
  cacheWriteTokens: subUsage.cacheWriteTokens,
9094
+ builderCreditsUsed: subUsage.builderCreditsUsed,
9095
+ engineName: engine.name,
9077
9096
  model: subUsage.model,
9078
9097
  label: `custom-agent:${ref.name}`,
9079
9098
  runId,
@@ -9178,6 +9197,7 @@ export function createProductionAgentHandler(options) {
9178
9197
  outputTokens: 0,
9179
9198
  cacheReadTokens: 0,
9180
9199
  cacheWriteTokens: 0,
9200
+ engineName: engine.name,
9181
9201
  model: effectiveModel,
9182
9202
  };
9183
9203
  const agentLoopOpts = {
@@ -9197,6 +9217,11 @@ export function createProductionAgentHandler(options) {
9197
9217
  turnUsage.outputTokens += usage.outputTokens;
9198
9218
  turnUsage.cacheReadTokens += usage.cacheReadTokens;
9199
9219
  turnUsage.cacheWriteTokens += usage.cacheWriteTokens;
9220
+ if (usage.builderCreditsUsed !== undefined) {
9221
+ turnUsage.builderCreditsUsed =
9222
+ (turnUsage.builderCreditsUsed ?? 0) + usage.builderCreditsUsed;
9223
+ }
9224
+ turnUsage.engineName = usage.engineName ?? turnUsage.engineName;
9200
9225
  turnUsage.model = usage.model;
9201
9226
  },
9202
9227
  ownerEmail,
@@ -9367,7 +9392,8 @@ export function createProductionAgentHandler(options) {
9367
9392
  (turnUsage.inputTokens > 0 ||
9368
9393
  turnUsage.outputTokens > 0 ||
9369
9394
  turnUsage.cacheReadTokens > 0 ||
9370
- turnUsage.cacheWriteTokens > 0)) {
9395
+ turnUsage.cacheWriteTokens > 0 ||
9396
+ turnUsage.builderCreditsUsed != null)) {
9371
9397
  const { recordUsage } = await import("../usage/store.js");
9372
9398
  await recordUsage({
9373
9399
  ownerEmail: resolvedOwnerEmail,
@@ -9375,6 +9401,8 @@ export function createProductionAgentHandler(options) {
9375
9401
  outputTokens: turnUsage.outputTokens,
9376
9402
  cacheReadTokens: turnUsage.cacheReadTokens,
9377
9403
  cacheWriteTokens: turnUsage.cacheWriteTokens,
9404
+ builderCreditsUsed: turnUsage.builderCreditsUsed,
9405
+ engineName: engine.name,
9378
9406
  model: turnUsage.model,
9379
9407
  label: turnUsageLabel || "chat",
9380
9408
  // token_usage has had run_id/thread_id/task_id since it was
@@ -373,6 +373,7 @@ control) {
373
373
  outputTokens: 0,
374
374
  cacheReadTokens: 0,
375
375
  cacheWriteTokens: 0,
376
+ engineName: opts.engine.name,
376
377
  model: opts.model,
377
378
  };
378
379
  const addUsage = (next) => {
@@ -380,6 +381,11 @@ control) {
380
381
  usage.outputTokens += next.outputTokens;
381
382
  usage.cacheReadTokens += next.cacheReadTokens;
382
383
  usage.cacheWriteTokens += next.cacheWriteTokens;
384
+ if (next.builderCreditsUsed !== undefined) {
385
+ usage.builderCreditsUsed =
386
+ (usage.builderCreditsUsed ?? 0) + next.builderCreditsUsed;
387
+ }
388
+ usage.engineName = next.engineName ?? usage.engineName;
383
389
  usage.model = next.model;
384
390
  // Without these, a retry that never got a usage report merges its zeros
385
391
  // over an earlier attempt's real numbers, and telemetry reports an
package/dist/cli/index.js CHANGED
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
  import * as Sentry from "@sentry/node";
8
8
  import { clearAgentNativeNitroPresetMarker, resolveAgentNativeNitroPreset, } from "../deploy/nitro-preset.js";
9
9
  import { resolveDeployPostBuildInvocation } from "./deploy-build.js";
10
- import { cliSpawnOptions } from "./process.js";
10
+ import { cliSpawnOptions, runDevServer } from "./process.js";
11
11
  import { findBinUpwards, findReactRouterInvocation, } from "./react-router-command.js";
12
12
  import { shouldTrackCliRun } from "./telemetry-routing.js";
13
13
  import { createCliTelemetry } from "./telemetry.js";
@@ -529,14 +529,14 @@ switch (command) {
529
529
  const vite = findViteBin();
530
530
  const { inspectFlag, rest } = extractNodeInspectFlag(args);
531
531
  if (!inspectFlag) {
532
- run(vite, rest);
532
+ runDevServer(vite, rest);
533
533
  break;
534
534
  }
535
535
  const viteJsEntry = findViteJsEntry();
536
536
  if (!viteJsEntry) {
537
537
  console.warn("[agent-native] Could not resolve Vite's JS entry; starting dev " +
538
538
  "server without the debugger.");
539
- run(vite, rest);
539
+ runDevServer(vite, rest);
540
540
  break;
541
541
  }
542
542
  // Attach inspect flag to server process (not Vite or Nitro process)
@@ -551,7 +551,7 @@ switch (command) {
551
551
  NITRO_DEV_RUNNER: process.env.NITRO_DEV_RUNNER ?? "node-process",
552
552
  };
553
553
  console.log(`[agent-native] API server debugger listening on ${target}`);
554
- run(process.execPath, ["--import", preload, viteJsEntry, ...rest], {
554
+ runDevServer(process.execPath, ["--import", preload, viteJsEntry, ...rest], {
555
555
  env,
556
556
  shell: false,
557
557
  });
@@ -1,7 +1,16 @@
1
- import type { SpawnOptions } from "node:child_process";
1
+ import { spawn, type SpawnOptions } from "node:child_process";
2
+ export declare const DEV_SERVER_SUPERVISOR_ENV = "AGENT_NATIVE_DEV_SUPERVISOR";
3
+ export declare const DEV_SERVER_RECOVERY_EXIT_CODE = 86;
2
4
  /**
3
5
  * Keep Windows command-name launches compatible with `.cmd` shims while
4
6
  * allowing callers that already have a real executable path to opt out of the
5
7
  * shell. The latter is important for paths such as `C:\\Program Files\\nodejs\\node.exe`.
6
8
  */
7
9
  export declare function cliSpawnOptions(options?: Pick<SpawnOptions, "env" | "shell" | "stdio">, platform?: NodeJS.Platform): Pick<SpawnOptions, "env" | "shell" | "stdio">;
10
+ export declare function runDevServer(command: string, args: string[], options?: {
11
+ env?: NodeJS.ProcessEnv;
12
+ shell?: boolean;
13
+ stdio?: "inherit" | "pipe";
14
+ spawnProcess?: typeof spawn;
15
+ exitProcess?: (code: number) => void;
16
+ }): void;
@@ -1,3 +1,7 @@
1
+ import { spawn, } from "node:child_process";
2
+ import { constants as osConstants } from "node:os";
3
+ export const DEV_SERVER_SUPERVISOR_ENV = "AGENT_NATIVE_DEV_SUPERVISOR";
4
+ export const DEV_SERVER_RECOVERY_EXIT_CODE = 86;
1
5
  /**
2
6
  * Keep Windows command-name launches compatible with `.cmd` shims while
3
7
  * allowing callers that already have a real executable path to opt out of the
@@ -10,3 +14,65 @@ export function cliSpawnOptions(options = {}, platform = process.platform) {
10
14
  env: options.env ?? process.env,
11
15
  };
12
16
  }
17
+ export function runDevServer(command, args, options = {}) {
18
+ const env = {
19
+ ...(options.env ?? process.env),
20
+ [DEV_SERVER_SUPERVISOR_ENV]: "1",
21
+ };
22
+ const spawnProcess = options.spawnProcess ?? spawn;
23
+ const exitProcess = options.exitProcess ?? ((code) => process.exit(code));
24
+ const signalHandlers = new Map();
25
+ let child;
26
+ let restartTimer;
27
+ let forceExitTimer;
28
+ let shuttingDown = false;
29
+ const cleanup = () => {
30
+ for (const [signal, handler] of signalHandlers) {
31
+ process.removeListener(signal, handler);
32
+ }
33
+ signalHandlers.clear();
34
+ if (restartTimer)
35
+ clearTimeout(restartTimer);
36
+ if (forceExitTimer)
37
+ clearTimeout(forceExitTimer);
38
+ };
39
+ const start = () => {
40
+ restartTimer = undefined;
41
+ child = spawnProcess(command, args, cliSpawnOptions({
42
+ env,
43
+ shell: options.shell,
44
+ stdio: options.stdio,
45
+ }));
46
+ child.once("exit", (code, signal) => {
47
+ if (code === DEV_SERVER_RECOVERY_EXIT_CODE && !shuttingDown) {
48
+ restartTimer = setTimeout(start, 250);
49
+ return;
50
+ }
51
+ cleanup();
52
+ exitProcess(code ?? (signal ? 128 + osConstants.signals[signal] : 0));
53
+ });
54
+ };
55
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
56
+ const handler = () => {
57
+ if (shuttingDown)
58
+ return;
59
+ shuttingDown = true;
60
+ if (restartTimer) {
61
+ clearTimeout(restartTimer);
62
+ restartTimer = undefined;
63
+ cleanup();
64
+ exitProcess(128 + osConstants.signals[signal]);
65
+ return;
66
+ }
67
+ child?.kill(signal);
68
+ forceExitTimer = setTimeout(() => {
69
+ child?.kill("SIGKILL");
70
+ exitProcess(1);
71
+ }, 5_000);
72
+ forceExitTimer.unref();
73
+ };
74
+ signalHandlers.set(signal, handler);
75
+ process.on(signal, handler);
76
+ }
77
+ start();
78
+ }
@@ -10,6 +10,7 @@ import { extractOAuthStateAppId } from "../shared/oauth-state.js";
10
10
  import { DEFAULT_WORKSPACE_APP_AUDIENCE, normalizeWorkspaceAppHomePath, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromPackageJson, } from "../shared/workspace-app-audience.js";
11
11
  import { inferWorkspaceAppRootHomePath, readConfiguredWorkspaceAppHomePath, } from "../workspace-app-config.js";
12
12
  import { attachGatewaySocketErrorSink, normalizeOrigin, rewriteRedirectLocation, escapeHtml, } from "./gateway-helpers.js";
13
+ import { DEV_SERVER_SUPERVISOR_ENV } from "./process.js";
13
14
  const DEFAULT_GATEWAY_HOST = "127.0.0.1";
14
15
  const DEFAULT_GATEWAY_PORT = 8080;
15
16
  const DEFAULT_APP_PORT_START = 8100;
@@ -692,6 +693,7 @@ export async function runWorkspaceDev(options = {}) {
692
693
  env: devWatcherEnv({
693
694
  ...env,
694
695
  APP_NAME: app.id,
696
+ [DEV_SERVER_SUPERVISOR_ENV]: "1",
695
697
  AGENT_NATIVE_WORKSPACE: "1",
696
698
  AGENT_NATIVE_WORKSPACE_APP_ID: app.id,
697
699
  AGENT_NATIVE_WORKSPACE_APPS_JSON: workspaceAppsJson(),
@@ -760,8 +760,18 @@ export function sendToAgentChat(opts) {
760
760
  const actionScope = opts.actionScope === undefined
761
761
  ? undefined
762
762
  : normalizeAgentActionScope(opts.actionScope);
763
- const isCodeRequest = routesToCodeFrame(opts);
764
- const localChatTarget = opts.chatTarget === "local" || keepsApprovalInAppChat(opts);
763
+ const mcpBridgeEnabled = isMcpAppChatBridgeEnabled();
764
+ const hasMcpAppLocalPayload = mcpBridgeEnabled &&
765
+ Boolean(opts.attachments?.length ||
766
+ opts.images?.length ||
767
+ opts.referenceImagePaths?.length ||
768
+ opts.uploadedReferenceImages?.length ||
769
+ opts.usageLabel ||
770
+ actionScope);
771
+ const isCodeRequest = routesToCodeFrame(opts) && !hasMcpAppLocalPayload;
772
+ const localChatTarget = opts.chatTarget === "local" ||
773
+ keepsApprovalInAppChat(opts) ||
774
+ hasMcpAppLocalPayload;
765
775
  const requestMode = normalizeAgentChatRequestMode(opts.requestMode ?? opts.mode) ??
766
776
  readStoredAgentChatRequestMode();
767
777
  if (opts.submit !== false && opts.message.trim()) {
@@ -793,18 +803,7 @@ export function sendToAgentChat(opts) {
793
803
  ...(requestMode ? { mode: requestMode, requestMode } : {}),
794
804
  },
795
805
  };
796
- if (opts.submit !== false &&
797
- !localChatTarget &&
798
- isMcpAppChatBridgeEnabled()) {
799
- // MCP host follow-up APIs carry neither attachment descriptors nor a usage
800
- // label. Use the normal wrapper transport when either needs to reach the
801
- // chat thread — a label silently downgraded to `chat` is exactly the run
802
- // the caller named it to be able to find. (Approval continuations never
803
- // get here: they stay in the app's own chat, see keepsApprovalInAppChat.)
804
- if (opts.attachments?.length || opts.usageLabel || actionScope) {
805
- window.parent.postMessage(payload, getFramePostMessageTargetOrigin() || "*");
806
- return tabId;
807
- }
806
+ if (opts.submit !== false && !localChatTarget && mcpBridgeEnabled) {
808
807
  const directHostMessage = sendMcpAppHostMessage({
809
808
  message: opts.message,
810
809
  context: opts.context,
@@ -21,7 +21,7 @@ export const BUILDER_SPACE_SETTINGS_URL = "https://builder.io/account/space?utm_
21
21
  export const NEW_CHAT_ACTION_HREF = "agent-native:new-chat";
22
22
  const OPEN_BUILDER_SPACE_SETTINGS_LABEL = "Open Builder space settings";
23
23
  const START_NEW_CHAT_LABEL = "Start new chat";
24
- const UPGRADE_AT_BUILDER_LABEL = "Upgrade at builder.io";
24
+ const ADD_CREDITS_IN_BUILDER_LABEL = "Add credits in Builder";
25
25
  const BUILDER_AUTHENTICATION_ERROR = "Builder rejected the connected credentials. Reconnect Builder.io (free tier available) in Settings, then retry.";
26
26
  /**
27
27
  * A 401 says the credential this request carried was refused. It does NOT say
@@ -100,7 +100,7 @@ export function formatChatErrorText(errorMessage, upgradeUrl, errorCode) {
100
100
  const normalized = normalizeChatError(errorMessage, errorCode);
101
101
  if (normalized.message === CREDITS_LIMIT_REACHED_MESSAGE) {
102
102
  return upgradeUrl && isSafeUpgradeUrl(upgradeUrl)
103
- ? `${normalized.message}\n\n[${UPGRADE_AT_BUILDER_LABEL}](${upgradeUrl})`
103
+ ? `${normalized.message}\n\n[${ADD_CREDITS_IN_BUILDER_LABEL}](${upgradeUrl})`
104
104
  : normalized.message;
105
105
  }
106
106
  if (!isServerChosenVisitorMessage(normalized.message) &&
@@ -118,7 +118,7 @@ export function formatChatErrorText(errorMessage, upgradeUrl, errorCode) {
118
118
  if (!upgradeUrl || !isSafeUpgradeUrl(upgradeUrl)) {
119
119
  return `Error: ${normalized.message}`;
120
120
  }
121
- return `Error: ${normalized.message}\n\n[${UPGRADE_AT_BUILDER_LABEL}](${upgradeUrl})`;
121
+ return `Error: ${normalized.message}\n\n[${ADD_CREDITS_IN_BUILDER_LABEL}](${upgradeUrl})`;
122
122
  }
123
123
  /**
124
124
  * True when the server already decided what this reader may be told.
@@ -229,7 +229,7 @@ const KNOWN_CHAT_ERROR_ACTION_KEYS = new Map([
229
229
  "agentChat.errorMessages.openBuilderSpaceSettings",
230
230
  ],
231
231
  ["Start new chat", "agentChat.errorMessages.startNewChat"],
232
- ["Upgrade at builder.io", "agentChat.errorMessages.upgradeAtBuilder"],
232
+ ["Add credits in Builder", "agentChat.errorMessages.addCreditsInBuilder"],
233
233
  ]);
234
234
  /** Localize only Core's own normalized error copy; preserve provider details. */
235
235
  export function localizeKnownChatErrorText(text, t) {
@@ -1,13 +1,15 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { IconActivity, IconMessages, IconThumbUp, IconThumbDown, IconClock, IconCoin, IconTool, IconMoodSmile, IconChartBar, IconAB2, IconMessageReport, IconMessageCircle, IconPencil, IconChevronRight, IconArrowLeft, IconLoader2, IconAlertTriangle, } from "@tabler/icons-react";
2
+ import { Skeleton } from "@agent-native/toolkit/ui/skeleton";
3
+ import { IconActivity, IconMessages, IconThumbUp, IconThumbDown, IconClock, IconCoin, IconTool, IconMoodSmile, IconChartBar, IconAB2, IconMessageReport, IconMessageCircle, IconPencil, IconChevronRight, IconArrowsMaximize, IconExternalLink, IconArrowLeft, IconLoader2, IconAlertTriangle, } from "@tabler/icons-react";
3
4
  import { useQueryClient } from "@tanstack/react-query";
4
5
  import { useState } from "react";
6
+ import { AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_OPEN, } from "../../shared/agent-sidebar-url.js";
5
7
  import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "../components/ui/dialog.js";
6
8
  import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
7
9
  import { useT } from "../i18n.js";
8
10
  import { cn } from "../utils.js";
9
11
  import { OutputPreview } from "./OutputPreview.js";
10
- import { useObservabilityOverview, useTraces, useTraceDetail, useFeedbackList, useFeedbackStats, useSatisfaction, useEvalStats, useExperiments, useExperimentDetail, useExperimentResults, useOutputReviews, useOutputReviewApp, useSaveInstructionUpdate, useSubmitFeedback, } from "./useObservability.js";
12
+ import { useObservabilityOverview, useTraces, useTraceDetail, useFeedbackList, useFeedbackStats, useSatisfaction, useEvalStats, useExperiments, useExperimentDetail, useExperimentResults, useOutputReviews, useOutputReviewDetail, useSaveInstructionUpdate, useSubmitFeedback, } from "./useObservability.js";
11
13
  // ─── Helpers ────────────────────────────────────────────────────────────
12
14
  function formatCost(centsX100) {
13
15
  const cents = centsX100 / 100;
@@ -47,6 +49,14 @@ function timeAgo(ts) {
47
49
  return `${Math.floor(diff / 3_600_000)}h ago`;
48
50
  return `${Math.floor(diff / 86_400_000)}d ago`;
49
51
  }
52
+ function reviewThreadHref(threadId) {
53
+ const isBrowser = typeof window !== "undefined";
54
+ const url = new URL(isBrowser ? window.location.href : "/", "http://agent-native.invalid");
55
+ url.searchParams.delete("threadId");
56
+ url.searchParams.set("thread", threadId);
57
+ url.searchParams.set(AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_OPEN);
58
+ return isBrowser ? url.toString() : `${url.pathname}${url.search}${url.hash}`;
59
+ }
50
60
  const RANGES = [
51
61
  { value: 7, label: "7d" },
52
62
  { value: 30, label: "30d" },
@@ -150,11 +160,12 @@ function ReviewTab({ days }) {
150
160
  const instructionMutation = useSaveInstructionUpdate();
151
161
  const queryClient = useQueryClient();
152
162
  const [selectedRunId, setSelectedRunId] = useState(null);
163
+ const [previewExpanded, setPreviewExpanded] = useState(false);
153
164
  const [openPopover, setOpenPopover] = useState(null);
154
165
  const [feedbackNote, setFeedbackNote] = useState(null);
155
166
  const [instructionDraft, setInstructionDraft] = useState(null);
156
167
  const selectedReview = reviews?.find((review) => review.runId === selectedRunId);
157
- const reviewAppQuery = useOutputReviewApp(selectedReview?.hasInlineApp ? selectedReview.runId : null);
168
+ const reviewDetailQuery = useOutputReviewDetail(selectedReview?.runId ?? null);
158
169
  if (isLoading)
159
170
  return _jsx(LoadingState, {});
160
171
  if (!reviews || reviews.length === 0) {
@@ -238,68 +249,87 @@ function ReviewTab({ days }) {
238
249
  value: "",
239
250
  target: "agent",
240
251
  };
241
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: "divide-y divide-border", children: reviews.map((review) => (_jsxs("button", { type: "button", "data-review-run-id": review.runId, onClick: () => setSelectedRunId(review.runId), className: "group flex w-full min-w-0 items-center gap-3 py-3 text-left first:pt-0 hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx("span", { className: "h-16 w-24 shrink-0 overflow-hidden rounded-md bg-muted/60 sm:h-20 sm:w-28", children: _jsx(OutputPreview, { answer: review.answer, inlineAppTitle: review.inlineAppTitle, previewLabel: t("observability.reviewPreview"), compact: true }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "line-clamp-2 break-words text-sm font-medium text-foreground", children: review.ask || "-" }), _jsxs("span", { className: "mt-1 block truncate text-xs text-muted-foreground", children: [review.model, " \u00B7 ", timeAgo(review.createdAt)] })] }), _jsx(IconChevronRight, { size: 16, className: "shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5" })] }, review.runId))) }), _jsx(Dialog, { open: Boolean(selectedReview), onOpenChange: (open) => {
242
- if (open)
243
- return;
244
- const closingRunId = selectedRunId;
245
- setSelectedRunId(null);
246
- setOpenPopover((current) => current?.runId === closingRunId ? null : current);
247
- setInstructionDraft((current) => current?.runId === closingRunId ? null : current);
248
- }, children: selectedReview && (_jsxs(DialogContent, { "aria-describedby": undefined, className: "flex max-h-[min(90dvh,900px)] w-[calc(100vw-2rem)] max-w-5xl flex-col gap-0 overflow-hidden p-0", children: [_jsx(DialogHeader, { className: "shrink-0 border-b border-border px-5 py-4", children: _jsx(DialogTitle, { className: "line-clamp-2 break-words text-left", children: selectedReview.ask || t("observability.reviewPreview") }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain px-5 py-4", children: [_jsxs("div", { className: "mb-3 truncate text-xs text-muted-foreground", children: [selectedReview.model, " \u00B7 ", timeAgo(selectedReview.createdAt)] }), _jsx("div", { className: "min-w-0 max-w-full overflow-hidden", children: _jsx(OutputPreview, { answer: selectedReview.answer, inlineApp: reviewAppQuery.data ?? undefined, maxAppHeight: 420, previewLabel: t("observability.reviewPreview") }) }), selectedNote?.value && (_jsxs("p", { className: "mt-4 flex items-start gap-2 whitespace-pre-wrap break-words text-xs text-muted-foreground", children: [_jsx(IconMessageCircle, { size: 14, className: "mt-0.5 shrink-0" }), selectedNote.value] }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1 border-t border-border px-4 py-3 sm:px-5", children: [_jsxs("div", { role: "group", "aria-label": t("observability.reviewFeedback"), className: "flex items-center gap-1", children: [_jsx("button", { type: "button", "aria-label": t("observability.thumbsUp"), "aria-pressed": selectedVote?.feedbackType === "thumbs_up", title: t("observability.thumbsUp"), disabled: feedbackMutation.isPending, onClick: () => saveFeedback(selectedReview.runId, selectedReview.threadId, "thumbs_up"), className: cn("rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50", selectedVote?.feedbackType === "thumbs_up" &&
249
- "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"), children: _jsx(IconThumbUp, { size: 16 }) }), _jsx("button", { type: "button", "aria-label": t("observability.thumbsDown"), "aria-pressed": selectedVote?.feedbackType === "thumbs_down", title: t("observability.thumbsDown"), disabled: feedbackMutation.isPending, onClick: () => saveFeedback(selectedReview.runId, selectedReview.threadId, "thumbs_down"), className: cn("rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50", selectedVote?.feedbackType === "thumbs_down" &&
250
- "bg-rose-500/10 text-rose-600 dark:text-rose-400"), children: _jsx(IconThumbDown, { size: 16 }) })] }), _jsxs(Popover, { open: feedbackOpen, onOpenChange: (open) => {
251
- if (open) {
252
- setFeedbackNote((current) => current?.runId === selectedReview.runId
253
- ? current
254
- : { runId: selectedReview.runId, value: "" });
255
- }
256
- setOpenPopover((current) => {
257
- if (open) {
258
- return { runId: selectedReview.runId, kind: "feedback" };
259
- }
260
- return current?.runId === selectedReview.runId &&
261
- current.kind === "feedback"
262
- ? null
263
- : current;
264
- });
265
- }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("observability.addFeedback"), title: t("observability.addFeedback"), className: "rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: _jsx(IconMessageCircle, { size: 16 }) }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 8, className: "w-[min(22rem,calc(100vw-2rem))] p-3", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "sr-only", children: t("observability.feedbackNote") }), _jsx("textarea", { value: feedbackNote?.runId === selectedReview.runId
266
- ? feedbackNote.value
267
- : "", onChange: (event) => setFeedbackNote({
268
- runId: selectedReview.runId,
269
- value: event.target.value,
270
- }), rows: 3, className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring", placeholder: t("observability.feedbackPlaceholder") })] }), _jsx("button", { type: "button", onClick: () => saveNote(selectedReview.runId, selectedReview.threadId), disabled: !(feedbackNote?.runId === selectedReview.runId
271
- ? feedbackNote.value.trim()
272
- : "") || feedbackMutation.isPending, className: "mt-2 rounded-md bg-primary px-2.5 py-1.5 text-xs font-medium text-primary-foreground disabled:opacity-50", children: t("observability.saveFeedback") })] })] }), _jsxs(Popover, { open: instructionOpen, onOpenChange: (open) => {
273
- if (open) {
274
- setInstructionDraft((current) => current?.runId === selectedReview.runId
275
- ? current
276
- : {
277
- runId: selectedReview.runId,
278
- value: "",
279
- target: "agent",
280
- });
281
- }
282
- setOpenPopover((current) => {
283
- if (open) {
284
- return {
285
- runId: selectedReview.runId,
286
- kind: "instruction",
287
- };
288
- }
289
- return current?.runId === selectedReview.runId &&
290
- current.kind === "instruction"
291
- ? null
292
- : current;
293
- });
294
- }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("observability.draftInstruction"), title: t("observability.draftInstruction"), className: "rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: _jsx(IconPencil, { size: 16 }) }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 8, className: "w-[min(26rem,calc(100vw-2rem))] p-3", children: [_jsx("div", { className: "mb-2 text-xs font-medium text-foreground", children: t("observability.updateInstructions") }), _jsx("p", { className: "mb-2 text-[11px] text-muted-foreground", children: t("observability.draftNotice") }), _jsxs("select", { value: activeInstructionDraft.target, onChange: (event) => setInstructionDraft({
295
- ...activeInstructionDraft,
296
- target: event.target
297
- .value,
298
- }), className: "w-full rounded-md border border-border bg-background px-2 py-1.5 text-xs text-foreground", "aria-label": t("observability.instructionTarget"), children: [_jsx("option", { value: "agent", children: t("observability.agentTarget") }), _jsx("option", { value: "developer", children: t("observability.developerTarget") }), _jsx("option", { value: "skill", children: t("observability.skillTarget") })] }), _jsx("textarea", { value: activeInstructionDraft.value, onChange: (event) => setInstructionDraft({
299
- ...activeInstructionDraft,
300
- value: event.target.value,
301
- }), rows: 4, className: "mt-2 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring", placeholder: t("observability.instructionPlaceholder") }), _jsx("button", { type: "button", onClick: () => saveInstruction(selectedReview.runId, selectedReview.threadId), disabled: !activeInstructionDraft.value.trim() ||
302
- instructionMutation.isPending, className: "mt-2 rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:opacity-50", children: t("observability.saveUpdate") })] })] })] })] })) })] }));
252
+ const reviewMessages = reviewDetailQuery.data?.messages.length
253
+ ? reviewDetailQuery.data.messages
254
+ : selectedReview
255
+ ? [
256
+ ...(selectedReview.ask
257
+ ? [{ role: "user", text: selectedReview.ask }]
258
+ : []),
259
+ ...(selectedReview.answer
260
+ ? [{ role: "assistant", text: selectedReview.answer }]
261
+ : []),
262
+ ]
263
+ : [];
264
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "divide-y divide-border", children: reviews.map((review) => {
265
+ const expanded = selectedRunId === review.runId;
266
+ const triggerId = `review-trigger-${encodeURIComponent(review.runId)}`;
267
+ const detailId = `review-details-${encodeURIComponent(review.runId)}`;
268
+ return (_jsxs("div", { className: "min-w-0", children: [_jsxs("button", { id: triggerId, type: "button", "data-review-run-id": review.runId, "aria-expanded": expanded, "aria-controls": detailId, onClick: () => {
269
+ setSelectedRunId((current) => current === review.runId ? null : review.runId);
270
+ setOpenPopover(null);
271
+ setPreviewExpanded(false);
272
+ }, className: "group flex w-full min-w-0 items-center gap-3 py-3 text-left first:pt-0 hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx("span", { className: "h-16 w-24 shrink-0 overflow-hidden rounded-md bg-muted/60 sm:h-20 sm:w-28", children: _jsx(OutputPreview, { answer: review.answer, inlineAppTitle: review.inlineAppTitle, previewLabel: t("observability.reviewPreview"), compact: true }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "line-clamp-2 break-words text-sm font-medium text-foreground", children: review.ask || "-" }), _jsxs("span", { className: "mt-1 block truncate text-xs text-muted-foreground", children: [review.model, " \u00B7 ", timeAgo(review.createdAt)] })] }), _jsx(IconChevronRight, { size: 16, className: cn("shrink-0 text-muted-foreground transition-transform", expanded ? "rotate-90" : "group-hover:translate-x-0.5") })] }), _jsx("div", { id: detailId, role: "region", "aria-labelledby": triggerId, "data-review-detail-for": review.runId, hidden: !expanded, children: expanded && selectedReview && (_jsxs("div", { className: "grid min-w-0 gap-0 border-t border-border lg:grid-cols-2", children: [_jsxs("section", { "data-review-preview": true, className: "min-w-0 p-3 sm:p-4", "aria-label": t("observability.reviewPreview"), children: [_jsxs("div", { className: "relative max-h-[min(38rem,65dvh)] min-h-64 overflow-auto rounded-md border border-border bg-background", children: [_jsx(OutputPreview, { answer: selectedReview.answer, inlineApp: reviewDetailQuery.data?.app ?? undefined, maxAppHeight: 420, previewLabel: t("observability.reviewPreview") }), _jsx("button", { type: "button", "data-review-lightbox-trigger": true, "aria-label": t("observability.reviewPreview"), title: t("observability.reviewPreview"), onClick: () => setPreviewExpanded(true), className: "absolute right-2 top-2 rounded-md border border-border bg-background/95 p-2 text-muted-foreground shadow-sm transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: _jsx(IconArrowsMaximize, { size: 16 }) })] }), selectedNote?.value && (_jsxs("p", { className: "mt-3 flex items-start gap-2 whitespace-pre-wrap break-words text-xs text-muted-foreground", children: [_jsx(IconMessageCircle, { size: 14, className: "mt-0.5 shrink-0" }), selectedNote.value] }))] }), _jsxs("section", { "data-review-transcript": true, className: "flex min-w-0 flex-col border-t border-border lg:border-l lg:border-t-0", children: [_jsx("div", { className: "flex max-h-[min(38rem,65dvh)] min-h-64 flex-1 flex-col gap-3 overflow-y-auto overscroll-contain p-3 sm:p-4", children: reviewDetailQuery.isLoading ? (_jsxs("div", { role: "status", "aria-label": t("agentChat.common.loading"), className: "space-y-4", children: [_jsx(Skeleton, { className: "ml-auto h-14 w-3/4 rounded-xl" }), _jsx(Skeleton, { className: "h-20 w-5/6 rounded-xl" }), _jsx(Skeleton, { className: "ml-auto h-12 w-2/3 rounded-xl" })] })) : reviewDetailQuery.isError ? (_jsx("p", { role: "alert", className: "text-sm text-muted-foreground", children: t("agentChat.common.chunkLoadFailed") })) : (reviewMessages.map((message, index) => (_jsxs("div", { className: cn("max-w-[92%] whitespace-pre-wrap break-words rounded-xl px-3 py-2 text-sm", message.role === "user"
273
+ ? "ml-auto bg-primary/10 text-foreground"
274
+ : "mr-auto bg-muted text-foreground"), children: [message.role === "assistant" && (_jsx("span", { className: "mb-1 block text-[10px] font-medium text-muted-foreground", children: t("agentChat.common.agent") })), message.text] }, `${message.role}-${index}`)))) }), selectedReview.threadId && (_jsxs("a", { href: reviewThreadHref(selectedReview.threadId), target: "_blank", rel: "noreferrer", className: "flex w-fit items-center gap-1.5 px-3 pb-3 text-xs text-muted-foreground hover:text-foreground sm:px-4", children: [t("agentTask.openThread"), _jsx(IconExternalLink, { size: 14 })] }))] }), _jsxs("div", { className: "col-span-full flex shrink-0 items-center gap-1 border-t border-border px-3 py-2 sm:px-4", children: [_jsxs("div", { role: "group", "aria-label": t("observability.reviewFeedback"), className: "flex items-center gap-1", children: [_jsx("button", { type: "button", "aria-label": t("observability.thumbsUp"), "aria-pressed": selectedVote?.feedbackType === "thumbs_up", title: t("observability.thumbsUp"), disabled: feedbackMutation.isPending, onClick: () => saveFeedback(selectedReview.runId, selectedReview.threadId, "thumbs_up"), className: cn("rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50", selectedVote?.feedbackType === "thumbs_up" &&
275
+ "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"), children: _jsx(IconThumbUp, { size: 16 }) }), _jsx("button", { type: "button", "aria-label": t("observability.thumbsDown"), "aria-pressed": selectedVote?.feedbackType === "thumbs_down", title: t("observability.thumbsDown"), disabled: feedbackMutation.isPending, onClick: () => saveFeedback(selectedReview.runId, selectedReview.threadId, "thumbs_down"), className: cn("rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50", selectedVote?.feedbackType === "thumbs_down" &&
276
+ "bg-rose-500/10 text-rose-600 dark:text-rose-400"), children: _jsx(IconThumbDown, { size: 16 }) })] }), _jsxs(Popover, { open: feedbackOpen, onOpenChange: (open) => {
277
+ if (open) {
278
+ setFeedbackNote((current) => current?.runId === selectedReview.runId
279
+ ? current
280
+ : { runId: selectedReview.runId, value: "" });
281
+ }
282
+ setOpenPopover((current) => {
283
+ if (open) {
284
+ return {
285
+ runId: selectedReview.runId,
286
+ kind: "feedback",
287
+ };
288
+ }
289
+ return current?.runId === selectedReview.runId &&
290
+ current.kind === "feedback"
291
+ ? null
292
+ : current;
293
+ });
294
+ }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("observability.addFeedback"), title: t("observability.addFeedback"), className: "rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: _jsx(IconMessageCircle, { size: 16 }) }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 8, className: "w-[min(22rem,calc(100vw-2rem))] p-3", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "sr-only", children: t("observability.feedbackNote") }), _jsx("textarea", { value: feedbackNote?.runId === selectedReview.runId
295
+ ? feedbackNote.value
296
+ : "", onChange: (event) => setFeedbackNote({
297
+ runId: selectedReview.runId,
298
+ value: event.target.value,
299
+ }), rows: 3, className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring", placeholder: t("observability.feedbackPlaceholder") })] }), _jsx("button", { type: "button", onClick: () => saveNote(selectedReview.runId, selectedReview.threadId), disabled: !(feedbackNote?.runId === selectedReview.runId
300
+ ? feedbackNote.value.trim()
301
+ : "") || feedbackMutation.isPending, className: "mt-2 rounded-md bg-primary px-2.5 py-1.5 text-xs font-medium text-primary-foreground disabled:opacity-50", children: t("observability.saveFeedback") })] })] }), _jsxs(Popover, { open: instructionOpen, onOpenChange: (open) => {
302
+ if (open) {
303
+ setInstructionDraft((current) => current?.runId === selectedReview.runId
304
+ ? current
305
+ : {
306
+ runId: selectedReview.runId,
307
+ value: "",
308
+ target: "agent",
309
+ });
310
+ }
311
+ setOpenPopover((current) => {
312
+ if (open) {
313
+ return {
314
+ runId: selectedReview.runId,
315
+ kind: "instruction",
316
+ };
317
+ }
318
+ return current?.runId === selectedReview.runId &&
319
+ current.kind === "instruction"
320
+ ? null
321
+ : current;
322
+ });
323
+ }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("observability.draftInstruction"), title: t("observability.draftInstruction"), className: "rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: _jsx(IconPencil, { size: 16 }) }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 8, className: "w-[min(26rem,calc(100vw-2rem))] p-3", children: [_jsx("div", { className: "mb-2 text-xs font-medium text-foreground", children: t("observability.updateInstructions") }), _jsx("p", { className: "mb-2 text-[11px] text-muted-foreground", children: t("observability.draftNotice") }), _jsxs("select", { value: activeInstructionDraft.target, onChange: (event) => setInstructionDraft({
324
+ ...activeInstructionDraft,
325
+ target: event.target
326
+ .value,
327
+ }), className: "w-full rounded-md border border-border bg-background px-2 py-1.5 text-xs text-foreground", "aria-label": t("observability.instructionTarget"), children: [_jsx("option", { value: "agent", children: t("observability.agentTarget") }), _jsx("option", { value: "developer", children: t("observability.developerTarget") }), _jsx("option", { value: "skill", children: t("observability.skillTarget") })] }), _jsx("textarea", { value: activeInstructionDraft.value, onChange: (event) => setInstructionDraft({
328
+ ...activeInstructionDraft,
329
+ value: event.target.value,
330
+ }), rows: 4, className: "mt-2 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring", placeholder: t("observability.instructionPlaceholder") }), _jsx("button", { type: "button", onClick: () => saveInstruction(selectedReview.runId, selectedReview.threadId), disabled: !activeInstructionDraft.value.trim() ||
331
+ instructionMutation.isPending, className: "mt-2 rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:opacity-50", children: t("observability.saveUpdate") })] })] })] })] })) })] }, review.runId));
332
+ }) }), _jsx(Dialog, { open: Boolean(selectedReview && previewExpanded), onOpenChange: setPreviewExpanded, children: selectedReview && (_jsxs(DialogContent, { "aria-describedby": undefined, "data-review-lightbox": true, className: "flex max-h-[min(90dvh,900px)] w-[calc(100vw-2rem)] max-w-6xl flex-col gap-0 overflow-hidden p-0", children: [_jsx(DialogHeader, { className: "shrink-0 border-b border-border px-4 py-3", children: _jsx(DialogTitle, { className: "line-clamp-2 break-words text-left", children: selectedReview.ask || t("observability.reviewPreview") }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto overscroll-contain p-4", children: _jsx(OutputPreview, { answer: selectedReview.answer, inlineApp: reviewDetailQuery.data?.app ?? undefined, maxAppHeight: 720, previewLabel: t("observability.reviewPreview") }) })] })) })] }));
303
333
  }
304
334
  // ─── Tab: Feedback ──────────────────────────────────────────────────────
305
335
  function FeedbackTab({ days }) {
@@ -28,9 +28,10 @@ export type OutputPreviewModel = {
28
28
  title?: string;
29
29
  summary?: string;
30
30
  imageUrl?: string;
31
+ previewUrl?: string;
31
32
  tokens: DesignToken[];
32
33
  };
33
- export declare function parseOutputPreview(answer: string): OutputPreviewModel;
34
+ export declare function parseOutputPreview(answer: string, baseOrigin?: string | undefined): OutputPreviewModel;
34
35
  export declare function OutputPreview({ answer, previewLabel, inlineApp, inlineAppTitle, compact, maxAppHeight, }: {
35
36
  answer: string;
36
37
  previewLabel: string;