@agent-native/toolkit 0.205.0 → 0.206.0-nightly-20261009011246

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 (143) hide show
  1. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  2. package/dist/app/agentkit/react/components.js +17 -9
  3. package/dist/app/agentkit/react/components.js.map +1 -1
  4. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  5. package/dist/app/auth/AuthPage.js +11 -0
  6. package/dist/app/auth/AuthPage.js.map +1 -1
  7. package/dist/app/chat/AgentKitAssistantChat.d.ts +2 -0
  8. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  9. package/dist/app/chat/AgentKitAssistantChat.js +24 -14
  10. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  11. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  12. package/dist/app/chat/AgentPanel.js +3 -2
  13. package/dist/app/chat/AgentPanel.js.map +1 -1
  14. package/dist/app/chat/MultiTabAssistantChat.d.ts +4 -1
  15. package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
  16. package/dist/app/chat/MultiTabAssistantChat.js +61 -3
  17. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  18. package/dist/app/chat/agentkit-chat/approval-card.d.ts.map +1 -1
  19. package/dist/app/chat/agentkit-chat/approval-card.js +2 -1
  20. package/dist/app/chat/agentkit-chat/approval-card.js.map +1 -1
  21. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  22. package/dist/app/chat/chat/run-recovery.js +74 -8
  23. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  24. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  25. package/dist/app/chat/chat/tool-call-display.js +6 -4
  26. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  27. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.d.ts.map +1 -1
  28. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js +2 -1
  29. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js.map +1 -1
  30. package/dist/app/chat/conversation/AgentConversation.d.ts.map +1 -1
  31. package/dist/app/chat/conversation/AgentConversation.js +10 -7
  32. package/dist/app/chat/conversation/AgentConversation.js.map +1 -1
  33. package/dist/app/chat/fallback-chat-title.d.ts.map +1 -1
  34. package/dist/app/chat/fallback-chat-title.js +2 -4
  35. package/dist/app/chat/fallback-chat-title.js.map +1 -1
  36. package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  37. package/dist/app/chat/mcp-apps/McpAppRenderer.js +3 -2
  38. package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
  39. package/dist/app/chat/session-replay-privacy.d.ts +7 -0
  40. package/dist/app/chat/session-replay-privacy.d.ts.map +1 -0
  41. package/dist/app/chat/session-replay-privacy.js +6 -0
  42. package/dist/app/chat/session-replay-privacy.js.map +1 -0
  43. package/dist/app/extensions/ExtensionViewer.d.ts +4 -1
  44. package/dist/app/extensions/ExtensionViewer.d.ts.map +1 -1
  45. package/dist/app/extensions/ExtensionViewer.js +3 -3
  46. package/dist/app/extensions/ExtensionViewer.js.map +1 -1
  47. package/dist/app/extensions/ExtensionViewerPage.d.ts +4 -1
  48. package/dist/app/extensions/ExtensionViewerPage.d.ts.map +1 -1
  49. package/dist/app/extensions/ExtensionViewerPage.js +2 -2
  50. package/dist/app/extensions/ExtensionViewerPage.js.map +1 -1
  51. package/dist/app/onboarding/FirstRunOnboarding.d.ts.map +1 -1
  52. package/dist/app/onboarding/FirstRunOnboarding.js +53 -12
  53. package/dist/app/onboarding/FirstRunOnboarding.js.map +1 -1
  54. package/dist/app/onboarding/OnboardingPanel.d.ts.map +1 -1
  55. package/dist/app/onboarding/OnboardingPanel.js +14 -4
  56. package/dist/app/onboarding/OnboardingPanel.js.map +1 -1
  57. package/dist/app/resources/ResourceEditor.d.ts +4 -1
  58. package/dist/app/resources/ResourceEditor.d.ts.map +1 -1
  59. package/dist/app/resources/ResourceEditor.js +18 -7
  60. package/dist/app/resources/ResourceEditor.js.map +1 -1
  61. package/dist/app/resources/ResourcesPanel.d.ts +4 -1
  62. package/dist/app/resources/ResourcesPanel.d.ts.map +1 -1
  63. package/dist/app/resources/ResourcesPanel.js +18 -18
  64. package/dist/app/resources/ResourcesPanel.js.map +1 -1
  65. package/dist/app/resources/custom-agent-model-options.d.ts +16 -0
  66. package/dist/app/resources/custom-agent-model-options.d.ts.map +1 -0
  67. package/dist/app/resources/custom-agent-model-options.js +53 -0
  68. package/dist/app/resources/custom-agent-model-options.js.map +1 -0
  69. package/dist/app/settings/SettingsPanel.d.ts.map +1 -1
  70. package/dist/app/settings/SettingsPanel.js +34 -3
  71. package/dist/app/settings/SettingsPanel.js.map +1 -1
  72. package/dist/app/settings/SettingsTabsPage.d.ts.map +1 -1
  73. package/dist/app/settings/SettingsTabsPage.js +2 -0
  74. package/dist/app/settings/SettingsTabsPage.js.map +1 -1
  75. package/dist/app/settings/api-keys/ApiKeyDialogs.d.ts.map +1 -1
  76. package/dist/app/settings/api-keys/ApiKeyDialogs.js +49 -9
  77. package/dist/app/settings/api-keys/ApiKeyDialogs.js.map +1 -1
  78. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js +2 -1
  79. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js.map +1 -1
  80. package/dist/app/settings/model/ModelSettingsPage.d.ts.map +1 -1
  81. package/dist/app/settings/model/ModelSettingsPage.js +43 -9
  82. package/dist/app/settings/model/ModelSettingsPage.js.map +1 -1
  83. package/dist/app/settings/model/ProviderDialog.d.ts +4 -0
  84. package/dist/app/settings/model/ProviderDialog.d.ts.map +1 -1
  85. package/dist/app/settings/model/ProviderDialog.js +107 -11
  86. package/dist/app/settings/model/ProviderDialog.js.map +1 -1
  87. package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
  88. package/dist/app/settings/useBuilderStatus.js +13 -10
  89. package/dist/app/settings/useBuilderStatus.js.map +1 -1
  90. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  91. package/dist/composer/TiptapComposer.js +51 -13
  92. package/dist/composer/TiptapComposer.js.map +1 -1
  93. package/package.json +4 -4
  94. package/src/app/agentkit/react/components.interaction.spec.tsx +229 -1
  95. package/src/app/agentkit/react/components.spec.tsx +42 -1
  96. package/src/app/agentkit/react/components.tsx +46 -12
  97. package/src/app/auth/AuthPage.tsx +13 -0
  98. package/src/app/chat/AgentKitAssistantChat.spec.tsx +116 -5
  99. package/src/app/chat/AgentKitAssistantChat.tsx +41 -20
  100. package/src/app/chat/AgentPanel.tsx +5 -1
  101. package/src/app/chat/MultiTabAssistantChat.spec.tsx +152 -7
  102. package/src/app/chat/MultiTabAssistantChat.tsx +71 -4
  103. package/src/app/chat/agentkit-chat/approval-card.spec.tsx +17 -0
  104. package/src/app/chat/agentkit-chat/approval-card.tsx +2 -0
  105. package/src/app/chat/chat/run-recovery.spec.tsx +354 -9
  106. package/src/app/chat/chat/run-recovery.tsx +178 -10
  107. package/src/app/chat/chat/tool-call-display.spec.tsx +77 -0
  108. package/src/app/chat/chat/tool-call-display.tsx +23 -6
  109. package/src/app/chat/chat-first/ChatFirstAgentActivityPanel.tsx +2 -1
  110. package/src/app/chat/conversation/AgentConversation.spec.tsx +190 -1
  111. package/src/app/chat/conversation/AgentConversation.tsx +32 -8
  112. package/src/app/chat/fallback-chat-title.spec.ts +44 -0
  113. package/src/app/chat/fallback-chat-title.ts +3 -4
  114. package/src/app/chat/mcp-apps/McpAppRenderer.spec.ts +24 -0
  115. package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -2
  116. package/src/app/chat/session-replay-privacy.spec.ts +14 -0
  117. package/src/app/chat/session-replay-privacy.ts +5 -0
  118. package/src/app/extensions/ExtensionViewer.tsx +15 -3
  119. package/src/app/extensions/ExtensionViewerPage.tsx +7 -3
  120. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +252 -2
  121. package/src/app/onboarding/FirstRunOnboarding.tsx +56 -9
  122. package/src/app/onboarding/OnboardingPanel.save-scope.spec.tsx +1 -0
  123. package/src/app/onboarding/OnboardingPanel.tsx +15 -3
  124. package/src/app/resources/ResourceEditor.spec.tsx +240 -5
  125. package/src/app/resources/ResourceEditor.tsx +57 -10
  126. package/src/app/resources/ResourcesPanel.spec.ts +86 -4
  127. package/src/app/resources/ResourcesPanel.tsx +49 -16
  128. package/src/app/resources/custom-agent-model-options.ts +92 -0
  129. package/src/app/settings/SettingsPanel.model-defaults.spec.tsx +26 -13
  130. package/src/app/settings/SettingsPanel.tsx +35 -3
  131. package/src/app/settings/SettingsTabsPage.spec.tsx +97 -0
  132. package/src/app/settings/SettingsTabsPage.tsx +2 -0
  133. package/src/app/settings/api-keys/ApiKeyDialogs.tsx +84 -22
  134. package/src/app/settings/api-keys/ApiKeysSettingsPage.spec.tsx +229 -1
  135. package/src/app/settings/api-keys/ApiKeysSettingsPage.tsx +3 -2
  136. package/src/app/settings/model/ModelSettingsPage.spec.tsx +99 -9
  137. package/src/app/settings/model/ModelSettingsPage.tsx +79 -11
  138. package/src/app/settings/model/ProviderDialog.spec.tsx +192 -1
  139. package/src/app/settings/model/ProviderDialog.tsx +225 -29
  140. package/src/app/settings/useBuilderStatus.spec.tsx +49 -0
  141. package/src/app/settings/useBuilderStatus.ts +16 -10
  142. package/src/composer/TiptapComposer.spec.ts +97 -2
  143. package/src/composer/TiptapComposer.tsx +67 -13
