@agent-native/core 0.131.5 → 0.131.7

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 (350) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +70 -0
  3. package/corpus/core/docs/content/agent-native-toolkit.mdx +13 -6
  4. package/corpus/core/docs/content/cloneable-saas.mdx +5 -5
  5. package/corpus/core/docs/content/creating-templates.mdx +9 -0
  6. package/corpus/core/docs/content/deployment.mdx +18 -12
  7. package/corpus/core/docs/content/doctor.mdx +8 -1
  8. package/corpus/core/docs/content/drop-in-agent.mdx +1 -1
  9. package/corpus/core/docs/content/extensions.mdx +18 -18
  10. package/corpus/core/docs/content/faq.mdx +4 -4
  11. package/corpus/core/docs/content/key-concepts.mdx +30 -21
  12. package/corpus/core/docs/content/security.mdx +2 -2
  13. package/corpus/core/docs/content/template-brain-developers.mdx +4 -1
  14. package/corpus/core/docs/content/template-mail-developers.mdx +4 -1
  15. package/corpus/core/docs/content/what-is-agent-native.mdx +6 -6
  16. package/corpus/core/package.json +1 -1
  17. package/corpus/core/src/a2a/client.ts +449 -66
  18. package/corpus/core/src/a2a/correlation.ts +31 -0
  19. package/corpus/core/src/a2a/invoke.ts +4 -2
  20. package/corpus/core/src/a2a/types.ts +4 -0
  21. package/corpus/core/src/action.ts +3 -0
  22. package/corpus/core/src/agent/action-continuation-guidance.ts +2 -1
  23. package/corpus/core/src/agent/engine/registry.ts +98 -6
  24. package/corpus/core/src/agent/production-agent.ts +151 -18
  25. package/corpus/core/src/agent/run-loop-with-resume.ts +190 -3
  26. package/corpus/core/src/agent/run-manager.ts +117 -41
  27. package/corpus/core/src/agent/thread-debug-history.ts +86 -0
  28. package/corpus/core/src/agent/types.ts +3 -1
  29. package/corpus/core/src/cli/create.ts +11 -1
  30. package/corpus/core/src/cli/templates-meta.ts +2 -2
  31. package/corpus/core/src/client/agent-chat-adapter.ts +91 -13
  32. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +11 -9
  33. package/corpus/core/src/client/sse-event-processor.ts +17 -3
  34. package/corpus/core/src/db/client.ts +57 -17
  35. package/corpus/core/src/guards/no-env-credentials.ts +2 -0
  36. package/corpus/core/src/integrations/adapters/slack.ts +8 -3
  37. package/corpus/core/src/localization/default-messages.ts +44 -0
  38. package/corpus/core/src/observability/traces.ts +78 -5
  39. package/corpus/core/src/scripts/call-agent.ts +294 -11
  40. package/corpus/core/src/scripts/describe-workspace-apps.ts +2 -2
  41. package/corpus/core/src/secrets/crypto.ts +156 -35
  42. package/corpus/core/src/secrets/storage.ts +64 -26
  43. package/corpus/core/src/server/agent-capabilities.ts +1 -1
  44. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +113 -7
  45. package/corpus/core/src/server/agent-chat/context-tools.ts +2 -0
  46. package/corpus/core/src/server/agent-chat/plugin-options.ts +34 -1
  47. package/corpus/core/src/server/agent-chat/prompt-resources.ts +1 -1
  48. package/corpus/core/src/server/agent-chat-plugin.ts +91 -59
  49. package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +19 -7
  50. package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
  51. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
  52. package/corpus/core/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
  53. package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
  54. package/corpus/core/src/vite/client.ts +1 -0
  55. package/corpus/templates/analytics/.agents/skills/dashboard-ops/SKILL.md +5 -4
  56. package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +6 -4
  57. package/corpus/templates/analytics/AGENTS.md +7 -4
  58. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -3
  59. package/corpus/templates/analytics/changelog/2026-07-30-delegated-analytics-questions-now-return-a-useful-current-me.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-07-30-scheduled-dashboard-emails-complete-every-panel-without-pool.md +6 -0
  61. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +28 -1
  62. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +18 -11
  63. package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +4 -4
  64. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -5
  65. package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +6 -2
  66. package/corpus/templates/analytics/server/lib/dashboard-panel-source-resolver.ts +11 -4
  67. package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +49 -31
  68. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -8
  69. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +9 -3
  70. package/corpus/templates/analytics/server/lib/production-serverless-runtime.ts +11 -0
  71. package/corpus/templates/analytics/server/lib/real-data-actions.ts +10 -0
  72. package/corpus/templates/analytics/server/plugins/agent-chat.ts +3 -2
  73. package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +2 -1
  74. package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +2 -1
  75. package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -12
  76. package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +54 -6
  77. package/corpus/templates/brain/.agents/skills/brain/SKILL.md +20 -9
  78. package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +33 -1
  79. package/corpus/templates/brain/AGENTS.md +3 -1
  80. package/corpus/templates/brain/README.md +4 -1
  81. package/corpus/templates/brain/actions/_schemas.ts +30 -8
  82. package/corpus/templates/brain/actions/ask-brain.ts +124 -14
  83. package/corpus/templates/brain/actions/create-source.ts +18 -2
  84. package/corpus/templates/brain/actions/enqueue-captures-distillation.ts +8 -123
  85. package/corpus/templates/brain/actions/enqueue-distillation.ts +6 -127
  86. package/corpus/templates/brain/actions/import-capture.ts +17 -5
  87. package/corpus/templates/brain/actions/import-transcript.ts +17 -5
  88. package/corpus/templates/brain/actions/update-source.ts +25 -5
  89. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +1 -1
  90. package/corpus/templates/brain/changelog/2026-07-30-blessed-resources-can-feed-cited-company-answers.md +6 -0
  91. package/corpus/templates/brain/changelog/2026-07-30-the-left-sidebar-no-longer-shows-a-blank-organization-placeh.md +6 -0
  92. package/corpus/templates/brain/server/lib/brain.ts +49 -19
  93. package/corpus/templates/brain/server/lib/distillation-queue.ts +147 -0
  94. package/corpus/templates/brain/server/lib/source-policy.ts +264 -0
  95. package/corpus/templates/brain/server/plugins/agent-chat.ts +3 -2
  96. package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +116 -19
  97. package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +51 -0
  98. package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
  99. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +3 -1
  100. package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +6 -0
  101. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +72 -8
  102. package/corpus/templates/content/changelog/2026-07-30-the-slash-command-hint-now-stays-on-only-the-active-empty-li.md +6 -0
  103. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +506 -11
  104. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +148 -17
  105. package/corpus/templates/design/app/components/design/canvas-interactions/design-canvas-interactions.ts +357 -0
  106. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +7 -466
  107. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +15 -311
  108. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +19 -997
  109. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +8 -1216
  110. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +31 -38
  111. package/corpus/templates/design/package.json +0 -1
  112. package/corpus/templates/dispatch/AGENTS.md +8 -0
  113. package/corpus/templates/dispatch/DEVELOPING.md +29 -0
  114. package/corpus/templates/dispatch/actions/view-screen.ts +34 -2
  115. package/corpus/templates/dispatch/app/i18n-data.ts +457 -0
  116. package/corpus/templates/dispatch/changelog/2026-07-30-delegated-dispatch-work-continues-reliably-in-the-background.md +6 -0
  117. package/corpus/templates/dispatch/changelog/2026-07-30-find-failed-runs-across-apps.md +6 -0
  118. package/corpus/templates/dispatch/netlify.toml +3 -0
  119. package/corpus/templates/plan/actions/get-visual-plan.ts +10 -6
  120. package/corpus/templates/plan/actions/patch-visual-plan-source.ts +117 -1
  121. package/corpus/templates/plan/actions/update-visual-plan.ts +56 -14
  122. package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -1
  123. package/corpus/templates/plan/changelog/2026-07-30-plan-agents-recover-with-targeted-writes.md +6 -0
  124. package/corpus/templates/plan/shared/plan-content.ts +35 -0
  125. package/corpus/templates/slides/.agents/skills/analytics-data-for-decks/SKILL.md +8 -12
  126. package/corpus/templates/slides/AGENTS.md +4 -4
  127. package/corpus/templates/slides/actions/create-deck.ts +1 -1
  128. package/corpus/templates/slides/actions/duplicate-deck.ts +19 -4
  129. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +2 -10
  130. package/corpus/templates/slides/app/components/deck/MermaidRenderer.tsx +12 -1
  131. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +30 -13
  132. package/corpus/templates/slides/app/components/editor/DeckEditorSkeleton.tsx +35 -0
  133. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +63 -56
  134. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +21 -76
  135. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1110 -292
  136. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +53 -0
  137. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +235 -18
  138. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +4 -1
  139. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +11 -0
  140. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +129 -0
  141. package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +70 -0
  142. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +519 -65
  143. package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +36 -426
  144. package/corpus/templates/slides/app/components/visual-editor/DrawOverlay.tsx +19 -458
  145. package/corpus/templates/slides/app/context/DeckContext.tsx +179 -72
  146. package/corpus/templates/slides/app/global.css +105 -0
  147. package/corpus/templates/slides/app/i18n/en-US.ts +8 -0
  148. package/corpus/templates/slides/app/lib/deck-editor-loading.ts +26 -0
  149. package/corpus/templates/slides/app/lib/mermaid-blocks.ts +27 -0
  150. package/corpus/templates/slides/app/pages/DeckEditor.tsx +71 -38
  151. package/corpus/templates/slides/app/pages/Index.tsx +98 -41
  152. package/corpus/templates/slides/changelog/2026-07-30-deck-loading-no-longer-flashes-an-unavailable-message.md +6 -0
  153. package/corpus/templates/slides/changelog/2026-07-30-duplicating-a-deck-from-the-deck-list-now-opens-the-copy-imm.md +6 -0
  154. package/corpus/templates/slides/changelog/2026-07-30-layout-overflow-warning-is-readable-and-dismissible.md +6 -0
  155. package/corpus/templates/slides/changelog/2026-07-30-new-decks-now-start-with-an-empty-slide-list-when-no-slides-.md +6 -0
  156. package/corpus/templates/slides/changelog/2026-07-30-shared-canvas-annotations.md +6 -0
  157. package/corpus/templates/slides/changelog/2026-07-30-slide-objects-can-be-moved-as-a-group-copied-and-pasted-with.md +6 -0
  158. package/corpus/templates/slides/changelog/2026-07-30-slide-text-editing-and-object-controls-now-match-google-slides.md +6 -0
  159. package/corpus/templates/slides/changelog/2026-07-30-style-panel-now-scrolls-with-speaker-notes-open-shows-the-sl.md +6 -0
  160. package/corpus/templates/slides/changelog/2026-07-30-top-toolbar-controls-now-use-consistent-sizing-with-undo-and.md +6 -0
  161. package/corpus/templates/slides/netlify.toml +3 -1
  162. package/corpus/templates/slides/package.json +0 -1
  163. package/corpus/templates/slides/server/lib/chat-attachments.ts +18 -0
  164. package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -0
  165. package/corpus/templates/slides/vite.config.ts +0 -1
  166. package/corpus/toolkit/CHANGELOG.md +13 -0
  167. package/corpus/toolkit/README.md +19 -0
  168. package/corpus/toolkit/agent-native.eject.json +38 -0
  169. package/corpus/toolkit/package.json +29 -1
  170. package/corpus/toolkit/src/canvas-annotations/CanvasCommentPins.tsx +861 -0
  171. package/corpus/toolkit/src/canvas-annotations/DrawOverlay.tsx +1233 -0
  172. package/corpus/toolkit/src/canvas-annotations/index.ts +15 -0
  173. package/corpus/toolkit/src/canvas-annotations/types.ts +14 -0
  174. package/corpus/toolkit/src/canvas-interactions/canvas-interactions.ts +933 -0
  175. package/corpus/toolkit/src/canvas-interactions/index.ts +67 -0
  176. package/corpus/toolkit/src/design-tweaks/scrub-input-utils.ts +311 -0
  177. package/corpus/toolkit/src/design-tweaks/scrub-input.tsx +491 -0
  178. package/corpus/toolkit/src/design-tweaks/visual-style-controls.tsx +26 -171
  179. package/corpus/toolkit/src/index.ts +1 -0
  180. package/dist/a2a/client.d.ts +23 -0
  181. package/dist/a2a/client.d.ts.map +1 -1
  182. package/dist/a2a/client.js +309 -44
  183. package/dist/a2a/client.js.map +1 -1
  184. package/dist/a2a/correlation.d.ts +1 -0
  185. package/dist/a2a/correlation.d.ts.map +1 -1
  186. package/dist/a2a/correlation.js +27 -0
  187. package/dist/a2a/correlation.js.map +1 -1
  188. package/dist/a2a/invoke.d.ts.map +1 -1
  189. package/dist/a2a/invoke.js +4 -2
  190. package/dist/a2a/invoke.js.map +1 -1
  191. package/dist/a2a/types.d.ts +4 -0
  192. package/dist/a2a/types.d.ts.map +1 -1
  193. package/dist/a2a/types.js.map +1 -1
  194. package/dist/action.d.ts +3 -0
  195. package/dist/action.d.ts.map +1 -1
  196. package/dist/action.js.map +1 -1
  197. package/dist/agent/action-continuation-guidance.d.ts.map +1 -1
  198. package/dist/agent/action-continuation-guidance.js +2 -1
  199. package/dist/agent/action-continuation-guidance.js.map +1 -1
  200. package/dist/agent/engine/registry.d.ts.map +1 -1
  201. package/dist/agent/engine/registry.js +82 -8
  202. package/dist/agent/engine/registry.js.map +1 -1
  203. package/dist/agent/production-agent.d.ts +40 -1
  204. package/dist/agent/production-agent.d.ts.map +1 -1
  205. package/dist/agent/production-agent.js +113 -15
  206. package/dist/agent/production-agent.js.map +1 -1
  207. package/dist/agent/run-loop-with-resume.d.ts +21 -0
  208. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  209. package/dist/agent/run-loop-with-resume.js +163 -4
  210. package/dist/agent/run-loop-with-resume.js.map +1 -1
  211. package/dist/agent/run-manager.d.ts +16 -9
  212. package/dist/agent/run-manager.d.ts.map +1 -1
  213. package/dist/agent/run-manager.js +93 -42
  214. package/dist/agent/run-manager.js.map +1 -1
  215. package/dist/agent/thread-debug-history.d.ts +10 -0
  216. package/dist/agent/thread-debug-history.d.ts.map +1 -0
  217. package/dist/agent/thread-debug-history.js +68 -0
  218. package/dist/agent/thread-debug-history.js.map +1 -0
  219. package/dist/agent/types.d.ts +3 -1
  220. package/dist/agent/types.d.ts.map +1 -1
  221. package/dist/agent/types.js.map +1 -1
  222. package/dist/cli/create.d.ts.map +1 -1
  223. package/dist/cli/create.js +6 -1
  224. package/dist/cli/create.js.map +1 -1
  225. package/dist/cli/templates-meta.js +2 -2
  226. package/dist/cli/templates-meta.js.map +1 -1
  227. package/dist/client/agent-chat-adapter.d.ts +1 -1
  228. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  229. package/dist/client/agent-chat-adapter.js +76 -13
  230. package/dist/client/agent-chat-adapter.js.map +1 -1
  231. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  232. package/dist/client/resources/McpIntegrationDialog.js +1 -1
  233. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  234. package/dist/client/sse-event-processor.d.ts.map +1 -1
  235. package/dist/client/sse-event-processor.js +12 -3
  236. package/dist/client/sse-event-processor.js.map +1 -1
  237. package/dist/collab/routes.d.ts +1 -1
  238. package/dist/db/client.d.ts.map +1 -1
  239. package/dist/db/client.js +48 -17
  240. package/dist/db/client.js.map +1 -1
  241. package/dist/guards/no-env-credentials.d.ts.map +1 -1
  242. package/dist/guards/no-env-credentials.js +2 -0
  243. package/dist/guards/no-env-credentials.js.map +1 -1
  244. package/dist/integrations/adapters/slack.js +7 -3
  245. package/dist/integrations/adapters/slack.js.map +1 -1
  246. package/dist/localization/default-messages.d.ts +43 -0
  247. package/dist/localization/default-messages.d.ts.map +1 -1
  248. package/dist/localization/default-messages.js +43 -0
  249. package/dist/localization/default-messages.js.map +1 -1
  250. package/dist/observability/traces.d.ts +3 -1
  251. package/dist/observability/traces.d.ts.map +1 -1
  252. package/dist/observability/traces.js +73 -4
  253. package/dist/observability/traces.js.map +1 -1
  254. package/dist/progress/routes.d.ts +1 -1
  255. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  256. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  257. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  258. package/dist/scripts/call-agent.d.ts.map +1 -1
  259. package/dist/scripts/call-agent.js +237 -12
  260. package/dist/scripts/call-agent.js.map +1 -1
  261. package/dist/scripts/describe-workspace-apps.js +2 -2
  262. package/dist/scripts/describe-workspace-apps.js.map +1 -1
  263. package/dist/secrets/crypto.d.ts +36 -21
  264. package/dist/secrets/crypto.d.ts.map +1 -1
  265. package/dist/secrets/crypto.js +134 -33
  266. package/dist/secrets/crypto.js.map +1 -1
  267. package/dist/secrets/storage.d.ts +6 -6
  268. package/dist/secrets/storage.d.ts.map +1 -1
  269. package/dist/secrets/storage.js +43 -26
  270. package/dist/secrets/storage.js.map +1 -1
  271. package/dist/server/agent-capabilities.js +1 -1
  272. package/dist/server/agent-capabilities.js.map +1 -1
  273. package/dist/server/agent-chat/action-filters-a2a.d.ts +17 -3
  274. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  275. package/dist/server/agent-chat/action-filters-a2a.js +94 -3
  276. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  277. package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
  278. package/dist/server/agent-chat/context-tools.js +2 -0
  279. package/dist/server/agent-chat/context-tools.js.map +1 -1
  280. package/dist/server/agent-chat/plugin-options.d.ts +23 -0
  281. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  282. package/dist/server/agent-chat/plugin-options.js +4 -1
  283. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  284. package/dist/server/agent-chat/prompt-resources.js +1 -1
  285. package/dist/server/agent-chat/prompt-resources.js.map +1 -1
  286. package/dist/server/agent-chat-plugin.d.ts +2 -1
  287. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  288. package/dist/server/agent-chat-plugin.js +79 -56
  289. package/dist/server/agent-chat-plugin.js.map +1 -1
  290. package/dist/server/realtime-token.d.ts +1 -1
  291. package/dist/templates/default/.agents/skills/secrets/SKILL.md +19 -7
  292. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
  293. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
  294. package/dist/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
  295. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
  296. package/dist/vite/client.d.ts.map +1 -1
  297. package/dist/vite/client.js +1 -0
  298. package/dist/vite/client.js.map +1 -1
  299. package/docs/content/agent-native-toolkit.mdx +13 -6
  300. package/docs/content/cloneable-saas.mdx +5 -5
  301. package/docs/content/creating-templates.mdx +9 -0
  302. package/docs/content/deployment.mdx +18 -12
  303. package/docs/content/doctor.mdx +8 -1
  304. package/docs/content/drop-in-agent.mdx +1 -1
  305. package/docs/content/extensions.mdx +18 -18
  306. package/docs/content/faq.mdx +4 -4
  307. package/docs/content/key-concepts.mdx +30 -21
  308. package/docs/content/security.mdx +2 -2
  309. package/docs/content/template-brain-developers.mdx +4 -1
  310. package/docs/content/template-mail-developers.mdx +4 -1
  311. package/docs/content/what-is-agent-native.mdx +6 -6
  312. package/package.json +3 -3
  313. package/src/a2a/client.ts +449 -66
  314. package/src/a2a/correlation.ts +31 -0
  315. package/src/a2a/invoke.ts +4 -2
  316. package/src/a2a/types.ts +4 -0
  317. package/src/action.ts +3 -0
  318. package/src/agent/action-continuation-guidance.ts +2 -1
  319. package/src/agent/engine/registry.ts +98 -6
  320. package/src/agent/production-agent.ts +151 -18
  321. package/src/agent/run-loop-with-resume.ts +190 -3
  322. package/src/agent/run-manager.ts +117 -41
  323. package/src/agent/thread-debug-history.ts +86 -0
  324. package/src/agent/types.ts +3 -1
  325. package/src/cli/create.ts +11 -1
  326. package/src/cli/templates-meta.ts +2 -2
  327. package/src/client/agent-chat-adapter.ts +91 -13
  328. package/src/client/resources/McpIntegrationDialog.tsx +11 -9
  329. package/src/client/sse-event-processor.ts +17 -3
  330. package/src/db/client.ts +57 -17
  331. package/src/guards/no-env-credentials.ts +2 -0
  332. package/src/integrations/adapters/slack.ts +8 -3
  333. package/src/localization/default-messages.ts +44 -0
  334. package/src/observability/traces.ts +78 -5
  335. package/src/scripts/call-agent.ts +294 -11
  336. package/src/scripts/describe-workspace-apps.ts +2 -2
  337. package/src/secrets/crypto.ts +156 -35
  338. package/src/secrets/storage.ts +64 -26
  339. package/src/server/agent-capabilities.ts +1 -1
  340. package/src/server/agent-chat/action-filters-a2a.ts +113 -7
  341. package/src/server/agent-chat/context-tools.ts +2 -0
  342. package/src/server/agent-chat/plugin-options.ts +34 -1
  343. package/src/server/agent-chat/prompt-resources.ts +1 -1
  344. package/src/server/agent-chat-plugin.ts +91 -59
  345. package/src/templates/default/.agents/skills/secrets/SKILL.md +19 -7
  346. package/src/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
  347. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
  348. package/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
  349. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
  350. package/src/vite/client.ts +1 -0
