@agent-native/core 0.154.4 → 0.155.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/account-health/SKILL.md +76 -0
  3. package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +3 -0
  4. package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +2 -1
  5. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +6 -5
  6. package/corpus/templates/analytics/AGENTS.md +3 -0
  7. package/corpus/templates/analytics/actions/account-deep-dive.ts +3 -2
  8. package/corpus/templates/analytics/actions/bigquery.ts +1 -1
  9. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -1
  10. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
  11. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyCombobox.tsx +12 -4
  12. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyValueCombobox.tsx +5 -2
  13. package/corpus/templates/analytics/app/pages/adhoc/explorer/property-label.ts +32 -0
  14. package/corpus/templates/analytics/changelog/2026-08-13-account-health-readouts-verify-customer-scope-and-contract-metrics.md +7 -0
  15. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +43 -17
  16. package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +17 -0
  17. package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
  18. package/corpus/templates/calendar/AGENTS.md +2 -0
  19. package/corpus/templates/calendar/DEVELOPING.md +4 -4
  20. package/corpus/templates/calendar/actions/get-settings.ts +4 -11
  21. package/corpus/templates/calendar/actions/update-availability.ts +15 -4
  22. package/corpus/templates/calendar/actions/update-settings.ts +15 -4
  23. package/corpus/templates/calendar/app/components/booking/DatePicker.tsx +9 -3
  24. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +21 -19
  25. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +9 -6
  26. package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +25 -3
  27. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +10 -3
  28. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +9 -9
  29. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -3
  30. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +10 -4
  31. package/corpus/templates/calendar/app/i18n-data.ts +30 -0
  32. package/corpus/templates/calendar/app/lib/availability-schedule.ts +53 -0
  33. package/corpus/templates/calendar/app/lib/event-form-utils.ts +5 -0
  34. package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +90 -28
  35. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +135 -61
  36. package/corpus/templates/calendar/app/pages/BookingPage.tsx +2 -0
  37. package/corpus/templates/calendar/app/pages/CalendarView.tsx +30 -15
  38. package/corpus/templates/calendar/app/pages/Settings.tsx +60 -1
  39. package/corpus/templates/calendar/changelog/2026-08-13-find-a-time-now-closes-from-a-clear-header-action.md +6 -0
  40. package/corpus/templates/calendar/server/handlers/bookings.ts +2 -1
  41. package/corpus/templates/calendar/server/handlers/settings.ts +12 -16
  42. package/corpus/templates/calendar/shared/api.ts +1 -0
  43. package/corpus/templates/calendar/shared/availability-schedule.ts +64 -0
  44. package/corpus/templates/calendar/shared/calendar-week.ts +21 -0
  45. package/corpus/templates/calendar/shared/settings.ts +44 -0
  46. package/corpus/templates/clips/AGENTS.md +2 -0
  47. package/corpus/templates/clips/actions/add-comment.ts +5 -2
  48. package/corpus/templates/clips/actions/finalize-recording.ts +31 -1
  49. package/corpus/templates/clips/actions/get-recording-player-data.ts +9 -0
  50. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +21 -1
  51. package/corpus/templates/clips/actions/reply-to-comment.ts +5 -2
  52. package/corpus/templates/clips/actions/update-comment.ts +9 -2
  53. package/corpus/templates/clips/app/components/library/search-bar.tsx +26 -13
  54. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +135 -3
  55. package/corpus/templates/clips/app/components/player/comments-panel.tsx +5 -99
  56. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +7 -3
  57. package/corpus/templates/clips/app/components/player/video-player.tsx +84 -24
  58. package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +11 -3
  59. package/corpus/templates/clips/app/i18n/en-US.ts +12 -0
  60. package/corpus/templates/clips/app/lib/public-meeting.ts +11 -3
  61. package/corpus/templates/clips/app/lib/search-focus.ts +15 -0
  62. package/corpus/templates/clips/app/root.tsx +2 -1
  63. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +94 -26
  64. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +24 -7
  65. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +8 -4
  66. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -7
  67. package/corpus/templates/clips/app/routes/share.$shareId.tsx +8 -4
  68. package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +72 -26
  69. package/corpus/templates/clips/changelog/2026-08-13-inline-markdown-comments.md +6 -0
  70. package/corpus/templates/clips/changelog/2026-08-13-private-meeting-agent-links.md +6 -0
  71. package/corpus/templates/clips/desktop/src/app.tsx +116 -19
  72. package/corpus/templates/clips/desktop/src/lib/recorder.ts +13 -29
  73. package/corpus/templates/clips/desktop/src/lib/recording-request.ts +39 -0
  74. package/corpus/templates/clips/desktop/src/lib/settings-navigation.ts +11 -0
  75. package/corpus/templates/clips/desktop/src/lib/whisper-model-picker.ts +7 -0
  76. package/corpus/templates/clips/desktop/src/styles.css +117 -0
  77. package/corpus/templates/clips/server/db/index.ts +10 -0
  78. package/corpus/templates/clips/server/db/schema.ts +1 -0
  79. package/corpus/templates/clips/server/lib/recordings.ts +3 -1
  80. package/corpus/templates/clips/server/lib/seekable-media-state.ts +90 -0
  81. package/corpus/templates/clips/server/plugins/db.ts +5 -0
  82. package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +37 -4
  83. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +16 -5
  84. package/corpus/templates/clips/shared/clips-ai-prefs.ts +3 -0
  85. package/corpus/templates/clips/shared/meeting-agent-access.ts +5 -0
  86. package/corpus/templates/content/.agents/skills/content/SKILL.md +2 -2
  87. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +20 -4
  88. package/corpus/templates/content/actions/_database-row-mutation.ts +1490 -0
  89. package/corpus/templates/content/actions/_database-utils.ts +44 -0
  90. package/corpus/templates/content/actions/_property-utils.ts +4 -1
  91. package/corpus/templates/content/actions/add-comment.ts +2 -1
  92. package/corpus/templates/content/actions/add-database-item.ts +62 -240
  93. package/corpus/templates/content/actions/configure-document-property.ts +165 -1
  94. package/corpus/templates/content/actions/delete-document-property.ts +18 -0
  95. package/corpus/templates/content/actions/set-document-property.ts +78 -1
  96. package/corpus/templates/content/actions/update-comment.ts +1 -1
  97. package/corpus/templates/content/actions/update-database-item.ts +62 -0
  98. package/corpus/templates/content/actions/upsert-database-item-by-key.ts +43 -796
  99. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +24 -15
  100. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +10 -8
  101. package/corpus/templates/content/app/hooks/use-content-database.ts +33 -32
  102. package/corpus/templates/content/changelog/2026-08-10-database-row-actions-now-validate-exact-schemas-and-safe-ret.md +6 -0
  103. package/corpus/templates/content/parity/matrix.md +1 -1
  104. package/corpus/templates/content/parity/matrix.ts +2 -0
  105. package/corpus/templates/content/server/db/schema.ts +39 -3
  106. package/corpus/templates/content/server/plugins/db.ts +30 -0
  107. package/corpus/templates/content/shared/api.ts +75 -2
  108. package/corpus/templates/design/actions/generate-design.ts +10 -7
  109. package/corpus/templates/design/actions/present-design-variants.ts +35 -6
  110. package/corpus/templates/design/actions/run-design-audit.ts +33 -0
  111. package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-catches-a-screen-that-renders-behind-an.md +6 -0
  112. package/corpus/templates/design/shared/design-review.ts +1 -0
  113. package/corpus/templates/design/shared/html-integrity.ts +66 -0
  114. package/corpus/templates/dispatch/.env.example +5 -0
  115. package/corpus/templates/dispatch/AGENTS.md +24 -19
  116. package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
  117. package/corpus/templates/dispatch/server/lib/identity-sso.ts +21 -90
  118. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +3 -12
  119. package/corpus/templates/dispatch/shared/feature-flags.ts +4 -0
  120. package/corpus/templates/factory/AGENTS.md +5 -1
  121. package/corpus/templates/factory/README.md +23 -0
  122. package/corpus/templates/factory/actions/view-screen.ts +17 -0
  123. package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +239 -0
  124. package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +2 -0
  125. package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +130 -7
  126. package/corpus/templates/factory/app/i18n/en-US.ts +20 -0
  127. package/corpus/templates/factory/app/routes/factory.tsx +41 -0
  128. package/corpus/templates/factory/package.json +1 -0
  129. package/corpus/templates/factory/server/factory-graph/contracts.ts +2 -0
  130. package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -0
  131. package/corpus/templates/factory/server/plugins/dispatch-db.ts +1 -0
  132. package/corpus/templates/factory/server/plugins/setup-dispatch.ts +3 -0
  133. package/corpus/templates/mail/app/lib/native-mcp-exclusions.ts +3 -0
  134. package/corpus/templates/mail/vite.config.ts +7 -0
  135. package/corpus/templates/plan/actions/reply-to-plan-comment.ts +7 -2
  136. package/corpus/templates/plan/actions/resolve-plan-comment.ts +2 -2
  137. package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
  138. package/corpus/templates/plan/app/pages/PlansPage.tsx +29 -20
  139. package/corpus/templates/slides/actions/add-slide-comment.ts +1 -1
  140. package/corpus/templates/slides/actions/update-slide-comment.ts +1 -1
  141. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +9 -6
  142. package/dist/action.d.ts +22 -0
  143. package/dist/action.js +27 -0
  144. package/dist/agent/production-agent.js +5 -1
  145. package/dist/agent/thread-data-builder.js +17 -1
  146. package/dist/cli/skills-content/help.js +6 -4
  147. package/dist/cli/skills.js +17 -2
  148. package/dist/cli/workspacify.d.ts +5 -0
  149. package/dist/cli/workspacify.js +65 -8
  150. package/dist/client/AgentPanel.d.ts +5 -0
  151. package/dist/client/AgentPanel.js +20 -4
  152. package/dist/client/AssistantChat.d.ts +21 -0
  153. package/dist/client/AssistantChat.js +66 -68
  154. package/dist/client/active-run-state.d.ts +14 -1
  155. package/dist/client/active-run-state.js +0 -0
  156. package/dist/client/agent-chat-adapter.js +107 -5
  157. package/dist/client/analytics.js +9 -2
  158. package/dist/client/chat/tool-call-display.js +4 -4
  159. package/dist/client/chat-first/apps-rail.js +9 -4
  160. package/dist/client/chat-first/primary-nav.d.ts +2 -1
  161. package/dist/client/chat-first/primary-nav.js +3 -3
  162. package/dist/client/chat-first/types.d.ts +5 -1
  163. package/dist/client/markdown/InlineMarkdown.d.ts +23 -0
  164. package/dist/client/markdown/InlineMarkdown.js +65 -0
  165. package/dist/client/markdown/index.d.ts +1 -0
  166. package/dist/client/markdown/index.js +1 -0
  167. package/dist/client/review/ReviewThreadPanel.js +2 -1
  168. package/dist/client/run-stream-ownership.d.ts +39 -0
  169. package/dist/client/run-stream-ownership.js +65 -0
  170. package/dist/collab/routes.d.ts +1 -1
  171. package/dist/collab/struct-routes.d.ts +1 -1
  172. package/dist/feature-flags/index.d.ts +1 -1
  173. package/dist/feature-flags/index.js +1 -1
  174. package/dist/feature-flags/store.d.ts +10 -0
  175. package/dist/feature-flags/store.js +112 -1
  176. package/dist/guards/no-env-credentials.js +4 -0
  177. package/dist/index.browser.d.ts +1 -1
  178. package/dist/index.browser.js +1 -1
  179. package/dist/index.d.ts +2 -2
  180. package/dist/index.js +2 -2
  181. package/dist/integrations/adapters/slack.js +5 -1
  182. package/dist/localization/default-messages.d.ts +1 -0
  183. package/dist/localization/default-messages.js +1 -0
  184. package/dist/notifications/routes.d.ts +1 -1
  185. package/dist/observability/routes.d.ts +1 -1
  186. package/dist/progress/routes.d.ts +1 -1
  187. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  188. package/dist/provider-api/actions/provider-api.d.ts +15 -15
  189. package/dist/resources/agents.js +40 -5
  190. package/dist/resources/handlers.d.ts +1 -1
  191. package/dist/resources/metadata.d.ts +10 -0
  192. package/dist/resources/metadata.js +7 -3
  193. package/dist/review/actions/create-review-comment.js +1 -1
  194. package/dist/review/actions/reply-review-comment.js +1 -1
  195. package/dist/review/actions/resolve-review-thread.js +1 -1
  196. package/dist/server/action-routes.js +42 -8
  197. package/dist/server/agent-discovery.js +14 -3
  198. package/dist/server/core-routes-plugin.js +1 -1
  199. package/dist/server/identity-sso-migrations.d.ts +7 -0
  200. package/dist/server/identity-sso-migrations.js +36 -0
  201. package/dist/server/identity-sso-store.d.ts +3 -2
  202. package/dist/server/identity-sso-store.js +8 -3
  203. package/dist/server/realtime-token.d.ts +1 -1
  204. package/dist/server/release-migrations.js +4 -0
  205. package/dist/tracking/providers.js +13 -3
  206. package/dist/workspace-files/tool.js +1 -1
  207. package/package.json +3 -2
