@agent-native/core 0.161.10 → 0.161.14

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/actions/clear-selected-dashboard-object.ts +71 -0
  3. package/corpus/templates/analytics/actions/view-screen.ts +68 -4
  4. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +41 -66
  5. package/corpus/templates/analytics/app/lib/selected-object.ts +53 -0
  6. package/corpus/templates/analytics/app/pages/Ask.tsx +21 -0
  7. package/corpus/templates/analytics/changelog/2026-08-18-ask-no-stale-dashboard-context.md +6 -0
  8. package/corpus/templates/calendar/app/root.tsx +12 -4
  9. package/corpus/templates/calendar/changelog/2026-08-18-calendar-stays-responsive-in-the-desktop-app-without-focus-t.md +6 -0
  10. package/corpus/templates/clips/app/components/library/recording-card.tsx +19 -3
  11. package/corpus/templates/clips/changelog/2026-08-18-deleting-a-recording-no-longer-freezes-clips.md +6 -0
  12. package/corpus/templates/clips/desktop/src/app.tsx +9 -10
  13. package/corpus/templates/clips/desktop/src/lib/recorder.ts +4 -3
  14. package/corpus/templates/clips/desktop/src/lib/recording-start-guard.ts +26 -0
  15. package/corpus/templates/content/actions/export-document.ts +68 -3
  16. package/corpus/templates/content/changelog/2026-08-06-database-exports-now-include-their-pages.md +6 -0
  17. package/corpus/templates/content/shared/document-export.ts +25 -0
  18. package/corpus/templates/design/.agents/skills/creative-context/SKILL.md +3 -0
  19. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +66 -0
  20. package/corpus/templates/design/app/pages/Index.tsx +45 -1
  21. package/corpus/templates/design/app/pages/design-editor/creative-context-precedent.ts +233 -0
  22. package/corpus/templates/design/changelog/2026-08-11-new-designs-now-skip-the-intake-questions-and-follow-your-ex.md +6 -0
  23. package/corpus/templates/dispatch/app/chat-first-pages.ts +40 -40
  24. package/corpus/templates/dispatch/app/i18n-data.ts +5 -5
  25. package/corpus/templates/factory/AGENTS.md +30 -29
  26. package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +56 -9
  27. package/dist/action.js +68 -2
  28. package/dist/agent/chat-health-alert.js +10 -1
  29. package/dist/client/AssistantChat.js +1 -0
  30. package/dist/client/chat-first/app-open-actions.js +1 -1
  31. package/dist/client/chat-first/copy.js +2 -2
  32. package/dist/client/conversation/use-near-bottom-autoscroll.js +34 -4
  33. package/dist/client/embed-auth.js +40 -3
  34. package/dist/client/mcp-apps/McpAppRenderer.js +7 -2
  35. package/dist/client/route-state.js +42 -5
  36. package/dist/client/use-change-version.js +71 -25
  37. package/dist/client/use-db-sync.d.ts +1 -0
  38. package/dist/client/use-db-sync.js +223 -45
  39. package/dist/collab/awareness.js +37 -4
  40. package/dist/collab/client.js +17 -1
  41. package/dist/collab/struct-routes.d.ts +1 -1
  42. package/dist/mcp/embed-app.js +71 -2
  43. package/dist/notifications/routes.d.ts +1 -1
  44. package/dist/observability/routes.d.ts +3 -3
  45. package/dist/secrets/routes.d.ts +9 -9
  46. package/dist/server/poll.d.ts +22 -14
  47. package/dist/server/poll.js +243 -67
  48. package/dist/styles/agent-conversation.css +25 -4
  49. package/package.json +1 -1
@@ -8,8 +8,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
8
8
  searchAppsPlaceholder: "依名稱或描述搜尋",
9
9
  clearAppSearch: "清除搜尋",
10
10
  noAppsMatch: "沒有符合搜尋條件的應用程式",
11
- pinApp: "釘選 {{name}}",
12
- unpinApp: "取消釘選 {{name}}",
11
+ pinApp: "釘選此應用程式",
12
+ unpinApp: "取消釘選此應用程式",
13
13
  appPinSaveFailed: "無法儲存應用程式釘選。",
14
14
  appPinSavedLocally: "應用程式釘選只儲存在此裝置上。",
15
15
  chatFirstCreateWorkspaceApp: "建立工作區應用程式",
@@ -18,8 +18,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
18
18
  chatFirstNoWorkspaceApps: "尚無工作區應用程式。",
19
19
  chatFirstCreateApp: "建立應用程式",
20
20
  chatFirstRetry: "重試",
21
- chatFirstUnpinApp: "取消釘選 {{name}}",
22
- chatFirstPinApp: "釘選 {{name}}",
21
+ chatFirstUnpinApp: "取消釘選此應用程式",
22
+ chatFirstPinApp: "釘選此應用程式",
23
23
  chatFirstRemovePinned: "從釘選應用程式移除",
24
24
  chatFirstPinTop: "將應用程式釘選至頂端",
25
25
  chatFirstOpenSideSurfaces: "開啟側邊面板",
@@ -85,8 +85,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
85
85
  searchAppsPlaceholder: "按名称或描述搜索",
86
86
  clearAppSearch: "清除搜索",
87
87
  noAppsMatch: "没有匹配搜索的应用",
88
- pinApp: "固定 {{name}}",
89
- unpinApp: "取消固定 {{name}}",
88
+ pinApp: "固定此应用",
89
+ unpinApp: "取消固定此应用",
90
90
  appPinSaveFailed: "无法保存应用固定设置。",
91
91
  appPinSavedLocally: "应用固定设置仅保存在此设备上。",
92
92
  chatFirstCreateWorkspaceApp: "创建工作区应用",
