@agent-native/core 0.84.18 → 0.84.21

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 (157) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +21 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/caller-auth.ts +37 -8
  5. package/corpus/core/src/a2a/client.ts +178 -67
  6. package/corpus/core/src/agent/production-agent.ts +19 -3
  7. package/corpus/core/src/agent/run-store.ts +180 -13
  8. package/corpus/core/src/agent/types.ts +1 -1
  9. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
  10. package/corpus/core/src/cli/recap.ts +43 -34
  11. package/corpus/core/src/client/AssistantChat.tsx +49 -0
  12. package/corpus/core/src/client/PoweredByBadge.tsx +70 -17
  13. package/corpus/core/src/client/active-run-state.ts +30 -0
  14. package/corpus/core/src/client/agent-chat-adapter.ts +5 -1
  15. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +41 -3
  16. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +133 -0
  17. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +2 -2
  18. package/corpus/core/src/client/i18n.tsx +10 -5
  19. package/corpus/core/src/client/sse-event-processor.ts +147 -5
  20. package/corpus/core/src/extensions/html-shell.ts +17 -3
  21. package/corpus/core/src/extensions/theme.ts +39 -0
  22. package/corpus/core/src/file-upload/builder.ts +35 -14
  23. package/corpus/core/src/file-upload/types.ts +3 -0
  24. package/corpus/core/src/integrations/a2a-continuation-processor.ts +49 -24
  25. package/corpus/core/src/mcp/builtin-tools.ts +3 -0
  26. package/corpus/core/src/mcp/org-directory.ts +66 -20
  27. package/corpus/core/src/scripts/call-agent.ts +7 -2
  28. package/corpus/templates/analytics/actions/provider-api-request.ts +8 -1
  29. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +2 -1
  30. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +91 -33
  31. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +161 -12
  32. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-load-large-production-recordings-from-scoped.md +6 -0
  33. package/corpus/templates/calendar/app/pages/BookingPage.tsx +14 -8
  34. package/corpus/templates/calendar/changelog/2026-07-01-meeting-invite-pages-have-cleaner-branding-a-black-dark-mo.md +6 -0
  35. package/corpus/templates/clips/actions/create-recording.ts +7 -2
  36. package/corpus/templates/clips/actions/finalize-recording.ts +11 -0
  37. package/corpus/templates/clips/actions/get-recording-player-data.ts +4 -2
  38. package/corpus/templates/clips/actions/import-loom-recording.ts +1 -0
  39. package/corpus/templates/clips/actions/lib/loom-video.ts +16 -0
  40. package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -2
  41. package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +4 -3
  42. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +39 -12
  43. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +65 -1
  44. package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +1 -22
  45. package/corpus/templates/clips/app/routes/record.tsx +48 -8
  46. package/corpus/templates/clips/changelog/2026-07-01-chrome-recording-uploads-now-serialize-chunks-to-avoid-resum.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-loom-imports-finish-sooner-by-skipping-the-.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-playback-recover-more-reliably-from-storage.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-01-desktop-recording-avoids-unsupported-browser-tab-capture.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-01-embedded-player-progress-is-easier-to-see-on-dark-videos.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-01-loom-imports-show-clear-guidance-when-loom-does-not-provide-a-downloadable-video.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-01-preserve-in-flight-streaming-uploads-when-the-streaming-kill.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-01-recording-controls-are-cleaner-and-the-countdown-uses-shadow.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-01-recordings-now-recover-when-final-upload-status-gets-lost.md +6 -0
  55. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  56. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +7 -224
  57. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +94 -29
  58. package/corpus/templates/clips/desktop/src/lib/recorder.ts +87 -5
  59. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +7 -0
  60. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +14 -1
  61. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +4 -1
  62. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +96 -1
  63. package/corpus/templates/clips/shared/finalize-recovery.ts +242 -0
  64. package/corpus/templates/design/changelog/2026-07-01-design-recovers-sooner-when-an-agent-gets-stuck-preparing-a-.md +6 -0
  65. package/corpus/templates/plan/app/global.css +1 -1
  66. package/dist/a2a/caller-auth.d.ts +1 -0
  67. package/dist/a2a/caller-auth.d.ts.map +1 -1
  68. package/dist/a2a/caller-auth.js +34 -8
  69. package/dist/a2a/caller-auth.js.map +1 -1
  70. package/dist/a2a/client.d.ts +4 -0
  71. package/dist/a2a/client.d.ts.map +1 -1
  72. package/dist/a2a/client.js +148 -58
  73. package/dist/a2a/client.js.map +1 -1
  74. package/dist/agent/production-agent.d.ts.map +1 -1
  75. package/dist/agent/production-agent.js +19 -4
  76. package/dist/agent/production-agent.js.map +1 -1
  77. package/dist/agent/run-store.d.ts +19 -6
  78. package/dist/agent/run-store.d.ts.map +1 -1
  79. package/dist/agent/run-store.js +163 -13
  80. package/dist/agent/run-store.js.map +1 -1
  81. package/dist/agent/types.d.ts +1 -0
  82. package/dist/agent/types.d.ts.map +1 -1
  83. package/dist/agent/types.js.map +1 -1
  84. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  85. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  86. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  87. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  88. package/dist/cli/recap.d.ts +3 -3
  89. package/dist/cli/recap.d.ts.map +1 -1
  90. package/dist/cli/recap.js +41 -34
  91. package/dist/cli/recap.js.map +1 -1
  92. package/dist/client/AssistantChat.d.ts +2 -0
  93. package/dist/client/AssistantChat.d.ts.map +1 -1
  94. package/dist/client/AssistantChat.js +36 -2
  95. package/dist/client/AssistantChat.js.map +1 -1
  96. package/dist/client/PoweredByBadge.d.ts +9 -3
  97. package/dist/client/PoweredByBadge.d.ts.map +1 -1
  98. package/dist/client/PoweredByBadge.js +47 -15
  99. package/dist/client/PoweredByBadge.js.map +1 -1
  100. package/dist/client/active-run-state.d.ts +3 -0
  101. package/dist/client/active-run-state.d.ts.map +1 -1
  102. package/dist/client/active-run-state.js +24 -0
  103. package/dist/client/active-run-state.js.map +1 -1
  104. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  105. package/dist/client/agent-chat-adapter.js +5 -1
  106. package/dist/client/agent-chat-adapter.js.map +1 -1
  107. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  108. package/dist/client/extensions/EmbeddedExtension.js +32 -3
  109. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  110. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  111. package/dist/client/extensions/ExtensionViewer.js +59 -2
  112. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  113. package/dist/client/extensions/ExtensionsSidebarSection.js +2 -2
  114. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  115. package/dist/client/i18n.d.ts +1 -1
  116. package/dist/client/i18n.d.ts.map +1 -1
  117. package/dist/client/i18n.js +6 -2
  118. package/dist/client/i18n.js.map +1 -1
  119. package/dist/client/sse-event-processor.d.ts +2 -0
  120. package/dist/client/sse-event-processor.d.ts.map +1 -1
  121. package/dist/client/sse-event-processor.js +121 -5
  122. package/dist/client/sse-event-processor.js.map +1 -1
  123. package/dist/collab/awareness.d.ts +2 -2
  124. package/dist/collab/awareness.d.ts.map +1 -1
  125. package/dist/collab/routes.d.ts +1 -1
  126. package/dist/extensions/html-shell.d.ts.map +1 -1
  127. package/dist/extensions/html-shell.js +17 -3
  128. package/dist/extensions/html-shell.js.map +1 -1
  129. package/dist/extensions/theme.d.ts +9 -0
  130. package/dist/extensions/theme.d.ts.map +1 -1
  131. package/dist/extensions/theme.js +38 -0
  132. package/dist/extensions/theme.js.map +1 -1
  133. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  134. package/dist/file-upload/builder.d.ts.map +1 -1
  135. package/dist/file-upload/builder.js +22 -8
  136. package/dist/file-upload/builder.js.map +1 -1
  137. package/dist/file-upload/types.d.ts +5 -1
  138. package/dist/file-upload/types.d.ts.map +1 -1
  139. package/dist/file-upload/types.js.map +1 -1
  140. package/dist/integrations/a2a-continuation-processor.js +43 -18
  141. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  142. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  143. package/dist/mcp/builtin-tools.js +3 -0
  144. package/dist/mcp/builtin-tools.js.map +1 -1
  145. package/dist/mcp/org-directory.d.ts.map +1 -1
  146. package/dist/mcp/org-directory.js +58 -20
  147. package/dist/mcp/org-directory.js.map +1 -1
  148. package/dist/notifications/routes.d.ts +3 -3
  149. package/dist/observability/routes.d.ts +6 -6
  150. package/dist/progress/routes.d.ts +1 -1
  151. package/dist/resources/handlers.d.ts +3 -3
  152. package/dist/scripts/call-agent.d.ts.map +1 -1
  153. package/dist/scripts/call-agent.js +2 -2
  154. package/dist/scripts/call-agent.js.map +1 -1
  155. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  156. package/dist/server/transcribe-voice.d.ts +1 -1
  157. package/package.json +1 -1