@@ -12,6 +12,10 @@ import { emailToColor, emailToName } from "@agent-native/core/client/collab";
12
12
  import { PromptComposer } from "@agent-native/core/client/composer";
13
13
  import { useActionQuery, useSession } from "@agent-native/core/client/hooks";
14
14
  import { useT } from "@agent-native/core/client/i18n";
15
+ import {
16
+ InlineMarkdown,
17
+ type InlineMarkdownProtectedSpan,
18
+ } from "@agent-native/core/client/markdown";
15
19
  import {
16
20
  useAcceptInvitation,
17
21
  useJoinByDomain,
@@ -976,30 +980,35 @@ function safeDecodeURIComponent(value: string): string {
976
980
  }
977
981
 
978
982
  function renderCommentMessage(message: string) {
979
- const parts: ReactNode[] = [];
980
- let lastIndex = 0;
983
+ const protectedSpans: InlineMarkdownProtectedSpan[] = [];
981
984
  const pattern = /@\[([^\]]+)\]\(mailto:([^)]+)\)/g;
982
985
  let match: RegExpExecArray | null;
983
986
  while ((match = pattern.exec(message)) !== null) {
984
- if (match.index > lastIndex) {
985
- parts.push(message.slice(lastIndex, match.index));
986
- }
987
987
  const label = match[1] ?? "";
988
988
  const email = safeDecodeURIComponent(match[2] ?? "");
989
- parts.push(
990
- <span
991
- key={`${email}-${match.index}`}
992
- className="mx-0.5 inline-flex max-w-[14rem] translate-y-[2px] items-center gap-1 rounded-full border border-primary/20 bg-primary/10 px-1.5 py-0.5 text-xs font-medium text-primary"
993
- title={email}
994
- >
995
- <IconAt className="size-3" />
996
- <span className="truncate">{label || email}</span>
997
- </span>,
998
- );
999
- lastIndex = pattern.lastIndex;
989
+ protectedSpans.push({
990
+ source: match[0],
991
+ label: label || email,
992
+ title: email,
993
+ });
1000
994
  }