@@ -95,8 +95,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
95
95
  chatFirstNoWorkspaceApps: "尚无工作区应用。",
96
96
  chatFirstCreateApp: "创建应用",
97
97
  chatFirstRetry: "重试",
98
- chatFirstUnpinApp: "取消固定 {{name}}",
99
- chatFirstPinApp: "固定 {{name}}",
98
+ chatFirstUnpinApp: "取消固定此应用",
99
+ chatFirstPinApp: "固定此应用",
100
100
  chatFirstRemovePinned: "从固定应用中移除",
101
101
  chatFirstPinTop: "固定应用到顶部",
102
102
  chatFirstOpenSideSurfaces: "打开侧边面板",
@@ -162,8 +162,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
162
162
  searchAppsPlaceholder: "Buscar por nombre o descripción",
163
163
  clearAppSearch: "Borrar búsqueda",
164
164
  noAppsMatch: "Ninguna app coincide con tu búsqueda",
165
- pinApp: "Fijar {{name}}",
166
- unpinApp: "Desfijar {{name}}",
165
+ pinApp: "Fijar esta app",
166
+ unpinApp: "Desfijar esta app",
167
167
  appPinSaveFailed: "No se pudieron guardar las apps fijadas.",
168
168
  appPinSavedLocally:
169
169
  "Las apps fijadas solo se guardan en este dispositivo.",
@@ -174,8 +174,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
174
174
  chatFirstNoWorkspaceApps: "Aún no hay apps del espacio de trabajo.",
175
175
  chatFirstCreateApp: "Crear app",
176
176
  chatFirstRetry: "Reintentar",
177
- chatFirstUnpinApp: "Quitar fijación a {{name}}",
178
- chatFirstPinApp: "Fijar {{name}}",
177
+ chatFirstUnpinApp: "Quitar fijación a esta app",
178
+ chatFirstPinApp: "Fijar esta app",
179
179
  chatFirstRemovePinned: "Quitar de las apps fijadas",
180
180
  chatFirstPinTop: "Fijar app arriba",
181
181
  chatFirstOpenSideSurfaces: "Abrir paneles laterales",
@@ -248,8 +248,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
248
248
  searchAppsPlaceholder: "Rechercher par nom ou description",
249
249
  clearAppSearch: "Effacer la recherche",
250
250
  noAppsMatch: "Aucune application ne correspond à votre recherche",
251
- pinApp: "Épingler {{name}}",
252
- unpinApp: "Désépingler {{name}}",
251
+ pinApp: "Épingler cette application",
252
+ unpinApp: "Désépingler cette application",
253
253
  appPinSaveFailed: "Impossible d’enregistrer les applications épinglées.",
254
254
  appPinSavedLocally:
255
255
  "Les applications épinglées sont enregistrées uniquement sur cet appareil.",
@@ -261,8 +261,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
261
261
  "Aucune application d’espace de travail pour le moment.",
262
262
  chatFirstCreateApp: "Créer une application",
263
263
  chatFirstRetry: "Réessayer",
264
- chatFirstUnpinApp: "Désépingler {{name}}",
265
- chatFirstPinApp: "Épingler {{name}}",
264
+ chatFirstUnpinApp: "Désépingler cette application",
265
+ chatFirstPinApp: "Épingler cette application",
266
266
  chatFirstRemovePinned: "Retirer des applications épinglées",
267
267
  chatFirstPinTop: "Épingler l’application en haut",
268
268
  chatFirstOpenSideSurfaces: "Ouvrir les panneaux latéraux",
@@ -336,8 +336,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
336
336
  searchAppsPlaceholder: "Nach Name oder Beschreibung suchen",
337
337
  clearAppSearch: "Suche löschen",
338
338
  noAppsMatch: "Keine Apps entsprechen deiner Suche",
339
- pinApp: "{{name}} anheften",
340
- unpinApp: "{{name}} loslösen",
339
+ pinApp: "Diese App anheften",
340
+ unpinApp: "Diese App loslösen",
341
341
  appPinSaveFailed: "App-Anheftungen konnten nicht gespeichert werden.",
342
342
  appPinSavedLocally:
343
343
  "App-Anheftungen werden nur auf diesem Gerät gespeichert.",
@@ -348,8 +348,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
348
348
  chatFirstNoWorkspaceApps: "Noch keine Arbeitsbereich-Apps.",
349
349
  chatFirstCreateApp: "App erstellen",
350
350
  chatFirstRetry: "Erneut versuchen",
351
- chatFirstUnpinApp: "{{name}} loslösen",
352
- chatFirstPinApp: "{{name}} anheften",
351
+ chatFirstUnpinApp: "Diese App loslösen",
352
+ chatFirstPinApp: "Diese App anheften",
353
353
  chatFirstRemovePinned: "Aus angehefteten Apps entfernen",
354
354
  chatFirstPinTop: "App oben anheften",
355
355
  chatFirstOpenSideSurfaces: "Seitenflächen öffnen",
@@ -423,8 +423,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
423
423
  searchAppsPlaceholder: "名前または説明で検索",
424
424
  clearAppSearch: "検索をクリア",
425
425
  noAppsMatch: "検索に一致するアプリはありません",
426
- pinApp: "{{name}}を固定",
427
- unpinApp: "{{name}}の固定を解除",
426
+ pinApp: "このアプリを固定",
427
+ unpinApp: "このアプリの固定を解除",
428
428
  appPinSaveFailed: "アプリの固定を保存できませんでした。",
429
429
  appPinSavedLocally: "アプリの固定はこのデバイスにのみ保存されます。",
430
430
  chatFirstCreateWorkspaceApp: "ワークスペースアプリを作成",
@@ -433,8 +433,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
433
433
  chatFirstNoWorkspaceApps: "ワークスペースアプリはまだありません。",
