@agent-native/core 0.180.0-nightly-20260915193344 → 0.180.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 (49) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  3. package/corpus/templates/calendar/CHANGELOG.md +39 -0
  4. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +2 -0
  5. package/corpus/templates/clips/CHANGELOG.md +19 -0
  6. package/corpus/templates/clips/actions/save-browser-diagnostics.ts +43 -4
  7. package/corpus/templates/clips/app/components/player/browser-diagnostics-panel.tsx +114 -1
  8. package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
  9. package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +108 -0
  10. package/corpus/templates/clips/app/routes/record.tsx +1 -0
  11. package/corpus/templates/clips/changelog/2026-09-15-recordings-include-a-structured-timeline-of-browser-interact.md +6 -0
  12. package/corpus/templates/clips/chrome-extension/public/manifest.json +7 -0
  13. package/corpus/templates/clips/chrome-extension/src/background.ts +235 -9
  14. package/corpus/templates/clips/chrome-extension/src/content-history-bridge.ts +80 -0
  15. package/corpus/templates/clips/chrome-extension/src/content-script.ts +166 -5
  16. package/corpus/templates/clips/chrome-extension/vite.config.ts +4 -0
  17. package/corpus/templates/clips/server/db/schema.ts +3 -0
  18. package/corpus/templates/clips/server/lib/public-agent-context.ts +40 -2
  19. package/corpus/templates/clips/server/plugins/db.ts +5 -0
  20. package/corpus/templates/clips/shared/browser-diagnostics.ts +171 -0
  21. package/corpus/templates/content/.agents/skills/content/SKILL.md +4 -2
  22. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +8 -1
  23. package/corpus/templates/content/AGENTS.md +5 -6
  24. package/corpus/templates/content/CHANGELOG.md +24 -0
  25. package/corpus/templates/content/actions/_document-edit-mutation.ts +74 -23
  26. package/corpus/templates/content/actions/edit-document.ts +48 -8
  27. package/corpus/templates/content/server/plugins/agent-chat.ts +1 -1
  28. package/corpus/templates/design/CHANGELOG.md +22 -0
  29. package/corpus/templates/factory/CHANGELOG.md +11 -0
  30. package/corpus/templates/forms/CHANGELOG.md +6 -0
  31. package/corpus/templates/mail/CHANGELOG.md +68 -0
  32. package/corpus/templates/slides/CHANGELOG.md +32 -0
  33. package/dist/automation/index.d.ts +1 -1
  34. package/dist/client/CommandMenu.js +14 -2
  35. package/dist/client/settings/AccountSettingsCard.js +3 -2
  36. package/dist/client/settings/SettingsTabsPage.js +9 -1
  37. package/dist/client/settings/agent-settings-search.js +5 -1
  38. package/dist/client/sign-out.d.ts +1 -0
  39. package/dist/client/sign-out.js +8 -0
  40. package/dist/collab/awareness.d.ts +2 -2
  41. package/dist/collab/routes.d.ts +1 -1
  42. package/dist/collab/struct-routes.d.ts +1 -1
  43. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  44. package/dist/notifications/routes.d.ts +3 -3
  45. package/dist/observability/routes.d.ts +3 -3
  46. package/dist/progress/routes.d.ts +1 -1
  47. package/dist/secrets/routes.d.ts +6 -6
  48. package/dist/server/core-routes-plugin.d.ts +1 -1
  49. package/package.json +4 -4
