@agent-native/core 0.77.16 → 0.77.17

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 (60) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +8 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +4 -0
  5. package/corpus/core/src/client/org/OrgSwitcher.tsx +4 -0
  6. package/corpus/core/src/client/use-db-sync.ts +19 -2
  7. package/corpus/core/src/server/auth-marketing.ts +15 -0
  8. package/corpus/core/src/server/onboarding-html.ts +47 -1
  9. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +76 -0
  10. package/corpus/templates/analytics/actions/update-dashboard.ts +1 -0
  11. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +13 -2
  12. package/corpus/templates/analytics/app/i18n-data.ts +10 -0
  13. package/corpus/templates/analytics/app/lib/sql-query.ts +97 -21
  14. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +22 -1
  15. package/corpus/templates/analytics/app/root.tsx +15 -6
  16. package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-no-longer-refresh-in-the-background-during-.md +6 -0
  17. package/corpus/templates/analytics/changelog/2026-06-25-first-party-dashboards-use-indexed-event-dates-for-faster-da.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-06-25-retention-and-active-user-dashboard-panels-now-count-account.md +6 -0
  19. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +58 -58
  20. package/corpus/templates/analytics/server/db/schema.ts +2 -0
  21. package/corpus/templates/analytics/server/handlers/sql-query.ts +1 -1
  22. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -1
  23. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -6
  24. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +88 -56
  25. package/corpus/templates/analytics/server/plugins/db.ts +73 -0
  26. package/corpus/templates/clips/AGENTS.md +5 -0
  27. package/corpus/templates/clips/changelog/2026-06-25-github-issue-and-pull-request-pages-can-now-preview-playable.md +6 -0
  28. package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +8 -2
  29. package/corpus/templates/clips/chrome-extension/public/manifest.json +14 -2
  30. package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +233 -0
  31. package/corpus/templates/clips/chrome-extension/src/github-preview.html +12 -0
  32. package/corpus/templates/clips/chrome-extension/src/github-preview.ts +414 -0
  33. package/corpus/templates/clips/chrome-extension/vite.config.ts +5 -0
  34. package/corpus/templates/mail/actions/archive-email.ts +13 -3
  35. package/corpus/templates/mail/app/hooks/use-emails.ts +5 -2
  36. package/corpus/templates/mail/changelog/2026-06-25-archive-failures-now-explain-when-gmail-needs-reconnecting-p.md +6 -0
  37. package/corpus/templates/mail/shared/archive-errors.ts +137 -0
  38. package/corpus/templates/plan/changelog/2026-06-25-hosted-signup-now-shows-how-to-switch-visual-plan-to-local-files.md +6 -0
  39. package/dist/client/AgentPanel.d.ts.map +1 -1
  40. package/dist/client/AgentPanel.js +2 -1
  41. package/dist/client/AgentPanel.js.map +1 -1
  42. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  43. package/dist/client/org/OrgSwitcher.js +3 -1
  44. package/dist/client/org/OrgSwitcher.js.map +1 -1
  45. package/dist/client/use-db-sync.d.ts +9 -0
  46. package/dist/client/use-db-sync.d.ts.map +1 -1
  47. package/dist/client/use-db-sync.js +8 -1
  48. package/dist/client/use-db-sync.js.map +1 -1
  49. package/dist/collab/awareness.d.ts +2 -2
  50. package/dist/collab/awareness.d.ts.map +1 -1
  51. package/dist/observability/routes.d.ts +5 -5
  52. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  53. package/dist/server/auth-marketing.d.ts +4 -0
  54. package/dist/server/auth-marketing.d.ts.map +1 -1
  55. package/dist/server/auth-marketing.js +9 -0
  56. package/dist/server/auth-marketing.js.map +1 -1
  57. package/dist/server/onboarding-html.d.ts.map +1 -1
  58. package/dist/server/onboarding-html.js +46 -1
  59. package/dist/server/onboarding-html.js.map +1 -1
  60. package/package.json +1 -1