434
434
  chatFirstCreateApp: "アプリを作成",
435
435
  chatFirstRetry: "再試行",
436
- chatFirstUnpinApp: "{{name}}の固定を解除",
437
- chatFirstPinApp: "{{name}}を固定",
436
+ chatFirstUnpinApp: "このアプリの固定を解除",
437
+ chatFirstPinApp: "このアプリを固定",
438
438
  chatFirstRemovePinned: "固定アプリから削除",
439
439
  chatFirstPinTop: "アプリを上部に固定",
440
440
  chatFirstOpenSideSurfaces: "サイドサーフェスを開く",
@@ -508,8 +508,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
508
508
  searchAppsPlaceholder: "이름 또는 설명으로 검색",
509
509
  clearAppSearch: "검색 지우기",
510
510
  noAppsMatch: "검색 결과가 없습니다",
511
- pinApp: "{{name}} 고정",
512
- unpinApp: "{{name}} 고정 해제",
511
+ pinApp: "이 앱 고정",
512
+ unpinApp: "이 앱 고정 해제",
513
513
  appPinSaveFailed: "앱 고정을 저장하지 못했습니다.",
514
514
  appPinSavedLocally: "앱 고정은 이 기기에만 저장됩니다.",
515
515
  chatFirstCreateWorkspaceApp: "워크스페이스 앱 만들기",
@@ -518,8 +518,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
518
518
  chatFirstNoWorkspaceApps: "아직 워크스페이스 앱이 없습니다.",
519
519
  chatFirstCreateApp: "앱 만들기",
520
520
  chatFirstRetry: "다시 시도",
521
- chatFirstUnpinApp: "{{name}} 고정 해제",
522
- chatFirstPinApp: "{{name}} 고정",
521
+ chatFirstUnpinApp: "이 앱 고정 해제",
522
+ chatFirstPinApp: "이 앱 고정",
523
523
  chatFirstRemovePinned: "고정된 앱에서 제거",
524
524
  chatFirstPinTop: "앱을 맨 위에 고정",
525
525
  chatFirstOpenSideSurfaces: "사이드 표면 열기",
@@ -589,8 +589,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
589
589
  searchAppsPlaceholder: "Pesquisar por nome ou descrição",
590
590
  clearAppSearch: "Limpar pesquisa",
591
591
  noAppsMatch: "Nenhum app corresponde à sua pesquisa",
592
- pinApp: "Fixar {{name}}",
593
- unpinApp: "Desafixar {{name}}",
592
+ pinApp: "Fixar este app",
593
+ unpinApp: "Desafixar este app",
594
594
  appPinSaveFailed: "Não foi possível salvar os apps fixados.",
595
595
  appPinSavedLocally:
596
596
  "Os apps fixados são salvos somente neste dispositivo.",
@@ -601,8 +601,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
601
601
  chatFirstNoWorkspaceApps: "Ainda não há apps do espaço de trabalho.",
602
602
  chatFirstCreateApp: "Criar app",
603
603
  chatFirstRetry: "Tentar novamente",
604
- chatFirstUnpinApp: "Desafixar {{name}}",
605
- chatFirstPinApp: "Fixar {{name}}",
604
+ chatFirstUnpinApp: "Desafixar este app",
605
+ chatFirstPinApp: "Fixar este app",
606
606
  chatFirstRemovePinned: "Remover dos apps fixados",
607
607
  chatFirstPinTop: "Fixar app no topo",
608
608
  chatFirstOpenSideSurfaces: "Abrir superfícies laterais",
@@ -675,8 +675,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
675
675
  searchAppsPlaceholder: "नाम या विवरण से खोजें",
676
676
  clearAppSearch: "खोज साफ़ करें",
677
677
  noAppsMatch: "आपकी खोज से कोई ऐप मेल नहीं खाता",
678
- pinApp: "{{name}} पिन करें",
679
- unpinApp: "{{name}} अनपिन करें",
678
+ pinApp: "इस ऐप को पिन करें",
679
+ unpinApp: "इस ऐप से पिन हटाएं",
680
680
  appPinSaveFailed: "ऐप पिन सहेजे नहीं जा सके।",
681
681
  appPinSavedLocally: "ऐप पिन केवल इस डिवाइस पर सहेजे गए हैं।",
682
682
  chatFirstCreateWorkspaceApp: "वर्कस्पेस ऐप बनाएँ",
@@ -685,8 +685,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
685
685
  chatFirstNoWorkspaceApps: "अभी कोई वर्कस्पेस ऐप नहीं है।",
686
686
  chatFirstCreateApp: "ऐप बनाएँ",
687
687
  chatFirstRetry: "पुनः प्रयास करें",
688
- chatFirstUnpinApp: "{{name}} को अनपिन करें",
689
- chatFirstPinApp: "{{name}} को पिन करें",
688
+ chatFirstUnpinApp: "इस ऐप से पिन हटाएं",
689
+ chatFirstPinApp: "इस ऐप को पिन करें",
690
690
  chatFirstRemovePinned: "पिन किए गए ऐप्स से हटाएँ",
691
691
  chatFirstPinTop: "ऐप को सबसे ऊपर पिन करें",
692
692
  chatFirstOpenSideSurfaces: "साइड पैनल खोलें",
@@ -754,8 +754,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
754
754
  searchAppsPlaceholder: "البحث بالاسم أو الوصف",
755
755
  clearAppSearch: "مسح البحث",
756
756
  noAppsMatch: "لا توجد تطبيقات تطابق بحثك",
757
- pinApp: "تثبيت {{name}}",
758
- unpinApp: "إلغاء تثبيت {{name}}",
757
+ pinApp: "تثبيت هذا التطبيق",
758
+ unpinApp: "إلغاء تثبيت هذا التطبيق",
759
759
  appPinSaveFailed: "تعذر حفظ تثبيتات التطبيقات.",