@@ -8,9 +8,9 @@
8
8
  * thin, deterministic glue around that:
9
9
  *
10
10
  * gate The security boundary: decide whether the recap runs at all
11
- * (skipping drafts, forks, bots, missing secrets, an invalid
12
- * agent/model, and PRs that touch recap-control files) and which
13
- * normalized backend agent to use.
11
+ * (skipping drafts, forks without secret access, bots, missing
12
+ * secrets, an invalid agent/model, and untrusted PRs that touch
13
+ * recap-control files) and which normalized backend agent to use.
14
14
  * collect-diff Collect the bounded base...head diff (excluding lockfiles,
15
15
  * build output, snapshots), cap it at ~600KB, and classify the
16
16
  * huge/tiny flags.
@@ -2846,7 +2846,14 @@ export async function runShot(
2846
2846
  });
2847
2847
  }
2848
2848
  const page = await context.newPage();
2849
- await page.goto(captureUrl, { waitUntil: "networkidle", timeout: 45_000 });
2849
+ await page.goto(captureUrl, {
2850
+ waitUntil: "domcontentloaded",
2851
+ timeout: 45_000,
2852
+ });
2853
+ await page.waitForLoadState("load", { timeout: 15_000 }).catch(() => {
2854
+ // The selectors below are the real readiness signal for screenshots.
2855
+ // Some recap pages keep long-lived/background requests open.
2856
+ });
2850
2857
  const selectors = [
2851
2858
  "[data-plan-document]",
2852
2859
  "[data-plan-block]",
@@ -3104,11 +3111,11 @@ export interface RecapGateInput {
3104
3111
  }
3105
3112
 
3106
3113
  /**
3107
- * Files that, if a PR touches them, would let that PR rewrite repo-pinned skill
3108
- * instructions or agent config the trusted recap job loads. The workflow runs
3109
- * the recap CLI from trusted base-branch source (or an installed package), so
3110
- * normal package code such as `packages/core/**` and recap workflow YAML can be
3111
- * recapped without executing PR-modified CLI code.
3114
+ * Files that, if an untrusted PR touches them, would let that PR rewrite
3115
+ * repo-pinned skill instructions or root agent config the trusted recap job
3116
+ * loads. The workflow runs the recap CLI from trusted base-branch source (or an
3117
+ * installed package), so normal package code, template-local AGENTS.md files,
3118
+ * and recap workflow YAML can be recapped without executing PR-modified CLI code.
3112
3119
  */
3113
3120
  function normalizeRecapSkillSourceMode(value: string | undefined): string {
3114
3121
  return (value || "auto").toLowerCase();
@@ -3124,10 +3131,10 @@ export function isRecapSensitivePath(
3124
3131
  ): boolean {
3125
3132
  const skillSource = options.skillSource;
3126
3133
  if (
3127
- /(^|\/)\.claude\//.test(p) ||
3128
- /(^|\/)CLAUDE\.md$/.test(p) ||
3129
- /(^|\/)AGENTS\.md$/.test(p) ||
3130
- /(^|\/)\.mcp\.json$/.test(p)
3134
+ p.startsWith(".claude/") ||
3135
+ p === "CLAUDE.md" ||
3136
+ p === "AGENTS.md" ||
3137
+ p === ".mcp.json"
3131
3138
  ) {
3132
3139
  return true;
3133
3140
  }
@@ -3227,16 +3234,18 @@ export function evaluateRecapGate(input: RecapGateInput): {
3227
3234
  );
3228
3235
  }
3229
3236
 
3230
- // Self-modifying guard: if this PR changes the visual-recap/visual-plan skill
3231
- // when CI is explicitly pinned to repo-local skill instructions, or any agent
3232
- // config the runner would load (.claude/**, CLAUDE.md, AGENTS.md, .mcp.json),
3233
- // skip the ENTIRE job — not just the agent — so a PR can never rewrite what
3234
- // the agent loads (skill, hooks, settings) and exfiltrate the publish/API
3235
- // secrets. In the default auto/latest modes the recap prompt comes from the
3236
- // trusted bundled skill, so visual skill and recap workflow files are ordinary
3237
- // reviewed content and may be recapped.
3237
+ // Self-modifying guard: if an untrusted PR changes the visual-recap/visual-plan
3238
+ // skill when CI is explicitly pinned to repo-local skill instructions, or root
3239
+ // agent config the runner would load (.claude/**, CLAUDE.md, AGENTS.md,
3240
+ // .mcp.json), skip the ENTIRE job — not just the agent — so a PR can never
3241
+ // rewrite what the agent loads (skill, hooks, settings) and exfiltrate the
3242
+ // publish/API secrets. In the default auto/latest modes the recap prompt comes
3243
+ // from the trusted bundled skill, so visual skill and recap workflow files are
3244
+ // ordinary reviewed content and may be recapped. Trusted write actors may edit
3245
+ // recap-control files as reviewable content; running the recap is useful signal
3246
+ // for those changes.
3238
3247
  const shouldApplySensitivePathGuard =
3239
- Boolean(pr) && (isFork || (!isPrivate && !isTrustedAuthor));
3248
+ Boolean(pr) && !isTrustedAuthor && (isFork || !isPrivate);
3240
3249
  const hits = shouldApplySensitivePathGuard
3241
3250
  ? input.changedFiles.filter((p) => isRecapSensitivePath(p, { skillSource }))
3242
3251
  : [];
@@ -3458,13 +3467,13 @@ export function appendGateSkipLine(
3458
3467
  existingBody: string,
3459
3468
  skipLine: string,
3460
3469
  ): string {
3461
- // Remove any previous gate-skip line (pattern: `_Recap skipped for ...._`)
3462
- const withoutPrev = existingBody
3463
- .split("\n")
3464
- .filter((l) => !/_Recap skipped for .+_$/.test(l.trim()))
3465
- .join("\n")
3466
- .trimEnd();
3467
- return `${withoutPrev}\n\n${skipLine}`;
3470
+ const planIdMatch = existingBody.match(
3471
+ /<!--\s*plan-id:\s*([A-Za-z0-9_-]{1,64})\s*-->/,
3472
+ );
3473
+ const planIdMarker = planIdMatch
3474
+ ? `\n\n<!-- plan-id: ${planIdMatch[1]} -->`
3475
+ : "";
3476
+ return `${buildGateSkipCommentBody()}${planIdMarker}\n\n${skipLine}`;
3468
3477
  }
3469
3478
 
3470
3479
  /* -------------------------------------------------------------------------- */
@@ -4095,11 +4104,11 @@ Usage:
4095
4104
  environment (HAS_PLAN / HAS_ANTHROPIC / HAS_OPENAI === 'true', AGENT,
4096
4105
  VISUAL_RECAP_MODEL), the repo from $GITHUB_REPOSITORY, and the PR's changed
4097
4106
  files from the GitHub REST API (paged, with GH_TOKEN/GITHUB_TOKEN). Skips
4098
- drafts, forks, bot authors, the missing-secret case, an invalid agent/model,
4099
- and any PR that touches recap-control files (repo-pinned skill instructions,
4100
- .claude/**, CLAUDE.md, AGENTS.md, .mcp.json) — failing CLOSED on any
4101
- file-list error. Writes run=<true|false> and agent=<claude|codex> to
4102
- $GITHUB_OUTPUT.
4107
+ drafts, forks without secret access, bot authors, the missing-secret case, an
4108
+ invalid agent/model, and any untrusted PR that touches recap-control files
4109
+ (repo-pinned skill instructions, .claude/**, root CLAUDE.md, root AGENTS.md,
4110
+ root .mcp.json) — failing CLOSED on any file-list error. Writes
4111
+ run=<true|false> and agent=<claude|codex> to $GITHUB_OUTPUT.
4103
4112
  npx @agent-native/core@latest recap agent-summary
4104
4113
  Read the captured Claude/Codex result file and write a sanitized one-line
4105
4114
  summary to stdout and $GITHUB_OUTPUT (summary). Used only when no plan URL
@@ -37,9 +37,11 @@ import React, {
37
37
  import { LLM_MISSING_CREDENTIALS_MESSAGE } from "../agent/engine/credential-errors.js";
38
38
  import type { ReasoningEffort } from "../shared/reasoning-effort.js";
39
39
  import {
40
+ getActiveRunActivityTool,
40
41
  getActiveRun,
41
42
  resolveReconnectAfterSeq,
42
43
  setActiveRun,
44
+ updateActiveRunActivity,
43
45
  updateActiveRunSeq,
44
46
  } from "./active-run-state.js";
45
47
  import {
@@ -553,6 +555,23 @@ function reconnectContentFollowKey(content: ContentPart[]): string {
553
555
  return content.map(contentPartFollowKey).join("|");
554
556
  }
555
557
 
558
+ export function reconnectActivityFallbackContent(
559
+ toolName: string | null | undefined,
560
+ ): ContentPart[] {
561
+ const tool = toolName?.trim();
562
+ if (!tool) return [];
563
+ return [
564
+ {
565
+ type: "tool-call",
566
+ toolCallId: `reconnect-activity:${tool}`,
567
+ toolName: tool,
568
+ argsText: "",
569
+ args: {},
570
+ activity: true,
571
+ },
572
+ ];
573
+ }
574
+
556
575
  const RECOVERY_USER_MESSAGE_PREFIXES = [
557
576
  "Continue from where you left off",
558
577
  "Continue from where you stopped",
@@ -1395,6 +1414,9 @@ const AssistantChatInner = forwardRef<
1395
1414
  const [runningActivityLabel, setRunningActivityLabel] = useState<
1396
1415
  string | null
1397
1416
  >(null);
1417
+ const [runningActivityTool, setRunningActivityTool] = useState<string | null>(
1418
+ null,
1419
+ );
1398
1420
  // Delayed-reveal state for the activity label (see ACTIVITY_LABEL_REVEAL_DELAY_MS).
1399
1421
  // `latest` holds the most recent activity label; `surfaced` flips true once the
1400
1422
  // reveal timer fires; `timer` is the pending one-shot reveal.
@@ -1409,6 +1431,8 @@ const AssistantChatInner = forwardRef<
1409
1431
  latestActivityLabelRef.current = null;
1410
1432
  activityLabelSurfacedRef.current = false;
1411
1433
  setRunningActivityLabel(null);
1434
+ setRunningActivityTool(null);
1435
+ updateActiveRunActivity(null);
1412
1436
  }, []);
1413
1437
  const [reconnectContent, setReconnectContent] = useState<ContentPart[]>([]);
1414
1438
  // When stop is clicked during reconnect, keep content visible (don't wipe it)
@@ -1445,6 +1469,18 @@ const AssistantChatInner = forwardRef<
1445
1469
  : isReconnecting && reconnectContent.length > 0
1446
1470
  ? "Reconnecting"
1447
1471
  : "Thinking";
1472
+ const reconnectActivityContent = useMemo(
1473
+ () =>
1474
+ (isReconnecting || reconnectFrozen) && reconnectContent.length === 0
1475
+ ? reconnectActivityFallbackContent(runningActivityTool)
1476
+ : [],
1477
+ [
1478
+ isReconnecting,
1479
+ reconnectFrozen,
1480
+ reconnectContent.length,
1481
+ runningActivityTool,
1482
+ ],
1483
+ );
1448
1484
  const lastBroadcastRunningRef = useRef(isRunning);
1449
1485
  const tiptapRef = useRef<TiptapComposerHandle>(null);
1450
1486
  // Stable ref to the "stop active run" action so addToQueue can abort
@@ -1700,11 +1736,14 @@ const AssistantChatInner = forwardRef<
1700
1736
 
1701
1737
  reconnectRunIdRef.current = runId;
1702
1738
  const afterSeq = resolveReconnectAfterSeq(threadId, runId);
1739
+ const storedActivityTool = getActiveRunActivityTool(threadId, runId);
1740
+ setRunningActivityTool(storedActivityTool);
1703
1741
  setReconnectAfterSeq(afterSeq);
1704
1742
  setActiveRun({
1705
1743
  threadId,
1706
1744
  runId,
1707
1745
  lastSeq: afterSeq > 0 ? afterSeq - 1 : -1,
1746
+ ...(storedActivityTool ? { activityTool: storedActivityTool } : {}),
1708
1747
  });
1709
1748
  setIsReconnecting(true);
1710
1749
  setReconnectFrozen(false);
@@ -2407,7 +2446,10 @@ const AssistantChatInner = forwardRef<
2407
2446
  const label =
2408
2447
  typeof detail?.label === "string" ? detail.label.trim() : "";
2409
2448
  if (!label) return;
2449
+ const tool = typeof detail?.tool === "string" ? detail.tool.trim() : "";
2410
2450
  setIsAutoResuming(false);
2451
+ setRunningActivityTool(tool || null);
2452
+ updateActiveRunActivity(tool || null);
2411
2453
  latestActivityLabelRef.current = label;
2412
2454
  // Already past the delay → keep the visible label current.
2413
2455
  if (activityLabelSurfacedRef.current) {
@@ -3549,6 +3591,13 @@ const AssistantChatInner = forwardRef<
3549
3591
  reconnectContent.length > 0 && (
3550
3592
  <ReconnectStreamMessage content={reconnectContent} />
3551
3593
  )}
3594
+ {(isReconnecting || reconnectFrozen) &&
3595
+ reconnectAfterSeq > 0 &&
3596
+ reconnectActivityContent.length > 0 && (
3597
+ <ReconnectStreamMessage
3598
+ content={reconnectActivityContent}
3599
+ />
3600
+ )}
3552
3601
  {showRunningInUI && (
3553
3602
  <RunningActivityStatus label={runningStatusLabel} />
3554
3603
  )}
@@ -2,13 +2,20 @@ import { IconBrandGithub } from "@tabler/icons-react";
2
2
  import type { CSSProperties } from "react";
3
3
 
4
4
  import { appPath } from "./api-path.js";
5
+ import { cn } from "./utils.js";
5
6
 
6
7
  export interface PoweredByBadgeProps {
7
8
  position?: "bottom-right" | "bottom-left";
9
+ /** Plain shows the logo only with no surrounding badge chrome. */
10
+ variant?: "badge" | "plain";
11
+ /** When true, positioning is handled by the parent container. */
12
+ embedded?: boolean;
8
13
  }
9
14
 
10
15
  export interface OpenSourceBadgeProps {
11
16
  position?: "bottom-left" | "bottom-right";
17
+ /** When true, positioning is handled by the parent container. */
18
+ embedded?: boolean;
12
19
  }
13
20
 
14
21
  const containerStyle = (
@@ -51,6 +58,8 @@ const darkQuery = "(prefers-color-scheme: dark)";
51
58
  */
52
59
  export function PoweredByBadge({
53
60
  position = "bottom-right",
61
+ variant = "badge",
62
+ embedded = false,
54
63
  }: PoweredByBadgeProps) {
55
64
  // Allow hiding via env var
56
65
  const hidden =
@@ -61,13 +70,14 @@ export function PoweredByBadge({
61
70
 
62
71
  const logoOnLight = appPath("/agent-native-logo-light.svg");
63
72
  const logoOnDark = appPath("/agent-native-logo-dark.svg");
73
+ const isPlain = variant === "plain";
64
74
 
65
75
  return (
66
76
  <>
67
77
  <style>{`
68
78
  .an-powered-logo {
69
79
  display: block;
70
- height: 14px;
80
+ height: ${isPlain ? "16.2px" : "14px"};
71
81
  width: auto;
72
82
  flex: none;
73
83
  }
@@ -75,14 +85,25 @@ export function PoweredByBadge({
75
85
  display: none;
76
86
  }
77
87
  @media (max-width: 640px) {
78
- .an-powered-badge {
88
+ .an-powered-badge:not(.an-powered-badge-embedded) {
79
89
  position: static !important;
80
90
  margin: 0.75rem auto 0 !important;
81
91
  width: max-content;
82
92
  max-width: calc(100vw - 2rem);
83
93
  }
84
94
  }
85
- .dark .an-powered-badge {
95
+ .an-powered-badge-plain {
96
+ background: transparent !important;
97
+ border-color: transparent !important;
98
+ backdrop-filter: none !important;
99
+ -webkit-backdrop-filter: none !important;
100
+ padding: 0 !important;
101
+ opacity: 1 !important;
102
+ }
103
+ .an-powered-badge-plain:hover {
104
+ opacity: 0.82 !important;
105
+ }
106
+ .dark .an-powered-badge:not(.an-powered-badge-plain) {
86
107
  background: rgba(255, 255, 255, 0.06) !important;
87
108
  border-color: rgba(255, 255, 255, 0.08) !important;
88
109
  color: rgba(215, 215, 215, 0.94) !important;
@@ -94,7 +115,7 @@ export function PoweredByBadge({
94
115
  display: block;
95
116
  }
96
117
  @media ${darkQuery} {
97
- .an-powered-badge {
118
+ .an-powered-badge:not(.an-powered-badge-plain) {
98
119
  background: rgba(255, 255, 255, 0.06) !important;
99
120
  border-color: rgba(255, 255, 255, 0.08) !important;
100
121
  color: rgba(215, 215, 215, 0.94) !important;
@@ -106,7 +127,7 @@ export function PoweredByBadge({
106
127
  display: block;
107
128
  }
108
129
  }
109
- .light .an-powered-badge {
130
+ .light .an-powered-badge:not(.an-powered-badge-plain) {
110
131
  background: rgba(0, 0, 0, 0.05) !important;
111
132
  border-color: rgba(0, 0, 0, 0.06) !important;
112
133
  color: rgba(95, 95, 95, 0.95) !important;
@@ -117,19 +138,19 @@ export function PoweredByBadge({
117
138
  .light .an-powered-logo-dark {
118
139
  display: none;
119
140
  }
120
- .an-powered-badge:hover {
141
+ .an-powered-badge:not(.an-powered-badge-plain):hover {
121
142
  opacity: 1 !important;
122
143
  color: rgba(70, 70, 70, 1) !important;
123
144
  }
124
145
  @media ${darkQuery} {
125
- .an-powered-badge:hover {
146
+ .an-powered-badge:not(.an-powered-badge-plain):hover {
126
147
  color: rgba(238, 238, 238, 1) !important;
127
148
  }
128
149
  }
129
- .dark .an-powered-badge:hover {
150
+ .dark .an-powered-badge:not(.an-powered-badge-plain):hover {
130
151
  color: rgba(238, 238, 238, 1) !important;
131
152
  }
132
- .light .an-powered-badge:hover {
153
+ .light .an-powered-badge:not(.an-powered-badge-plain):hover {
133
154
  color: rgba(70, 70, 70, 1) !important;
134
155
  }
135
156
  `}</style>
@@ -137,11 +158,24 @@ export function PoweredByBadge({
137
158
  href="https://agent-native.com"
138
159
  target="_blank"
139
160
  rel="noopener noreferrer"
140
- className="an-powered-badge"
141
- style={containerStyle(position)}
161
+ className={cn(
162
+ "an-powered-badge",
163
+ isPlain && "an-powered-badge-plain",
164
+ embedded && "an-powered-badge-embedded",
165
+ )}
166
+ style={
167
+ embedded
168
+ ? {
169
+ display: "inline-flex",
170
+ alignItems: "center",
171
+ textDecoration: "none",
172
+ transition: "opacity 0.2s",
173
+ }
174
+ : containerStyle(position)
175
+ }
142
176
  aria-label="Built with Agent Native"
143
177
  >
144
- <span>Built with</span>
178
+ {!isPlain && <span>Built with</span>}
145
179
  <img
146
180
  src={logoOnLight}
147
181
  alt="Agent Native"
@@ -158,12 +192,13 @@ export function PoweredByBadge({
158
192
  }
159
193
 
160
194
  /**
161
- * Small GitHub badge: "100% free and open source"
195
+ * Small GitHub badge: "Free and open source"
162
196
  *
163
197
  * Intended to pair with PoweredByBadge on public pages.
164
198
  */
165
199
  export function OpenSourceBadge({
166
200
  position = "bottom-left",
201
+ embedded = false,
167
202
  }: OpenSourceBadgeProps) {
168
203
  const hidden =
169
204
  (import.meta.env as Record<string, string | undefined>)
@@ -189,7 +224,7 @@ export function OpenSourceBadge({
189
224
  opacity: 0.95 !important;
190
225
  }
191
226
  @media (max-width: 640px) {
192
- .an-open-source-badge {
227
+ .an-open-source-badge:not(.an-open-source-badge-embedded) {
193
228
  position: static !important;
194
229
  margin: 1rem auto 0 !important;
195
230
  width: max-content;
@@ -227,11 +262,29 @@ export function OpenSourceBadge({
227
262
  href="https://github.com/BuilderIO/agent-native"
228
263
  target="_blank"
229
264
  rel="noopener noreferrer"
230
- className="an-open-source-badge"
231
- style={containerStyle(position)}
265
+ className={cn(
266
+ "an-open-source-badge",
267
+ embedded && "an-open-source-badge-embedded",
268
+ )}
269
+ style={
270
+ embedded
271
+ ? {
272
+ display: "flex",
273
+ alignItems: "center",
274
+ gap: 6,
275
+ fontSize: 12,
276
+ fontFamily:
277
+ '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
278
+ fontWeight: 600,
279
+ lineHeight: 1,
280
+ textDecoration: "none",
281
+ transition: "opacity 0.2s, color 0.2s",
282
+ }
283
+ : containerStyle(position)
284
+ }
232
285
  >
233
286
  <IconBrandGithub aria-hidden="true" stroke={1.8} />
234
- <span>100% free and open source</span>
287
+ <span>Free and open source</span>
235
288
  </a>
236
289
  </>
237
290
  );
@@ -4,6 +4,13 @@ export interface ActiveRunState {
4
4
  threadId: string;
5
5
  runId: string;
6
6
  lastSeq: number;
7
+ activityTool?: string | null;
8
+ }
9
+
10
+ function normalizeActivityTool(toolName: unknown): string | null {
11
+ if (typeof toolName !== "string") return null;
12
+ const tool = toolName.trim();
13
+ return tool || null;
7
14
  }
8
15
 
9
16
  export function setActiveRun(state: ActiveRunState): void {
@@ -30,6 +37,29 @@ export function updateActiveRunSeq(seq: number): void {
30
37
  }
31
38
  }
32
39
 
40
+ export function updateActiveRunActivity(
41
+ toolName: string | null | undefined,
42
+ ): void {
43
+ const state = getActiveRun();
44
+ if (!state) return;
45
+ const activityTool = normalizeActivityTool(toolName);
46
+ if (activityTool) {
47
+ setActiveRun({ ...state, activityTool });
48
+ return;
49
+ }
50
+ const { activityTool: _activityTool, ...nextState } = state;
51
+ setActiveRun(nextState);
52
+ }
53
+
54
+ export function getActiveRunActivityTool(
55
+ threadId: string,
56
+ runId: string,
57
+ ): string | null {
58
+ const stored = getActiveRun();
59
+ if (stored?.threadId !== threadId || stored.runId !== runId) return null;
60
+ return normalizeActivityTool(stored.activityTool);
61
+ }
62
+
33
63
  export function clearActiveRun(): void {
34
64
  try {
35
65
  sessionStorage.removeItem(STORAGE_KEY);
@@ -944,6 +944,8 @@ function incrementalActionGuidance(tool: string): string | undefined {
944
944
  case "generate-design":
945
945
  case "update-design":
946
946
  return 'if an existing design file or snapshot is already in history, especially after a Design variant pick, stop retrying `generate-design`: call `get-design-snapshot` for the selected file, then call `edit-design` once on that same `fileId` (`mode: "replace-file"` for a compact full-file replacement, or search/replace for smaller edits). Use `generate-design` only for a brand-new compact first file when no target file exists yet';
947
+ case "edit-design":
948
+ return "retry with a smaller `edit-design` payload: prefer a handful of exact search/replace edits against the already-snapshotted file, and avoid `replacementContent` for a huge full-file rewrite. If a selected variant needs expansion, upgrade the highest-value visible sections first, save once, and summarize any remaining refinements";
947
949
  case "present-design-variants":
948
950
  return 'call `present-design-variants` with concise labels, descriptions, accent colors, and feature bullets; omit large `content` HTML when needed so the action can render compact representative screens. After the user picks a direction, delete unchosen screens, snapshot the selected `fileId`, and refine that same file with `edit-design` (`mode: "replace-file"` for placeholder expansion). Do not call `generate-design` after the variant pick';
949
951
  case "create-visual-plan":
@@ -980,7 +982,9 @@ function autoContinueMessage(signal: AgentAutoContinueSignal): string {
980
982
  // toward a compact first version it can actually finish in a single run, then
981
983
  // incremental refinements — never re-streaming the same oversized payload.
982
984
  const cutoffPreparingAction =
983
- signal.reason === "run_timeout" || signal.reason === "stream_ended";
985
+ signal.reason === "run_timeout" ||
986
+ signal.reason === "stream_ended" ||
987
+ signal.reason === "no_progress";
984
988
  let actionInputNote = "";
985
989
  if (cutoffPreparingAction && tool) {
986
990
  const guidance = incrementalActionGuidance(tool);
@@ -9,8 +9,10 @@ import { useEffect, useMemo, useRef, useState } from "react";
9
9
  import { useNavigate } from "react-router";
10
10
 
11
11
  import { extensionPath } from "../../extensions/path.js";
12
+ import { THEME_VAR_NAMES } from "../../extensions/theme.js";
12
13
  import { sendToAgentChat } from "../agent-chat.js";
13
14
  import { agentNativePath } from "../api-path.js";
15
+ import { useAppearance } from "../appearance.js";
14
16
  import {
15
17
  Popover,
16
18
  PopoverContent,
@@ -53,6 +55,22 @@ interface Extension {
53
55
  };
54
56
  }
55
57
 
58
+ // Read the host app's *actual* computed theme values for the shared token set
59
+ // (THEME_VAR_NAMES). The iframe ships with a generic baked palette
60
+ // (getThemeVars); syncing the live values keeps embedded extensions visually
61
+ // identical to the surrounding app even when a template overrides the default
62
+ // palette (e.g. analytics uses a neutral-gray dark theme, not near-black).
63
+ function readHostThemeVars(): Record<string, string> {
64
+ if (typeof document === "undefined") return {};
65
+ const computed = getComputedStyle(document.documentElement);
66
+ const vars: Record<string, string> = {};
67
+ for (const name of THEME_VAR_NAMES) {
68
+ const value = computed.getPropertyValue(name).trim();
69
+ if (value) vars[name] = value;
70
+ }
71
+ return vars;
72
+ }
73
+
56
74
  function serializeChatValue(value: unknown): string | undefined {
57
75
  if (value === undefined || value === null) return undefined;
58
76
  if (typeof value === "string") return value;
@@ -112,7 +130,11 @@ export function EmbeddedExtension({
112
130
  onReadyRef.current?.();
113
131
  };
114
132
  const [height, setHeight] = useState<number>(initialHeight);
115
- const [isDark, setIsDark] = useState(false);
133
+ const [isDark, setIsDark] = useState(
134
+ () =>
135
+ typeof document !== "undefined" &&
136
+ document.documentElement.classList.contains("dark"),
137
+ );
116
138
  // (audit H4) Mirror ExtensionViewer's role-aware gating; deny-by-default until
117
139
  // the iframe's render binding announcement arrives.
118
140
  const bridgeContextRef = useRef<BridgePolicyContext>({
@@ -200,11 +222,16 @@ export function EmbeddedExtension({
200
222
  readyFiredRef.current = false;
201
223
  }, [extensionId, extension?.updatedAt]);
202
224
 
225
+ const appearance = useAppearance();
226
+
203
227
  useEffect(() => {
204
228
  const win = iframeRef.current?.contentWindow;
205
229
  if (!win) return;
206
- win.postMessage({ type: "agent-native-theme-update", isDark }, "*");
207
- }, [isDark]);
230
+ win.postMessage(
231
+ { type: "agent-native-theme-update", isDark, vars: readHostThemeVars() },
232
+ "*",
233
+ );
234
+ }, [isDark, appearance]);
208
235
 
209
236
  // Forward slot context whenever it changes. The iframe's own load handler
210
237
  // posts the initial value once it's ready; this effect handles updates.
@@ -372,6 +399,17 @@ export function EmbeddedExtension({
372
399
  { type: "agent-native-slot-context", context: context ?? {} },
373
400
  "*",
374
401
  );
402
+ // Re-assert theme once the iframe document is live. The src bakes in
403
+ // the initial dark state, but this covers the race where isDark
404
+ // settled before the iframe's message listener existed.
405
+ iframeRef.current?.contentWindow?.postMessage(
406
+ {
407
+ type: "agent-native-theme-update",
408
+ isDark,
409
+ vars: readHostThemeVars(),
410
+ },
411
+ "*",
412
+ );
375
413
  // Fallback readiness signal in case the extension never reports a
376
414
  // height (e.g. fixed-height content that skips auto-resize).
377
415
  fireReady();