@@ -64,7 +64,8 @@ function validateDocumentEditSnapshot(
64
64
  document: typeof schema.documents.$inferSelect | undefined,
65
65
  base: { revision: number; contentHash: string },
66
66
  baseRevision: string,
67
- edits: DocumentTextEdit[],
67
+ edits: DocumentTextEdit[] | undefined,
68
+ initializeContent?: string,
68
69
  ) {
69
70
  if (!document) {
70
71
  throw new ActionContractError("Document not found.", {
@@ -88,7 +89,19 @@ function validateDocumentEditSnapshot(
88
89
  currentContentHash: beforeHash,
89
90
  });
90
91
  }
91
- const resolved = resolveDocumentTextEdits(beforeContent, edits);
92
+ const resolved = edits
93
+ ? resolveDocumentTextEdits(beforeContent, edits)
94
+ : beforeContent === ""
95
+ ? {
96
+ ok: true as const,
97
+ content: initializeContent as string,
98
+ ranges: [{ start: 0, end: 0 }],
99
+ }
100
+ : conflict(
101
+ "DOCUMENT_BODY_NOT_EMPTY",
102
+ "Document initialization requires a literally empty body.",
103
+ { currentRevision: baseRevision },
104
+ );
92
105
  if (!resolved.ok) {
93
106
  conflict(
94
107
  resolved.error.kind === "missing"
@@ -178,19 +191,24 @@ export interface DocumentEditCreativeContext {
178
191
  }>;
179
192
  }
180
193
 
181
- export async function mutateDocumentBody(args: {
182
- documentId: string;
183
- baseRevision: string;
184
- idempotencyKey: string;
185
- edits: DocumentTextEdit[];
186
- creativeContext?: DocumentEditCreativeContext;
187
- creativeContextDigest?: unknown;
188
- resolveCreativeContext?: () => Promise<
189
- DocumentEditCreativeContext | undefined
190
- >;
191
- ctx: ActionRunContext;
192
- db?: Db;
193
- }): Promise<DocumentEditMutationResult> {
194
+ type DocumentBodyMutation =
195
+ | { edits: DocumentTextEdit[]; initializeContent?: never }
196
+ | { edits?: never; initializeContent: string };
197
+
198
+ export async function mutateDocumentBody(
199
+ args: {
200
+ documentId: string;
201
+ baseRevision: string;
202
+ idempotencyKey: string;
203
+ creativeContext?: DocumentEditCreativeContext;
204
+ creativeContextDigest?: unknown;
205
+ resolveCreativeContext?: () => Promise<
206
+ DocumentEditCreativeContext | undefined
207
+ >;
208
+ ctx: ActionRunContext;
209
+ db?: Db;
210
+ } & DocumentBodyMutation,
211
+ ): Promise<DocumentEditMutationResult> {
194
212
  const db = args.db ?? getDb();
195
213
  const scope = callerScope(args.ctx);
196
214
  const base = parseDocumentRevisionToken(args.baseRevision);
@@ -203,16 +221,47 @@ export async function mutateDocumentBody(args: {
203
221
  },
204
222
  );
205
223
  }
206
- const normalizedEdits = args.edits.map(({ find, replace }) => ({
224
+ const normalizedEdits = args.edits?.map(({ find, replace }) => ({
207
225
  find,
208
226
  replace,
209
227
  }));
210
- const payloadDigest = digest({
211
- documentId: args.documentId,
212
- baseRevision: args.baseRevision,
213
- edits: normalizedEdits,
214
- creativeContext: args.creativeContextDigest ?? args.creativeContext ?? null,
215
- });
228
+ const initializationContent = args.initializeContent;
229
+ if (!normalizedEdits && initializationContent === undefined) {
230
+ throw new ActionContractError("A document body mutation is required.", {
231
+ errorCode: "DOCUMENT_EDIT_MODE_REQUIRED",
232
+ statusCode: 400,
233
+ });
234
+ }
235
+ if (
236
+ initializationContent !== undefined &&
237
+ initializationContent.trim().length === 0
238
+ ) {
239
+ throw new ActionContractError(
240
+ "initializeContent must contain non-whitespace content.",
241
+ {
242
+ errorCode: "DOCUMENT_INITIALIZATION_CONTENT_REQUIRED",
243
+ statusCode: 400,
244
+ },
245
+ );
246
+ }
247
+ const payloadDigest = digest(
248
+ normalizedEdits
249
+ ? {
250
+ documentId: args.documentId,
251
+ baseRevision: args.baseRevision,
252
+ edits: normalizedEdits,
253
+ creativeContext:
254
+ args.creativeContextDigest ?? args.creativeContext ?? null,
255
+ }
256
+ : {
257
+ documentId: args.documentId,
258
+ baseRevision: args.baseRevision,
259
+ operation: "initialize",
260
+ initializeContent: initializationContent,
261
+ creativeContext:
262
+ args.creativeContextDigest ?? args.creativeContext ?? null,
263
+ },
264
+ );
216
265
 
217
266
  const readReplay = async (client: Db) => {
218
267
  const [stored] = await client
@@ -250,6 +299,7 @@ export async function mutateDocumentBody(args: {
250
299
  base,
251
300
  args.baseRevision,
252
301
  normalizedEdits,
302
+ initializationContent,
253
303
  );
254
304
  const creativeContext = args.resolveCreativeContext
255
305
  ? await args.resolveCreativeContext()
@@ -274,6 +324,7 @@ export async function mutateDocumentBody(args: {
274
324
  base,
275
325
  args.baseRevision,
276
326
  normalizedEdits,
327
+ initializationContent,
277
328
  );
278
329
  const { beforeContent, beforeHash, resolved } = validated;
279
330
 
@@ -352,7 +403,7 @@ export async function mutateDocumentBody(args: {
352
403
  }));
353
404
  const result: DocumentEditMutationResult = {
354
405
  applied: changed ? resolved.ranges.length : 0,
355
- total: normalizedEdits.length,
406
+ total: normalizedEdits?.length ?? 1,
356
407
  receipt: {
357
408
  receiptId,
358
409
  outcome: changed ? "applied" : "unchanged",
@@ -96,6 +96,16 @@ const editDocumentSchema = z.object({
96
96
  .describe(
97
97
  "JSON array of {find, replace} objects for a snapshot-stable batch; use instead of find/replace.",
98
98
  ),
99
+ initializeContent: z
100
+ .string()
101
+ .min(1)
102
+ .refine((value) => value.trim().length > 0, {
103
+ message: "initializeContent must contain non-whitespace content.",
104
+ })
105
+ .optional()
106
+ .describe(
107
+ "Exact Markdown containing non-whitespace content, used only to initialize a literally empty document body; mutually exclusive with find, replace, and edits.",
108
+ ),
99
109
  contextPackId: z
100
110
  .string()
101
111
  .optional()
@@ -207,7 +217,7 @@ async function resolveEditCreativeContext(args: {
207
217
 
208
218
  export default defineAction({
209
219
  description:
210
- "Surgically edit an existing document's Markdown with exact search-and-replace operations. Prefer this over update-document when preserving the rest of the document; every find string must match exactly once in the immutable base. First call get-document, then pass its baseRevision and a caller-generated idempotencyKey.",
220
+ "Edit an existing document's Markdown with exact search-and-replace operations, or initialize a literally empty body with initializeContent. Every find string must match exactly once in the immutable base. First call get-document, then pass its baseRevision and a caller-generated idempotencyKey.",
211
221
  deferLoading: false,
212
222
  mcpTool: true,
213
223
  agentInputSchema: externalEditDocumentSchema,
@@ -223,9 +233,24 @@ export default defineAction({
223
233
  const isAgentCaller =
224
234
  ctx?.caller === "tool" || ctx?.caller === "mcp" || ctx?.caller === "a2a";
225
235
 
226
- let edits: TextEdit[];
236
+ let edits: TextEdit[] = [];
237
+ const initializesBody = args.initializeContent !== undefined;
227
238
 
228
- if (Array.isArray(args.edits)) {
239
+ if (
240
+ initializesBody &&
241
+ (args.find !== undefined ||
242
+ args.replace !== undefined ||
243
+ args.edits !== undefined)
244
+ ) {
245
+ throw new ActionContractError(
246
+ "initializeContent is mutually exclusive with find, replace, and edits.",
247
+ { errorCode: "DOCUMENT_EDIT_MODE_CONFLICT", statusCode: 400 },
248
+ );
249
+ }
250
+
251
+ if (initializesBody) {
252
+ edits = [];
253
+ } else if (Array.isArray(args.edits)) {
229
254
  edits = args.edits;
230
255
  } else if (args.edits !== undefined) {
231
256
  throw new Error("--edits must be a JSON array");
@@ -233,7 +258,10 @@ export default defineAction({
233
258
  if (!args.find) throw new Error("--find cannot be empty");
234
259
  edits = [{ find: args.find, replace: args.replace ?? "" }];
235
260
  } else {
236
- throw new Error("Either --find or --edits is required");
261
+ throw new ActionContractError(
262
+ "One of initializeContent, find, or edits is required.",
263
+ { errorCode: "DOCUMENT_EDIT_MODE_REQUIRED", statusCode: 400 },
264
+ );
237
265
  }
238
266
 
239
267
  const access = await assertAccess("document", id, "editor");
@@ -243,13 +271,19 @@ export default defineAction({
243
271
  ctx?.caller === "mcp" ||
244
272
  ctx?.caller === "webmcp" ||
245
273
  ctx?.caller === "a2a";
246
- if (isExternalCaller) {
274
+ if (isExternalCaller || initializesBody) {
247
275
  if (!args.baseRevision || !args.idempotencyKey) {
248
276
  throw new ActionContractError(
249
277
  "External document edits require baseRevision and idempotencyKey from get-document.",
250
278
  { errorCode: "DOCUMENT_EDIT_PROTOCOL_REQUIRED", statusCode: 400 },
251
279
  );
252
280
  }
281
+ if (!ctx) {
282
+ throw new ActionContractError(
283
+ "Revisioned document edits require an authenticated caller context.",
284
+ { errorCode: "CALLER_SCOPE_REQUIRED", statusCode: 401 },
285
+ );
286
+ }
253
287
  const isLinkedLocalSource =
254
288
  existing.sourceMode === "local-files" &&
255
289
  existing.sourceKind !== "folder" &&
@@ -265,11 +299,14 @@ export default defineAction({
265
299
  },
266
300
  );
267
301
  }
302
+ const mutation = initializesBody
303
+ ? { initializeContent: args.initializeContent as string }
304
+ : { edits };
268
305
  const result = await mutateDocumentBody({
269
306
  documentId: id,
270
307
  baseRevision: args.baseRevision,
271
308
  idempotencyKey: args.idempotencyKey,
272
- edits,
309
+ ...mutation,
273
310
  creativeContextDigest: {
274
311
  contextPackId: args.contextPackId ?? null,
275
312
  contextModeOverride: args.contextModeOverride ?? null,
@@ -290,7 +327,10 @@ export default defineAction({
290
327
  edit: {
291
328
  descriptor: {
292
329
  kind: "text",
293
- quote: edits[0]?.replace.slice(0, 80) ?? "",
330
+ quote:
331
+ args.initializeContent?.slice(0, 80) ??
332
+ edits?.[0]?.replace.slice(0, 80) ??
333
+ "",
294
334
  },
295
335
  label: existing.title || undefined,
296
336
  },
@@ -307,7 +347,7 @@ export default defineAction({
307
347
  output_id: id,
308
348
  output_type: "document",
309
349
  edit_count: result.applied,
310
- refine_type: "exact_replace",
350
+ refine_type: initializesBody ? "full_update" : "exact_replace",
311
351
  },
312
352
  ctx,
313
353
  );
@@ -32,7 +32,7 @@ export default createAgentChatPlugin({
32
32
  mcp: {
33
33
  externalAgents: { writes: "allowlisted" },
34
34
  instructions:
35
- "Find documents with list-documents or search-documents; read one with get-document (pull-document for the raw markdown you will edit). Author content yourself and persist it with create-document, or edit-document for a targeted change and update-document for a full replace. For Notion or other provider data use provider-api-catalog → provider-api-docs → provider-api-request instead of guessing endpoints.",
35
+ "Find documents with list-documents or search-documents; read with get-document (pull-document for raw Markdown). Author and persist content with create-document. For body changes use revision-guarded edit-document; pass initializeContent only when get-document returns an empty body. Use update-document for metadata and browser rewrites. For provider data use provider-api-catalog → provider-api-docs → provider-api-request.",
36
36
  },
37
37
  anonymousOwner: resolvePublicViewerOwner,
38
38
  extraContext: publicDocumentExtraContext,
@@ -9,8 +9,30 @@ from the command menu (Cmd+K → "What's new") or from Settings.
9
9
 
10
10
  - A message that only mentions a design, or pastes Design's own guidance back into chat, no longer ends with a false "couldn't confirm that a Design artifact was saved" reply.
11
11
 
12
+ ## 2026-09-14
13
+
14
+ ### Added
15
+
16
+ - Component duplicates stay linked to their main across Screens, and detaching keeps their current appearance
17
+
18
+ ### Fixed
19
+
20
+ - Rapid layout and padding edits preserve previously committed styles.
21
+ - Screen creation preserves drawn dimensions and layer identity for immediate visual edits.
22
+ - Selecting an element in a live React app now anchors to the authored component file and owner instead of the JSX runtime, so source edits and repeated-instance counts target the real code
23
+ - Undo and redo follow the order of edits across screens.
24
+ - Zoom to selection includes selected layers across Screens and the board, and multiline text stays a single Text layer.
25
+
26
+ ### Changed
27
+
28
+ - Creative Context is now in Labs and off by default
29
+
12
30
  ## 2026-09-13
13
31
 
32
+ ### Improved
33
+
34
+ - Screen layout, style edits, keyboard shortcuts, and edits to AI-generated designs work more reliably through undo and reload
35
+
14
36
  ### Fixed
15
37
 
16
38
  - Connect Code imports explain when a design-system name is already in use
@@ -3,6 +3,17 @@
3
3
  All notable user-facing changes to Chat are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-14
7
+
8
+ ### Improved
9
+
10
+ - Factory PR babysit uses a fair queue so re-queued pull requests are not starved, re-prioritizes open PRs when new review work arrives without a commit, and posts a v2 ping comment with per-thread disposition guidance.
11
+ - PR babysit now terminalizes merged and closed pull requests instead of re-listing them with "Seen before".
12
+
13
+ ### Fixed
14
+
15
+ - Factory automations keep display names, Slack channels, and custom prompts through metadata repair, tab switches, and accidental clears.
16
+
6
17
  ## 2026-09-11
7
18
 
8
19
  ### Improved
@@ -3,6 +3,12 @@
3
3
  All notable user-facing changes to Agent-Native Forms are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-15
7
+
8
+ ### Fixed
9
+
10
+ - Asking the agent for a second, unrelated form now creates a separate form instead of overwriting the one already open in the chat.
11
+
6
12
  ## 2026-09-12
7
13
 
8
14
  ### Fixed
@@ -3,6 +3,74 @@
3
3
  All notable user-facing changes to Agent-Native Mail are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-15
7
+
8
+ ### Improved
9
+
10
+ - Mail labels recipient and account controls for keyboard and assistive-technology parity.
11
+ - Match Superhuman's compact compose card geometry on desktop
12
+
13
+ ### Fixed
14
+
15
+ - New drafts focus the To field immediately for keyboard-first composing
16
+
17
+ ## 2026-09-14
18
+
19
+ ### Added
20
+
21
+ - Find implemented keyboard shortcuts by context from the Command menu.
22
+
23
+ ### Improved
24
+
25
+ - Choose a send time by typing a date or time in natural language.
26
+ - Keyboard navigation keeps recipient suggestions visible
27
+ - Mail Trash shortcuts now also support #
28
+ - Make Mail search clear buttons keyboard-activatable while preserving input focus
29
+ - New and reopened drafts open in a compact compose card, with fullscreen opt-in.
30
+ - Replies can be sent and marked Done with a shortcut or preference
31
+ - Screen readers can identify Mail's Search and clear controls.
32
+ - Send Later and Send + Mark Done are available in the compose command palette
33
+
34
+ ### Fixed
35
+
36
+ - Canceled inbox swipes no longer suppress the next tap on a message.
37
+ - Expired multi-key shortcuts no longer fire after a layout refresh.
38
+ - Mail move actions stay on the selected mailbox account
39
+ - Mail now prompts you to finish or clear recipient text before sending or scheduling.
40
+ - Mail triage actions stay on the selected mailbox account
41
+ - Recipient autocomplete resets its highlighted suggestion when the search query changes.
42
+ - Search works on keyboard layouts that require Shift to type the slash key.
43
+ - Starting another draft brings it into view even when an existing draft is minimized.
44
+ - The message-list A shortcut now opens a Reply All draft
45
+ - Forward drafts now retain the original message attachments
46
+ - Undo for archive and trash actions now targets only the latest operation and expires after 10 seconds.
47
+
48
+ ## 2026-09-13
49
+
50
+ ### Added
51
+
52
+ - Compose can suggest common phrases on desktop when autocomplete is enabled
53
+
54
+ ### Improved
55
+
56
+ - Escape clears the command search before closing the palette and returns focus to the control that opened it.
57
+ - Tab stays in compose fields, and a shortcut opens Bcc directly.
58
+
59
+ ### Fixed
60
+
61
+ - All Mail and Archive now show their correct keyboard shortcuts in the command palette.
62
+ - Drafts and sent messages now use the selected Gmail account.
63
+ - Fixed Mail to keep scheduled sends bound to the selected account, validate recipients, and report incomplete account and move operations.
64
+ - G+A now opens All Mail instead of Archive.
65
+ - Gmail drafts report account refresh failures clearly, and multi-account inbox sync avoids repeated lookups.
66
+ - Harden scheduled sends and mixed-account moves; localize move confirmations.
67
+ - Mail chooses a usable connected account when the default mailbox cannot refresh
68
+ - Mail fetches labels from a managed Gmail account when its cache is empty alongside OAuth accounts
69
+ - Mail no longer shows a failed OAuth account as connected through a same-address workspace grant.
70
+ - Mail reports Gmail account read failures instead of showing incomplete results as empty.
71
+ - Search and recipient autocomplete keep keyboard selection aligned with current suggestions
72
+ - Search opens and focuses from the command menu
73
+
6
74
  ## 2026-09-12
7
75
 
8
76
  ### Improved
@@ -3,6 +3,38 @@
3
3
  All notable user-facing changes to Agent-Native Slides are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-14
7
+
8
+ ### Improved
9
+
10
+ - Deck filter button now shows when the list is limited to your own decks.
11
+ - Slides comments can now target slide objects and positions with full thread actions, reactions, and Google-style shortcut behavior.
12
+
13
+ ### Fixed
14
+
15
+ - An attached PDF, PowerPoint, or Word reference is now read before a new deck is generated, and its typography, palette, and page proportions steer the design instead of being silently dropped
16
+ - Creating a design system no longer fails silently when Builder indexing is unavailable.
17
+ - Design system creation from onboarding now opens inline instead of a new tab, so onboarding progress is no longer lost
18
+ - Every icon in the collapsed left sidebar now shows its name on hover
19
+ - Sending a selection, drawing, or pending update to the agent now opens the agent panel instead of appearing to do nothing.
20
+ - Style requests like matching one slide's background across the deck now succeed instead of failing after repeated rejected edits.
21
+ - Switching organizations now shows only decks from the current organization
22
+
23
+ ### Changed
24
+
25
+ - Creative Context is now in Labs and off by default
26
+
27
+ ## 2026-09-13
28
+
29
+ ### Improved
30
+
31
+ - Control+M adds a slide with the current slide's layout.
32
+
33
+ ### Fixed
34
+
35
+ - Slides support keyboard navigation, selection, and reordering in the slide rail.
36
+ - Slides route navigation, selection, clipboard, and reorder shortcuts through the focused editor surface.
37
+
6
38
  ## 2026-09-12
7
39
 
8
40
  ### Fixed
@@ -186,8 +186,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
186
186
  duplicate?: undefined;
187
187
  eventId?: undefined;
188
188
  } | {
189
- error?: undefined;
190
189
  accepted: boolean;
191
190
  duplicate: boolean;
192
191
  eventId: string;
192
+ error?: undefined;
193
193
  }>;
@@ -16,7 +16,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
16
16
  * </CommandMenu>
17
17
  */
18
18
  import { Command as CommandPrimitive, CommandGroup as CommandGroupPrimitive, CommandInput as CommandInputPrimitive, CommandItem as CommandItemPrimitive, CommandList as CommandListPrimitive, CommandSeparator as CommandSeparatorPrimitive, CommandShortcut as CommandShortcutPrimitive, } from "@agent-native/toolkit/ui/command";
19
- import { IconBook2, IconExternalLink, IconInfoCircle, IconMessage, IconHistory, } from "@tabler/icons-react";
19
+ import { IconBook2, IconExternalLink, IconInfoCircle, IconMessage, IconHistory, IconLogout, } from "@tabler/icons-react";
20
20
  import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
21
21
  import { parseChangelog } from "../changelog/parse.js";
22
22
  import { AboutAgentNativeDialog } from "./AboutAgentNativeDialog.js";
@@ -24,6 +24,7 @@ import { sendToAgentChat } from "./agent-chat.js";
24
24
  import { ChangelogDialog, useChangelogSeen } from "./changelog/Changelog.js";
25
25
  import { Dialog, DialogContent, DialogTitle } from "./components/ui/dialog.js";
26
26
  import { useT } from "./i18n.js";
27
+ import { signOut, SIGN_OUT_SEARCH_TERMS } from "./sign-out.js";
27
28
  import { cn } from "./utils.js";
28
29
  const CommandMenuContext = createContext(null);
29
30
  function useCommandMenuContext() {
@@ -235,6 +236,16 @@ export function CommandMenu({ open, onOpenChange, children, renderResults, rende
235
236
  .toLowerCase()
236
237
  .includes(search.toLowerCase());
237
238
  const showAboutRow = showAbout && aboutRowMatches;
239
+ const signOutLabel = t("agentChat.auth.logOut");
240
+ const showSignOutRow = !search ||
241
+ [signOutLabel, ...SIGN_OUT_SEARCH_TERMS]
242
+ .join(" ")
243
+ .toLowerCase()
244
+ .includes(search.toLowerCase());
245
+ const handleSignOut = useCallback(() => {
246
+ onOpenChange(false);
247
+ void signOut();
248
+ }, [onOpenChange]);
238
249
  // Filter children based on search
239
250
  const filterChildren = (nodes) => {
240
251
  return React.Children.map(nodes, (child) => {
@@ -298,9 +309,10 @@ export function CommandMenu({ open, onOpenChange, children, renderResults, rende
298
309
  const hasResults = React.Children.toArray(filteredChildren).some((child) => React.isValidElement(child) &&
299
310
  (child.type === CommandGroup || child.type === CommandDocsGroup));
300
311
  const dynamicResults = open ? renderResults?.(search) : null;
301
- const renderList = (results = dynamicResults) => (_jsxs(CommandListPrimitive, { children: [results, hasResults && filteredChildren, showChangelogRow && (_jsxs(_Fragment, { children: [hasResults && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openChangelog, children: [_jsx(IconHistory, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: changelogLabel }), changelogUnseen && (_jsx("span", { className: "ms-auto h-2 w-2 rounded-full bg-primary", "aria-label": "New updates available" }))] }) })] })), showAboutRow && (_jsxs(_Fragment, { children: [(hasResults || showChangelogRow) && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openAbout, children: [_jsx(IconInfoCircle, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: aboutLabel })] }) })] })), showAgentFallback && (_jsxs(_Fragment, { children: [(hasResults ||
312
+ const renderList = (results = dynamicResults) => (_jsxs(CommandListPrimitive, { children: [results, hasResults && filteredChildren, showChangelogRow && (_jsxs(_Fragment, { children: [hasResults && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openChangelog, children: [_jsx(IconHistory, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: changelogLabel }), changelogUnseen && (_jsx("span", { className: "ms-auto h-2 w-2 rounded-full bg-primary", "aria-label": "New updates available" }))] }) })] })), showAboutRow && (_jsxs(_Fragment, { children: [(hasResults || showChangelogRow) && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openAbout, children: [_jsx(IconInfoCircle, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: aboutLabel })] }) })] })), showSignOutRow && (_jsxs(_Fragment, { children: [(hasResults || showChangelogRow || showAboutRow) && (_jsx(CommandSeparator, {})), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: handleSignOut, children: [_jsx(IconLogout, { className: "h-4 w-4 text-muted-foreground rtl:-scale-x-100" }), _jsx("span", { children: signOutLabel })] }) })] })), showAgentFallback && (_jsxs(_Fragment, { children: [(hasResults ||
302
313
  showChangelogRow ||
303
314
  showAboutRow ||
315
+ showSignOutRow ||
304
316
  Boolean(results)) && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: handleSubmitToAgent, children: [_jsx(IconMessage, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: search.trim() ? (_jsxs(_Fragment, { children: ["Ask AI:", " ", _jsxs("span", { className: "text-muted-foreground", children: ["\"", search, "\""] })] })) : (_jsx("span", { className: "text-muted-foreground", children: "Ask AI anything..." })) }), search.trim() && (_jsx("span", { className: "ms-auto text-xs text-muted-foreground", children: "\u21B5" }))] }) })] }))] }));
305
317
  return (_jsxs(_Fragment, { children: [_jsx(Dialog, { open: open, onOpenChange: (nextOpen) => {
306
318
  if (!nextOpen && nestedDialogsRef.current.length > 0)
@@ -1,12 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ActionButton, Avatar, IconButton, TextField, } from "@agent-native/toolkit/design-system";
3
- import { IconCamera, IconCheck, IconLock, IconPencil, } from "@tabler/icons-react";
3
+ import { IconCamera, IconCheck, IconLock, IconLogout, IconPencil, } from "@tabler/icons-react";
4
4
  import { useEffect, useRef, useState } from "react";
5
5
  import { PASSWORD_MIN_LENGTH } from "../../shared/password-policy.js";
6
6
  import { agentNativePath } from "../api-path.js";
7
7
  import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
8
8
  import { Tooltip, TooltipContent, TooltipTrigger, } from "../components/ui/tooltip.js";
9
9
  import { useT } from "../i18n.js";
10
+ import { signOut } from "../sign-out.js";
10
11
  import { useActionMutation, useActionQuery } from "../use-action.js";
11
12
  import { uploadAvatar, useAvatarUrl } from "../use-avatar.js";
12
13
  import { useSession } from "../use-session.js";
@@ -236,7 +237,7 @@ export function AccountSettingsForm({ compact = false, }) {
236
237
  ? t("settings.profileSaving")
237
238
  : t("settings.profileSave") })] })) : (_jsxs("div", { className: "flex w-full items-center justify-end gap-2 sm:w-80", children: [_jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-foreground", children: displayName }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(IconButton, { type: "button", intent: "neutral", emphasis: "ghost", size: "compact", icon: _jsx(IconPencil, { size: 14 }), label: t("settings.profileNameEdit"), title: t("settings.profileNameEdit"), disabled: !email, onPress: handleProfileEdit }) }), _jsx(TooltipContent, { children: t("settings.profileNameEdit") })] })] })) }), _jsx(SettingsRow, { id: "timezone", label: t("settings.timezoneLabel", { defaultValue: "Timezone" }), description: t("settings.timezoneHint", {
238
239
  defaultValue: "Used for timestamps and scheduled automations.",
239
- }), control: _jsx(SchedulingTimezoneField, { compact: true }) }), _jsx(PasswordSettings, {})] }));
240
+ }), control: _jsx(SchedulingTimezoneField, { compact: true }) }), _jsx(PasswordSettings, {}), _jsx(SettingsRow, { id: "sign-out", label: t("agentChat.auth.logOut"), icon: _jsx(IconLogout, { className: "size-4" }), control: _jsx(ActionButton, { type: "button", intent: "danger", emphasis: "outline", size: "compact", onPress: () => void signOut(), children: t("agentChat.auth.logOut") }) })] }));
240
241
  }
241
242
  export function AccountSettingsCard({ className }) {
242
243
  return (_jsx("div", { className: cn("w-full", className), children: _jsx(AccountSettingsForm, {}) }));
@@ -5,7 +5,9 @@ import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
5
5
  import { Link, useInRouterContext, useLocation } from "react-router";
6
6
  import { appMountPath, appMountedPath } from "../../client/api-path.js";
7
7
  import { buildSettingsRoute, STANDARD_APP_ROUTES, } from "../../navigation/index.js";
8
+ import { useT } from "../i18n.js";
8
9
  import { LabsSettings } from "../labs/LabsSettings.js";
10
+ import { SIGN_OUT_SEARCH_TERMS } from "../sign-out.js";
9
11
  import { cn } from "../utils.js";
10
12
  function normalizeTabId(value) {
11
13
  let decoded = value?.replace(/^#/, "").trim() ?? "";
@@ -175,6 +177,7 @@ function SettingsTabsPageContent({ general, account, team, whatsNew, extraTabs =
175
177
  const searchInputRef = useRef(null);
176
178
  const autoFocusedSearchRef = useRef(false);
177
179
  const controlledHashRef = useRef(null);
180
+ const t = useT();
178
181
  const tabs = useMemo(() => {
179
182
  const hasOrganizationTab = extraTabs.some((tab) => tab.id === "organization");
180
183
  const inlineTabs = extraTabs.filter((tab) => !tab.href);
@@ -194,7 +197,11 @@ function SettingsTabsPageContent({ general, account, team, whatsNew, extraTabs =
194
197
  label: accountLabel,
195
198
  icon: IconUserCircle,
196
199
  content: account,
197
- keywords: "profile photo avatar identity signed in email name",
200
+ keywords: [
201
+ "profile photo avatar identity signed in email name",
202
+ ...SIGN_OUT_SEARCH_TERMS,
203
+ t("agentChat.auth.logOut"),
204
+ ].join(" "),
198
205
  });
199
206
  }
200
207
  next.push(...inlineTabs);
@@ -248,6 +255,7 @@ function SettingsTabsPageContent({ general, account, team, whatsNew, extraTabs =
248
255
  teamLabel,
249
256
  whatsNew,
250
257
  whatsNewLabel,
258
+ t,
251
259
  ]);
252
260
  const fallbackTab = tabs.some((tab) => tab.id === defaultTab)
253
261
  ? defaultTab
@@ -1,4 +1,5 @@
1
1
  import { mcpSettingsMessagesForLocale } from "../../localization/mcp-settings-messages.js";
2
+ import { SIGN_OUT_SEARCH_TERMS } from "../sign-out.js";
2
3
  export const SETTINGS_SECTION_IDS = new Set([
3
4
  "account",
4
5
  "llm",
@@ -61,7 +62,10 @@ export const WORKSPACE_SETTINGS_SECTIONS = [
61
62
  const SETTINGS_SECTION_SEARCH_META = {
62
63
  account: {
63
64
  label: "Account",
64
- keywords: "profile photo avatar identity signed in email name timezone time zone schedule scheduling clock",
65
+ keywords: [
66
+ "profile photo avatar identity signed in email name timezone time zone schedule scheduling clock",
67
+ ...SIGN_OUT_SEARCH_TERMS,
68
+ ].join(" "),
65
69
  },
66
70
  llm: {
67
71
  label: "LLM",
@@ -1,3 +1,4 @@
1
+ export declare const SIGN_OUT_SEARCH_TERMS: readonly ["log out", "log-out", "logout", "sign out", "sign-out", "signout"];
1
2
  export interface SignOutOptions {
2
3
  /**
3
4
  * Where to send the browser once the session is revoked. Defaults to the
@@ -27,6 +27,14 @@ import { buildSignInReturnHref } from "./require-session.js";
27
27
  import { beginSignOut, completeSignOut } from "./use-session.js";
28
28
  const LOGOUT_PATH = "/_agent-native/auth/logout";
29
29
  const SIGN_OUT_REQUEST_TIMEOUT_MS = 15_000;
30
+ export const SIGN_OUT_SEARCH_TERMS = [
31
+ "log out",
32
+ "log-out",
33
+ "logout",
34
+ "sign out",
35
+ "sign-out",
36
+ "signout",
37
+ ];
30
38
  let signOutOperation = null;
31
39
  function setBetaRedirectSignOutSignal() {
32
40
  const signOutWindow = window;
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- ok?: undefined;
30
29
  error: string;
30
+ ok?: undefined;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;