@@ -1,11 +1,23 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
3
  import { AgentNativeI18nProvider } from "@agent-native/core/client/i18n";
4
+ import { SESSION_REPLAY_BLOCK_ATTRIBUTE } from "@agent-native/core/client/session-replay-privacy";
4
5
  import React, { act } from "react";
5
6
  import { createRoot, type Root } from "react-dom/client";
6
7
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
8
 
8
- import { AgentConversationMessageView } from "./AgentConversation.js";
9
+ import {
10
+ clearToolRenderersForTests,
11
+ registerToolRenderer,
12
+ } from "../chat/tool-render-registry.js";
13
+ import {
14
+ AgentConversation,
15
+ AgentConversationMessageView,
16
+ } from "./AgentConversation.js";
17
+
18
+ vi.mock("../mcp-apps/McpAppRenderer.js", () => ({
19
+ McpAppRenderer: () => <div data-testid="conversation-mcp-app" />,
20
+ }));
9
21
 
10
22
  vi.mock("../../extensions/index.js", () => ({
11
23
  InlineExtensionFrame: ({ extensionId, extension }: any) => (
@@ -34,10 +46,187 @@ describe("AgentConversationMessageView", () => {
34
46
  root.unmount();
35
47
  });
36
48
  container.remove();
49
+ clearToolRenderersForTests();
37
50
  vi.restoreAllMocks();
38
51
  vi.unstubAllGlobals();
39
52
  });
40
53
 
54
+ it("masks the conversation error without masking its container", () => {
55
+ act(() =>
56
+ root.render(
57
+ <AgentConversation
58
+ messages={[]}
59
+ error="Example Person's example run failed."
60
+ />,
61
+ ),
62
+ );
63
+ const alert = container.querySelector('[role="alert"]');
64
+ expect(alert?.querySelector("span")?.hasAttribute("data-an-mask")).toBe(
65
+ true,
66
+ );
67
+ expect(alert?.hasAttribute("data-an-mask")).toBe(false);
68
+ });
69
+
70
+ it.each(["errored", "completed"] as const)(
71
+ "masks only errored conversation tool diagnostics (%s)",
72
+ (state) => {
73
+ act(() =>
74
+ root.render(
75
+ <AgentConversationMessageView
76
+ message={{
77
+ id: "message-example",
78
+ role: "assistant",
79
+ parts: [
80
+ {
81
+ id: "tool-example",
82
+ type: "tool",
83
+ tool: {
84
+ id: "tool-example",
85
+ name: "example-tool",
86
+ state,
87
+ summary: "Example Person's example notes.",
88
+ result: "Example Document failed.",
89
+ },
90
+ },
91
+ ],
92
+ }}
93
+ />,
94
+ ),
95
+ );
96
+ const summary = container.querySelector(
97
+ ".agent-conversation-tool__summary",
98
+ );
99
+ const result = container.querySelector("pre span");
100
+ expect(summary?.hasAttribute("data-an-mask")).toBe(state === "errored");
101
+ expect(result?.textContent).toBe("Example Document failed.");
102
+ expect(result?.hasAttribute("data-an-mask")).toBe(state === "errored");
103
+ expect(
104
+ container
105
+ .querySelector(".agent-conversation-tool__name")
106
+ ?.closest("[data-an-mask]"),
107
+ ).toBeNull();
108
+ expect(
109
+ container.querySelector("pre strong")?.closest("[data-an-mask]"),
110
+ ).toBeNull();
111
+ },
112
+ );
113
+
114
+ it("shows an errored tool through the masked fallback, not its renderer", () => {
115
+ registerToolRenderer({
116
+ id: "example-renderer",
117
+ match: "example-tool",
118
+ Component: ({ context }) => (
119
+ <div data-testid="example-renderer">{context.resultText}</div>
120
+ ),
121
+ });
122
+ act(() =>
123
+ root.render(
124
+ <AgentConversationMessageView
125
+ message={{
126
+ id: "message-example",
127
+ role: "assistant",
128
+ parts: [
129
+ {
130
+ id: "tool-example",
131
+ type: "tool",
132
+ tool: {
133
+ id: "tool-example",
134
+ name: "example-tool",
135
+ state: "errored",
136
+ result: "Example Document failed.",
137
+ },
138
+ },
139
+ ],
140
+ }}
141
+ />,
142
+ ),
143
+ );
144
+ expect(container.querySelector('[data-testid="example-renderer"]')).toBe(
145
+ null,
146
+ );
147
+ expect(
148
+ container.querySelector("pre span")?.hasAttribute("data-an-mask"),
149
+ ).toBe(true);
150
+ });
151
+
152
+ it.each(["errored", "completed"] as const)(
153
+ "blocks only an errored tool's MCP App from replays (%s)",
154
+ (state) => {
155
+ act(() =>
156
+ root.render(
157
+ <AgentConversationMessageView
158
+ message={{
159
+ id: "message-example",
160
+ role: "assistant",
161
+ parts: [
162
+ {
163
+ id: "tool-example",
164
+ type: "tool",
165
+ tool: {
166
+ id: "tool-example",
167
+ name: "example-tool",
168
+ state,
169
+ result: "Example Document failed.",
170
+ mcpApp: {
171
+ serverId: "server",
172
+ toolName: "example-tool",
173
+ originalToolName: "example-tool",
174
+ resourceUri: "ui://example-tool",
175
+ toolInput: {},
176
+ toolResult: {},
177
+ },
178
+ },
179
+ },
180
+ ],
181
+ }}
182
+ />,
183
+ ),
184
+ );
185
+ const app = container.querySelector(
186
+ '[data-testid="conversation-mcp-app"]',
187
+ );
188
+ expect(app).not.toBeNull();
189
+ expect(app?.closest(`[${SESSION_REPLAY_BLOCK_ATTRIBUTE}]`) !== null).toBe(
190
+ state === "errored",
191
+ );
192
+ },
193
+ );
194
+
195
+ it.each(["error", "warning", "info"] as const)(
196
+ "masks notice text but not its title or action (%s)",
197
+ (tone) => {
198
+ act(() =>
199
+ root.render(
200
+ <AgentConversationMessageView
201
+ message={{
202
+ id: "message-example",
203
+ role: "assistant",
204
+ notices: [
205
+ {
206
+ id: "notice-example",
207
+ tone,
208
+ title: "Run status",
209
+ text: "Example Person's example run status.",
210
+ action: <button>Retry</button>,
211
+ },
212
+ ],
213
+ }}
214
+ />,
215
+ ),
216
+ );
217
+ const notice = container.querySelector(".agent-conversation-notice");
218
+ expect(notice?.querySelector("span")?.hasAttribute("data-an-mask")).toBe(
219
+ true,
220
+ );
221
+ expect(
222
+ notice?.querySelector("strong")?.closest("[data-an-mask]"),
223
+ ).toBeNull();
224
+ expect(
225
+ notice?.querySelector("button")?.closest("[data-an-mask]"),
226
+ ).toBeNull();
227
+ },
228
+ );
229
+
41
230
  it("renders text and tool parts in transcript order", () => {
42
231
  act(() => {
43
232
  root.render(
@@ -34,6 +34,10 @@ import {
34
34
  } from "../components/ui/message-scroller.js";
35
35
  import { HighlightedCodeBlock as SharedHighlightedCodeBlock } from "../HighlightedCodeBlock.js";
36
36
  import { McpAppRenderer } from "../mcp-apps/McpAppRenderer.js";
37
+ import {
38
+ SESSION_REPLAY_BLOCK_PROPS,
39
+ SESSION_REPLAY_MASK_PROPS,
40
+ } from "../session-replay-privacy.js";
37
41
  import type {
38
42
  AgentConversationAttachment,
39
43
  AgentConversationArtifact,
@@ -70,7 +74,7 @@ export function AgentConversation({
70
74
  {error && (
71
75
  <div className="agent-conversation__error" role="alert">
72
76
  <IconAlertTriangle size={15} strokeWidth={1.8} />
73
- <span>{error}</span>
77
+ <span {...SESSION_REPLAY_MASK_PROPS}>{error}</span>
74
78
  </div>
75
79
  )}
76
80
  <MessageScrollerProvider autoScroll>
@@ -412,9 +416,11 @@ function ConversationToolCall({ tool }: { tool: AgentConversationToolCall }) {
412
416
  chatUI: tool.chatUI,
413
417
  };
414
418
  const NativeToolRenderer =
415
- resolveBuiltinActionChatRenderer(nativeToolContext) ??
416
- resolveToolRenderer(nativeToolContext) ??
417
- resolveBuiltinFallbackToolRenderer(nativeToolContext);
419
+ tool.state === "errored"
420
+ ? null
421
+ : (resolveBuiltinActionChatRenderer(nativeToolContext) ??
422
+ resolveToolRenderer(nativeToolContext) ??
423
+ resolveBuiltinFallbackToolRenderer(nativeToolContext));
418
424
  if (NativeToolRenderer) {
419
425
  return (
420
426
  <ActionChatUiSurface
@@ -446,7 +452,12 @@ function ConversationToolCall({ tool }: { tool: AgentConversationToolCall }) {
446
452
  {toolLabel(t, tool.name)}
447
453
  </span>
448
454
  {tool.summary && (
449
- <span className="agent-conversation-tool__summary">{tool.summary}</span>
455
+ <span
456
+ {...(tool.state === "errored" ? SESSION_REPLAY_MASK_PROPS : {})}
457
+ className="agent-conversation-tool__summary"
458
+ >
459
+ {tool.summary}
460
+ </span>
450
461
  )}
451
462
  </>
452
463
  );
@@ -468,7 +479,14 @@ function ConversationToolCall({ tool }: { tool: AgentConversationToolCall }) {
468
479
  />
469
480
  </summary>
470
481
  <div className="agent-conversation-tool__details">
471
- {tool.mcpApp && <McpAppRenderer app={tool.mcpApp} />}
482
+ {tool.mcpApp && (
483
+ // The recorder keeps iframe attributes, and a snapshot's srcdoc can show the failure.
484
+ <div
485
+ {...(tool.state === "errored" ? SESSION_REPLAY_BLOCK_PROPS : {})}
486
+ >
487
+ <McpAppRenderer app={tool.mcpApp} />
488
+ </div>
489
+ )}
472
490
  {tool.input && (
473
491
  <pre>
474
492
  <strong>input</strong>
@@ -478,7 +496,11 @@ function ConversationToolCall({ tool }: { tool: AgentConversationToolCall }) {
478
496
  {tool.result && (
479
497
  <pre>
480
498
  <strong>result</strong>
481
- {tool.result}
499
+ <span
500
+ {...(tool.state === "errored" ? SESSION_REPLAY_MASK_PROPS : {})}
501
+ >
502
+ {tool.result}
503
+ </span>
482
504
  </pre>
483
505
  )}
484
506
  </div>
@@ -497,7 +519,9 @@ function ConversationNotice({ notice }: { notice: AgentConversationNotice }) {
497
519
  <IconAlertTriangle size={15} />
498
520
  <div>
499
521
  {notice.title && <strong>{notice.title}</strong>}
500
- <span>{notice.text}</span>
522
+ {/* Tone doesn't mark failures: a harness error that mentions approval
523
+ is classified as an approval and rendered with the warning tone. */}
524
+ <span {...SESSION_REPLAY_MASK_PROPS}>{notice.text}</span>
501
525
  </div>
502
526
  {notice.action}
503
527
  </div>
@@ -0,0 +1,44 @@
1
+ import { appendAgentChatContextToMessage } from "@agent-native/core/shared";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { fallbackChatTitle } from "./fallback-chat-title.js";
5
+
6
+ describe("fallbackChatTitle", () => {
7
+ it("strips inline legacy context whether it is closed or unclosed", () => {
8
+ expect(fallbackChatTitle("Plan <context>private</context> next")).toBe(
9
+ "Plan next",
10
+ );
11
+ expect(fallbackChatTitle("Plan <context>private remainder")).toBe("Plan");
12
+ });
13
+
14
+ it("does not let a nested encoded-looking marker escape legacy context", () => {
15
+ expect(
16
+ fallbackChatTitle(
17
+ 'Plan <context>private <context data-agentkit-context-encoding="entities-v1">nested</context> private</context> next',
18
+ ),
19
+ ).toBe("Plan next");
20
+ });
21
+
22
+ it("does not strip longer or dotted tag names", () => {
23
+ const prompt =
24
+ "<context-menu>ok</context-menu> <Context.Provider>ok</Context.Provider>";
25
+
26
+ expect(fallbackChatTitle(prompt)).toBe(prompt);
27
+ });
28
+
29
+ it("preserves raw lookalike tags before a canonical encoded context block", () => {
30
+ const message =
31
+ "A <context-menu>x</context-menu> <Context.Provider>y</Context.Provider>";
32
+ const prompt = `${message}\n\n<context data-agentkit-context-encoding="entities-v1">\nprivate\n</context>`;
33
+
34
+ expect(fallbackChatTitle(prompt)).toBe(message);
35
+ });
36
+
37
+ it("preserves an authored exact marker through the encoded producer", () => {
38
+ expect(
39
+ fallbackChatTitle(
40
+ appendAgentChatContextToMessage("<context>", "private </context>"),
41
+ ),
42
+ ).toBe("<context>");
43
+ });
44
+ });
@@ -1,8 +1,7 @@
1
+ import { stripAgentChatContextFromMessage } from "@agent-native/core/shared";
2
+
1
3
  export function fallbackChatTitle(message: string): string {
2
- return message
3
- .replace(/<context\b[^>]*>[\s\S]*?<\/context>\n?/gi, "")
4
- .replace(/<context\b[^>]*>[\s\S]*$/gi, "")
5
- .replace(/<\/context>/gi, "")
4
+ return stripAgentChatContextFromMessage(message)
6
5
  .replace(/@\[([^\]|]+)\|[^\]]*\]/g, "@$1")
7
6
  .replace(/\s+/g, " ")
8
7
  .trim()
@@ -78,6 +78,7 @@ vi.mock(
78
78
  },
79
79
  );
80
80
 
81
+ import { SESSION_REPLAY_BLOCK_ATTRIBUTE } from "@agent-native/core/client/session-replay-privacy";
81
82
  import {
82
83
  AGENT_NATIVE_EMBED_MESSAGE_TYPES,
83
84
  AGENT_NATIVE_EMBED_PROTOCOL,
@@ -291,6 +292,29 @@ describe("McpAppRenderer security helpers", () => {
291
292
  );
292
293
  });
293
294
 
295
+ it("blocks the frame from replays once the app fails on its own", async () => {
296
+ vi.useFakeTimers();
297
+ const payload = mcpAppPayload({
298
+ resourceHtml: "<!doctype html><html><body>Jane Doe's notes</body></html>",
299
+ openUrl: "https://plan.agent-native.com/plans/plan-123",
300
+ });
301
+
302
+ await act(async () => {
303
+ root.render(React.createElement(McpAppRenderer, { app: payload }));
304
+ });
305
+
306
+ const iframe = container.querySelector("iframe");
307
+ expect(iframe?.hasAttribute(SESSION_REPLAY_BLOCK_ATTRIBUTE)).toBe(false);
308
+
309
+ await act(async () => {
310
+ vi.advanceTimersByTime(MCP_APP_INITIALIZE_TIMEOUT_MS + 1);
311
+ });
312
+
313
+ expect(container.querySelector(".agent-mcp-app__error-box")).toBeTruthy();
314
+ expect(container.querySelector("iframe")).toBe(iframe);
315
+ expect(iframe?.hasAttribute(SESSION_REPLAY_BLOCK_ATTRIBUTE)).toBe(true);
316
+ });
317
+
294
318
  it("initializes the v2 bridge and proxies MCP tool listing and calls through the app API", async () => {
295
319
  appBridgeHarness.enabled = true;
296
320
  const callTool = vi.fn(async () => ({
@@ -32,6 +32,11 @@ import {
32
32
  useState,
33
33
  } from "react";
34
34
 
35
+ import {
36
+ SESSION_REPLAY_BLOCK_PROPS,
37
+ SESSION_REPLAY_MASK_PROPS,
38
+ } from "../session-replay-privacy.js";
39
+
35
40
  type CallToolResult = Parameters<
36
41
  InstanceType<typeof AppBridge>["sendToolResult"]
37
42
  >[0];
@@ -409,12 +414,13 @@ export function McpAppRenderer({
409
414
  <div className="agent-mcp-app__error" role="alert">
410
415
  <div className="agent-mcp-app__error-box">
411
416
  <IconAlertTriangle size={15} />
412
- <span>{error}</span>
417
+ <span {...SESSION_REPLAY_MASK_PROPS}>{error}</span>
413
418
  </div>
414
419
  </div>
415
420
  )}
416
421
  {srcDoc ? (
417
422
  <iframe
423
+ {...(error ? SESSION_REPLAY_BLOCK_PROPS : {})}
418
424
  ref={iframeRef}
419
425
  title={app.tool?.title ?? app.originalToolName}
420
426
  srcDoc={srcDoc}
@@ -454,7 +460,7 @@ export function McpAppRenderer({
454
460
  <div className="agent-mcp-app__error" role="alert">
455
461
  <div className="agent-mcp-app__error-box">
456
462
  <IconAlertTriangle size={15} />
457
- <span>{error}</span>
463
+ <span {...SESSION_REPLAY_MASK_PROPS}>{error}</span>
458
464
  {externalOpenUrl && !readOnly && (
459
465
  <button
460
466
  type="button"
@@ -469,7 +475,9 @@ export function McpAppRenderer({
469
475
  </div>
470
476
  </div>
471
477
  )}
478
+ {/* A failed app stays mounted, and the recorder keeps its frame attributes. */}
472
479
  <iframe
480
+ {...(error ? SESSION_REPLAY_BLOCK_PROPS : {})}
473
481
  ref={iframeRef}
474
482
  title={app.tool?.title ?? app.originalToolName}
475
483
  srcDoc={srcDoc}
@@ -0,0 +1,14 @@
1
+ import * as core from "@agent-native/core/client/session-replay-privacy";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import {
5
+ SESSION_REPLAY_BLOCK_PROPS,
6
+ SESSION_REPLAY_MASK_PROPS,
7
+ } from "./session-replay-privacy.js";
8
+
9
+ describe("toolkit session replay markers", () => {
10
+ it("match the markers core's recorder honors", () => {
11
+ expect(SESSION_REPLAY_MASK_PROPS).toEqual(core.SESSION_REPLAY_MASK_PROPS);
12
+ expect(SESSION_REPLAY_BLOCK_PROPS).toEqual(core.SESSION_REPLAY_BLOCK_PROPS);
13
+ });
14
+ });
@@ -0,0 +1,5 @@
1
+ // Toolkit's core peer range includes releases without
2
+ // @agent-native/core/client/session-replay-privacy, so it repeats core's replay
3
+ // markers instead of importing them. Its spec asserts core's attribute names.
4
+ export const SESSION_REPLAY_MASK_PROPS = { "data-an-mask": "" } as const;
5
+ export const SESSION_REPLAY_BLOCK_PROPS = { "data-an-block": "" } as const;
@@ -36,7 +36,14 @@ import {
36
36
  IconX,
37
37
  } from "@tabler/icons-react";
38
38
  import { useQuery, useQueryClient } from "@tanstack/react-query";
39
- import { useState, useEffect, useRef, useCallback, useMemo } from "react";
39
+ import {
40
+ useState,
41
+ useEffect,
42
+ useRef,
43
+ useCallback,
44
+ useMemo,
45
+ type ReactNode,
46
+ } from "react";
40
47
  import { Link, useLocation, useNavigate } from "react-router";
41
48
 
42
49
  import { AgentToggleButton } from "../chat/AgentSidebar.js";
@@ -124,6 +131,8 @@ interface Extension {
124
131
 
125
132
  export interface ExtensionViewerProps {
126
133
  extensionId: string;
134
+ /** Trailing toolbar controls. Defaults to the agent toggle. */
135
+ headerActions?: ReactNode;
127
136
  }
128
137
 
129
138
  function readExtensionTitleSuffix(): string | null {
@@ -698,7 +707,10 @@ function ExtensionHistoryPopover({
698
707
  );
699
708
  }
700
709
 
701
- export function ExtensionViewer({ extensionId }: ExtensionViewerProps) {
710
+ export function ExtensionViewer({
711
+ extensionId,
712
+ headerActions = <AgentToggleButton />,
713
+ }: ExtensionViewerProps) {
702
714
  const t = useT();
703
715
  const location = useLocation();
704
716
  const navigate = useNavigate();
@@ -1242,7 +1254,7 @@ export function ExtensionViewer({ extensionId }: ExtensionViewerProps) {
1242
1254
  />
1243
1255
  </>
1244
1256
  )}
1245
- <AgentToggleButton />
1257
+ {headerActions}
1246
1258
  </div>
1247
1259
  </div>
1248
1260
  {isLocalExtension && (
@@ -1,13 +1,17 @@
1
1
  import { agentNativePath } from "@agent-native/core/client/api-path";
2
2
  import { getBrowserTabId } from "@agent-native/core/client/hooks";
3
- import { useEffect } from "react";
3
+ import { useEffect, type ReactNode } from "react";
4
4
  import { useParams } from "react-router";
5
5
 
6
6
  import { incrementExtensionView } from "./extension-popularity.js";
7
7
  import { ExtensionsListPage } from "./ExtensionsListPage.js";
8
8
  import { ExtensionViewer } from "./ExtensionViewer.js";
9
9
 
10
- export function ExtensionViewerPage() {
10
+ export function ExtensionViewerPage({
11
+ headerActions,
12
+ }: {
13
+ headerActions?: ReactNode;
14
+ } = {}) {
11
15
  const { id } = useParams<{ id: string }>();
12
16
 
13
17
  useEffect(() => {
@@ -33,5 +37,5 @@ export function ExtensionViewerPage() {
33
37
  return <ExtensionsListPage />;
34
38
  }
35
39
  if (!id) return null;
36
- return <ExtensionViewer extensionId={id} />;
40
+ return <ExtensionViewer extensionId={id} headerActions={headerActions} />;
37
41
  }