@@ -0,0 +1,414 @@
1
+ import { captureExtensionError, initExtensionSentry } from "./sentry";
2
+
3
+ initExtensionSentry("github-preview");
4
+
5
+ const DEFAULT_HOST = "clips.agent-native.com";
6
+ const CLIPS_HOSTS = new Set(["clips.agent-native.com"]);
7
+ const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1"]);
8
+
9
+ type PreviewParams = {
10
+ frameId: string;
11
+ embedUrl: URL;
12
+ metadataUrl: URL;
13
+ sourceUrl: URL;
14
+ clipId: string;
15
+ host: string;
16
+ };
17
+
18
+ type PublicRecordingResponse = {
19
+ recording?: {
20
+ title?: string | null;
21
+ status?: string | null;
22
+ videoUrl?: string | null;
23
+ visibility?: string | null;
24
+ hasPassword?: boolean | null;
25
+ } | null;
26
+ error?: string;
27
+ passwordRequired?: boolean;
28
+ expired?: boolean;
29
+ };
30
+
31
+ type PreviewState =
32
+ | { kind: "checking" }
33
+ | { kind: "playable"; title: string }
34
+ | { kind: "unavailable"; title: string; detail: string };
35
+
36
+ function parseUrl(value: string | null): URL | null {
37
+ if (!value) return null;
38
+ try {
39
+ const parsed = new URL(value);
40
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
41
+ return null;
42
+ }
43
+ return parsed;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ function isAllowedPreviewOrigin(url: URL): boolean {
50
+ if (CLIPS_HOSTS.has(url.hostname)) return true;
51
+ if (LOCAL_HOSTS.has(url.hostname)) return true;
52
+ return false;
53
+ }
54
+
55
+ function validPreviewParams(params: PreviewParams): boolean {
56
+ if (!isAllowedPreviewOrigin(params.embedUrl)) return false;
57
+ if (!isAllowedPreviewOrigin(params.metadataUrl)) return false;
58
+ if (!isAllowedPreviewOrigin(params.sourceUrl)) return false;
59
+ if (params.embedUrl.origin !== params.metadataUrl.origin) return false;
60
+ if (params.sourceUrl.origin !== params.metadataUrl.origin) return false;
61
+ return /(?:^|\/)api\/public-recording$/.test(params.metadataUrl.pathname);
62
+ }
63
+
64
+ function readParams(): PreviewParams | null {
65
+ const params = new URLSearchParams(location.search);
66
+ const frameId = params.get("frameId")?.trim();
67
+ const embedUrl = parseUrl(params.get("embedUrl"));
68
+ const metadataUrl = parseUrl(params.get("metadataUrl"));
69
+ const sourceUrl = parseUrl(params.get("sourceUrl"));
70
+ const clipId = params.get("clipId")?.trim() ?? "";
71
+ const host = params.get("host")?.trim() || DEFAULT_HOST;
72
+ if (!frameId || !embedUrl || !metadataUrl || !sourceUrl || !clipId) {
73
+ return null;
74
+ }
75
+ const preview = { frameId, embedUrl, metadataUrl, sourceUrl, clipId, host };
76
+ return validPreviewParams(preview) ? preview : null;
77
+ }
78
+
79
+ function postHeight(frameId: string): void {
80
+ const height = document.documentElement.scrollHeight;
81
+ window.parent.postMessage(
82
+ {
83
+ source: "clips-github-preview",
84
+ frameId,
85
+ height,
86
+ },
87
+ "*",
88
+ );
89
+ }
90
+
91
+ function installAutoResize(frameId: string): void {
92
+ const emit = () => postHeight(frameId);
93
+ try {
94
+ const observer = new ResizeObserver(emit);
95
+ observer.observe(document.documentElement);
96
+ observer.observe(document.body);
97
+ } catch {
98
+ window.addEventListener("resize", emit);
99
+ }
100
+ requestAnimationFrame(emit);
101
+ setTimeout(emit, 250);
102
+ }
103
+
104
+ function renderInvalid(root: HTMLElement): void {
105
+ root.textContent = "";
106
+ const card = document.createElement("section");
107
+ card.className = "clips-preview-card clips-preview-error";
108
+ const title = document.createElement("strong");
109
+ title.textContent = "Clip preview unavailable";
110
+ const body = document.createElement("p");
111
+ body.textContent = "This Clips link could not be parsed by the extension.";
112
+ card.append(title, body);
113
+ root.append(card);
114
+ }
115
+
116
+ function renderPreview(
117
+ root: HTMLElement,
118
+ preview: PreviewParams,
119
+ state: PreviewState,
120
+ ): void {
121
+ root.textContent = "";
122
+
123
+ const card = document.createElement("section");
124
+ card.className = "clips-preview-card";
125
+
126
+ const header = document.createElement("header");
127
+ header.className = "clips-preview-header";
128
+
129
+ const titleWrap = document.createElement("div");
130
+ titleWrap.className = "clips-preview-title-wrap";
131
+
132
+ const title = document.createElement("div");
133
+ title.className = "clips-preview-title";
134
+ title.textContent =
135
+ state.kind === "playable" || state.kind === "unavailable"
136
+ ? state.title
137
+ : "Clips preview";
138
+
139
+ const subtitle = document.createElement("div");
140
+ subtitle.className = "clips-preview-subtitle";
141
+ subtitle.textContent = preview.host;
142
+
143
+ titleWrap.append(title, subtitle);
144
+
145
+ const open = document.createElement("a");
146
+ open.className = "clips-preview-open";
147
+ open.href = preview.sourceUrl.toString();
148
+ open.target = "_blank";
149
+ open.rel = "noreferrer noopener";
150
+ open.textContent = "Open clip";
151
+
152
+ header.append(titleWrap, open);
153
+ card.append(header);
154
+
155
+ if (state.kind === "playable") {
156
+ const player = document.createElement("div");
157
+ player.className = "clips-preview-player";
158
+
159
+ const frame = document.createElement("iframe");
160
+ frame.title = "Playable Clips embed";
161
+ frame.src = preview.embedUrl.toString();
162
+ frame.allow = "autoplay; fullscreen; picture-in-picture";
163
+ frame.allowFullscreen = true;
164
+ frame.referrerPolicy = "no-referrer";
165
+
166
+ player.append(frame);
167
+ card.append(player);
168
+ } else {
169
+ const body = document.createElement("div");
170
+ body.className = "clips-preview-message";
171
+ body.textContent =
172
+ state.kind === "checking"
173
+ ? "Checking whether this clip can play inline..."
174
+ : state.detail;
175
+ card.append(body);
176
+ }
177
+
178
+ root.append(card);
179
+ }
180
+
181
+ async function checkPublicPlayable(
182
+ preview: PreviewParams,
183
+ ): Promise<PreviewState> {
184
+ try {
185
+ const response = await fetch(preview.metadataUrl.toString(), {
186
+ credentials: "omit",
187
+ headers: { Accept: "application/json" },
188
+ });
189
+ const data = (await response
190
+ .json()
191
+ .catch(() => ({}))) as PublicRecordingResponse;
192
+
193
+ if (!response.ok) {
194
+ if (response.status === 401 || data.passwordRequired) {
195
+ return {
196
+ kind: "unavailable",
197
+ title: "Protected Clips link",
198
+ detail: "Open this clip in Clips to enter the password.",
199
+ };
200
+ }
201
+ if (response.status === 410 || data.expired) {
202
+ return {
203
+ kind: "unavailable",
204
+ title: "Expired Clips link",
205
+ detail: "This clip has expired. Open it in Clips for details.",
206
+ };
207
+ }
208
+ return {
209
+ kind: "unavailable",
210
+ title: "Clips link",
211
+ detail: "Open this clip in Clips to view it.",
212
+ };
213
+ }
214
+
215
+ const recording = data.recording;
216
+ const title = recording?.title?.trim() || "Clips preview";
217
+ if (
218
+ recording?.visibility !== "public" ||
219
+ recording.hasPassword ||
220
+ recording.status !== "ready" ||
221
+ !recording.videoUrl
222
+ ) {
223
+ return {
224
+ kind: "unavailable",
225
+ title,
226
+ detail: "Open this clip in Clips to view it.",
227
+ };
228
+ }
229
+
230
+ return { kind: "playable", title };
231
+ } catch {
232
+ return {
233
+ kind: "unavailable",
234
+ title: "Clips link",
235
+ detail: "Open this clip in Clips to view it.",
236
+ };
237
+ }
238
+ }
239
+
240
+ function installStyles(): void {
241
+ const style = document.createElement("style");
242
+ style.textContent = `
243
+ :root {
244
+ color-scheme: light dark;
245
+ --clips-bg: #ffffff;
246
+ --clips-fg: #24292f;
247
+ --clips-muted: #57606a;
248
+ --clips-border: #d0d7de;
249
+ --clips-button-bg: #f6f8fa;
250
+ --clips-button-hover: #eef1f4;
251
+ --clips-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
252
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
253
+ background: transparent;
254
+ }
255
+
256
+ @media (prefers-color-scheme: dark) {
257
+ :root {
258
+ --clips-bg: #0d1117;
259
+ --clips-fg: #f0f6fc;
260
+ --clips-muted: #8b949e;
261
+ --clips-border: #30363d;
262
+ --clips-button-bg: #21262d;
263
+ --clips-button-hover: #30363d;
264
+ --clips-shadow: 0 8px 24px rgba(1, 4, 9, 0.38);
265
+ }
266
+ }
267
+
268
+ * {
269
+ box-sizing: border-box;
270
+ }
271
+
272
+ html,
273
+ body {
274
+ margin: 0;
275
+ min-width: 0;
276
+ background: transparent;
277
+ }
278
+
279
+ body {
280
+ padding: 0;
281
+ overflow: hidden;
282
+ color: var(--clips-fg);
283
+ -webkit-font-smoothing: antialiased;
284
+ -moz-osx-font-smoothing: grayscale;
285
+ }
286
+
287
+ .clips-preview-card {
288
+ overflow: hidden;
289
+ border: 1px solid var(--clips-border);
290
+ border-radius: 8px;
291
+ background: var(--clips-bg);
292
+ box-shadow: var(--clips-shadow);
293
+ }
294
+
295
+ .clips-preview-header {
296
+ display: flex;
297
+ align-items: center;
298
+ gap: 12px;
299
+ min-height: 44px;
300
+ padding: 10px 12px;
301
+ border-bottom: 1px solid var(--clips-border);
302
+ }
303
+
304
+ .clips-preview-title-wrap {
305
+ min-width: 0;
306
+ flex: 1;
307
+ }
308
+
309
+ .clips-preview-title {
310
+ overflow: hidden;
311
+ text-overflow: ellipsis;
312
+ white-space: nowrap;
313
+ font-size: 13px;
314
+ font-weight: 600;
315
+ line-height: 18px;
316
+ }
317
+
318
+ .clips-preview-subtitle {
319
+ overflow: hidden;
320
+ text-overflow: ellipsis;
321
+ white-space: nowrap;
322
+ color: var(--clips-muted);
323
+ font-size: 12px;
324
+ line-height: 16px;
325
+ }
326
+
327
+ .clips-preview-open {
328
+ flex: 0 0 auto;
329
+ border: 1px solid var(--clips-border);
330
+ border-radius: 6px;
331
+ background: var(--clips-button-bg);
332
+ color: var(--clips-fg);
333
+ padding: 5px 9px;
334
+ font-size: 12px;
335
+ font-weight: 600;
336
+ line-height: 18px;
337
+ text-decoration: none;
338
+ }
339
+
340
+ .clips-preview-open:hover,
341
+ .clips-preview-open:focus-visible {
342
+ background: var(--clips-button-hover);
343
+ text-decoration: none;
344
+ outline: none;
345
+ }
346
+
347
+ .clips-preview-player {
348
+ position: relative;
349
+ width: 100%;
350
+ aspect-ratio: 16 / 9;
351
+ min-height: 240px;
352
+ background: #000000;
353
+ }
354
+
355
+ .clips-preview-player iframe {
356
+ position: absolute;
357
+ inset: 0;
358
+ width: 100%;
359
+ height: 100%;
360
+ border: 0;
361
+ background: #000000;
362
+ }
363
+
364
+ .clips-preview-message {
365
+ padding: 12px;
366
+ color: var(--clips-muted);
367
+ font-size: 12px;
368
+ line-height: 18px;
369
+ }
370
+
371
+ .clips-preview-error {
372
+ padding: 12px;
373
+ }
374
+
375
+ .clips-preview-error strong {
376
+ display: block;
377
+ font-size: 13px;
378
+ line-height: 18px;
379
+ }
380
+
381
+ .clips-preview-error p {
382
+ margin: 4px 0 0;
383
+ color: var(--clips-muted);
384
+ font-size: 12px;
385
+ line-height: 18px;
386
+ }
387
+ `;
388
+ document.head.append(style);
389
+ }
390
+
391
+ async function main(): Promise<void> {
392
+ try {
393
+ installStyles();
394
+ const root = document.getElementById("root");
395
+ if (!root) throw new Error("Missing preview root");
396
+
397
+ const preview = readParams();
398
+ if (!preview) {
399
+ renderInvalid(root);
400
+ return;
401
+ }
402
+
403
+ renderPreview(root, preview, { kind: "checking" });
404
+ installAutoResize(preview.frameId);
405
+ renderPreview(root, preview, await checkPublicPlayable(preview));
406
+ } catch (error) {
407
+ captureExtensionError(error, {
408
+ tags: { surface: "github-preview" },
409
+ extra: { pageUrl: location.href },
410
+ });
411
+ }
412
+ }
413
+
414
+ main();
@@ -69,6 +69,11 @@ export default defineConfig({
69
69
  input: {
70
70
  background: resolve(root, "src/background.ts"),
71
71
  "content-script": resolve(root, "src/content-script.ts"),
72
+ "github-preview-content": resolve(
73
+ root,
74
+ "src/github-preview-content.ts",
75
+ ),
76
+ "github-preview": resolve(root, "src/github-preview.html"),
72
77
  offscreen: resolve(root, "src/offscreen.html"),
73
78
  overlay: resolve(root, "src/overlay.html"),
74
79
  permission: resolve(root, "src/permission.html"),
@@ -1,10 +1,17 @@
1
1
  import { defineAction } from "@agent-native/core";
2
2
  import { writeAppState } from "@agent-native/core/application-state";
3
3
  import { getRequestUserEmail } from "@agent-native/core/server";
4
+ import { summarizeArchiveFailures } from "@shared/archive-errors.js";
4
5
  import { z } from "zod";
5
6
 
6
7
  import { archiveEmail } from "../server/lib/email-state.js";
7
8
 
9
+ function userFacingActionError(message: string, statusCode: number): Error {
10
+ const error = new Error(message) as Error & { statusCode: number };
11
+ error.statusCode = statusCode;
12
+ return error;
13
+ }
14
+
8
15
  export default defineAction({
9
16
  description:
10
17
  "Archive one or more emails by ID. The UI handles navigation to the next email automatically.",
@@ -61,9 +68,12 @@ export default defineAction({
61
68
  const failed = results.filter((r) => !r.success);
62
69
 
63
70
  if (failed.length > 0) {
64
- throw new Error(
65
- `Archived ${succeeded}/${ids.length} email(s). Failures: ${failed.map((r) => `${r.id}: ${r.error}`).join("; ")}`,
66
- );
71
+ const summary = summarizeArchiveFailures({
72
+ succeeded,
73
+ total: ids.length,
74
+ failures: failed.map((r) => r.error ?? "failed"),
75
+ });
76
+ throw userFacingActionError(summary.message, summary.statusCode);
67
77
  }
68
78
  return `Archived ${succeeded} email(s) successfully`;
69
79
  },
@@ -1,4 +1,5 @@
1
1
  import { appApiPath, callAction, useT } from "@agent-native/core/client";
2
+ import { archiveFailureToastMessage } from "@shared/archive-errors";
2
3
  import { markdownPreviewSnippet } from "@shared/markdown";
3
4
  import type {
4
5
  ComposeAttachment,
@@ -809,10 +810,12 @@ export function useArchiveEmail() {
809
810
  );
810
811
  return { previous, threadId };
811
812
  },
812
- onError: (_err, _vars, context) => {
813
+ onError: (err, _vars, context) => {
813
814
  if (context?.threadId) unsuppressThread(context.threadId);
814
815
  context?.previous.forEach(([key, data]) => qc.setQueryData(key, data));
815
- toast.error(t("mail.toasts.archiveFailed"));
816
+ toast.error(
817
+ archiveFailureToastMessage(err, t("mail.toasts.archiveFailed")),
818
+ );
816
819
  },
817
820
  onSettled: () => delayedInvalidate(qc, [["emails"], ["labels"]]),
818
821
  });
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Archive failures now explain when Gmail needs reconnecting, permission, or a retry.
@@ -0,0 +1,137 @@
1
+ export interface ArchiveFailureMessage {
2
+ message: string;
3
+ statusCode: number;
4
+ }
5
+
6
+ interface ArchiveFailureDetail {
7
+ detail: string;
8
+ statusCode: number;
9
+ }
10
+
11
+ function errorText(error: unknown): string {
12
+ if (error instanceof Error) return error.message;
13
+ if (typeof error === "string") return error;
14
+ return "";
15
+ }
16
+
17
+ function withoutActionPrefix(message: string): string {
18
+ return message.replace(/^Action\s+archive-email\s+failed:\s*/i, "").trim();
19
+ }
20
+
21
+ function quotaDetail(message: string): string {
22
+ const wait = message.match(/ready again in about ([^.]+)\./i)?.[1];
23
+ if (wait) {
24
+ return `Gmail is briefly busy and will be ready again in about ${wait}.`;
25
+ }
26
+ return "Gmail is briefly busy. Try again in a moment.";
27
+ }
28
+
29
+ function classifyArchiveFailure(message: string): ArchiveFailureDetail {
30
+ const lower = message.toLowerCase();
31
+
32
+ if (
33
+ /\b(429|quota|rate limit|ratelimit|user-rate)\b/i.test(message) ||
34
+ lower.includes("briefly busy")
35
+ ) {
36
+ return { detail: quotaDetail(message), statusCode: 429 };
37
+ }
38
+
39
+ if (
40
+ lower.includes("invalid_grant") ||
41
+ lower.includes("invalid_client") ||
42
+ lower.includes("unauthorized_client") ||
43
+ lower.includes("oauth token refresh failed") ||
44
+ lower.includes("no valid access token") ||
45
+ /\b401\b/.test(lower)
46
+ ) {
47
+ return {
48
+ detail: "Gmail needs to be reconnected.",
49
+ statusCode: 409,
50
+ };
51
+ }
52
+
53
+ if (
54
+ lower.includes("insufficient_scope") ||
55
+ lower.includes("insufficient permissions") ||
56
+ lower.includes("forbidden") ||
57
+ lower.includes("gmail.modify") ||
58
+ /\b403\b/.test(lower)
59
+ ) {
60
+ return {
61
+ detail:
62
+ "this Google connection does not have permission to modify Gmail. Reconnect Gmail and grant Mail access.",
63
+ statusCode: 409,
64
+ };
65
+ }
66
+
67
+ if (lower.includes("no google account connected")) {
68
+ return {
69
+ detail: "no Google account is connected. Connect Gmail before archiving.",
70
+ statusCode: 409,
71
+ };
72
+ }
73
+
74
+ if (lower.includes("account not owned") || lower.includes("not connected")) {
75
+ return {
76
+ detail: "the Gmail account for this conversation is not connected.",
77
+ statusCode: 409,
78
+ };
79
+ }
80
+
81
+ if (
82
+ lower.includes("not found") ||
83
+ lower.includes("thread not found") ||
84
+ /\b404\b/.test(lower)
85
+ ) {
86
+ return {
87
+ detail:
88
+ "Gmail no longer has that conversation. Refresh Mail and try again.",
89
+ statusCode: 409,
90
+ };
91
+ }
92
+
93
+ return {
94
+ detail: "Gmail did not accept the change. Refresh Mail and try again.",
95
+ statusCode: 409,
96
+ };
97
+ }
98
+
99
+ export function summarizeArchiveFailures(input: {
100
+ succeeded: number;
101
+ total: number;
102
+ failures: string[];
103
+ }): ArchiveFailureMessage {
104
+ const firstFailure = input.failures.find((failure) => failure.trim()) ?? "";
105
+ const classified = classifyArchiveFailure(firstFailure);
106
+ const failedCount = Math.max(0, input.total - input.succeeded);
107
+
108
+ if (input.total <= 1) {
109
+ return {
110
+ message: `Archive failed because ${classified.detail}`,
111
+ statusCode: classified.statusCode,
112
+ };
113
+ }
114
+
115
+ return {
116
+ message: `Archived ${input.succeeded}/${input.total} conversations. Could not archive ${failedCount} because ${classified.detail}`,
117
+ statusCode: classified.statusCode,
118
+ };
119
+ }
120
+
121
+ export function archiveFailureToastMessage(
122
+ error: unknown,
123
+ fallback: string,
124
+ ): string {
125
+ const message = withoutActionPrefix(errorText(error));
126
+ if (!message || /^internal server error$/i.test(message)) return fallback;
127
+
128
+ if (/\bFailures:/i.test(message)) {
129
+ return summarizeArchiveFailures({
130
+ succeeded: 0,
131
+ total: 1,
132
+ failures: [message],
133
+ }).message;
134
+ }
135
+
136
+ return message;
137
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Hosted signup now shows how to switch `/visual-plan` to local files only.
@@ -1 +1 @@
1
- {"version":3,"file":"AgentPanel.d.ts","sourceRoot":"","sources":["../../src/client/AgentPanel.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AgentPanel.d.ts","sourceRoot":"","sources":["../../src/client/AgentPanel.tsx"],"names":[],"mappings":"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"}
@@ -314,6 +314,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
314
314
  /Mac|iPhone|iPad/.test(navigator.userAgent), []);
315
315
  const closeTabHint = isMac ? "\u2303W" : "Alt+W";
316
316
  const closeAllTabsHint = isMac ? "\u2303\u2325W" : "Ctrl+Alt+W";
317
+ const toggleSidebarHint = isMac ? "\u2318\\" : "Ctrl+\\";
317
318
  const [execMode, setExecMode] = useState(() => {
318
319
  try {
319
320
  const saved = localStorage.getItem(execModeKey);
@@ -538,7 +539,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
538
539
  const [headerMenuOpen, setHeaderMenuOpen] = useState(false);
539
540
  const [feedbackOpen, setFeedbackOpen] = useState(false);
540
541
  const renderHeaderActions = useCallback(({ activeChatSessionId, activeTabId, addTab, clearActiveTab, closeAllTabs, closeOtherTabs, closeTab, showHistory, tabs, toggleHistory, }) => (_jsxs("div", { className: "relative flex shrink-0 items-center gap-0.5", children: [SHOW_ONBOARDING && (_jsx(Suspense, { fallback: null, children: _jsx(SetupButton, {}) })), _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" }) }), mode === "chat" && (_jsx(IconTooltip, { content: t("agentPanel.newChat"), children: _jsx("button", { onClick: addTab, "aria-label": t("agentPanel.newChat"), className: "agent-sidebar-hover-reveal 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 }) }) })), mode === "cli" && canUseCodeTools && (_jsx(IconTooltip, { content: t("agentPanel.newTerminal"), children: _jsx("button", { onClick: addCliTab, "aria-label": t("agentPanel.newTerminal"), className: "agent-sidebar-hover-reveal 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") &&
541
- "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(DropdownMenuSeparator, {})] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: toggleHistory, children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
542
+ "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, {})] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: toggleHistory, children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
542
543
  ? t("agentPanel.hideChats")
543
544
  : t("agentPanel.allChats")] })), mode === "chat" && (_jsx(RunsTrayMenuItem, { pollMs: 2000, 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
544
545
  ? "font-medium"