@agent-native/core 0.84.13 → 0.84.16

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 (96) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +25 -0
  3. package/corpus/core/package.json +2 -2
  4. package/corpus/core/src/agent/durable-background.ts +18 -5
  5. package/corpus/core/src/agent/engine/builder-engine.ts +7 -3
  6. package/corpus/core/src/agent/production-agent.ts +48 -7
  7. package/corpus/core/src/agent/run-manager.ts +39 -2
  8. package/corpus/core/src/agent/run-store.ts +55 -11
  9. package/corpus/core/src/client/AssistantChat.tsx +30 -4
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +11 -3
  11. package/corpus/core/src/client/agent-chat-adapter.ts +21 -3
  12. package/corpus/core/src/client/agent-chat.ts +18 -3
  13. package/corpus/core/src/client/blocks/library/diagram.tsx +3 -3
  14. package/corpus/core/src/client/chat/message-components.tsx +17 -0
  15. package/corpus/core/src/client/use-agent-engine-configured.ts +7 -4
  16. package/corpus/core/src/db/client.ts +10 -1
  17. package/corpus/core/src/observability/traces.ts +17 -3
  18. package/corpus/core/src/server/agent-chat-plugin.ts +80 -44
  19. package/corpus/core/src/styles/blocks.css +44 -1
  20. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +18 -24
  21. package/corpus/templates/analytics/changelog/2026-07-01-dashboard-charts-no-longer-show-future-dates-when-clients-se.md +6 -0
  22. package/corpus/templates/analytics/changelog/2026-07-01-dashboard-filters-take-less-space-and-keep-view-actions-tucked.md +6 -0
  23. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +34 -9
  24. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +6 -2
  25. package/corpus/templates/analytics/server/lib/session-replay.ts +109 -50
  26. package/corpus/templates/analytics/server/plugins/db.ts +21 -0
  27. package/corpus/templates/clips/changelog/2026-07-01-chrome-extension-recordings-stream-uploads-while-recording.md +6 -0
  28. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  29. package/corpus/templates/clips/chrome-extension/src/background.ts +8 -0
  30. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +61 -16
  31. package/corpus/templates/design/app/pages/DesignEditor.tsx +176 -13
  32. package/corpus/templates/design/changelog/2026-07-01-exporting-a-design-as-png-or-svg-now-downloads-just-the-sele.md +6 -0
  33. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +44 -1
  34. package/corpus/templates/plan/changelog/2026-07-01-visual-plan-and-recap-diagram-labels-now-wrap-inside-their-b.md +6 -0
  35. package/dist/agent/durable-background.d.ts +1 -0
  36. package/dist/agent/durable-background.d.ts.map +1 -1
  37. package/dist/agent/durable-background.js +17 -3
  38. package/dist/agent/durable-background.js.map +1 -1
  39. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  40. package/dist/agent/engine/builder-engine.js +7 -1
  41. package/dist/agent/engine/builder-engine.js.map +1 -1
  42. package/dist/agent/production-agent.d.ts.map +1 -1
  43. package/dist/agent/production-agent.js +32 -7
  44. package/dist/agent/production-agent.js.map +1 -1
  45. package/dist/agent/run-manager.d.ts +2 -0
  46. package/dist/agent/run-manager.d.ts.map +1 -1
  47. package/dist/agent/run-manager.js +28 -3
  48. package/dist/agent/run-manager.js.map +1 -1
  49. package/dist/agent/run-store.d.ts +9 -0
  50. package/dist/agent/run-store.d.ts.map +1 -1
  51. package/dist/agent/run-store.js +47 -11
  52. package/dist/agent/run-store.js.map +1 -1
  53. package/dist/client/AssistantChat.d.ts +8 -2
  54. package/dist/client/AssistantChat.d.ts.map +1 -1
  55. package/dist/client/AssistantChat.js +18 -2
  56. package/dist/client/AssistantChat.js.map +1 -1
  57. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  58. package/dist/client/MultiTabAssistantChat.js +7 -3
  59. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  60. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  61. package/dist/client/agent-chat-adapter.js +19 -3
  62. package/dist/client/agent-chat-adapter.js.map +1 -1
  63. package/dist/client/agent-chat.d.ts +7 -0
  64. package/dist/client/agent-chat.d.ts.map +1 -1
  65. package/dist/client/agent-chat.js +5 -1
  66. package/dist/client/agent-chat.js.map +1 -1
  67. package/dist/client/blocks/library/diagram.js +3 -3
  68. package/dist/client/blocks/library/diagram.js.map +1 -1
  69. package/dist/client/chat/message-components.d.ts +3 -1
  70. package/dist/client/chat/message-components.d.ts.map +1 -1
  71. package/dist/client/chat/message-components.js +16 -2
  72. package/dist/client/chat/message-components.js.map +1 -1
  73. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  74. package/dist/client/use-agent-engine-configured.js +7 -1
  75. package/dist/client/use-agent-engine-configured.js.map +1 -1
  76. package/dist/collab/routes.d.ts +2 -2
  77. package/dist/db/client.d.ts +6 -0
  78. package/dist/db/client.d.ts.map +1 -1
  79. package/dist/db/client.js +8 -1
  80. package/dist/db/client.js.map +1 -1
  81. package/dist/notifications/routes.d.ts +2 -2
  82. package/dist/observability/routes.d.ts +3 -3
  83. package/dist/observability/traces.d.ts +5 -0
  84. package/dist/observability/traces.d.ts.map +1 -1
  85. package/dist/observability/traces.js +9 -3
  86. package/dist/observability/traces.js.map +1 -1
  87. package/dist/progress/routes.d.ts +1 -1
  88. package/dist/resources/handlers.d.ts +3 -3
  89. package/dist/server/agent-chat-plugin.d.ts +2 -1
  90. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  91. package/dist/server/agent-chat-plugin.js +64 -42
  92. package/dist/server/agent-chat-plugin.js.map +1 -1
  93. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  94. package/dist/server/transcribe-voice.d.ts +1 -1
  95. package/dist/styles/blocks.css +44 -1
  96. package/package.json +2 -2