760
760
  appPinSavedLocally: "تُحفظ تثبيتات التطبيقات على هذا الجهاز فقط.",
761
761
  chatFirstCreateWorkspaceApp: "إنشاء تطبيق مساحة عمل",
@@ -764,8 +764,8 @@ export const chatFirstPageTranslations: Record<string, ChatFirstPageMessages> =
764
764
  chatFirstNoWorkspaceApps: "لا توجد تطبيقات لمساحة العمل بعد.",
765
765
  chatFirstCreateApp: "إنشاء تطبيق",
766
766
  chatFirstRetry: "إعادة المحاولة",
767
- chatFirstUnpinApp: "إلغاء تثبيت {{name}}",
768
- chatFirstPinApp: "تثبيت {{name}}",
767
+ chatFirstUnpinApp: "إلغاء تثبيت هذا التطبيق",
768
+ chatFirstPinApp: "تثبيت هذا التطبيق",
769
769
  chatFirstRemovePinned: "إزالة من التطبيقات المثبتة",
770
770
  chatFirstPinTop: "تثبيت التطبيق في الأعلى",
771
771
  chatFirstOpenSideSurfaces: "فتح الأسطح الجانبية",
@@ -432,11 +432,11 @@ const enUS = {
432
432
  threadDebugSelectPrompt: "Select a failed run or thread to inspect.",
433
433
  chatFirstWorkspaceApps: "Workspace apps",
434
434
  searchApps: "Search apps",
435
- searchAppsPlaceholder: "Search by name or description",
435
+ searchAppsPlaceholder: "Search apps",
436
436
  clearAppSearch: "Clear search",
437
437
  noAppsMatch: "No apps match your search",
438
- pinApp: "Pin {{name}}",
439
- unpinApp: "Unpin {{name}}",
438
+ pinApp: "Pin this app",
439
+ unpinApp: "Unpin this app",
440
440
  appPinSaveFailed: "App pins could not be saved.",
441
441
  appPinSavedLocally: "App pins are saved on this device only.",
442
442
  chatFirstCreateWorkspaceApp: "Create workspace app",
@@ -445,8 +445,8 @@ const enUS = {
445
445
  chatFirstNoWorkspaceApps: "No workspace apps yet.",
446
446
  chatFirstCreateApp: "Create app",
447
447
  chatFirstRetry: "Retry",
448
- chatFirstUnpinApp: "Unpin {{name}}",
449
- chatFirstPinApp: "Pin {{name}}",
448
+ chatFirstUnpinApp: "Unpin this app",
449
+ chatFirstPinApp: "Pin this app",
450
450
  chatFirstRemovePinned: "Remove from pinned apps",
451
451
  chatFirstPinTop: "Pin app to the top",
452
452
  chatFirstOpenSideSurfaces: "Open side surfaces",
@@ -22,11 +22,20 @@ decisions, feedback, agent runs, and provider audit records.
22
22
  `reconciliation_required` state.
23
23
  - Deduplicate by Factory item and rule/run identity, not provider comment ID.
24
24
  - Use the generic Slack adapter: clear-bug automations add 👀 and tag
25
- `@builderio`; GitHub/Sentry clear bugs use the Builder run API. Clips, Design,
26
- and Content stay owner-managed outside autonomous dispatch and PR governance.
27
- - PR governance requires verified BuilderIO membership, a clear bug, passing CI,
28
- and handled review feedback; product/UX implications stay manual. Auto-merge
29
- also requires a verified Factory Builder run.
25
+ `@builder.io`, ask Builder to run `/address-feedback`, and group repeated
26
+ reports into one Builder thread; GitHub/Sentry clear bugs use the Builder run
27
+ API. Clips, Design, and Content stay owner-managed outside autonomous
28
+ dispatch and PR governance.
29
+ - PR governance follows `review-prs`: verify current BuilderIO membership and
30
+ the complete diff/review/check evidence, never approve external or
31
+ unverified authors, and keep ultra-scary security, auth, tenant-isolation,
32
+ secrets, data-loss, execution, payment, and deployment risks manual. The
33
+ verified internal-author evidence does not waive ordinary failed or
34
+ unresolved checks or feedback; those gates remain required and their exact
35
+ states must stay visible. It never waives membership or the ultra-scary
36
+ gate.
37
+ Product/UX ownership still needs the verified owner, with the documented Sid
38
+ exception. Auto-merge also requires a verified Factory Builder run.
30
39
  - Graph edits create immutable blueprint versions. AI proposes with `source=ai`;
31
40
  a person reviews and publishes through the same action surface.
32
41
  - Provider credentials belong to Dispatch/shared workspace integrations, never
@@ -35,47 +44,39 @@ decisions, feedback, agent runs, and provider audit records.
35
44
 
36
45
  ## Application state
37
46
 
38
- - `navigation.view`: `factory` for the Factory workspace or `agents` for the
39
- shared Agents sidebar surface.
40
- - `navigation.factoryId`: selected Factory id when present.
41
- - `navigation.factoryTab`: `map` | `inbox` | `rules` | `automations` | `agents` | `audit` | `settings`.
42
- - `navigation.factoryAuditRunId`: selected automation run in the audit view when present.
43
- - `navigation.factoryNodeId` / `navigation.factoryEdgeId`: selected graph item.
44
- - A selected graph node or edge is part of `navigation` context. Read
45
- `view-screen` before answering why a route exists or changing the selected
46
- Factory.
47
- - The `/agents` surface shows mounted agentic apps and reusable agent packs from
48
- the same Dispatch SQL/action registry. Use `view-screen` there before
49
- answering questions about the visible agent inventory.
47
+ - `navigation.view` is `factory` or `agents`; `factoryId`, `factoryTab`,
48
+ `factoryAuditRunId`, `factoryNodeId`, and `factoryEdgeId` hold the selected
49
+ Factory context. Read `view-screen` before explaining a route, changing the
50
+ selected Factory, or answering about the `/agents` inventory.
50
51
 
51
52
  ## Action contract
52
53
 
53
54
  | Action | Purpose |
54
55
  | --- | --- |
55
- | `list-triage-items` / `get-triage-item` | Inspect queue and evidence; scheduled reviewers must pass `needsReview: true` with a bounded `source` and `limit`. |
56
+ | `list-triage-items` / `get-triage-item` | Inspect bounded queue evidence; scheduled reviewers pass `needsReview: true`, `source`, and `limit`. |
56
57
  | `poll-slack-channel` | Observe Slack history; never writes to Slack. |
57
58
  | `get-slack-feedback-context` | Read the bounded full Slack thread before classification. |
58
- | `poll-github-sources` / `poll-sentry-errors` | Observe bounded GitHub and Sentry source queues. |
59
+ | `poll-github-sources` / `poll-sentry-errors` | Observe bounded source queues. |
59
60
  | `ingest-github-observation` | Store read-only PR evidence. |
60
- | `list-triage-rules` / `save-triage-rule` | Tune prompt rules and guards. |
61
+ | `list-triage-rules` / `save-triage-rule` | Tune rules and guards. |
61
62
  | `evaluate-triage-item` | Append a decision. |
62
63
  | `record-triage-feedback` | Capture human correction for learning. |
63
64
  | `approve-factory-item` | Explicitly authorize one bounded run. |
64
65
  | `start-builder-for-item` | Govern clear-bug dispatch through Slack or Builder API. |
65
- | `govern-agent-native-pull-request` | Apply CI, review, author, product, and owner gates. |
66
- | `list-factory-automations` / `save-factory-automation` / `run-factory-automation` | Inspect or edit org-owned prompts, schedules, and runs. |
66
+ | `govern-agent-native-pull-request` | Apply PR evidence and ownership gates. |
67
+ | `list-factory-automations` / `save-factory-automation` / `run-factory-automation` | Inspect or edit org-owned automations. |
67
68
  | `list-factory-audit` | Inspect automation runs, evidence, decisions, and provider actions. |
68
69
  | `get-factory-automation-health` | Inspect scheduler heartbeat and last error. |
69
- | `suggest-factory-rules` | Mine feedback and fast approvals into proposals. |
70
+ | `suggest-factory-rules` | Mine feedback into proposals. |
70
71
  | `reconcile-triage-run` | Persist callback/provider reconciliation. |
71
- | `list-factories` / `get-factory-graph` | Inspect Factory definitions, graph versions, and live evidence metrics. |
72
+ | `list-factories` / `get-factory-graph` | Inspect definitions, versions, and metrics. |
72
73
  | `save-factory-graph` | Create or version a complete visual graph; never starts provider work. |
73
74
  | `list-factory-comments` / `add-factory-comment` | Read or attach comments to a canvas, node, or edge. |
74
- | `provider-api-catalog` / `provider-api-docs` / `provider-api-request` | Discover and use connected provider APIs with shared workspace credentials; never request raw keys. |
75
- | `list-workspace-apps` / `update-workspace-app-metadata` | Inventory and edit mounted agentic apps in the shared workspace. |
76
- | `list-workspace-resources` / `create-workspace-resource` / `update-workspace-resource` | Manage shared resource records used by agents and apps. |
77
- | `import-agent` / `import-agent-pack` / `list-agent-pack` | Import a simple profile or a Claude/Cowork-style folder-backed agent pack. |
78
- | `start-workspace-app-creation` | Promote an agent and all of its pack resource ids into an app-creation handoff. |
75
+ | `provider-api-catalog` / `provider-api-docs` / `provider-api-request` | Use connected provider APIs with shared credentials; never request raw keys. |
76
+ | `list-workspace-apps` / `update-workspace-app-metadata` | Inventory and edit mounted apps. |
77
+ | `list-workspace-resources` / `create-workspace-resource` / `update-workspace-resource` | Manage shared agent resources. |
78
+ | `import-agent` / `import-agent-pack` / `list-agent-pack` | Import profiles or agent packs. |
79
+ | `start-workspace-app-creation` | Promote an agent and its pack into an app handoff. |
79
80
 
80
81
  Rules start in shadow mode; hard guards always apply. Organization automations
81
82
  execute stored prompts, and every external mutation needs a durable run,
@@ -15,7 +15,25 @@ export function isAgentSidebarVisible() {
15
15
  if (style.display === "none" || style.visibility === "hidden") return false;
16
16
 
17
17
  const rect = panel.getBoundingClientRect();
18
- return rect.width > 0 && rect.height > 0;
18
+ if (rect.width <= 0 || rect.height <= 0) return false;
19
+
20
+ for (
21
+ let ancestor = panel.parentElement;
22
+ ancestor && ancestor !== document.body;
23
+ ancestor = ancestor.parentElement
24
+ ) {
25
+ if (ancestor.getAttribute("aria-hidden") === "true") return false;
26
+ if (ancestor.inert) return false;
27
+ const ancestorStyle = window.getComputedStyle(ancestor);
28
+ if (
29
+ ancestorStyle.display === "none" ||
30
+ ancestorStyle.visibility === "hidden"
31
+ ) {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ return true;
19
37
  }
20
38
 
21
39
  function useAgentSidebarVisible() {
@@ -25,20 +43,49 @@ function useAgentSidebarVisible() {
25
43
  const update = () => setVisible(isAgentSidebarVisible());
26
44
  update();
27
45
 
28
- const observer = new MutationObserver(update);
29
- observer.observe(document.body, {
30
- attributes: true,
31
- attributeFilter: ["aria-hidden", "class", "inert", "style"],
32
- childList: true,
33
- subtree: true,
34
- });
46
+ // The panel is a portal and is normally a direct child of body. Discover
47
+ // portal mount/unmounts with a shallow child-list observer, then watch only
48
+ // the panel and its immediate parent. Observing every body attribute and
49
+ // descendant mutation made this tiny indicator run on every editor render.
50
+ let panelObserver: MutationObserver | null = null;
51
+ let parentObserver: MutationObserver | null = null;
52
+
53
+ const observePanel = () => {
54
+ panelObserver?.disconnect();
55
+ parentObserver?.disconnect();
56
+ const panel = document.querySelector<HTMLElement>(".agent-sidebar-panel");
57
+ if (!panel) return;
58
+
59
+ panelObserver = new MutationObserver(update);
60
+ panelObserver.observe(panel, {
61
+ attributes: true,
62
+ attributeFilter: ["aria-hidden", "class", "inert", "style"],
63
+ });
64
+ if (panel.parentElement && panel.parentElement !== document.body) {
65
+ parentObserver = new MutationObserver(update);
66
+ parentObserver.observe(panel.parentElement, {
67
+ attributes: true,
68
+ attributeFilter: ["aria-hidden", "class", "inert", "style"],
69
+ childList: true,
70
+ });
71
+ }
72
+ };
73
+ const updateAndObserve = () => {
74
+ update();
75
+ observePanel();
76
+ };
77
+ const discovery = new MutationObserver(updateAndObserve);
78
+ discovery.observe(document.body, { childList: true });
79
+ updateAndObserve();
35
80
  window.addEventListener("resize", update);
36
81
  window.addEventListener("agent-panel:open", update);
37
82
  window.addEventListener("agent-panel:toggle", update);
38
83
  window.addEventListener("agent-panel:set-mode", update);
39
84
 
40
85
  return () => {
41
- observer.disconnect();
86
+ discovery.disconnect();
87
+ panelObserver?.disconnect();
88
+ parentObserver?.disconnect();
42
89
  window.removeEventListener("resize", update);
43
90
  window.removeEventListener("agent-panel:open", update);
44
91
  window.removeEventListener("agent-panel:toggle", update);
package/dist/action.js CHANGED
@@ -326,8 +326,8 @@ const SUBSCHEMA_MAP_KEYS = [
326
326
  "dependentSchemas",
327
327
  ];
328
328
  /**
329
- * Remove JSON Schema keywords that some providers' function-calling schema
330
- * validators reject. OpenAI (and Gemini via the Builder gateway) reject
329
+ * Remove or rewrite JSON Schema keywords that some providers' function-calling
330
+ * schema validators reject. OpenAI (and Gemini via the Builder gateway) reject
331
331
  * `propertyNames` — which Zod v4 emits for `z.record(z.string(), …)` — with a
332
332
  * `400 invalid_function_parameters` error, causing the model turn to produce no
333
333
  * content (surfacing as an empty assistant response). Anthropic ignores the
@@ -335,11 +335,64 @@ const SUBSCHEMA_MAP_KEYS = [
335
335
  * portable. `propertyNames` only constrained object *keys*; the value/shape of
336
336
  * the object is unaffected by its removal.
337
337
  *
338
+ * OpenAI rejects `oneOf` the same way — "Invalid schema for function 'x': …
339
+ * 'oneOf' is not permitted" — while accepting `anyOf`. Zod v4's `toJSONSchema`
340
+ * emits `oneOf` for every `z.discriminatedUnion`, so any action with one was
341
+ * 400'd for the whole request before a token streamed, and the gateway folded
342
+ * that 400 into a generic 500. Measured: 178k events / 786 users over seven
343
+ * weeks from a single action. For a discriminated union the two keywords accept
344
+ * the same documents, and the tool-input validator already handles both
345
+ * (see `narrowUnionBranchErrors`), so the rewrite is behaviour-preserving.
346
+ *
338
347
  * Only descends through actual subschema positions (properties, items, union
339
348
  * branches, definitions, etc.) — never through value-bearing keywords like
340
349
  * `default`, `const`, `enum`, or `examples`, whose objects may legitimately
341
350
  * contain a `propertyNames` data key that must be preserved.
342
351
  */
352
+ /**
353
+ * A schema position with no `type` — what Zod emits for `z.unknown()` /
354
+ * `z.any()` — is rejected by OpenAI with "schema must have a 'type' key",
355
+ * 400ing the whole request exactly like `oneOf` did. There are 137 such sites
356
+ * across the templates, so this has to be answered at the boundary rather than
357
+ * by retyping every action.
358
+ *
359
+ * Expressed as a union of concrete JSON types because that shape is already
360
+ * proven against the live validator: `setFilterDefault.value`, a
361
+ * `z.union([string, number, boolean, null])` sitting in the same tool schema,
362
+ * was never flagged while the typeless sibling next to it was. `true` for
363
+ * `additionalProperties` is a boolean, not a subschema, so it needs no type of
364
+ * its own; array items get one bounded level rather than recursing forever.
365
+ */
366
+ const JSON_VALUE_BRANCHES = [
367
+ { type: "string" },
368
+ { type: "number" },
369
+ { type: "boolean" },
370
+ { type: "null" },
371
+ { type: "object", additionalProperties: true },
372
+ {
373
+ type: "array",
374
+ items: {
375
+ anyOf: [
376
+ { type: "string" },
377
+ { type: "number" },
378
+ { type: "boolean" },
379
+ { type: "null" },
380
+ { type: "object", additionalProperties: true },
381
+ ],
382
+ },
383
+ },
384
+ ];
385
+ /** Keywords that make a `type`-less schema meaningful on their own. */
386
+ const TYPE_SUBSTITUTE_KEYS = [
387
+ "type",
388
+ "anyOf",
389
+ "oneOf",
390
+ "allOf",
391
+ "enum",
392
+ "const",
393
+ "$ref",
394
+ "not",
395
+ ];
343
396
  function stripUnsupportedSchemaKeywords(node) {
344
397
  if (!node || typeof node !== "object" || Array.isArray(node))
345
398
  return node;
@@ -371,6 +424,19 @@ function stripUnsupportedSchemaKeywords(node) {
371
424
  }
372
425
  }
373
426
  }
427
+ // After descending, so nested unions are rewritten too. Merged rather than
428
+ // overwritten on the pathological case where a schema carries both: dropping
429
+ // either arm would silently narrow what the tool accepts.
430
+ if (Array.isArray(obj.oneOf)) {
431
+ const existing = Array.isArray(obj.anyOf) ? obj.anyOf : [];
432
+ obj.anyOf = [...existing, ...obj.oneOf];
433
+ delete obj.oneOf;
434
+ }
435
+ // Last, so a node that only became typed above (oneOf -> anyOf) is not also
436
+ // given a redundant value union.
437
+ if (!TYPE_SUBSTITUTE_KEYS.some((key) => obj[key] !== undefined)) {
438
+ obj.anyOf = JSON_VALUE_BRANCHES.map((branch) => ({ ...branch }));
439
+ }
374
440
  return node;
375
441
  }
376
442
  /**
@@ -84,7 +84,16 @@ export async function checkChatHealthAndAlert(now = Date.now()) {
84
84
  if (badRate < BAD_RATE_THRESHOLD) {
85
85
  return { status: "healthy", turns: counts.turns, badRate };
86
86
  }
87
- const stored = await getSetting(LAST_ALERT_SETTING_KEY).catch(() => null);
87
+ // A cooldown stamp we could not READ is not a cooldown that is absent:
88
+ // treating it as absent pages on every sweep for as long as settings stay
89
+ // unreadable, which is exactly the spam the cooldown exists to prevent.
90
+ let stored;
91
+ try {
92
+ stored = (await getSetting(LAST_ALERT_SETTING_KEY));
93
+ }
94
+ catch (error) {
95
+ return { status: "check-failed", reason: String(error) };
96
+ }
88
97
  const lastPagedAt = Number(stored?.at ?? 0);
89
98
  if (Number.isFinite(lastPagedAt) && now - lastPagedAt < COOLDOWN_MS) {
90
99
  return {
@@ -2603,6 +2603,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2603
2603
  // A restored tab can be reclassified as client-only after the thread list
2604
2604
  // loads. Once that happens, there is no server row to restore, so show the
2605
2605
  // empty composer instead of leaving the per-thread restore skeleton up.
2606
+ setThreadRestoreError(null);
2606
2607
  setIsRestoring(false);
2607
2608
  }, [isNewThread, threadId]);
2608
2609
  // Restore messages from server on mount (when threadId is set). The
@@ -21,5 +21,5 @@ export function AppOpenActions({ name, href, target, rel, labels: labelOverrides
21
21
  if (!canOpen && !hasMenu) {
22
22
  return (_jsx(Button, { size: "sm", variant: "outline", disabled: true, children: labels.openApp }));
23
23
  }
24
- return (_jsxs("div", { className: cn("app-open-actions flex shrink-0 overflow-hidden rounded-md border border-border focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2", className), children: [_jsx(Button, { asChild: primaryUsesHref, size: "sm", variant: "outline", className: "app-open-actions__primary h-7 rounded-none border-0 border-e border-border px-0 pe-2 ps-3 text-xs", disabled: !canOpen, onClick: onOpen, type: primaryUsesHref ? undefined : "button", "aria-label": !primaryUsesHref ? `Open ${name}` : undefined, children: primaryUsesHref ? (_jsx("a", { href: href ?? undefined, target: target, rel: rel, children: labels.openApp })) : (_jsx("span", { children: labels.openApp })) }), hasMenu ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { type: "button", size: "sm", variant: "outline", className: "app-open-actions__menu size-7 rounded-none border-0 p-0 text-xs", "aria-label": `Open options for ${name}`, children: _jsx(IconChevronDown, { size: 15 }) }) }), _jsxs(DropdownMenuContent, { align: "end", className: APP_ACTION_MENU_CONTENT_CLASS, children: [onAddApp ? (_jsxs(DropdownMenuItem, { onSelect: onAddApp, children: [_jsx(IconPlus, { size: 14, "aria-hidden": "true" }), labels.addApp] })) : null, showInlineOption && href && onOpenInline ? (_jsxs(DropdownMenuItem, { onSelect: onOpenInline, children: [_jsx(IconAppWindow, { size: 14, "aria-hidden": "true" }), labels.openInline] })) : null, canOfferNewTab && href ? (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs("a", { href: href ?? undefined, target: "_blank", rel: "noreferrer", children: [_jsx(IconExternalLink, { size: 14, "aria-hidden": "true" }), labels.openInNewTab] }) })) : null, menuItems?.map((item) => (_jsxs(DropdownMenuItem, { onSelect: item.onSelect, children: [item.icon ? (_jsx("span", { "aria-hidden": "true", className: "shrink-0", children: item.icon })) : null, item.label] }, item.id)))] })] })) : null] }));
24
+ return (_jsxs("div", { className: cn("app-open-actions flex shrink-0 overflow-hidden rounded-md border border-border focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2", className), children: [_jsx(Button, { asChild: primaryUsesHref, size: "sm", variant: "outline", className: "app-open-actions__primary h-7 rounded-none border-0 border-e border-border px-0 pe-2 ps-3 text-xs", disabled: !canOpen, onClick: onOpen, type: primaryUsesHref ? undefined : "button", "aria-label": !primaryUsesHref ? `Open ${name}` : undefined, children: primaryUsesHref ? (_jsx("a", { href: href ?? undefined, target: target, rel: rel, children: labels.openApp })) : (_jsx("span", { children: labels.openApp })) }), hasMenu ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { type: "button", size: "sm", variant: "outline", className: "app-open-actions__menu size-7 rounded-none border-0 p-0 text-xs", "aria-label": `Open options for ${name}`, children: _jsx(IconChevronDown, { size: 15 }) }) }), _jsxs(DropdownMenuContent, { align: "end", className: cn(APP_ACTION_MENU_CONTENT_CLASS, "min-w-max"), children: [onAddApp ? (_jsxs(DropdownMenuItem, { onSelect: onAddApp, children: [_jsx(IconPlus, { size: 14, "aria-hidden": "true" }), labels.addApp] })) : null, showInlineOption && href && onOpenInline ? (_jsxs(DropdownMenuItem, { onSelect: onOpenInline, children: [_jsx(IconAppWindow, { size: 14, "aria-hidden": "true" }), labels.openInline] })) : null, canOfferNewTab && href ? (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs("a", { href: href ?? undefined, target: "_blank", rel: "noreferrer", children: [_jsx(IconExternalLink, { size: 14, "aria-hidden": "true" }), labels.openInNewTab] }) })) : null, menuItems?.map((item) => (_jsxs(DropdownMenuItem, { onSelect: item.onSelect, children: [item.icon ? (_jsx("span", { "aria-hidden": "true", className: "shrink-0", children: item.icon })) : null, item.label] }, item.id)))] })] })) : null] }));
25
25
  }
@@ -12,8 +12,8 @@ const DEFAULT_COPY = {
12
12
  createApp: "Create app",
13
13
  retry: "Retry",
14
14
  dismiss: "Dismiss",
15
- unpinApp: "Unpin {name}",
16
- pinApp: "Pin {name}",
15
+ unpinApp: "Unpin this app",
16
+ pinApp: "Pin this app",
17
17
  removePinned: "Remove from pinned apps",
18
18
  pinTop: "Pin app to the top",
19
19
  moveUp: "Move up",
@@ -50,7 +50,27 @@ export function useNearBottomAutoscroll({ followKey, streaming = false, threshol
50
50
  const followGenerationRef = useRef(0);
51
51
  const lastScrollTopRef = useRef(0);
52
52
  const lastTouchYRef = useRef(null);
53
+ const pendingAnimationFrameIdsRef = useRef(new Set());
54
+ const pendingTimeoutIdsRef = useRef(new Set());
53
55
  const [showScrollToBottom, setShowScrollToBottom] = useState(false);
56
+ const cancelPendingScrolls = useCallback(() => {
57
+ for (const id of pendingAnimationFrameIdsRef.current) {
58
+ window.cancelAnimationFrame(id);
59
+ }
60
+ pendingAnimationFrameIdsRef.current.clear();
61
+ for (const id of pendingTimeoutIdsRef.current) {
62
+ window.clearTimeout(id);
63
+ }
64
+ pendingTimeoutIdsRef.current.clear();
65
+ }, []);
66
+ const scheduleAnimationFrame = useCallback((callback) => {
67
+ let id = 0;
68
+ id = window.requestAnimationFrame((time) => {
69
+ pendingAnimationFrameIdsRef.current.delete(id);
70
+ callback(time);
71
+ });
72
+ pendingAnimationFrameIdsRef.current.add(id);
73
+ }, []);
54
74
  const isAtBottom = useCallback((el) => el.scrollHeight - el.scrollTop - el.clientHeight <= threshold, [threshold]);
55
75
  const setFollowingBottom = useCallback((following, forceGeneration = false, el) => {
56
76
  if (forceGeneration || isNearBottomRef.current !== following) {
@@ -213,14 +233,24 @@ export function useNearBottomAutoscroll({ followKey, streaming = false, threshol
213
233
  };
214
234
  }, [detachFromBottom, enabled, scrollToBottomIfFollowing, updateBottomState]);
215
235
  const scrollToBottomAfterPaint = useCallback(() => {
236
+ cancelPendingScrolls();
216
237
  const generation = followGenerationRef.current;
217
238
  scrollToBottomIfFollowing(generation);
218
- requestAnimationFrame(() => {
239
+ scheduleAnimationFrame(() => {
219
240
  scrollToBottomIfFollowing(generation);
220
- requestAnimationFrame(() => scrollToBottomIfFollowing(generation));
241
+ scheduleAnimationFrame(() => scrollToBottomIfFollowing(generation));
221
242
  });
222
- window.setTimeout(() => scrollToBottomIfFollowing(generation), 80);
223
- }, [scrollToBottomIfFollowing]);
243
+ const timeoutId = window.setTimeout(() => {
244
+ pendingTimeoutIdsRef.current.delete(timeoutId);
245
+ scrollToBottomIfFollowing(generation);
246
+ }, 80);
247
+ pendingTimeoutIdsRef.current.add(timeoutId);
248
+ }, [cancelPendingScrolls, scheduleAnimationFrame, scrollToBottomIfFollowing]);
249
+ useEffect(() => {
250
+ if (!enabled)
251
+ cancelPendingScrolls();
252
+ return cancelPendingScrolls;
253
+ }, [cancelPendingScrolls, enabled]);
224
254
  const resumeFollowing = useCallback(() => {
225
255
  const el = scrollRef.current;
226
256
  setFollowingBottom(true, true, el ?? undefined);