1001
- if (lastIndex < message.length) parts.push(message.slice(lastIndex));
1002
- return parts.length > 0 ? parts : message;
995
+ return (
996
+ <InlineMarkdown
997
+ content={message}
998
+ inline
999
+ protectedSpans={protectedSpans}
1000
+ renderProtectedSpan={(span, children) => (
1001
+ <span
1002
+ key={span.source}
1003
+ className="mx-0.5 inline-flex max-w-[14rem] translate-y-[2px] items-center gap-1 rounded-full border border-primary/20 bg-primary/10 px-1.5 py-0.5 text-xs font-medium text-primary"
1004
+ title={span.title}
1005
+ >
1006
+ <IconAt className="size-3" />
1007
+ <span className="truncate">{children}</span>
1008
+ </span>
1009
+ )}
1010
+ />
1011
+ );
1003
1012
  }
1004
1013
 
1005
1014
  function CommentAvatar({
@@ -9066,9 +9075,9 @@ function CommentThreadMessage({
9066
9075
  </p>
9067
9076
  )}
9068
9077
  </div>
9069
- <p className="mt-1 whitespace-pre-wrap text-sm leading-6">
9078
+ <div className="mt-1 text-sm leading-6">
9070
9079
  {renderCommentMessage(comment.message)}
9071
- </p>
9080
+ </div>
9072
9081
  </div>
9073
9082
  {action}
9074
9083
  </div>
@@ -17,7 +17,7 @@ function displayNameFromEmail(email: string): string {
17
17
 
18
18
  export default defineAction({
19
19
  description:
20
- "Add a comment to a slide. Omit threadId to start a new thread; provide threadId to reply.",
20
+ "Add a comment to a slide. Inline Markdown supports emphasis, inline code, links, and line breaks; headings are flattened. Omit threadId to start a new thread; provide threadId to reply.",
21
21
  schema: z.object({
22
22
  deckId: z.string().describe("Deck ID"),
23
23
  slideId: z.string().describe("Slide ID"),
@@ -8,7 +8,7 @@ import { getDb, schema } from "../server/db/index.js";
8
8
 
9
9
  export default defineAction({
10
10
  description:
11
- "Update a slide comment. Resolving or reopening a comment applies to the full thread.",
11
+ "Update a slide comment. Comment text supports inline Markdown without headings. Resolving or reopening a comment applies to the full thread.",
12
12
  schema: z.object({
13
13
  id: z.string().describe("Comment ID"),
14
14
  deckId: z.string().optional().describe("Deck ID"),
@@ -1,4 +1,5 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
+ import { InlineMarkdown } from "@agent-native/core/client/markdown";
2
3
  import {
3
4
  IconX,
4
5
  IconCheck,
@@ -99,9 +100,10 @@ function CommentItem({
99
100
  )}
100
101
  </div>
101
102
  </div>
102
- <p className="text-[12px] text-foreground/90 mt-0.5 break-words leading-relaxed">
103
- {comment.content}
104
- </p>
103
+ <InlineMarkdown
104
+ content={comment.content}
105
+ className="mt-0.5 text-[12px] text-foreground/90 leading-relaxed"
106
+ />
105
107
  </div>
106
108
  </div>
107
109
  );
@@ -374,9 +376,10 @@ function ThreadCard({
374
376
  )}
375
377
  </div>
376
378
  </div>
377
- <p className="text-[12px] text-foreground/90 mt-0.5 break-words leading-relaxed">
378
- {rootComment.content}
379
- </p>
379
+ <InlineMarkdown
380
+ content={rootComment.content}
381
+ className="mt-0.5 text-[12px] text-foreground/90 leading-relaxed"
382
+ />
380
383
  </div>
381
384
  </div>
382
385
 
package/dist/action.d.ts CHANGED
@@ -135,6 +135,28 @@ export interface AgentActionStopOptions {
135
135
  /** Optional short tool-result text. Defaults to the user-facing message. */
136
136
  toolResult?: string;
137
137
  }
138
+ export interface ActionContractErrorOptions {
139
+ /** Stable machine-readable code safe to expose on every action transport. */
140
+ errorCode: string;
141
+ /** Safe structured context for callers. Never include secrets or raw driver errors. */
142
+ details?: Record<string, unknown>;
143
+ /** HTTP status for the action route. Contract conflicts normally use 409. */
144
+ statusCode?: number;
145
+ }
146
+ /**
147
+ * A deterministic, caller-correctable action contract failure.
148
+ *
149
+ * Unlike an ordinary Error, its code and explicitly safe details survive the
150
+ * framework HTTP transport. Internal failures remain generic 500 responses.
151
+ */
152
+ export declare class ActionContractError extends Error {
153
+ readonly actionContractError = true;
154
+ readonly errorCode: string;
155
+ readonly details?: Record<string, unknown>;
156
+ readonly statusCode: number;
157
+ constructor(message: string, options: ActionContractErrorOptions);
158
+ }
159
+ export declare function isActionContractError(error: unknown): error is ActionContractError;
138
160
  /**
139
161
  * Throw from an action when the agent should stop the current turn instead of
140
162
  * feeding the failure back to the model for another retry.
package/dist/action.js CHANGED
@@ -1,5 +1,32 @@
1
1
  import { normalizeActionChatUIConfig, } from "./action-ui.js";
2
2
  import { normalizeAuditConfig, resolveAuditAttach } from "./audit/config.js";
3
+ /**
4
+ * A deterministic, caller-correctable action contract failure.
5
+ *
6
+ * Unlike an ordinary Error, its code and explicitly safe details survive the
7
+ * framework HTTP transport. Internal failures remain generic 500 responses.
8
+ */
9
+ export class ActionContractError extends Error {
10
+ actionContractError = true;
11
+ errorCode;
12
+ details;
13
+ statusCode;
14
+ constructor(message, options) {
15
+ super(message);
16
+ this.name = "ActionContractError";
17
+ this.errorCode = options.errorCode;
18
+ this.details = options.details;
19
+ this.statusCode = options.statusCode ?? 409;
20
+ }
21
+ }
22
+ export function isActionContractError(error) {
23
+ return (error instanceof ActionContractError ||
24
+ (!!error &&
25
+ typeof error === "object" &&
26
+ error.actionContractError ===
27
+ true &&
28
+ typeof error.errorCode === "string"));
29
+ }
3
30
  /**
4
31
  * Throw from an action when the agent should stop the current turn instead of
5
32
  * feeding the failure back to the model for another retry.
@@ -7376,7 +7376,11 @@ export function createProductionAgentHandler(options) {
7376
7376
  }
7377
7377
  const profilePrompt = `${requestSystemPrompt}\n\n<custom-agent-profile name="${profile.name}" path="${profile.path}">\n` +
7378
7378
  (profile.description ? `${profile.description}\n\n` : "") +
7379
- `${profile.instructions}\n</custom-agent-profile>`;
7379
+ `${profile.instructions}` +
7380
+ (profile.workspace?.resources.length
7381
+ ? `\n\nAgent pack resources (read these with the resources tools when relevant):\n${profile.workspace.resources.map((resource) => `- ${resource.path}${resource.name ? ` (${resource.name})` : ""}`).join("\n")}`
7382
+ : "") +
7383
+ `\n</custom-agent-profile>`;
7380
7384
  let responseText = "";
7381
7385
  const subUsage = await runAgentLoop({
7382
7386
  engine,
@@ -94,8 +94,24 @@ export function buildAssistantMessage(events, runId, options = {}) {
94
94
  continue;
95
95
  }
96
96
  if (event.type === "tool_start") {
97
+ const explicitToolCallId = event.id?.trim();
98
+ // A tool_start whose id is already in this turn is a REPLAY, not a new
99
+ // call: the tool-call journal and zombie-ledger recovery paths re-emit
100
+ // tool_start/tool_done for calls that already ran in an interrupted
101
+ // chunk. The live client coalesces those onto the original card, so a
102
+ // blind push here persisted a second copy of a call the user had only
103
+ // ever seen once — the duplicate that appears only after a reload.
104
+ // Matching the tool name too, so an id reused across different tools
105
+ // stays two cards rather than being silently merged into one.
106
+ if (explicitToolCallId) {
107
+ const replayed = content.some((part) => part.type === "tool-call" &&
108
+ part.toolCallId === explicitToolCallId &&
109
+ part.toolName === (event.tool ?? "unknown"));
110
+ if (replayed)
111
+ continue;
112
+ }
97
113
  toolCallCounter += 1;
98
- const toolCallId = event.id?.trim() ||
114
+ const toolCallId = explicitToolCallId ||
99
115
  (runId ? `${runId}:tc_${toolCallCounter}` : `tc_${toolCallCounter}`);
100
116
  const args = (event.input ?? {});
101
117
  content.push({
@@ -73,7 +73,9 @@ run "npx @agent-native/core@latest recap setup" / "npx @agent-native/core@latest
73
73
  verify GitHub Actions. Docs: ${docsUrl("pr-visual-recap")}.
74
74
 
75
75
  The status/update commands inspect copied Agent Native skill folders and refresh
76
- their instruction files from the current @agent-native/core package. In generated
77
- apps/workspaces, "skills update scaffold --project" refreshes the framework
78
- skills copied into the scaffold and repairs AGENTS.md / CLAUDE.md and
79
- .agents/skills / .claude/skills compatibility links.`;
76
+ their instruction files from the current @agent-native/core package. In a
77
+ standalone generated app, "skills update scaffold --project" refreshes the
78
+ framework skill copies. In a generated workspace, it refreshes workspace-core,
79
+ removes stale generic framework copies, repairs inherited .agents/skills links
80
+ for every app, and repairs AGENTS.md / CLAUDE.md / .claude/skills compatibility
81
+ links.`;
@@ -20,6 +20,7 @@ import { PR_VISUAL_RECAP_SETUP, writePrVisualRecapWorkflow } from "./recap.js";
20
20
  import { setupAgentSymlinks } from "./setup-agents.js";
21
21
  import { ASSETS_SKILL_MD, CANVAS_REFERENCE_MD, CONNECTION_REFERENCE_MD, CONTENT_SKILL_MD, DESIGN_EXPLORATION_SKILL_MD, DESIGN_VISUAL_EDIT_SKILL_MD, DOCUMENT_QUALITY_REFERENCE_MD, EXEMPLAR_REFERENCE_MD, HELP, LOCAL_FILES_REFERENCE_MD, REWIND_SKILL_MD, VISUAL_PLANS_SKILL_MD, VISUAL_RECAP_SKILL_MD, VISUALIZE_REPO_SKILL_MD, WIREFRAME_REFERENCE_MD, } from "./skills-content/index.js";
22
22
  import { createCliTelemetry } from "./telemetry.js";
23
+ import { linkDefaultWorkspaceSkills, removeCopiedFrameworkSkills, } from "./workspacify.js";
23
24
  export { CANVAS_REFERENCE_MD, CONNECTION_REFERENCE_MD, DOCUMENT_QUALITY_REFERENCE_MD, EXEMPLAR_REFERENCE_MD, LOCAL_FILES_REFERENCE_MD, VISUAL_PLANS_SKILL_MD, VISUAL_RECAP_SKILL_MD, VISUALIZE_REPO_SKILL_MD, WIREFRAME_REFERENCE_MD, };
24
25
  export const BUILT_IN_APP_SKILLS = {
25
26
  assets: {
@@ -1401,6 +1402,17 @@ function repairScaffoldAgentLinks(states) {
1401
1402
  continue;
1402
1403
  const appDir = path.join(appsDir, entry.name);
1403
1404
  if (fs.existsSync(path.join(appDir, "package.json"))) {
1405
+ const preserved = new Set([
1406
+ ...removeCopiedFrameworkSkills(appDir, {
1407
+ workspaceRoot: state.workspaceRoot,
1408
+ }),
1409
+ ...linkDefaultWorkspaceSkills(appDir, state.workspaceRoot),
1410
+ ]);
1411
+ if (preserved.size > 0) {
1412
+ console.warn(`[skills] Preserved app-local framework-named skills in ${appDir}: ${[
1413
+ ...preserved,
1414
+ ].join(", ")}. Migrate them explicitly before inheriting workspace copies.`);
1415
+ }
1404
1416
  setupAgentSymlinks(appDir);
1405
1417
  refreshCopiedClaudeSkills(appDir);
1406
1418
  }
@@ -3171,8 +3183,11 @@ function runSkillsStatusOrUpdate(parsed, options, update) {
3171
3183
  const scaffoldChanged = update
3172
3184
  ? updateScaffoldGuidanceStates(scaffoldBefore, parsed.dryRun)
3173
3185
  : [];
3174
- if (update && !parsed.dryRun && scaffoldChanged.length > 0) {
3175
- repairScaffoldAgentLinks(scaffoldChanged);
3186
+ if (update && !parsed.dryRun) {
3187
+ const workspaceStates = scaffoldBefore.filter((state) => state.workspaceRoot && state.sharedPackageDir);
3188
+ if (scaffoldChanged.length > 0 || workspaceStates.length > 0) {
3189
+ repairScaffoldAgentLinks([...scaffoldChanged, ...workspaceStates]);
3190
+ }
3176
3191
  }
3177
3192
  const skillAfter = update && !parsed.dryRun
3178
3193
  ? collectSkillInstallStates(parsed, options)
@@ -17,6 +17,11 @@ export interface WorkspacifyOptions {
17
17
  toolkitDependencyVersion?: string;
18
18
  }
19
19
  export declare function workspacifyApp(opts: WorkspacifyOptions): void;
20
+ export declare function removeCopiedFrameworkSkills(appDir: string, options?: {
21
+ workspaceRoot?: string;
22
+ allowUnverified?: boolean;
23
+ }): string[];
24
+ export declare function linkDefaultWorkspaceSkills(appDir: string, workspaceRoot: string): string[];
20
25
  /**
21
26
  * Parse a workspace core package name into its npm scope.
22
27
  * "@my-company/shared" → "my-company"
@@ -144,27 +144,83 @@ export function workspacifyApp(opts) {
144
144
  }
145
145
  }
146
146
  function linkInheritedWorkspaceSkills(opts) {
147
- const appSkillsDir = path.join(opts.appDir, ".agents", "skills");
148
147
  const workspaceSkillsDir = path.join(opts.workspaceRoot, ".agents", "skills");
149
148
  if (!fs.existsSync(workspaceSkillsDir))
150
149
  return;
151
- fs.mkdirSync(appSkillsDir, { recursive: true });
150
+ removeCopiedFrameworkSkills(opts.appDir, { allowUnverified: true });
151
+ linkDefaultWorkspaceSkills(opts.appDir, opts.workspaceRoot);
152
+ }
153
+ function skillDirContentsMatch(sourceDir, targetDir) {
154
+ const sourceStat = fs.lstatSync(sourceDir, { throwIfNoEntry: false });
155
+ const targetStat = fs.lstatSync(targetDir, { throwIfNoEntry: false });
156
+ if (!sourceStat?.isDirectory() || !targetStat?.isDirectory())
157
+ return false;
158
+ const sourceEntries = fs.readdirSync(sourceDir, { withFileTypes: true });
159
+ const targetEntries = fs.readdirSync(targetDir, { withFileTypes: true });
160
+ if (sourceEntries.length !== targetEntries.length)
161
+ return false;
162
+ return sourceEntries.every((sourceEntry) => {
163
+ const sourcePath = path.join(sourceDir, sourceEntry.name);
164
+ const targetPath = path.join(targetDir, sourceEntry.name);
165
+ const targetEntry = targetEntries.find((entry) => entry.name === sourceEntry.name);
166
+ if (!targetEntry)
167
+ return false;
168
+ if (sourceEntry.isSymbolicLink()) {
169
+ return (targetEntry.isSymbolicLink() &&
170
+ fs.readlinkSync(sourcePath) === fs.readlinkSync(targetPath));
171
+ }
172
+ if (sourceEntry.isDirectory()) {
173
+ return (targetEntry.isDirectory() &&
174
+ skillDirContentsMatch(sourcePath, targetPath));
175
+ }
176
+ return (sourceEntry.isFile() &&
177
+ targetEntry.isFile() &&
178
+ fs.readFileSync(sourcePath).equals(fs.readFileSync(targetPath)));
179
+ });
180
+ }
181
+ export function removeCopiedFrameworkSkills(appDir, options = {}) {
182
+ const appSkillsDir = path.join(appDir, ".agents", "skills");
183
+ const workspaceSkillsDir = options.workspaceRoot
184
+ ? path.join(options.workspaceRoot, ".agents", "skills")
185
+ : undefined;
186
+ const preserved = [];
152
187
  for (const skill of FRAMEWORK_TEMPLATE_SHARED_SKILLS) {
153
188
  const localPath = path.join(appSkillsDir, skill);
154
- if (fs.existsSync(localPath) ||
155
- fs.lstatSync(localPath, { throwIfNoEntry: false })) {
156
- fs.rmSync(localPath, { recursive: true, force: true });
189
+ const stat = fs.lstatSync(localPath, { throwIfNoEntry: false });
190
+ if (!stat || stat.isSymbolicLink())
191
+ continue;
192
+ const workspacePath = workspaceSkillsDir
193
+ ? path.join(workspaceSkillsDir, skill)
194
+ : undefined;
195
+ if (!options.allowUnverified &&
196
+ (!workspacePath || !skillDirContentsMatch(workspacePath, localPath))) {
197
+ preserved.push(skill);
198
+ continue;
157
199
  }
200
+ fs.rmSync(localPath, { recursive: true, force: true });
158
201
  }
202
+ return preserved;
203
+ }
204
+ export function linkDefaultWorkspaceSkills(appDir, workspaceRoot) {
205
+ const appSkillsDir = path.join(appDir, ".agents", "skills");
206
+ const workspaceSkillsDir = path.join(workspaceRoot, ".agents", "skills");
207
+ if (!fs.existsSync(workspaceSkillsDir))
208
+ return [];
209
+ fs.mkdirSync(appSkillsDir, { recursive: true });
210
+ const preserved = [];
159
211
  for (const skill of DEFAULT_WORKSPACE_SKILLS) {
160
212
  const inheritedPath = path.join(workspaceSkillsDir, skill);
161
213
  if (!fs.existsSync(inheritedPath))
162
214
  continue;
163
215
  const linkPath = path.join(appSkillsDir, skill);
164
- if (fs.existsSync(linkPath) ||
165
- fs.lstatSync(linkPath, { throwIfNoEntry: false })) {
166
- fs.rmSync(linkPath, { recursive: true, force: true });
216
+ const existing = fs.lstatSync(linkPath, { throwIfNoEntry: false });
217
+ if (existing &&
218
+ !existing.isSymbolicLink() &&
219
+ !skillDirContentsMatch(inheritedPath, linkPath)) {
220
+ preserved.push(skill);
221
+ continue;
167
222
  }
223
+ fs.rmSync(linkPath, { recursive: true, force: true });
168
224
  try {
169
225
  fs.symlinkSync(path.relative(appSkillsDir, inheritedPath), linkPath, "dir");
170
226
  }
@@ -172,6 +228,7 @@ function linkInheritedWorkspaceSkills(opts) {
172
228
  throw new Error(`Could not link inherited workspace skill ${skill} into ${appSkillsDir}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
173
229
  }
174
230
  }
231
+ return preserved;
175
232
  }
176
233
  /**
177
234
  * The version an existing workspace already pins for a framework package.
@@ -29,6 +29,11 @@ export declare function normalizeAgentPanelModeForSurface(mode: PanelMode, allow
29
29
  export declare function deferAgentPanelOverlayOpen(event: {
30
30
  preventDefault: () => void;
31
31
  }, closeMenu: () => void, openOverlay: () => void): void;
32
+ export declare function consumeAgentPanelOverlayFocusRestore(pendingOverlayRef: {
33
+ current: boolean;
34
+ }, event: {
35
+ preventDefault: () => void;
36
+ }): void;
32
37
  export declare function getAgentPanelChatTabGroups(tabs: MultiTabAssistantChatHeaderProps["tabs"], activeTabId: string): {
33
38
  activeTab: import("./MultiTabAssistantChat.js").ChatTab | undefined;
34
39
  childTabs: import("./MultiTabAssistantChat.js").ChatTab[];
@@ -157,6 +157,12 @@ export function deferAgentPanelOverlayOpen(event, closeMenu, openOverlay) {
157
157
  setTimeout(openOverlay, 0);
158
158
  }
159
159
  }
160
+ export function consumeAgentPanelOverlayFocusRestore(pendingOverlayRef, event) {
161
+ if (!pendingOverlayRef.current)
162
+ return;
163
+ pendingOverlayRef.current = false;
164
+ event.preventDefault();
165
+ }
160
166
  function useAvailableClis() {
161
167
  const [clis, setClis] = useState([]);
162
168
  useEffect(() => {
@@ -667,6 +673,11 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
667
673
  const [headerMenuOpen, setHeaderMenuOpen] = useState(false);
668
674
  const [feedbackOpen, setFeedbackOpen] = useState(false);
669
675
  const [shareFromMenuOpen, setShareFromMenuOpen] = useState(false);
676
+ const preventHeaderMenuFocusRestoreRef = useRef(false);
677
+ const closeHeaderMenuForOverlay = useCallback(() => {
678
+ preventHeaderMenuFocusRestoreRef.current = true;
679
+ setHeaderMenuOpen(false);
680
+ }, []);
670
681
  const getChatThreadShareUrl = useCallback((threadId) => {
671
682
  if (typeof window === "undefined")
672
683
  return undefined;
@@ -712,7 +723,11 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
712
723
  }
713
724
  return (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, allowedRoles: ["viewer", "editor", "admin"], resourceTitle: activeTab.label || "Chat", shareUrl: getChatThreadShareUrl(activeTab.id), trigger: "icon", triggerClassName: "h-7 w-7", defaultOpen: onCollapse && shareFromMenuOpen, onOpenChange: onCollapse ? setShareFromMenuOpen : undefined }));
714
725
  })(), feedbackEnabled ? (_jsx(FeedbackButton, { variant: "icon", side: "bottom", align: "end", chatSessionId: activeChatSessionId, chatStorageKey: storageKey, open: feedbackOpen, onOpenChange: setFeedbackOpen, trigger: _jsx("button", { type: "button", tabIndex: -1, "aria-hidden": "true", className: "pointer-events-none absolute end-0 top-full h-px w-px opacity-0" }) })) : null, mode === "chat" && (_jsx(IconTooltip, { content: t("agentPanel.newChat"), children: _jsx("button", { onClick: addTab, "aria-label": t("agentPanel.newChat"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), !onCollapse && mode === "cli" && canUseCodeTools && (_jsx(IconTooltip, { content: t("agentPanel.newTerminal"), children: _jsx("button", { onClick: addCliTab, "aria-label": t("agentPanel.newTerminal"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), _jsxs(DropdownMenu, { open: headerMenuOpen, onOpenChange: setHeaderMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: cn("flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", (headerMenuOpen || mode === "settings") &&
715
- "bg-accent text-foreground"), "aria-label": t("agentPanel.panelOptions"), children: _jsx(IconDotsVertical, { size: 14 }) }) }), _jsxs(DropdownMenuContent, { align: "end", sideOffset: 6, className: "w-48", children: [onCollapse && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: onCollapse, children: [_jsx(IconLayoutSidebarRightCollapse, { size: 14, className: "shrink-0" }), t("agentPanel.collapseSidebar"), _jsx(DropdownMenuShortcut, { children: toggleSidebarHint })] }), _jsx(DropdownMenuSeparator, {})] })), onCollapse && mode === "chat" && wideDrawerAction ? (_jsxs(DropdownMenuItem, { onSelect: wideDrawerAction, children: [_jsx(IconArrowsHorizontal, { size: 14, className: "shrink-0" }), wideDrawerLabel, _jsx(DropdownMenuShortcut, { children: widenChatHint })] })) : null, fullViewAction?.kind === "callback" && onFullViewRequest ? (_jsxs(DropdownMenuItem, { onSelect: onFullViewRequest, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] })) : fullViewAction?.kind === "link" ? (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs(Link, { to: fullViewAction.href, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] }) })) : null, (onCollapse && mode === "chat" && wideDrawerAction) ||
726
+ "bg-accent text-foreground"), "aria-label": t("agentPanel.panelOptions"), children: _jsx(IconDotsVertical, { size: 14 }) }) }), _jsxs(DropdownMenuContent, { align: "end", sideOffset: 6, className: "w-48", onCloseAutoFocus: (event) => {
727
+ // A sibling overlay owns focus next; restoring it to the menu
728
+ // trigger would dismiss that overlay as an outside interaction.
729
+ consumeAgentPanelOverlayFocusRestore(preventHeaderMenuFocusRestoreRef, event);
730
+ }, children: [onCollapse && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: onCollapse, children: [_jsx(IconLayoutSidebarRightCollapse, { size: 14, className: "shrink-0" }), t("agentPanel.collapseSidebar"), _jsx(DropdownMenuShortcut, { children: toggleSidebarHint })] }), _jsx(DropdownMenuSeparator, {})] })), onCollapse && mode === "chat" && wideDrawerAction ? (_jsxs(DropdownMenuItem, { onSelect: wideDrawerAction, children: [_jsx(IconArrowsHorizontal, { size: 14, className: "shrink-0" }), wideDrawerLabel, _jsx(DropdownMenuShortcut, { children: widenChatHint })] })) : null, fullViewAction?.kind === "callback" && onFullViewRequest ? (_jsxs(DropdownMenuItem, { onSelect: onFullViewRequest, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] })) : fullViewAction?.kind === "link" ? (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs(Link, { to: fullViewAction.href, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] }) })) : null, (onCollapse && mode === "chat" && wideDrawerAction) ||
716
731
  fullViewAction ? (_jsx(DropdownMenuSeparator, {})) : null, onCollapse && mode === "chat" && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: addTab, children: [_jsx(IconPlus, { size: 14, className: "shrink-0" }), t("agentPanel.newChat")] }), (() => {
717
732
  const activeTab = activeChatSessionId
718
733
  ? tabs.find((tab) => tab.id === activeChatSessionId)
@@ -721,18 +736,19 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
721
736
  (activeTabMessageCount <= 0 && activeTab.status === "idle")) {
722
737
  return null;
723
738
  }
724
- return (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, () => setHeaderMenuOpen(false), () => setShareFromMenuOpen(true)), children: [_jsx(IconShare3, { size: 14, className: "shrink-0" }), "Share"] }));
725
- })()] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, () => setHeaderMenuOpen(false), toggleHistory), children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
739
+ return (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, closeHeaderMenuForOverlay, () => setShareFromMenuOpen(true)), children: [_jsx(IconShare3, { size: 14, className: "shrink-0" }), "Share"] }));
740
+ })()] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, closeHeaderMenuForOverlay, toggleHistory), children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
726
741
  ? t("agentPanel.hideChats")
727
742
  : t("agentPanel.allChats")] })), mode === "chat" && (_jsx(RunsTrayMenuItem, { pollMs: 0, limit: 12, showRecent: true, onOpenThread: openRunThread })), mode === "chat" && _jsx(DropdownMenuSeparator, {}), mode === "cli" && availableClis.length > 0 && (_jsxs(_Fragment, { children: [availableClis.map((cli) => (_jsxs(DropdownMenuItem, { onSelect: () => selectCli(cli.command), className: cn(cli.command === selectedCli
728
743
  ? "font-medium"
729
- : "text-muted-foreground"), children: [cli.command === selectedCli ? (_jsx(IconCheck, { size: 12, className: "shrink-0" })) : (_jsx("span", { className: "w-3" })), cli.label] }, cli.command))), _jsx(DropdownMenuSeparator, {})] })), allowSettingsMode && (_jsxs(DropdownMenuItem, { onSelect: () => switchMode("settings"), className: cn(mode === "settings" ? "font-medium" : "text-muted-foreground"), children: [_jsx(IconSettings, { size: 14, className: "shrink-0" }), t("agentPanel.settings")] })), feedbackEnabled ? (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, () => setHeaderMenuOpen(false), () => setFeedbackOpen(true)), children: [_jsx(IconMessageDots, { size: 14, className: "shrink-0" }), t("agentPanel.feedback")] })) : null, ((mode === "chat" && activeTabId) ||
744
+ : "text-muted-foreground"), children: [cli.command === selectedCli ? (_jsx(IconCheck, { size: 12, className: "shrink-0" })) : (_jsx("span", { className: "w-3" })), cli.label] }, cli.command))), _jsx(DropdownMenuSeparator, {})] })), allowSettingsMode && (_jsxs(DropdownMenuItem, { onSelect: () => switchMode("settings"), className: cn(mode === "settings" ? "font-medium" : "text-muted-foreground"), children: [_jsx(IconSettings, { size: 14, className: "shrink-0" }), t("agentPanel.settings")] })), feedbackEnabled ? (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, closeHeaderMenuForOverlay, () => setFeedbackOpen(true)), children: [_jsx(IconMessageDots, { size: 14, className: "shrink-0" }), t("agentPanel.feedback")] })) : null, ((mode === "chat" && activeTabId) ||
730
745
  (mode === "cli" && canUseCodeTools && activeCliTab)) && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), mode === "chat" ? (shouldShowAgentPanelChatTabBar(tabs, activeTabId) ? (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: () => closeTab(activeTabId), children: [_jsx(IconX, { size: 14, className: "shrink-0" }), t("agentPanel.closeTab"), _jsx(DropdownMenuShortcut, { children: closeTabHint })] }), _jsx(DropdownMenuItem, { onSelect: () => closeOtherTabs(activeTabId), children: t("agentPanel.closeOtherTabs") }), _jsxs(DropdownMenuItem, { onSelect: () => closeAllTabs(), children: [t("agentPanel.closeAllTabs"), _jsx(DropdownMenuShortcut, { children: closeAllTabsHint })] })] })) : (_jsxs(DropdownMenuItem, { onSelect: clearActiveTab, children: [_jsx(IconX, { size: 14, className: "shrink-0" }), t("agentPanel.clearChat")] }))) : (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: () => closeCliTab(activeCliTab), children: [_jsx(IconX, { size: 14, className: "shrink-0" }), t("agentPanel.closeTab"), _jsx(DropdownMenuShortcut, { children: closeTabHint })] }), _jsx(DropdownMenuItem, { onSelect: () => closeOtherCliTabs(activeCliTab), children: t("agentPanel.closeOtherTabs") }), _jsxs(DropdownMenuItem, { onSelect: () => closeAllCliTabs(), children: [t("agentPanel.closeAllTabs"), _jsx(DropdownMenuShortcut, { children: closeAllTabsHint })] })] }))] }))] })] }), onCollapse && (_jsx(IconTooltip, { content: t("agentPanel.collapseSidebar"), children: _jsx("button", { type: "button", onClick: onCollapse, "aria-label": t("agentPanel.collapseSidebar"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconX, { size: 14 }) }) }))] })), [
731
746
  activeCliTab,
732
747
  addCliTab,
733
748
  allowSettingsMode,
734
749
  availableClis,
735
750
  canUseCodeTools,
751
+ closeHeaderMenuForOverlay,
736
752
  closeAllCliTabs,
737
753
  closeAllTabsHint,
738
754
  closeCliTab,
@@ -90,6 +90,27 @@ export declare function settleInterruptedAssistantToolCallsInRepo<T extends {
90
90
  };
91
91
  export declare function waitForThreadRunToClear(apiUrl: string, threadId?: string): Promise<boolean>;
92
92
  export declare function reconnectActivityFallbackContent(toolName: string | null | undefined): ContentPart[];
93
+ /**
94
+ * Whether the reconnect overlay — a SECOND fold of a run, rendered as a sibling
95
+ * of the message list — may appear.
96
+ *
97
+ * The adapter runtime and the reconnect reader both fold the same SSE events
98
+ * into their own accumulator. Whenever both are on screen the user sees the
99
+ * turn twice: duplicate tool cards (one spinning, one static) and the final
100
+ * message streaming in two places. Content-similarity dedupe cannot reliably
101
+ * hide the second copy, because the two readers disagree on tool-call identity
102
+ * (id-less activity cards get reader-local ids) and on how a turn is split
103
+ * across assistant messages.
104
+ *
105
+ * So ownership decides visibility, not similarity: if a runtime owns the turn,
106
+ * the overlay does not render. Keep this a pure function — it is the invariant
107
+ * the duplicate-render bug kept violating, and it must stay falsifiable.
108
+ */
109
+ export declare function shouldShowReconnectOverlay(state: {
110
+ isRuntimeRunning: boolean;
111
+ isReconnecting: boolean;
112
+ reconnectFrozen: boolean;
113
+ }): boolean;
93
114
  export declare function dedupeReconnectContentAgainstMessages(content: ContentPart[], messages: readonly unknown[], options?: {
94
115
  suppressToolRepeats?: boolean;
95
116
  trimTailTextOverlap?: boolean;