@@ -1461,8 +1461,29 @@ export function createAgentChatAdapter(
1461
1461
  const nextContinuationToolCallId = () =>
1462
1462
  `continuation_tc_${++continuationToolCallCounter}`;
1463
1463
 
1464
+ const runDebugContextDetails = (): string => {
1465
+ const pageOrigin =
1466
+ typeof window !== "undefined" && window.location?.origin
1467
+ ? window.location.origin
1468
+ : "";
1469
+ return [
1470
+ `api_url: ${apiUrl}`,
1471
+ pageOrigin ? `page_origin: ${pageOrigin}` : "",
1472
+ tabId ? `tab_id: ${tabId}` : "",
1473
+ threadId ? `thread_id: ${threadId}` : "",
1474
+ `turn_id: ${turnId}`,
1475
+ runId ? `current_run: ${runId}` : "",
1476
+ attemptedRunIds.length > 0
1477
+ ? `attempted_runs: ${attemptedRunIds.join(", ")}`
1478
+ : "",
1479
+ ]
1480
+ .filter(Boolean)
1481
+ .join("\n");
1482
+ };
1483
+
1464
1484
  const connectionRecoveryDetails = (): string => {
1465
1485
  return [
1486
+ runDebugContextDetails(),
1466
1487
  lastAutoContinueReason
1467
1488
  ? `last_auto_continue_reason: ${lastAutoContinueReason}`
1468
1489
  : "",
@@ -1488,9 +1509,6 @@ export function createAgentChatAdapter(
1488
1509
  ? `activity_trail: ${formatActivityTrail(lastActivityTrail)}`
1489
1510
  : "",
1490
1511
  `total_transient_continuations: ${totalTransientContinuationAttempts}`,
1491
- attemptedRunIds.length > 0
1492
- ? `attempted_runs: ${attemptedRunIds.join(", ")}`
1493
- : "",
1494
1512
  ]
1495
1513
  .filter(Boolean)
1496
1514
  .join("\n");
@@ -113,6 +113,13 @@ export interface AgentChatContextSetOptions extends AgentChatContextItem {
113
113
  * Defaults to true so the user can see the staged context.
114
114
  */
115
115
  openSidebar?: boolean;
116
+ /**
117
+ * Whether to move keyboard focus into the composer when staging the item.
118
+ * Defaults to true. Pass `false` for context that mirrors ambient UI state
119
+ * (e.g. a canvas element selection) so staging never steals focus from an
120
+ * unrelated editor — such as an inline text editor in a design canvas.
121
+ */
122
+ focus?: boolean;
116
123
  }
117
124
 
118
125
  /** @deprecated Use `AgentChatContextSetOptions` instead. */
@@ -865,9 +872,17 @@ export function setAgentChatContextItem(
865
872
  publishAgentChatContextItems(
866
873
  withReplacedAgentChatContextItem(agentChatContextState.items, item),
867
874
  );
868
- postAgentChatContextMessage(AGENT_CHAT_SET_CONTEXT_MESSAGE_TYPE, item, {
869
- openSidebar: opts.openSidebar !== false,
870
- });
875
+ // Forward an explicit `focus: false` so the receiving composer can stage the
876
+ // chip without stealing focus. Focus stays enabled by default (field omitted)
877
+ // for every existing caller.
878
+ const messageData = opts.focus === false ? { ...item, focus: false } : item;
879
+ postAgentChatContextMessage(
880
+ AGENT_CHAT_SET_CONTEXT_MESSAGE_TYPE,
881
+ messageData,
882
+ {
883
+ openSidebar: opts.openSidebar !== false,
884
+ },
885
+ );
871
886
  }
872
887
 
873
888
  /** @deprecated Use `setAgentChatContextItem` instead. */
@@ -291,7 +291,7 @@ function PositionedDiagram({
291
291
  return (
292
292
  <span
293
293
  key={`${edge.from}-${edge.to}-${index}-label`}
294
- className="absolute z-10 max-w-[130px] -translate-x-1/2 -translate-y-1/2 rounded-full border border-border bg-background px-2 py-0.5 text-center text-[11px] font-semibold text-muted-foreground shadow-sm"
294
+ className="absolute z-10 max-w-[130px] -translate-x-1/2 -translate-y-1/2 break-words rounded-full border border-border bg-background px-2 py-0.5 text-center text-[11px] font-semibold text-muted-foreground shadow-sm [overflow-wrap:anywhere]"
295
295
  style={{
296
296
  left: `${(from.displayX + to.displayX) / 2}%`,
297
297
  top: `${(from.displayY + to.displayY) / 2}%`,
@@ -305,7 +305,7 @@ function PositionedDiagram({
305
305
  {displayNodesForDirection.map((node, index) => (
306
306
  <article
307
307
  key={node.id}
308
- className="absolute z-20 -translate-x-1/2 -translate-y-1/2 rounded-xl border-2 border-border bg-background p-3 text-foreground shadow-sm"
308
+ className="absolute z-20 -translate-x-1/2 -translate-y-1/2 break-words rounded-xl border-2 border-border bg-background p-3 text-foreground shadow-sm [overflow-wrap:anywhere]"
309
309
  style={{
310
310
  left: `${node.displayX}%`,
311
311
  top: `${node.displayY}%`,
@@ -485,7 +485,7 @@ function SequenceDiagram({
485
485
  {next && (
486
486
  <div className="grid min-w-[72px] justify-items-center gap-1 text-muted-foreground">
487
487
  {edge?.label && (
488
- <span className="max-w-[96px] truncate rounded-full border border-border px-2 py-0.5 text-[11px] font-semibold">
488
+ <span className="max-w-[96px] whitespace-normal break-words rounded-full border border-border px-2 py-0.5 text-center text-[11px] font-semibold [overflow-wrap:anywhere]">
489
489
  {edge.label}
490
490
  </span>
491
491
  )}
@@ -784,19 +784,31 @@ function assistantMessageStatusIsTerminal(message: {
784
784
  return statusType === "complete" || statusType === "incomplete";
785
785
  }
786
786
 
787
+ export function assistantMessageHasUnresolvedTool(content: unknown): boolean {
788
+ if (!Array.isArray(content)) return false;
789
+ return content.some((part): boolean => {
790
+ if (!part || typeof part !== "object") return false;
791
+ const record = part as { type?: unknown; result?: unknown };
792
+ return record.type === "tool-call" && record.result === undefined;
793
+ });
794
+ }
795
+
787
796
  export function shouldShowAssistantMessageFooter({
788
797
  isLast,
789
798
  chatRunning,
790
799
  hasRenderableContent,
791
800
  statusIsTerminal,
801
+ hasUnresolvedTool,
792
802
  }: {
793
803
  isLast: boolean;
794
804
  chatRunning: boolean;
795
805
  hasRenderableContent: boolean;
796
806
  statusIsTerminal: boolean;
807
+ hasUnresolvedTool?: boolean;
797
808
  }): boolean {
798
809
  if (!hasRenderableContent) return false;
799
810
  if (!isLast) return true;
811
+ if (hasUnresolvedTool) return false;
800
812
  return !chatRunning && statusIsTerminal;
801
813
  }
802
814
 
@@ -813,11 +825,13 @@ export function AssistantMessage() {
813
825
  thread.messages.length > 0 &&
814
826
  thread.messages[thread.messages.length - 1].id === msg.id;
815
827
  const hasRenderableContent = assistantMessageHasRenderableContent(msg);
828
+ const hasUnresolvedTool = assistantMessageHasUnresolvedTool(msg.content);
816
829
  const isComplete = shouldShowAssistantMessageFooter({
817
830
  isLast,
818
831
  chatRunning,
819
832
  hasRenderableContent,
820
833
  statusIsTerminal: assistantMessageStatusIsTerminal(msg),
834
+ hasUnresolvedTool,
821
835
  });
822
836
  const cpCtx = React.useContext(CheckpointContext);
823
837
 
@@ -907,6 +921,9 @@ export function AssistantMessage() {
907
921
  {isComplete && hasCodeAgentTools && msgContent && (
908
922
  <FilesChangedSummary parts={msgContent} />
909
923
  )}
924
+ {isLast && hasUnresolvedTool && !chatRunning && (
925
+ <RunningActivityStatus label="Thinking" />
926
+ )}
910
927
  </div>
911
928
  {isComplete && (
912
929
  <div className="mt-1 flex items-center justify-between">
@@ -35,10 +35,13 @@ async function fetchStatusJson(
35
35
  }, timeoutMs);
36
36
  });
37
37
 
38
- const request = fetch(
39
- agentNativePath(path),
40
- controller ? { signal: controller.signal } : undefined,
41
- )
38
+ // Never serve a stale status from the HTTP cache: this is re-fetched right
39
+ // after a provider connects, and a cached "missing" would keep the composer
40
+ // gate and error banner pinned even though a provider is now configured.
41
+ const request = fetch(agentNativePath(path), {
42
+ cache: "no-store",
43
+ ...(controller ? { signal: controller.signal } : {}),
44
+ })
42
45
  .then((r) => (r.ok ? r.json() : null))
43
46
  .catch(() => null)
44
47
  .finally(() => {
@@ -837,13 +837,22 @@ export function neonPoolMax(): number {
837
837
  * (agent/durable-background.ts), replicated here to avoid a `db` → `agent`
838
838
  * import cycle. Keep the signals in sync with that function.
839
839
  */
840
- function isBackgroundFunctionPoolContext(): boolean {
840
+ export function isBackgroundFunctionPoolContext(): boolean {
841
841
  if (
842
842
  (globalThis as Record<string, unknown>)
843
843
  .__AGENT_NATIVE_BACKGROUND_RUNTIME__ === true
844
844
  ) {
845
845
  return true;
846
846
  }
847
+ // Set by the HMAC-authenticated agent-chat `_process-run` route before it
848
+ // re-enters the normal chat handler. This mirrors the marker-only runtime
849
+ // proof used by durable-background.ts without importing agent code into db/.
850
+ if (
851
+ (globalThis as Record<string, unknown>)
852
+ .__AGENT_NATIVE_BACKGROUND_RUNTIME_EXPECTED__ === true
853
+ ) {
854
+ return true;
855
+ }
847
856
  const lambdaName = process.env.AWS_LAMBDA_FUNCTION_NAME;
848
857
  if (
849
858
  typeof lambdaName === "string" &&
@@ -89,6 +89,14 @@ export async function instrumentAgentLoop(opts: {
89
89
  * reads. */
90
90
  userId: string | null;
91
91
  config: ObservabilityConfig;
92
+ classifyError?: (error: unknown) =>
93
+ | {
94
+ status?: "success" | "error";
95
+ errorMessage?: string | null;
96
+ metadata?: Record<string, unknown> | null;
97
+ }
98
+ | null
99
+ | undefined;
92
100
  }): Promise<AgentLoopUsage> {
93
101
  const { runAgentLoop, loopOpts, runId, threadId, userId, config } = opts;
94
102
  const runStart = Date.now();
@@ -260,11 +268,17 @@ export async function instrumentAgentLoop(opts: {
260
268
  let usage: AgentLoopUsage | undefined;
261
269
  let runStatus: "success" | "error" = "success";
262
270
  let errorMessage: string | null = null;
271
+ let runMetadata: Record<string, unknown> | null = null;
263
272
  try {
264
273
  usage = await runAgentLoop({ ...loopOpts, send: instrumentedSend });
265
274
  } catch (err: any) {
266
- runStatus = "error";
267
- errorMessage = err?.message ?? String(err);
275
+ const classification = opts.classifyError?.(err) ?? null;
276
+ runStatus = classification?.status ?? "error";
277
+ errorMessage =
278
+ classification?.errorMessage === undefined
279
+ ? (err?.message ?? String(err))
280
+ : classification.errorMessage;
281
+ runMetadata = classification?.metadata ?? null;
268
282
  throw err;
269
283
  } finally {
270
284
  const runEnd = Date.now();
@@ -325,7 +339,7 @@ export async function instrumentAgentLoop(opts: {
325
339
  durationMs: totalDurationMs,
326
340
  status: runStatus,
327
341
  errorMessage,
328
- metadata: null,
342
+ metadata: runMetadata,
329
343
  createdAt: runStart,
330
344
  };
331
345
  spans.push(parentSpan);
@@ -34,8 +34,9 @@ import {
34
34
  } from "../agent/app-model-defaults.js";
35
35
  import { DEFAULT_ANTHROPIC_MODEL } from "../agent/default-model.js";
36
36
  import {
37
+ AGENT_CHAT_BACKGROUND_RUN_FIELD,
37
38
  AGENT_CHAT_PROCESS_RUN_PATH,
38
- extractProcessRunId,
39
+ backgroundRunMarkerExpectsBackgroundRuntime,
39
40
  isInBackgroundFunctionRuntime,
40
41
  prepareProcessRunRequest,
41
42
  } from "../agent/durable-background.js";
@@ -69,6 +70,7 @@ import {
69
70
  recordRunDiagnostic,
70
71
  RUN_DIAG_STAGE,
71
72
  setRunError,
73
+ setRunTerminalReason,
72
74
  updateRunStatusIfRunning,
73
75
  } from "../agent/run-store.js";
74
76
  import { buildRuntimeContextPrompt } from "../agent/runtime-context.js";
@@ -3876,6 +3878,7 @@ type AgentChatProcessRunFailureDeps = {
3876
3878
  readBackgroundRunClaim?: typeof readBackgroundRunClaim;
3877
3879
  recordRunDiagnostic?: typeof recordRunDiagnostic;
3878
3880
  setRunError?: typeof setRunError;
3881
+ setRunTerminalReason?: typeof setRunTerminalReason;
3879
3882
  updateRunStatusIfRunning?: typeof updateRunStatusIfRunning;
3880
3883
  ensureTerminalRunEvent?: typeof ensureTerminalRunEvent;
3881
3884
  };
@@ -3888,6 +3891,7 @@ export async function finalizeClaimedAgentChatProcessRunFailure(
3888
3891
  const readClaim = deps.readBackgroundRunClaim ?? readBackgroundRunClaim;
3889
3892
  const record = deps.recordRunDiagnostic ?? recordRunDiagnostic;
3890
3893
  const setError = deps.setRunError ?? setRunError;
3894
+ const setTerminalReason = deps.setRunTerminalReason ?? setRunTerminalReason;
3891
3895
  const updateStatus =
3892
3896
  deps.updateRunStatusIfRunning ?? updateRunStatusIfRunning;
3893
3897
  const ensureTerminal = deps.ensureTerminalRunEvent ?? ensureTerminalRunEvent;
@@ -3908,7 +3912,13 @@ export async function finalizeClaimedAgentChatProcessRunFailure(
3908
3912
  CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT.errorCode,
3909
3913
  `${CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT.details} setupError=${message}`,
3910
3914
  ).catch(() => {});
3911
- await updateStatus(runId, "errored").catch(() => {});
3915
+ const statusUpdated = await updateStatus(runId, "errored").catch(() => false);
3916
+ if (statusUpdated) {
3917
+ await setTerminalReason(
3918
+ runId,
3919
+ CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT.errorCode,
3920
+ ).catch(() => {});
3921
+ }
3912
3922
  await ensureTerminal(
3913
3923
  runId,
3914
3924
  CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT,
@@ -7400,6 +7410,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7400
7410
  // unreadable Netlify background-function logs — read
7401
7411
  // `/runs/active?threadId=...` and inspect `diagStage`.
7402
7412
  dispatchMode: run.dispatchMode ?? null,
7413
+ terminalReason: run.terminalReason ?? null,
7403
7414
  diagStage: run.diagStage ?? null,
7404
7415
  workerStage: workerClaim?.workerStage ?? null,
7405
7416
  // Server clock so the client computes "stuck" elapsed time
@@ -7983,18 +7994,6 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7983
7994
  setResponseStatus(event, 405);
7984
7995
  return { error: "Method not allowed" };
7985
7996
  }
7986
- // DIAGNOSTIC: load the run-store diagnostic recorder. Each stage we
7987
- // reach is written onto the run row (diag_stage) so a silent failure
7988
- // INSIDE the Netlify background function — whose logs we cannot read —
7989
- // is still diagnosable from the client via /runs/active. Best-effort:
7990
- // the import + every record call is wrapped so diagnostics can never
7991
- // break the worker path.
7992
- const diag = await import("../agent/run-store.js")
7993
- .then((m) => ({
7994
- record: m.recordRunDiagnostic,
7995
- stages: m.RUN_DIAG_STAGE,
7996
- }))
7997
- .catch(() => null);
7998
7997
 
7999
7998
  // Consume the body ONCE (h3 v2's web Request stream is single-use).
8000
7999
  let processBody: any;
@@ -8005,18 +8004,6 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
8005
8004
  return { error: "Invalid request body" };
8006
8005
  }
8007
8006
 
8008
- // Record "the route handler was entered" against the run BEFORE auth
8009
- // runs. This is the proof the bg-fn invocation actually reached Nitro
8010
- // (vs. dying at the function entry / never being invoked). The runId
8011
- // is parsed without authenticating so we can attach it even on a
8012
- // subsequent auth failure.
8013
- const diagRunId = extractProcessRunId(processBody);
8014
- if (diag && diagRunId) {
8015
- await diag
8016
- .record(diagRunId, diag.stages.routeEntered)
8017
- .catch(() => {});
8018
- }
8019
-
8020
8007
  // Validate + HMAC-authenticate the self-dispatch and prepare the
8021
8008
  // background-worker body. Pure decision (unit-tested in
8022
8009
  // durable-background.spec.ts); the route only wires it to h3.
@@ -8031,6 +8018,12 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
8031
8018
  // bypassing session auth) inside the unreadable bg function would
8032
8019
  // leave the run to time out with NO clue. The detail carries the
8033
8020
  // status + whether A2A_SECRET is even present in this isolate.
8021
+ const diag = await import("../agent/run-store.js")
8022
+ .then((m) => ({
8023
+ record: m.recordRunDiagnostic,
8024
+ stages: m.RUN_DIAG_STAGE,
8025
+ }))
8026
+ .catch(() => null);
8034
8027
  if (diag && prepared.runId) {
8035
8028
  const a2aPresent = Boolean(
8036
8029
  process.env.A2A_SECRET && process.env.A2A_SECRET.length > 0,
@@ -8047,27 +8040,58 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
8047
8040
  return { error: prepared.error };
8048
8041
  }
8049
8042
 
8050
- // DIAGNOSTIC: auth + body validation passed. Reaching here proves the
8051
- // request was authenticated and we are about to invoke the worker.
8052
- if (diag) {
8053
- await diag
8054
- .record(prepared.runId, diag.stages.authPassed)
8055
- .catch(() => {});
8043
+ const preparedMarker = (prepared.body as Record<string, unknown>)[
8044
+ AGENT_CHAT_BACKGROUND_RUN_FIELD
8045
+ ];
8046
+ const expectsBackgroundRuntime =
8047
+ backgroundRunMarkerExpectsBackgroundRuntime(preparedMarker);
8048
+ const runtimeGlobals = globalThis as Record<string, unknown>;
8049
+ const hadExpectedRuntimeMarker = Object.prototype.hasOwnProperty.call(
8050
+ runtimeGlobals,
8051
+ "__AGENT_NATIVE_BACKGROUND_RUNTIME_EXPECTED__",
8052
+ );
8053
+ const previousExpectedRuntimeMarker =
8054
+ runtimeGlobals.__AGENT_NATIVE_BACKGROUND_RUNTIME_EXPECTED__;
8055
+ if (expectsBackgroundRuntime) {
8056
+ runtimeGlobals.__AGENT_NATIVE_BACKGROUND_RUNTIME_EXPECTED__ = true;
8056
8057
  }
8057
8058
 
8058
- // Stash the verified+augmented body for the handler — the body stream
8059
- // is already consumed, so the handler reads this instead.
8060
- (event as any).context = (event as any).context ?? {};
8061
- (event as any).context.__agentChatBackgroundBody = prepared.body;
8062
-
8063
- // Durable owner context: this self-dispatch is cookieless (HMAC-only).
8064
- // Resolve the owner from the persisted run row, never the request body,
8065
- // then invoke the normal handler. The shared agent-run context helper
8066
- // expands that owner into the same user/org AsyncLocalStorage context the
8067
- // foreground request uses, so credential and data scoping stay aligned.
8068
- await seedBackgroundAgentRunOwnerContext(event, prepared.runId);
8069
-
8070
8059
  try {
8060
+ // DIAGNOSTIC: load the run-store diagnostic recorder only after the
8061
+ // authenticated marker has been mirrored into globalThis. run-store
8062
+ // can initialize the DB pool; the pool must see the same background
8063
+ // proof as the agent timeout logic before that happens.
8064
+ const diag = await import("../agent/run-store.js")
8065
+ .then((m) => ({
8066
+ record: m.recordRunDiagnostic,
8067
+ stages: m.RUN_DIAG_STAGE,
8068
+ }))
8069
+ .catch(() => null);
8070
+
8071
+ // Record "the route handler was entered" against the run after auth
8072
+ // succeeds. This is the proof the bg-fn invocation actually reached
8073
+ // Nitro (vs. dying at the function entry / never being invoked).
8074
+ if (diag) {
8075
+ await diag
8076
+ .record(prepared.runId, diag.stages.routeEntered)
8077
+ .catch(() => {});
8078
+ await diag
8079
+ .record(prepared.runId, diag.stages.authPassed)
8080
+ .catch(() => {});
8081
+ }
8082
+
8083
+ // Stash the verified+augmented body for the handler — the body stream
8084
+ // is already consumed, so the handler reads this instead.
8085
+ (event as any).context = (event as any).context ?? {};
8086
+ (event as any).context.__agentChatBackgroundBody = prepared.body;
8087
+
8088
+ // Durable owner context: this self-dispatch is cookieless (HMAC-only).
8089
+ // Resolve the owner from the persisted run row, never the request
8090
+ // body, then invoke the normal handler. The shared agent-run context
8091
+ // helper expands that owner into the same user/org AsyncLocalStorage
8092
+ // context the foreground request uses, so credential and data scoping
8093
+ // stay aligned.
8094
+ await seedBackgroundAgentRunOwnerContext(event, prepared.runId);
8071
8095
  return await invokeAgentChatHandler(event);
8072
8096
  } catch (err: any) {
8073
8097
  console.error("[agent-chat] _process-run failed:", err);
@@ -8089,6 +8113,18 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
8089
8113
  );
8090
8114
  setResponseStatus(event, 500);
8091
8115
  return { error: "process-run failed" };
8116
+ } finally {
8117
+ if (expectsBackgroundRuntime) {
8118
+ if (hadExpectedRuntimeMarker) {
8119
+ runtimeGlobals.__AGENT_NATIVE_BACKGROUND_RUNTIME_EXPECTED__ =
8120
+ previousExpectedRuntimeMarker;
8121
+ } else {
8122
+ Reflect.deleteProperty(
8123
+ runtimeGlobals,
8124
+ "__AGENT_NATIVE_BACKGROUND_RUNTIME_EXPECTED__",
8125
+ );
8126
+ }
8127
+ }
8092
8128
  }
8093
8129
  }),
8094
8130
  );
@@ -1355,8 +1355,37 @@
1355
1355
  color: var(--wf-ink) !important;
1356
1356
  }
1357
1357
 
1358
- .plan-diagram-frame :is(.diagram-node, .diagram-box, .diagram-card) {
1358
+ .plan-diagram-frame
1359
+ :is(
1360
+ .diagram-panel,
1361
+ .diagram-card,
1362
+ .diagram-node,
1363
+ .diagram-box,
1364
+ [class*="card"],
1365
+ [class*="panel"],
1366
+ [class*="box"]
1367
+ ) {
1359
1368
  max-width: 100%;
1369
+ overflow-wrap: anywhere !important;
1370
+ word-break: break-word;
1371
+ white-space: normal !important;
1372
+ }
1373
+
1374
+ .plan-diagram-frame
1375
+ :is(
1376
+ .diagram-panel,
1377
+ .diagram-card,
1378
+ .diagram-node,
1379
+ .diagram-box,
1380
+ [class*="card"],
1381
+ [class*="panel"],
1382
+ [class*="box"]
1383
+ )
1384
+ :is(h1, h2, h3, p, small, strong, span, li) {
1385
+ max-width: 100%;
1386
+ overflow-wrap: anywhere !important;
1387
+ word-break: break-word;
1388
+ white-space: normal !important;
1360
1389
  }
1361
1390
 
1362
1391
  /* Base padding so primitive text never touches the box edge, even when an author
@@ -1422,6 +1451,7 @@
1422
1451
  display: inline-flex;
1423
1452
  align-items: center;
1424
1453
  justify-content: center;
1454
+ flex-wrap: wrap;
1425
1455
  /* A pill hugs its label. `fit-content` is a definite cross size, so a flex
1426
1456
  * column with the default `align-items: stretch` no longer blows the pill out
1427
1457
  * to full width (the number chips and short status labels stay pill-shaped).
@@ -1434,6 +1464,19 @@
1434
1464
  padding: 3px 11px;
1435
1465
  color: var(--wf-ink);
1436
1466
  background: var(--wf-paper);
1467
+ overflow-wrap: anywhere !important;
1468
+ text-align: center;
1469
+ white-space: normal !important;
1470
+ word-break: break-word;
1471
+ }
1472
+
1473
+ .plan-diagram-frame
1474
+ :is(.diagram-pill, [class*="pill"], [class*="badge"], [class*="chip"])
1475
+ > * {
1476
+ min-width: 0;
1477
+ max-width: 100%;
1478
+ overflow-wrap: anywhere !important;
1479
+ white-space: normal !important;
1437
1480
  }
1438
1481
 
1439
1482
  .plan-diagram-frame :is(.diagram-accent, .diagram-pill.accent) {
@@ -228,18 +228,25 @@ export function DashboardFilterBar({
228
228
  <Collapsible
229
229
  open={filtersOpen}
230
230
  onOpenChange={setFiltersOpen}
231
- className="group rounded-lg border border-border bg-card p-3"
231
+ className="group rounded-lg border border-border bg-card px-3 py-2"
232
232
  >
233
- <div className="flex items-center justify-between gap-3">
234
- <div className="flex items-baseline gap-2">
235
- <h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
236
- {t("sqlDashboard.filters")}
237
- </h3>
238
- <span className="text-[10px] text-muted-foreground/60">
239
- {t("sqlDashboard.autoApplied")}
240
- </span>
241
- </div>
242
- <div className="flex items-center gap-1 opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100 group-data-[state=closed]:opacity-100">
233
+ <div className="flex flex-wrap items-end justify-between gap-3">
234
+ <CollapsibleContent className="min-w-0 flex-1">
235
+ <div className="flex flex-wrap gap-3 items-end">
236
+ {uniqueFilters.map((f) => (
237
+ <FilterControl
238
+ key={f.id}
239
+ filter={f}
240
+ vars={vars}
241
+ hasParam={(key) =>
242
+ searchParams.has(FILTER_PARAM_PREFIX + key)
243
+ }
244
+ setValue={(updates) => setParam(updates)}
245
+ />
246
+ ))}
247
+ </div>
248
+ </CollapsibleContent>
249
+ <div className="flex shrink-0 items-center gap-1 self-center opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100 group-data-[state=closed]:opacity-100">
243
250
  {onSaveView && filtersActive && (
244
251
  <Button
245
252
  variant="ghost"
@@ -284,19 +291,6 @@ export function DashboardFilterBar({
284
291
  </CollapsibleTrigger>
285
292
  </div>
286
293
  </div>
287
- <CollapsibleContent className="pt-3">
288
- <div className="flex flex-wrap gap-3 items-end">
289
- {uniqueFilters.map((f) => (
290
- <FilterControl
291
- key={f.id}
292
- filter={f}
293
- vars={vars}
294
- hasParam={(key) => searchParams.has(FILTER_PARAM_PREFIX + key)}
295
- setValue={(updates) => setParam(updates)}
296
- />
297
- ))}
298
- </div>
299
- </CollapsibleContent>
300
294
  </Collapsible>
301
295
 
302
296
  <Dialog open={saveDialogOpen} onOpenChange={setSaveDialogOpen}>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Dashboard charts no longer show future dates when clients send bad event timestamps.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Dashboard filters take less space and keep view actions tucked to the side.