@@ -0,0 +1,933 @@
1
+ /**
2
+ * Headless canvas interaction primitives shared by visual editors.
3
+ *
4
+ * This module deliberately knows nothing about React, DOM elements, or how an
5
+ * application persists objects. An app supplies its object adapter and turns
6
+ * the returned semantic commands into its own mutations.
7
+ */
8
+
9
+ export const DEFAULT_CANVAS_DRAG_THRESHOLD = 3;
10
+ export const DEFAULT_CANVAS_NUDGE = 1;
11
+ export const DEFAULT_CANVAS_ACCELERATED_NUDGE = 10;
12
+ export const DEFAULT_CANVAS_MIN_SIZE = 24;
13
+
14
+ export type CanvasTextActivation = "single-click" | "double-click";
15
+ export type CanvasEscapeBehavior =
16
+ | "select-object"
17
+ | "clear-selection"
18
+ | "cancel-edit";
19
+ export type CanvasDuplicateModifier = "alt" | "meta" | "ctrl" | "none";
20
+ export type CanvasResizeHandle =
21
+ | "nw"
22
+ | "n"
23
+ | "ne"
24
+ | "w"
25
+ | "e"
26
+ | "sw"
27
+ | "s"
28
+ | "se";
29
+
30
+ export interface CanvasPoint {
31
+ x: number;
32
+ y: number;
33
+ }
34
+
35
+ export interface CanvasRect extends CanvasPoint {
36
+ width: number;
37
+ height: number;
38
+ }
39
+
40
+ export interface CanvasViewport {
41
+ left: number;
42
+ top: number;
43
+ width: number;
44
+ height: number;
45
+ }
46
+
47
+ export interface CanvasSize {
48
+ width: number;
49
+ height: number;
50
+ }
51
+
52
+ export interface CanvasModifiers {
53
+ altKey?: boolean;
54
+ ctrlKey?: boolean;
55
+ metaKey?: boolean;
56
+ shiftKey?: boolean;
57
+ }
58
+
59
+ export interface CanvasTextEditPolicy {
60
+ activation?: CanvasTextActivation;
61
+ escapeBehavior?: CanvasEscapeBehavior;
62
+ }
63
+
64
+ export interface CanvasDragPolicy {
65
+ threshold?: number;
66
+ duplicateModifier?: CanvasDuplicateModifier;
67
+ }
68
+
69
+ export interface CanvasNudgePolicy {
70
+ amount?: number;
71
+ acceleratedAmount?: number;
72
+ }
73
+
74
+ /** The interaction surface an app can durably support. */
75
+ export interface CanvasInteractionCapabilities {
76
+ selection: boolean;
77
+ multiSelection: boolean;
78
+ move: boolean;
79
+ resize: boolean;
80
+ textEditing: boolean;
81
+ nudge: boolean;
82
+ duplicate: boolean;
83
+ clipboard: boolean;
84
+ delete: boolean;
85
+ arrange: boolean;
86
+ snapping: boolean;
87
+ alignment: boolean;
88
+ distribution: boolean;
89
+ grouping: boolean;
90
+ rotation: boolean;
91
+ marquee: boolean;
92
+ }
93
+
94
+ export const DEFAULT_CANVAS_INTERACTION_CAPABILITIES: CanvasInteractionCapabilities =
95
+ {
96
+ selection: true,
97
+ multiSelection: false,
98
+ move: true,
99
+ resize: true,
100
+ textEditing: true,
101
+ nudge: true,
102
+ duplicate: true,
103
+ clipboard: true,
104
+ delete: true,
105
+ arrange: true,
106
+ snapping: false,
107
+ alignment: false,
108
+ distribution: false,
109
+ grouping: false,
110
+ rotation: false,
111
+ marquee: false,
112
+ };
113
+
114
+ export interface CanvasInteractionConfig {
115
+ capabilities?: Partial<CanvasInteractionCapabilities>;
116
+ textEditing?: CanvasTextEditPolicy;
117
+ drag?: CanvasDragPolicy;
118
+ nudge?: CanvasNudgePolicy;
119
+ minSize?: number;
120
+ shortcuts?: readonly CanvasShortcut[];
121
+ }
122
+
123
+ /** The only persistence seam required by the shared interaction core. */
124
+ export interface CanvasInteractionAdapter<TObjectId = string> {
125
+ readonly capabilities: CanvasInteractionCapabilities;
126
+ dispatch(command: CanvasCommand<TObjectId>): CanvasCommandDispatchResult;
127
+ }
128
+
129
+ export type CanvasCommandDispatchResult =
130
+ | { handled: true }
131
+ | {
132
+ handled: false;
133
+ reason: "no-adapter" | "unsupported" | "unhandled";
134
+ };
135
+
136
+ export type CanvasCommandId =
137
+ | "select-all"
138
+ | "undo"
139
+ | "redo"
140
+ | "copy"
141
+ | "cut"
142
+ | "paste"
143
+ | "duplicate"
144
+ | "delete"
145
+ | "nudge"
146
+ | "arrange-front"
147
+ | "arrange-back"
148
+ | "bring-forward"
149
+ | "bring-to-front"
150
+ | "send-backward"
151
+ | "send-to-back"
152
+ | "align-left"
153
+ | "align-center"
154
+ | "align-right"
155
+ | "align-top"
156
+ | "align-middle"
157
+ | "align-bottom"
158
+ | "distribute-horizontal"
159
+ | "distribute-vertical"
160
+ | "group"
161
+ | "ungroup"
162
+ | "frame-selection"
163
+ | "escape"
164
+ | "enter"
165
+ | "nudge-left"
166
+ | "nudge-right"
167
+ | "nudge-up"
168
+ | "nudge-down";
169
+
170
+ export interface CanvasCommand<TObjectId = string> {
171
+ id: CanvasCommandId;
172
+ objectIds?: readonly TObjectId[];
173
+ delta?: CanvasPoint;
174
+ }
175
+
176
+ export type CanvasShortcutModifier =
177
+ | "primary"
178
+ | "shift"
179
+ | "alt"
180
+ | "ctrl"
181
+ | "meta";
182
+
183
+ export interface CanvasShortcut {
184
+ command: CanvasCommandId;
185
+ key: string;
186
+ /**
187
+ * Optional physical key identity for shifted punctuation. `KeyboardEvent.key`
188
+ * changes `]` to `}` under Shift, while `KeyboardEvent.code` stays stable.
189
+ */
190
+ code?: string;
191
+ modifiers?: readonly CanvasShortcutModifier[];
192
+ }
193
+
194
+ export interface CanvasShortcutInput extends CanvasModifiers {
195
+ key: string;
196
+ code?: string;
197
+ }
198
+
199
+ export interface CanvasShortcutRegistry {
200
+ readonly shortcuts: readonly CanvasShortcut[];
201
+ resolve(input: CanvasShortcutInput): CanvasCommandId | null;
202
+ }
203
+
204
+ export interface CanvasTextActivationInput {
205
+ clickCount: number;
206
+ textEditable: boolean;
207
+ }
208
+
209
+ export type CanvasTextActivationResult = "edit" | "select";
210
+
211
+ export interface CanvasEscapeInput<TObjectId = string> {
212
+ editingObjectId: TObjectId | null;
213
+ selectedObjectIds?: readonly TObjectId[];
214
+ }
215
+
216
+ export type CanvasEscapeResult<TObjectId = string> =
217
+ | {
218
+ action: "select-object";
219
+ editingObjectId: null;
220
+ selectedObjectIds: readonly TObjectId[];
221
+ }
222
+ | {
223
+ action: "cancel-edit";
224
+ editingObjectId: null;
225
+ selectedObjectIds: readonly TObjectId[];
226
+ }
227
+ | {
228
+ action: "clear-selection";
229
+ editingObjectId: null;
230
+ selectedObjectIds: readonly TObjectId[];
231
+ }
232
+ | {
233
+ action: "none";
234
+ editingObjectId: null;
235
+ selectedObjectIds: readonly TObjectId[];
236
+ };
237
+
238
+ export interface CanvasResizeInput {
239
+ handle: CanvasResizeHandle;
240
+ delta: CanvasPoint;
241
+ preserveAspectRatio?: boolean;
242
+ minWidth?: number;
243
+ minHeight?: number;
244
+ }
245
+
246
+ export interface CanvasNudgeInput extends CanvasModifiers {
247
+ key: string;
248
+ }
249
+
250
+ export interface CanvasNudgeResult {
251
+ command: Extract<
252
+ CanvasCommandId,
253
+ "nudge-left" | "nudge-right" | "nudge-up" | "nudge-down"
254
+ >;
255
+ delta: CanvasPoint;
256
+ }
257
+
258
+ /** A pointer expressed in browser client coordinates. */
259
+ export interface CanvasGesturePointer extends CanvasPoint, CanvasModifiers {}
260
+
261
+ export type CanvasGestureKind = "move" | "resize";
262
+ export type CanvasGesturePhase = "idle" | "pending" | "active";
263
+
264
+ export interface CanvasGestureBase<TObjectId = string> {
265
+ readonly kind: CanvasGestureKind;
266
+ readonly objectIds: readonly TObjectId[];
267
+ /** Pointer location when the gesture began, in browser client coordinates. */
268
+ readonly startPointer: CanvasGesturePointer;
269
+ readonly pointer: CanvasGesturePointer;
270
+ readonly clientDelta: CanvasPoint;
271
+ readonly canvasDelta: CanvasPoint;
272
+ /** Whether this gesture should create copies as part of its one final commit. */
273
+ readonly duplicate: boolean;
274
+ }
275
+
276
+ export interface CanvasMoveGesture<
277
+ TObjectId = string,
278
+ > extends CanvasGestureBase<TObjectId> {
279
+ readonly kind: "move";
280
+ }
281
+
282
+ export interface CanvasResizeGesture<
283
+ TObjectId = string,
284
+ > extends CanvasGestureBase<TObjectId> {
285
+ readonly kind: "resize";
286
+ readonly handle: CanvasResizeHandle;
287
+ readonly startRect: CanvasRect;
288
+ readonly rect: CanvasRect;
289
+ }
290
+
291
+ export type CanvasGesture<TObjectId = string> =
292
+ | CanvasMoveGesture<TObjectId>
293
+ | CanvasResizeGesture<TObjectId>;
294
+
295
+ export interface CanvasMoveGestureStart<TObjectId = string> {
296
+ readonly kind: "move";
297
+ readonly objectIds: readonly TObjectId[];
298
+ readonly pointer: CanvasGesturePointer;
299
+ readonly viewport: CanvasViewport;
300
+ readonly canvas: CanvasSize;
301
+ }
302
+
303
+ export interface CanvasResizeGestureStart<TObjectId = string> {
304
+ readonly kind: "resize";
305
+ readonly objectIds: readonly TObjectId[];
306
+ readonly pointer: CanvasGesturePointer;
307
+ readonly viewport: CanvasViewport;
308
+ readonly canvas: CanvasSize;
309
+ readonly handle: CanvasResizeHandle;
310
+ readonly rect: CanvasRect;
311
+ }
312
+
313
+ export type CanvasGestureStart<TObjectId = string> =
314
+ | CanvasMoveGestureStart<TObjectId>
315
+ | CanvasResizeGestureStart<TObjectId>;
316
+
317
+ /** Typed result returned by host preview, commit, and cancel callbacks. */
318
+ export type CanvasGestureAdapterResult =
319
+ | { handled: true }
320
+ | { handled: false; reason: "unsupported" | "unhandled" };
321
+
322
+ /**
323
+ * Persistence-neutral callbacks for a single object gesture. Previews are
324
+ * transient; `commit` is invoked at most once after an active gesture ends.
325
+ */
326
+ export interface CanvasGestureAdapter<TObjectId = string> {
327
+ preview?(gesture: CanvasGesture<TObjectId>): CanvasGestureAdapterResult;
328
+ commit(gesture: CanvasGesture<TObjectId>): CanvasGestureAdapterResult;
329
+ cancel?(gesture: CanvasGesture<TObjectId>): CanvasGestureAdapterResult;
330
+ }
331
+
332
+ export interface CanvasGestureControllerConfig<
333
+ TObjectId = string,
334
+ > extends CanvasInteractionConfig {
335
+ adapter: CanvasGestureAdapter<TObjectId>;
336
+ }
337
+
338
+ export interface CanvasGestureState<TObjectId = string> {
339
+ readonly phase: CanvasGesturePhase;
340
+ readonly gesture: CanvasGesture<TObjectId> | null;
341
+ }
342
+
343
+ export type CanvasGestureBeginResult<TObjectId = string> =
344
+ | { accepted: true; state: CanvasGestureState<TObjectId> }
345
+ | {
346
+ accepted: false;
347
+ reason: "unsupported" | "already-active" | "invalid-resize";
348
+ state: CanvasGestureState<TObjectId>;
349
+ };
350
+
351
+ export type CanvasGestureUpdateResult<TObjectId = string> =
352
+ | { phase: "idle"; state: CanvasGestureState<TObjectId> }
353
+ | { phase: "pending"; state: CanvasGestureState<TObjectId> }
354
+ | {
355
+ phase: "active";
356
+ gesture: CanvasGesture<TObjectId>;
357
+ preview: CanvasGestureAdapterResult | null;
358
+ state: CanvasGestureState<TObjectId>;
359
+ };
360
+
361
+ export type CanvasGestureEndResult<TObjectId = string> =
362
+ | {
363
+ committed: true;
364
+ gesture: CanvasGesture<TObjectId>;
365
+ result: CanvasGestureAdapterResult;
366
+ state: CanvasGestureState<TObjectId>;
367
+ }
368
+ | {
369
+ committed: false;
370
+ reason: "idle" | "below-threshold" | "unsupported";
371
+ state: CanvasGestureState<TObjectId>;
372
+ };
373
+
374
+ export type CanvasGestureCancelResult<TObjectId = string> =
375
+ | {
376
+ cancelled: true;
377
+ gesture: CanvasGesture<TObjectId>;
378
+ result: CanvasGestureAdapterResult | null;
379
+ state: CanvasGestureState<TObjectId>;
380
+ }
381
+ | { cancelled: false; reason: "idle"; state: CanvasGestureState<TObjectId> };
382
+
383
+ export const DEFAULT_CANVAS_SHORTCUTS: readonly CanvasShortcut[] = [
384
+ { command: "select-all", key: "a", modifiers: ["primary"] },
385
+ { command: "undo", key: "z", modifiers: ["primary"] },
386
+ { command: "redo", key: "z", modifiers: ["primary", "shift"] },
387
+ { command: "copy", key: "c", modifiers: ["primary"] },
388
+ { command: "cut", key: "x", modifiers: ["primary"] },
389
+ { command: "paste", key: "v", modifiers: ["primary"] },
390
+ { command: "duplicate", key: "d", modifiers: ["primary"] },
391
+ { command: "delete", key: "Backspace" },
392
+ { command: "delete", key: "Delete" },
393
+ {
394
+ command: "arrange-front",
395
+ key: "]",
396
+ code: "BracketRight",
397
+ modifiers: ["primary", "shift"],
398
+ },
399
+ {
400
+ command: "arrange-back",
401
+ key: "[",
402
+ code: "BracketLeft",
403
+ modifiers: ["primary", "shift"],
404
+ },
405
+ ];
406
+
407
+ function primaryModifierIsPressed(modifiers: CanvasModifiers): boolean {
408
+ return Boolean(modifiers.metaKey || modifiers.ctrlKey);
409
+ }
410
+
411
+ function hasShortcutModifier(
412
+ modifier: CanvasShortcutModifier,
413
+ input: CanvasShortcutInput,
414
+ ): boolean {
415
+ switch (modifier) {
416
+ case "primary":
417
+ return primaryModifierIsPressed(input);
418
+ case "shift":
419
+ return Boolean(input.shiftKey);
420
+ case "alt":
421
+ return Boolean(input.altKey);
422
+ case "ctrl":
423
+ return Boolean(input.ctrlKey);
424
+ case "meta":
425
+ return Boolean(input.metaKey);
426
+ }
427
+ }
428
+
429
+ function hasOnlyShortcutModifiers(
430
+ shortcut: CanvasShortcut,
431
+ input: CanvasShortcutInput,
432
+ ): boolean {
433
+ const modifiers = new Set(shortcut.modifiers ?? []);
434
+ const expectedPrimary = modifiers.has("primary");
435
+ const expectedCtrl = modifiers.has("ctrl") || expectedPrimary;
436
+ const expectedMeta = modifiers.has("meta") || expectedPrimary;
437
+
438
+ return (
439
+ Boolean(input.shiftKey) === modifiers.has("shift") &&
440
+ Boolean(input.altKey) === modifiers.has("alt") &&
441
+ (!input.ctrlKey || expectedCtrl) &&
442
+ (!input.metaKey || expectedMeta) &&
443
+ (!expectedPrimary || primaryModifierIsPressed(input))
444
+ );
445
+ }
446
+
447
+ /** Resolves click-to-edit without attaching event listeners. */
448
+ export function resolveCanvasTextActivation(
449
+ input: CanvasTextActivationInput,
450
+ policy: CanvasTextEditPolicy = {},
451
+ ): CanvasTextActivationResult {
452
+ if (!input.textEditable) return "select";
453
+ const activation = policy.activation ?? "double-click";
454
+ return input.clickCount >= (activation === "single-click" ? 1 : 2)
455
+ ? "edit"
456
+ : "select";
457
+ }
458
+
459
+ /**
460
+ * Escape has one explicit owner: editing wins over any box-selection state.
461
+ * The host applies this result before handing Escape to generic UI dismissal.
462
+ */
463
+ export function resolveCanvasEscape<TObjectId>(
464
+ input: CanvasEscapeInput<TObjectId>,
465
+ policy: CanvasTextEditPolicy = {},
466
+ ): CanvasEscapeResult<TObjectId> {
467
+ const selectedObjectIds = input.selectedObjectIds ?? [];
468
+ if (input.editingObjectId === null) {
469
+ return { action: "none", editingObjectId: null, selectedObjectIds };
470
+ }
471
+
472
+ if ((policy.escapeBehavior ?? "select-object") === "cancel-edit") {
473
+ return { action: "cancel-edit", editingObjectId: null, selectedObjectIds };
474
+ }
475
+
476
+ if ((policy.escapeBehavior ?? "select-object") === "clear-selection") {
477
+ return {
478
+ action: "clear-selection",
479
+ editingObjectId: null,
480
+ selectedObjectIds: [],
481
+ };
482
+ }
483
+
484
+ return {
485
+ action: "select-object",
486
+ editingObjectId: null,
487
+ selectedObjectIds: [input.editingObjectId],
488
+ };
489
+ }
490
+
491
+ /** Converts a browser client point into unscaled canvas coordinates. */
492
+ export function clientPointToCanvasPoint(
493
+ point: CanvasPoint,
494
+ viewport: CanvasViewport,
495
+ canvas: CanvasSize,
496
+ ): CanvasPoint {
497
+ if (viewport.width <= 0 || viewport.height <= 0) return { x: 0, y: 0 };
498
+ return {
499
+ x: ((point.x - viewport.left) / viewport.width) * canvas.width,
500
+ y: ((point.y - viewport.top) / viewport.height) * canvas.height,
501
+ };
502
+ }
503
+
504
+ /** Converts a client-space drag delta to the canvas's unscaled coordinate space. */
505
+ export function clientDeltaToCanvasDelta(
506
+ delta: CanvasPoint,
507
+ viewport: CanvasViewport,
508
+ canvas: CanvasSize,
509
+ ): CanvasPoint {
510
+ if (viewport.width <= 0 || viewport.height <= 0) return { x: 0, y: 0 };
511
+ return {
512
+ x: (delta.x / viewport.width) * canvas.width,
513
+ y: (delta.y / viewport.height) * canvas.height,
514
+ };
515
+ }
516
+
517
+ /** Whether the client pointer crossed the intentional-drag threshold. */
518
+ export function hasCrossedCanvasDragThreshold(
519
+ start: CanvasPoint,
520
+ current: CanvasPoint,
521
+ threshold = DEFAULT_CANVAS_DRAG_THRESHOLD,
522
+ ): boolean {
523
+ const dx = current.x - start.x;
524
+ const dy = current.y - start.y;
525
+ return dx * dx + dy * dy >= threshold * threshold;
526
+ }
527
+
528
+ /** Locks a drag to its dominant axis, used for Shift-drag. */
529
+ export function constrainCanvasDragDelta(
530
+ delta: CanvasPoint,
531
+ lockAxis = false,
532
+ ): CanvasPoint {
533
+ if (!lockAxis) return delta;
534
+ return Math.abs(delta.x) >= Math.abs(delta.y)
535
+ ? { x: delta.x, y: 0 }
536
+ : { x: 0, y: delta.y };
537
+ }
538
+
539
+ /**
540
+ * Resizes against the opposite edge. Shift preserves aspect ratio on corners;
541
+ * midpoint handles stay single-axis so their fixed edge remains predictable.
542
+ */
543
+ export function resizeCanvasRect(
544
+ start: CanvasRect,
545
+ input: CanvasResizeInput,
546
+ ): CanvasRect {
547
+ const fromWest =
548
+ input.handle === "nw" || input.handle === "w" || input.handle === "sw";
549
+ const fromEast =
550
+ input.handle === "ne" || input.handle === "e" || input.handle === "se";
551
+ const fromNorth =
552
+ input.handle === "nw" || input.handle === "n" || input.handle === "ne";
553
+ const fromSouth =
554
+ input.handle === "sw" || input.handle === "s" || input.handle === "se";
555
+ const resizesHorizontally = fromWest || fromEast;
556
+ const resizesVertically = fromNorth || fromSouth;
557
+ let width =
558
+ start.width + (fromWest ? -input.delta.x : fromEast ? input.delta.x : 0);
559
+ let height =
560
+ start.height + (fromNorth ? -input.delta.y : fromSouth ? input.delta.y : 0);
561
+
562
+ const minWidth = input.minWidth ?? DEFAULT_CANVAS_MIN_SIZE;
563
+ const minHeight = input.minHeight ?? DEFAULT_CANVAS_MIN_SIZE;
564
+
565
+ if (
566
+ input.preserveAspectRatio &&
567
+ resizesHorizontally &&
568
+ resizesVertically &&
569
+ start.width > 0 &&
570
+ start.height > 0
571
+ ) {
572
+ const horizontalScale = width / start.width;
573
+ const verticalScale = height / start.height;
574
+ const scale =
575
+ Math.abs(horizontalScale - 1) >= Math.abs(verticalScale - 1)
576
+ ? horizontalScale
577
+ : verticalScale;
578
+ const minScale = Math.max(minWidth / start.width, minHeight / start.height);
579
+ width = start.width * Math.max(minScale, scale);
580
+ height = start.height * Math.max(minScale, scale);
581
+ }
582
+
583
+ width = Math.max(minWidth, width);
584
+ height = Math.max(minHeight, height);
585
+ return {
586
+ x: fromWest ? start.x + start.width - width : start.x,
587
+ y: fromNorth ? start.y + start.height - height : start.y,
588
+ width,
589
+ height,
590
+ };
591
+ }
592
+
593
+ /** Whether this drag gesture should duplicate selected objects before moving. */
594
+ export function shouldDuplicateCanvasDrag(
595
+ modifiers: CanvasModifiers,
596
+ duplicateModifier: CanvasDuplicateModifier = "alt",
597
+ ): boolean {
598
+ switch (duplicateModifier) {
599
+ case "alt":
600
+ return Boolean(modifiers.altKey);
601
+ case "meta":
602
+ return Boolean(modifiers.metaKey);
603
+ case "ctrl":
604
+ return Boolean(modifiers.ctrlKey);
605
+ case "none":
606
+ return false;
607
+ }
608
+ }
609
+
610
+ /** Resolves standard arrow-key movement, including Shift's accelerated nudge. */
611
+ export function resolveCanvasNudge(
612
+ input: CanvasNudgeInput,
613
+ policy: CanvasNudgePolicy = {},
614
+ ): CanvasNudgeResult | null {
615
+ const amount = input.shiftKey
616
+ ? (policy.acceleratedAmount ?? DEFAULT_CANVAS_ACCELERATED_NUDGE)
617
+ : (policy.amount ?? DEFAULT_CANVAS_NUDGE);
618
+ switch (input.key) {
619
+ case "ArrowLeft":
620
+ return { command: "nudge-left", delta: { x: -amount, y: 0 } };
621
+ case "ArrowRight":
622
+ return { command: "nudge-right", delta: { x: amount, y: 0 } };
623
+ case "ArrowUp":
624
+ return { command: "nudge-up", delta: { x: 0, y: -amount } };
625
+ case "ArrowDown":
626
+ return { command: "nudge-down", delta: { x: 0, y: amount } };
627
+ default:
628
+ return null;
629
+ }
630
+ }
631
+
632
+ /** Looks up a semantic command without referring to platform-specific events. */
633
+ export function resolveCanvasShortcut(
634
+ input: CanvasShortcutInput,
635
+ shortcuts: readonly CanvasShortcut[] = DEFAULT_CANVAS_SHORTCUTS,
636
+ ): CanvasCommandId | null {
637
+ const normalizedKey = input.key.toLowerCase();
638
+ const shortcut = shortcuts.find(
639
+ (candidate) =>
640
+ (candidate.key.toLowerCase() === normalizedKey ||
641
+ (candidate.code !== undefined && candidate.code === input.code)) &&
642
+ (candidate.modifiers ?? []).every((modifier) =>
643
+ hasShortcutModifier(modifier, input),
644
+ ) &&
645
+ hasOnlyShortcutModifiers(candidate, input),
646
+ );
647
+ return shortcut?.command ?? null;
648
+ }
649
+
650
+ /** Creates a reusable, immutable shortcut lookup for one editor policy. */
651
+ export function createCanvasShortcutRegistry(
652
+ shortcuts: readonly CanvasShortcut[] = DEFAULT_CANVAS_SHORTCUTS,
653
+ ): CanvasShortcutRegistry {
654
+ return {
655
+ shortcuts,
656
+ resolve: (input) => resolveCanvasShortcut(input, shortcuts),
657
+ };
658
+ }
659
+
660
+ /**
661
+ * Creates an app-configured pure interaction core. `dispatch` is optional so
662
+ * hosts can either use the helpers directly or receive semantic commands via
663
+ * their adapter without the Toolkit owning any state.
664
+ */
665
+ export function createCanvasInteractionCore<TObjectId = string>(
666
+ config: CanvasInteractionConfig = {},
667
+ adapter?: CanvasInteractionAdapter<TObjectId>,
668
+ ) {
669
+ const textEditing = config.textEditing ?? {};
670
+ const capabilities: CanvasInteractionCapabilities = {
671
+ ...DEFAULT_CANVAS_INTERACTION_CAPABILITIES,
672
+ ...adapter?.capabilities,
673
+ ...config.capabilities,
674
+ };
675
+ const drag = config.drag ?? {};
676
+ const nudge = config.nudge ?? {};
677
+ const shortcuts = createCanvasShortcutRegistry(config.shortcuts);
678
+
679
+ const supportsCommand = (command: CanvasCommand<TObjectId>): boolean => {
680
+ switch (command.id) {
681
+ case "copy":
682
+ case "cut":
683
+ case "paste":
684
+ return capabilities.clipboard;
685
+ case "duplicate":
686
+ return capabilities.duplicate;
687
+ case "delete":
688
+ return capabilities.delete;
689
+ case "nudge":
690
+ case "nudge-left":
691
+ case "nudge-right":
692
+ case "nudge-up":
693
+ case "nudge-down":
694
+ return capabilities.nudge;
695
+ case "arrange-front":
696
+ case "arrange-back":
697
+ case "bring-forward":
698
+ case "bring-to-front":
699
+ case "send-backward":
700
+ case "send-to-back":
701
+ return capabilities.arrange;
702
+ case "align-left":
703
+ case "align-center":
704
+ case "align-right":
705
+ case "align-top":
706
+ case "align-middle":
707
+ case "align-bottom":
708
+ return capabilities.alignment;
709
+ case "distribute-horizontal":
710
+ case "distribute-vertical":
711
+ return capabilities.distribution;
712
+ case "group":
713
+ case "ungroup":
714
+ return capabilities.grouping;
715
+ case "frame-selection":
716
+ return capabilities.selection;
717
+ default:
718
+ return true;
719
+ }
720
+ };
721
+
722
+ return {
723
+ capabilities,
724
+ textActivation: (input: CanvasTextActivationInput) =>
725
+ capabilities.textEditing
726
+ ? resolveCanvasTextActivation(input, textEditing)
727
+ : "select",
728
+ escape: (input: CanvasEscapeInput<TObjectId>) =>
729
+ resolveCanvasEscape(input, textEditing),
730
+ clientPointToCanvas: (
731
+ point: CanvasPoint,
732
+ viewport: CanvasViewport,
733
+ canvas: CanvasSize,
734
+ ) => clientPointToCanvasPoint(point, viewport, canvas),
735
+ clientDeltaToCanvas: (
736
+ delta: CanvasPoint,
737
+ viewport: CanvasViewport,
738
+ canvas: CanvasSize,
739
+ ) => clientDeltaToCanvasDelta(delta, viewport, canvas),
740
+ hasCrossedDragThreshold: (start: CanvasPoint, current: CanvasPoint) =>
741
+ hasCrossedCanvasDragThreshold(start, current, drag.threshold),
742
+ constrainDrag: (delta: CanvasPoint, modifiers: CanvasModifiers = {}) =>
743
+ constrainCanvasDragDelta(delta, Boolean(modifiers.shiftKey)),
744
+ resize: (start: CanvasRect, input: CanvasResizeInput) =>
745
+ resizeCanvasRect(start, {
746
+ ...input,
747
+ minWidth: input.minWidth ?? config.minSize,
748
+ minHeight: input.minHeight ?? config.minSize,
749
+ }),
750
+ shouldDuplicateDrag: (modifiers: CanvasModifiers) =>
751
+ capabilities.duplicate &&
752
+ shouldDuplicateCanvasDrag(modifiers, drag.duplicateModifier),
753
+ nudge: (input: CanvasNudgeInput) =>
754
+ capabilities.nudge ? resolveCanvasNudge(input, nudge) : null,
755
+ shortcut: (input: CanvasShortcutInput) => shortcuts.resolve(input),
756
+ dispatch: (command: CanvasCommand<TObjectId>) => {
757
+ if (!adapter) return { handled: false, reason: "no-adapter" } as const;
758
+ if (!supportsCommand(command)) {
759
+ return { handled: false, reason: "unsupported" } as const;
760
+ }
761
+ return adapter.dispatch(command);
762
+ },
763
+ };
764
+ }
765
+
766
+ /**
767
+ * Creates a small state machine for one pointer gesture at a time. It keeps
768
+ * browser coordinates at its boundary, while every emitted preview and commit
769
+ * is in stable canvas coordinates. Hosts own rendering and persistence.
770
+ */
771
+ export function createCanvasGestureController<TObjectId = string>(
772
+ config: CanvasGestureControllerConfig<TObjectId>,
773
+ ) {
774
+ const core = createCanvasInteractionCore(config);
775
+ let start: CanvasGestureStart<TObjectId> | null = null;
776
+ let activeGesture: CanvasGesture<TObjectId> | null = null;
777
+
778
+ const state = (): CanvasGestureState<TObjectId> => ({
779
+ phase: activeGesture ? "active" : start ? "pending" : "idle",
780
+ gesture: activeGesture,
781
+ });
782
+
783
+ const canStart = (kind: CanvasGestureKind) =>
784
+ kind === "move" ? core.capabilities.move : core.capabilities.resize;
785
+
786
+ const buildGesture = (
787
+ gestureStart: CanvasGestureStart<TObjectId>,
788
+ pointer: CanvasGesturePointer,
789
+ ): CanvasGesture<TObjectId> => {
790
+ const clientDelta = {
791
+ x: pointer.x - gestureStart.pointer.x,
792
+ y: pointer.y - gestureStart.pointer.y,
793
+ };
794
+ const convertedDelta = clientDeltaToCanvasDelta(
795
+ clientDelta,
796
+ gestureStart.viewport,
797
+ gestureStart.canvas,
798
+ );
799
+ const duplicate =
800
+ gestureStart.kind === "move" && core.capabilities.duplicate
801
+ ? core.shouldDuplicateDrag(gestureStart.pointer) &&
802
+ core.shouldDuplicateDrag(pointer)
803
+ : false;
804
+
805
+ if (gestureStart.kind === "move") {
806
+ return {
807
+ kind: "move",
808
+ objectIds: gestureStart.objectIds,
809
+ startPointer: gestureStart.pointer,
810
+ pointer,
811
+ clientDelta,
812
+ canvasDelta: core.constrainDrag(convertedDelta, pointer),
813
+ duplicate,
814
+ };
815
+ }
816
+
817
+ return {
818
+ kind: "resize",
819
+ objectIds: gestureStart.objectIds,
820
+ startPointer: gestureStart.pointer,
821
+ pointer,
822
+ clientDelta,
823
+ canvasDelta: convertedDelta,
824
+ duplicate: false,
825
+ handle: gestureStart.handle,
826
+ startRect: gestureStart.rect,
827
+ rect: core.resize(gestureStart.rect, {
828
+ handle: gestureStart.handle,
829
+ delta: convertedDelta,
830
+ preserveAspectRatio: Boolean(pointer.shiftKey),
831
+ }),
832
+ };
833
+ };
834
+
835
+ const preview = (
836
+ gesture: CanvasGesture<TObjectId>,
837
+ ): CanvasGestureUpdateResult<TObjectId> => {
838
+ activeGesture = gesture;
839
+ return {
840
+ phase: "active",
841
+ gesture,
842
+ preview: config.adapter.preview?.(gesture) ?? null,
843
+ state: state(),
844
+ };
845
+ };
846
+
847
+ const update = (
848
+ pointer: CanvasGesturePointer,
849
+ ): CanvasGestureUpdateResult<TObjectId> => {
850
+ if (!start) return { phase: "idle", state: state() };
851
+ if (!activeGesture) {
852
+ const crossedThreshold = core.hasCrossedDragThreshold(
853
+ start.pointer,
854
+ pointer,
855
+ );
856
+ if (!crossedThreshold) return { phase: "pending", state: state() };
857
+ }
858
+ return preview(buildGesture(start, pointer));
859
+ };
860
+
861
+ const hasSamePointerState = (
862
+ first: CanvasGesturePointer,
863
+ second: CanvasGesturePointer,
864
+ ) =>
865
+ first.x === second.x &&
866
+ first.y === second.y &&
867
+ Boolean(first.altKey) === Boolean(second.altKey) &&
868
+ Boolean(first.ctrlKey) === Boolean(second.ctrlKey) &&
869
+ Boolean(first.metaKey) === Boolean(second.metaKey) &&
870
+ Boolean(first.shiftKey) === Boolean(second.shiftKey);
871
+
872
+ return {
873
+ capabilities: core.capabilities,
874
+ getState: state,
875
+ pointerDown: (
876
+ gestureStart: CanvasGestureStart<TObjectId>,
877
+ ): CanvasGestureBeginResult<TObjectId> => {
878
+ if (start) {
879
+ return { accepted: false, reason: "already-active", state: state() };
880
+ }
881
+ if (!canStart(gestureStart.kind)) {
882
+ return { accepted: false, reason: "unsupported", state: state() };
883
+ }
884
+ if (
885
+ gestureStart.kind === "resize" &&
886
+ (gestureStart.rect.width <= 0 || gestureStart.rect.height <= 0)
887
+ ) {
888
+ return { accepted: false, reason: "invalid-resize", state: state() };
889
+ }
890
+ start = gestureStart;
891
+ return { accepted: true, state: state() };
892
+ },
893
+ pointerMove: update,
894
+ pointerUp: (
895
+ pointer: CanvasGesturePointer,
896
+ ): CanvasGestureEndResult<TObjectId> => {
897
+ const shouldUpdate =
898
+ activeGesture === null ||
899
+ !hasSamePointerState(activeGesture.pointer, pointer);
900
+ const beforeEnd = shouldUpdate
901
+ ? update(pointer)
902
+ : ({ phase: "active", state: state() } as const);
903
+ const gesture = activeGesture;
904
+ start = null;
905
+ activeGesture = null;
906
+ if (!gesture) {
907
+ return {
908
+ committed: false,
909
+ reason: beforeEnd.phase === "idle" ? "idle" : "below-threshold",
910
+ state: state(),
911
+ };
912
+ }
913
+ return {
914
+ committed: true,
915
+ gesture,
916
+ result: config.adapter.commit(gesture),
917
+ state: state(),
918
+ };
919
+ },
920
+ cancel: (): CanvasGestureCancelResult<TObjectId> => {
921
+ const gesture = activeGesture;
922
+ start = null;
923
+ activeGesture = null;
924
+ if (!gesture) return { cancelled: false, reason: "idle", state: state() };
925
+ return {
926
+ cancelled: true,
927
+ gesture,
928
+ result: config.adapter.cancel?.(gesture) ?? null,
929
+ state: state(),
930
+ };
931
+ },
932
+ };
933
+ }