@agent-native/core 0.112.0 → 0.113.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (490) hide show
  1. package/corpus/README.md +4 -2
  2. package/corpus/core/CHANGELOG.md +22 -0
  3. package/corpus/core/docs/AGENTS.md +7 -0
  4. package/corpus/core/docs/content/integrations.mdx +63 -2
  5. package/corpus/core/docs/design/provider-integration-authority.md +117 -0
  6. package/corpus/core/package.json +6 -1
  7. package/corpus/core/scripts/materialize-source-corpus.mjs +9 -2
  8. package/corpus/core/src/cli/atomic-json-file.ts +324 -0
  9. package/corpus/core/src/cli/claude-code-participant.ts +515 -0
  10. package/corpus/core/src/cli/code-agent-executor.ts +18 -10
  11. package/corpus/core/src/cli/code-agent-runs.concurrent-worker.ts +33 -0
  12. package/corpus/core/src/cli/code-agent-runs.ts +55 -62
  13. package/corpus/core/src/cli/codex-cli-participant.ts +496 -0
  14. package/corpus/core/src/cli/multi-frontier-runs.ts +1246 -135
  15. package/corpus/core/src/client/chat/ChatHistoryList.tsx +7 -404
  16. package/corpus/core/src/integrations/index.ts +10 -0
  17. package/corpus/core/src/integrations/slack-oauth.ts +1 -0
  18. package/corpus/core/src/integrations/webhook-delivery.ts +55 -0
  19. package/corpus/core/src/notifications/channels.ts +4 -10
  20. package/corpus/core/src/provider-api/actions/custom-provider-registration.ts +197 -0
  21. package/corpus/core/src/provider-api/actions/delete-staged-dataset.ts +3 -42
  22. package/corpus/core/src/provider-api/actions/list-staged-datasets.ts +3 -45
  23. package/corpus/core/src/provider-api/actions/provider-api-audit.ts +88 -0
  24. package/corpus/core/src/provider-api/actions/provider-api.ts +460 -0
  25. package/corpus/core/src/provider-api/actions/query-staged-dataset.ts +3 -140
  26. package/corpus/core/src/provider-api/actions/staged-datasets.ts +359 -0
  27. package/corpus/core/src/provider-api/gong.ts +108 -0
  28. package/corpus/core/src/scripts/docs/source-search.ts +2 -1
  29. package/corpus/core/src/server/google-oauth-credentials.ts +80 -3
  30. package/corpus/core/src/server/index.ts +6 -0
  31. package/corpus/core/src/styles/chat-history-list.css +1 -313
  32. package/corpus/core/src/templates/default/.agents/skills/actions/SKILL.md +32 -12
  33. package/corpus/core/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +10 -0
  34. package/corpus/core/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  35. package/corpus/core/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +323 -0
  36. package/corpus/core/src/templates/default/.agents/skills/onboarding/SKILL.md +56 -0
  37. package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +273 -0
  38. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +11 -2
  39. package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  40. package/corpus/core/src/templates/default/AGENTS.md +25 -18
  41. package/corpus/core/src/templates/headless/.agents/skills/actions/SKILL.md +512 -0
  42. package/corpus/core/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +10 -0
  43. package/corpus/core/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  44. package/corpus/core/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +323 -0
  45. package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +273 -0
  46. package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  47. package/corpus/core/src/templates/headless/AGENTS.md +7 -0
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -12
  49. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +10 -0
  50. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  51. package/corpus/core/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  52. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +11 -2
  53. package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  54. package/corpus/core/src/templates/workspace-core/AGENTS.md +4 -0
  55. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
  56. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +32 -12
  57. package/corpus/templates/analytics/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  58. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +11 -2
  59. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  60. package/corpus/templates/analytics/actions/delete-staged-dataset.ts +3 -23
  61. package/corpus/templates/analytics/actions/list-staged-datasets.ts +3 -27
  62. package/corpus/templates/analytics/actions/provider-api-catalog.ts +16 -19
  63. package/corpus/templates/analytics/actions/provider-api-docs.ts +3 -5
  64. package/corpus/templates/analytics/actions/provider-api-request.ts +162 -185
  65. package/corpus/templates/analytics/actions/query-staged-dataset.ts +3 -47
  66. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +52 -161
  67. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +32 -12
  68. package/corpus/templates/assets/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  69. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +11 -2
  70. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  71. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +63 -160
  72. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +32 -12
  73. package/corpus/templates/brain/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  74. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +11 -2
  75. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  76. package/corpus/templates/brain/actions/_schemas.ts +13 -7
  77. package/corpus/templates/brain/actions/delete-staged-dataset.ts +3 -24
  78. package/corpus/templates/brain/actions/list-staged-datasets.ts +3 -28
  79. package/corpus/templates/brain/actions/provider-api-catalog.ts +5 -11
  80. package/corpus/templates/brain/actions/provider-api-docs.ts +3 -5
  81. package/corpus/templates/brain/actions/provider-api-request.ts +3 -37
  82. package/corpus/templates/brain/actions/query-staged-dataset.ts +3 -48
  83. package/corpus/templates/brain/actions/update-source.ts +5 -1
  84. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +48 -154
  85. package/corpus/templates/brain/app/lib/brain.ts +55 -0
  86. package/corpus/templates/brain/app/lib/ingest-handoff.ts +35 -0
  87. package/corpus/templates/brain/changelog/2026-07-19-slack-backfills-can-now-join-configured-public-channels-befo.md +6 -0
  88. package/corpus/templates/brain/changelog/2026-07-19-slack-backfills-now-continue-across-the-configured-number-of.md +6 -0
  89. package/corpus/templates/brain/changelog/2026-07-19-slack-backfills-now-start-with-a-safe-four-week-window-and-r.md +6 -0
  90. package/corpus/templates/brain/server/lib/connectors.ts +53 -2
  91. package/corpus/templates/brain/server/lib/slack-channel-membership.ts +44 -0
  92. package/corpus/templates/brain/shared/slack-source-config.ts +60 -0
  93. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +32 -12
  94. package/corpus/templates/calendar/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  95. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +11 -2
  96. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  97. package/corpus/templates/calendar/actions/delete-staged-dataset.ts +3 -24
  98. package/corpus/templates/calendar/actions/list-staged-datasets.ts +3 -28
  99. package/corpus/templates/calendar/actions/provider-api-catalog.ts +5 -11
  100. package/corpus/templates/calendar/actions/provider-api-docs.ts +3 -5
  101. package/corpus/templates/calendar/actions/provider-api-request.ts +3 -37
  102. package/corpus/templates/calendar/actions/query-staged-dataset.ts +3 -48
  103. package/corpus/templates/calendar/server/handlers/gong.ts +6 -61
  104. package/corpus/templates/calendar/server/handlers/google-auth.ts +10 -8
  105. package/corpus/templates/calendar/server/lib/google-calendar.ts +7 -45
  106. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +32 -12
  107. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +10 -0
  108. package/corpus/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  109. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +11 -2
  110. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  111. package/corpus/templates/chat/AGENTS.md +5 -2
  112. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +51 -157
  113. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +32 -12
  114. package/corpus/templates/clips/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  115. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +11 -2
  116. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  117. package/corpus/templates/clips/AGENTS.md +11 -0
  118. package/corpus/templates/clips/actions/export-to-brain.ts +505 -171
  119. package/corpus/templates/clips/changelog/2026-07-19-ready-clips-transcripts-can-be-backfilled-to-brain-for-a-bou.md +6 -0
  120. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +1 -1
  121. package/corpus/templates/clips/server/lib/google-calendar-client.ts +12 -22
  122. package/corpus/templates/clips/server/lib/google-calendar-oauth.ts +9 -6
  123. package/corpus/templates/clips/server/lib/slack-oauth.ts +12 -47
  124. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +2 -6
  125. package/corpus/templates/clips/server/routes/api/auth/google-calendar.get.ts +4 -4
  126. package/corpus/templates/content/.agents/skills/actions/SKILL.md +32 -12
  127. package/corpus/templates/content/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  128. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +11 -2
  129. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  130. package/corpus/templates/content/actions/delete-staged-dataset.ts +3 -24
  131. package/corpus/templates/content/actions/list-staged-datasets.ts +3 -28
  132. package/corpus/templates/content/actions/provider-api-catalog.ts +5 -11
  133. package/corpus/templates/content/actions/provider-api-docs.ts +3 -5
  134. package/corpus/templates/content/actions/provider-api-request.ts +3 -37
  135. package/corpus/templates/content/actions/query-staged-dataset.ts +3 -48
  136. package/corpus/templates/design/.agents/skills/actions/SKILL.md +32 -12
  137. package/corpus/templates/design/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  138. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +11 -2
  139. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  140. package/corpus/templates/design/actions/delete-staged-dataset.ts +4 -23
  141. package/corpus/templates/design/actions/list-staged-datasets.ts +4 -27
  142. package/corpus/templates/design/actions/provider-api-catalog.ts +15 -18
  143. package/corpus/templates/design/actions/provider-api-docs.ts +57 -48
  144. package/corpus/templates/design/actions/provider-api-request.ts +140 -164
  145. package/corpus/templates/design/actions/query-staged-dataset.ts +4 -47
  146. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +32 -12
  147. package/corpus/templates/dispatch/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  148. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +11 -2
  149. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  150. package/corpus/templates/dispatch/actions/delete-staged-dataset.ts +4 -23
  151. package/corpus/templates/dispatch/actions/list-staged-datasets.ts +4 -30
  152. package/corpus/templates/dispatch/actions/provider-api-catalog.ts +15 -18
  153. package/corpus/templates/dispatch/actions/provider-api-docs.ts +57 -48
  154. package/corpus/templates/dispatch/actions/provider-api-request.ts +138 -163
  155. package/corpus/templates/dispatch/actions/query-staged-dataset.ts +4 -47
  156. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +32 -12
  157. package/corpus/templates/forms/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  158. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +11 -2
  159. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  160. package/corpus/templates/forms/server/lib/integrations.ts +23 -22
  161. package/corpus/templates/macros/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  162. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  163. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +32 -12
  164. package/corpus/templates/mail/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  165. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +11 -2
  166. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  167. package/corpus/templates/mail/actions/delete-staged-dataset.ts +4 -23
  168. package/corpus/templates/mail/actions/list-staged-datasets.ts +4 -27
  169. package/corpus/templates/mail/actions/provider-api-catalog.ts +15 -18
  170. package/corpus/templates/mail/actions/provider-api-docs.ts +57 -48
  171. package/corpus/templates/mail/actions/provider-api-request.ts +146 -173
  172. package/corpus/templates/mail/actions/query-staged-dataset.ts +4 -47
  173. package/corpus/templates/mail/server/handlers/gong.ts +6 -61
  174. package/corpus/templates/mail/server/lib/google-auth.ts +10 -13
  175. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +32 -12
  176. package/corpus/templates/plan/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  177. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +11 -2
  178. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  179. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +47 -156
  180. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +32 -12
  181. package/corpus/templates/slides/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  182. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +11 -2
  183. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  184. package/corpus/templates/slides/actions/delete-staged-dataset.ts +4 -23
  185. package/corpus/templates/slides/actions/list-staged-datasets.ts +4 -27
  186. package/corpus/templates/slides/actions/provider-api-catalog.ts +15 -18
  187. package/corpus/templates/slides/actions/provider-api-docs.ts +57 -48
  188. package/corpus/templates/slides/actions/provider-api-request.ts +145 -172
  189. package/corpus/templates/slides/actions/query-staged-dataset.ts +4 -47
  190. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +11 -36
  191. package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +32 -12
  192. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +10 -0
  193. package/corpus/templates/tasks/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  194. package/corpus/templates/tasks/.agents/skills/self-modifying-code/SKILL.md +11 -2
  195. package/corpus/templates/tasks/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  196. package/corpus/toolkit/CHANGELOG.md +125 -0
  197. package/corpus/toolkit/README.md +60 -0
  198. package/corpus/toolkit/export-snapshot.json +35 -0
  199. package/corpus/toolkit/migration-manifest.json +4 -0
  200. package/corpus/toolkit/package.json +281 -0
  201. package/corpus/toolkit/scripts/finalize-build.mjs +55 -0
  202. package/corpus/toolkit/src/app-shell/header-actions.tsx +84 -0
  203. package/corpus/toolkit/src/app-shell/index.ts +1 -0
  204. package/corpus/toolkit/src/chat-history/ChatHistoryList.tsx +469 -0
  205. package/corpus/toolkit/src/chat-history/index.ts +7 -0
  206. package/corpus/toolkit/src/chat-history.css +314 -0
  207. package/corpus/toolkit/src/collab-ui/AgentPresenceChip.tsx +69 -0
  208. package/corpus/toolkit/src/collab-ui/LiveCursorOverlay.tsx +241 -0
  209. package/corpus/toolkit/src/collab-ui/PresenceBar.tsx +346 -0
  210. package/corpus/toolkit/src/collab-ui/RecentEditHighlights.tsx +225 -0
  211. package/corpus/toolkit/src/collab-ui/RemoteSelectionRings.tsx +227 -0
  212. package/corpus/toolkit/src/collab-ui/agent-identity.ts +16 -0
  213. package/corpus/toolkit/src/collab-ui/index.ts +39 -0
  214. package/corpus/toolkit/src/collab-ui/lead-client.ts +32 -0
  215. package/corpus/toolkit/src/collab-ui/types.ts +96 -0
  216. package/corpus/toolkit/src/composer/AgentComposerFrame.tsx +59 -0
  217. package/corpus/toolkit/src/composer/ComposerPlusMenu.tsx +983 -0
  218. package/corpus/toolkit/src/composer/MentionPopover.tsx +412 -0
  219. package/corpus/toolkit/src/composer/PastedTextChip.tsx +150 -0
  220. package/corpus/toolkit/src/composer/PromptComposer.tsx +718 -0
  221. package/corpus/toolkit/src/composer/RealtimeVoiceMode.tsx +856 -0
  222. package/corpus/toolkit/src/composer/TiptapComposer.tsx +2936 -0
  223. package/corpus/toolkit/src/composer/VoiceButton.tsx +255 -0
  224. package/corpus/toolkit/src/composer/asset-picker-url.ts +80 -0
  225. package/corpus/toolkit/src/composer/attachment-accept.ts +71 -0
  226. package/corpus/toolkit/src/composer/draft-key.ts +7 -0
  227. package/corpus/toolkit/src/composer/extensions/FileReference.tsx +55 -0
  228. package/corpus/toolkit/src/composer/extensions/MentionReference.tsx +93 -0
  229. package/corpus/toolkit/src/composer/extensions/SkillReference.tsx +56 -0
  230. package/corpus/toolkit/src/composer/index.ts +79 -0
  231. package/corpus/toolkit/src/composer/pasted-text.ts +160 -0
  232. package/corpus/toolkit/src/composer/prompt-attachments.ts +93 -0
  233. package/corpus/toolkit/src/composer/realtime-voice-audio-level.ts +64 -0
  234. package/corpus/toolkit/src/composer/realtime-voice-transcript.ts +134 -0
  235. package/corpus/toolkit/src/composer/runtime-adapters.tsx +343 -0
  236. package/corpus/toolkit/src/composer/types.ts +66 -0
  237. package/corpus/toolkit/src/composer/use-file-search.ts +64 -0
  238. package/corpus/toolkit/src/composer/use-mention-search.ts +90 -0
  239. package/corpus/toolkit/src/composer/use-skills.ts +45 -0
  240. package/corpus/toolkit/src/composer/useRealtimeVoiceMode.tsx +2117 -0
  241. package/corpus/toolkit/src/composer/useVoiceDictation.ts +1191 -0
  242. package/corpus/toolkit/src/context-ui/ContextMeter.tsx +101 -0
  243. package/corpus/toolkit/src/context-ui/ContextSegmentRow.tsx +184 -0
  244. package/corpus/toolkit/src/context-ui/ContextTreemap.tsx +125 -0
  245. package/corpus/toolkit/src/context-ui/ContextXRayPanel.tsx +377 -0
  246. package/corpus/toolkit/src/context-ui/format.ts +42 -0
  247. package/corpus/toolkit/src/context-ui/index.ts +15 -0
  248. package/corpus/toolkit/src/context-ui/types.ts +43 -0
  249. package/corpus/toolkit/src/design-tweaks/index.ts +1 -0
  250. package/corpus/toolkit/src/design-tweaks/visual-style-controls.tsx +1084 -0
  251. package/corpus/toolkit/src/editor/BubbleToolbar.tsx +253 -0
  252. package/corpus/toolkit/src/editor/CodeBlockNode.tsx +254 -0
  253. package/corpus/toolkit/src/editor/DragHandle.ts +1437 -0
  254. package/corpus/toolkit/src/editor/ImageExtension.ts +306 -0
  255. package/corpus/toolkit/src/editor/RegistryBlockContext.tsx +107 -0
  256. package/corpus/toolkit/src/editor/RegistryBlockNode.tsx +652 -0
  257. package/corpus/toolkit/src/editor/RichMarkdownEditor.tsx +81 -0
  258. package/corpus/toolkit/src/editor/RunId.ts +61 -0
  259. package/corpus/toolkit/src/editor/SharedRichEditor.tsx +321 -0
  260. package/corpus/toolkit/src/editor/SlashCommandMenu.tsx +310 -0
  261. package/corpus/toolkit/src/editor/extensions.ts +402 -0
  262. package/corpus/toolkit/src/editor/gfmDoc.ts +92 -0
  263. package/corpus/toolkit/src/editor/index.ts +93 -0
  264. package/corpus/toolkit/src/editor/registrySlashCommands.ts +131 -0
  265. package/corpus/toolkit/src/editor/surgical-apply.ts +192 -0
  266. package/corpus/toolkit/src/editor/useCollabReconcile.ts +589 -0
  267. package/corpus/toolkit/src/editor.css +510 -0
  268. package/corpus/toolkit/src/hooks/index.ts +2 -0
  269. package/corpus/toolkit/src/hooks/use-mobile.tsx +21 -0
  270. package/corpus/toolkit/src/hooks/use-toast.ts +188 -0
  271. package/corpus/toolkit/src/index.ts +8 -0
  272. package/corpus/toolkit/src/onboarding/index.ts +1 -0
  273. package/corpus/toolkit/src/provider.tsx +47 -0
  274. package/corpus/toolkit/src/sharing/VisibilityBadge.tsx +37 -0
  275. package/corpus/toolkit/src/sharing/index.ts +4 -0
  276. package/corpus/toolkit/src/styles.css +21 -0
  277. package/corpus/toolkit/src/ui/accordion.tsx +56 -0
  278. package/corpus/toolkit/src/ui/alert-dialog.tsx +141 -0
  279. package/corpus/toolkit/src/ui/alert.tsx +59 -0
  280. package/corpus/toolkit/src/ui/aspect-ratio.tsx +5 -0
  281. package/corpus/toolkit/src/ui/avatar.tsx +48 -0
  282. package/corpus/toolkit/src/ui/badge.tsx +37 -0
  283. package/corpus/toolkit/src/ui/breadcrumb.tsx +115 -0
  284. package/corpus/toolkit/src/ui/button.tsx +78 -0
  285. package/corpus/toolkit/src/ui/calendar.tsx +214 -0
  286. package/corpus/toolkit/src/ui/card.tsx +86 -0
  287. package/corpus/toolkit/src/ui/carousel.tsx +260 -0
  288. package/corpus/toolkit/src/ui/chart.tsx +375 -0
  289. package/corpus/toolkit/src/ui/checkbox.tsx +28 -0
  290. package/corpus/toolkit/src/ui/collapsible.tsx +9 -0
  291. package/corpus/toolkit/src/ui/command.tsx +174 -0
  292. package/corpus/toolkit/src/ui/context-menu.tsx +202 -0
  293. package/corpus/toolkit/src/ui/date-picker.tsx +64 -0
  294. package/corpus/toolkit/src/ui/dialog.tsx +146 -0
  295. package/corpus/toolkit/src/ui/drawer.tsx +126 -0
  296. package/corpus/toolkit/src/ui/dropdown-menu.tsx +218 -0
  297. package/corpus/toolkit/src/ui/form.tsx +177 -0
  298. package/corpus/toolkit/src/ui/hover-card.tsx +27 -0
  299. package/corpus/toolkit/src/ui/index.ts +52 -0
  300. package/corpus/toolkit/src/ui/input-otp.tsx +69 -0
  301. package/corpus/toolkit/src/ui/input.tsx +22 -0
  302. package/corpus/toolkit/src/ui/label.tsx +24 -0
  303. package/corpus/toolkit/src/ui/logo.tsx +36 -0
  304. package/corpus/toolkit/src/ui/menubar.tsx +235 -0
  305. package/corpus/toolkit/src/ui/navigation-menu.tsx +128 -0
  306. package/corpus/toolkit/src/ui/pagination.tsx +121 -0
  307. package/corpus/toolkit/src/ui/popover.tsx +44 -0
  308. package/corpus/toolkit/src/ui/progress.tsx +26 -0
  309. package/corpus/toolkit/src/ui/radio-group.tsx +42 -0
  310. package/corpus/toolkit/src/ui/resizable.tsx +43 -0
  311. package/corpus/toolkit/src/ui/scroll-area.tsx +46 -0
  312. package/corpus/toolkit/src/ui/select.tsx +158 -0
  313. package/corpus/toolkit/src/ui/separator.tsx +29 -0
  314. package/corpus/toolkit/src/ui/sheet.tsx +160 -0
  315. package/corpus/toolkit/src/ui/sidebar.tsx +777 -0
  316. package/corpus/toolkit/src/ui/skeleton.tsx +18 -0
  317. package/corpus/toolkit/src/ui/slider.tsx +26 -0
  318. package/corpus/toolkit/src/ui/sonner.tsx +58 -0
  319. package/corpus/toolkit/src/ui/spinner.tsx +17 -0
  320. package/corpus/toolkit/src/ui/switch.tsx +27 -0
  321. package/corpus/toolkit/src/ui/table.tsx +117 -0
  322. package/corpus/toolkit/src/ui/tabs.tsx +53 -0
  323. package/corpus/toolkit/src/ui/textarea.tsx +23 -0
  324. package/corpus/toolkit/src/ui/toast.tsx +127 -0
  325. package/corpus/toolkit/src/ui/toaster.tsx +33 -0
  326. package/corpus/toolkit/src/ui/toggle-group.tsx +59 -0
  327. package/corpus/toolkit/src/ui/toggle.tsx +43 -0
  328. package/corpus/toolkit/src/ui/tooltip.tsx +53 -0
  329. package/corpus/toolkit/src/ui/use-toast.ts +3 -0
  330. package/corpus/toolkit/src/utils.ts +8 -0
  331. package/corpus/toolkit/tsconfig.json +23 -0
  332. package/dist/cli/atomic-json-file.d.ts +30 -0
  333. package/dist/cli/atomic-json-file.d.ts.map +1 -0
  334. package/dist/cli/atomic-json-file.js +240 -0
  335. package/dist/cli/atomic-json-file.js.map +1 -0
  336. package/dist/cli/claude-code-participant.d.ts +77 -0
  337. package/dist/cli/claude-code-participant.d.ts.map +1 -0
  338. package/dist/cli/claude-code-participant.js +355 -0
  339. package/dist/cli/claude-code-participant.js.map +1 -0
  340. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  341. package/dist/cli/code-agent-executor.js +18 -10
  342. package/dist/cli/code-agent-executor.js.map +1 -1
  343. package/dist/cli/code-agent-runs.concurrent-worker.d.ts +2 -0
  344. package/dist/cli/code-agent-runs.concurrent-worker.d.ts.map +1 -0
  345. package/dist/cli/code-agent-runs.concurrent-worker.js +32 -0
  346. package/dist/cli/code-agent-runs.concurrent-worker.js.map +1 -0
  347. package/dist/cli/code-agent-runs.d.ts.map +1 -1
  348. package/dist/cli/code-agent-runs.js +43 -56
  349. package/dist/cli/code-agent-runs.js.map +1 -1
  350. package/dist/cli/codex-cli-participant.d.ts +77 -0
  351. package/dist/cli/codex-cli-participant.d.ts.map +1 -0
  352. package/dist/cli/codex-cli-participant.js +354 -0
  353. package/dist/cli/codex-cli-participant.js.map +1 -0
  354. package/dist/cli/multi-frontier-runs.d.ts +115 -0
  355. package/dist/cli/multi-frontier-runs.d.ts.map +1 -1
  356. package/dist/cli/multi-frontier-runs.js +831 -113
  357. package/dist/cli/multi-frontier-runs.js.map +1 -1
  358. package/dist/client/chat/ChatHistoryList.d.ts +1 -87
  359. package/dist/client/chat/ChatHistoryList.d.ts.map +1 -1
  360. package/dist/client/chat/ChatHistoryList.js +1 -82
  361. package/dist/client/chat/ChatHistoryList.js.map +1 -1
  362. package/dist/integrations/index.d.ts +2 -1
  363. package/dist/integrations/index.d.ts.map +1 -1
  364. package/dist/integrations/index.js +2 -1
  365. package/dist/integrations/index.js.map +1 -1
  366. package/dist/integrations/slack-manifest.d.ts +1 -1
  367. package/dist/integrations/slack-oauth.d.ts +1 -1
  368. package/dist/integrations/slack-oauth.d.ts.map +1 -1
  369. package/dist/integrations/slack-oauth.js +1 -0
  370. package/dist/integrations/slack-oauth.js.map +1 -1
  371. package/dist/integrations/webhook-delivery.d.ts +23 -0
  372. package/dist/integrations/webhook-delivery.d.ts.map +1 -0
  373. package/dist/integrations/webhook-delivery.js +32 -0
  374. package/dist/integrations/webhook-delivery.js.map +1 -0
  375. package/dist/notifications/channels.d.ts.map +1 -1
  376. package/dist/notifications/channels.js +4 -9
  377. package/dist/notifications/channels.js.map +1 -1
  378. package/dist/notifications/routes.d.ts +3 -3
  379. package/dist/provider-api/actions/custom-provider-registration.d.ts +136 -0
  380. package/dist/provider-api/actions/custom-provider-registration.d.ts.map +1 -0
  381. package/dist/provider-api/actions/custom-provider-registration.js +154 -0
  382. package/dist/provider-api/actions/custom-provider-registration.js.map +1 -0
  383. package/dist/provider-api/actions/delete-staged-dataset.d.ts +1 -1
  384. package/dist/provider-api/actions/delete-staged-dataset.d.ts.map +1 -1
  385. package/dist/provider-api/actions/delete-staged-dataset.js +3 -34
  386. package/dist/provider-api/actions/delete-staged-dataset.js.map +1 -1
  387. package/dist/provider-api/actions/list-staged-datasets.d.ts +2 -3
  388. package/dist/provider-api/actions/list-staged-datasets.d.ts.map +1 -1
  389. package/dist/provider-api/actions/list-staged-datasets.js +3 -40
  390. package/dist/provider-api/actions/list-staged-datasets.js.map +1 -1
  391. package/dist/provider-api/actions/provider-api-audit.d.ts +7 -0
  392. package/dist/provider-api/actions/provider-api-audit.d.ts.map +1 -0
  393. package/dist/provider-api/actions/provider-api-audit.js +74 -0
  394. package/dist/provider-api/actions/provider-api-audit.js.map +1 -0
  395. package/dist/provider-api/actions/provider-api.d.ts +370 -0
  396. package/dist/provider-api/actions/provider-api.d.ts.map +1 -0
  397. package/dist/provider-api/actions/provider-api.js +280 -0
  398. package/dist/provider-api/actions/provider-api.js.map +1 -0
  399. package/dist/provider-api/actions/query-staged-dataset.d.ts +1 -1
  400. package/dist/provider-api/actions/query-staged-dataset.d.ts.map +1 -1
  401. package/dist/provider-api/actions/query-staged-dataset.js +3 -116
  402. package/dist/provider-api/actions/query-staged-dataset.js.map +1 -1
  403. package/dist/provider-api/actions/staged-datasets.d.ts +190 -0
  404. package/dist/provider-api/actions/staged-datasets.d.ts.map +1 -0
  405. package/dist/provider-api/actions/staged-datasets.js +210 -0
  406. package/dist/provider-api/actions/staged-datasets.js.map +1 -0
  407. package/dist/provider-api/gong.d.ts +44 -0
  408. package/dist/provider-api/gong.d.ts.map +1 -0
  409. package/dist/provider-api/gong.js +56 -0
  410. package/dist/provider-api/gong.js.map +1 -0
  411. package/dist/scripts/docs/source-search.d.ts +2 -1
  412. package/dist/scripts/docs/source-search.d.ts.map +1 -1
  413. package/dist/scripts/docs/source-search.js +2 -1
  414. package/dist/scripts/docs/source-search.js.map +1 -1
  415. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  416. package/dist/server/google-oauth-credentials.d.ts +35 -0
  417. package/dist/server/google-oauth-credentials.d.ts.map +1 -1
  418. package/dist/server/google-oauth-credentials.js +48 -2
  419. package/dist/server/google-oauth-credentials.js.map +1 -1
  420. package/dist/server/index.d.ts +1 -1
  421. package/dist/server/index.d.ts.map +1 -1
  422. package/dist/server/index.js +1 -1
  423. package/dist/server/index.js.map +1 -1
  424. package/dist/styles/chat-history-list.css +1 -313
  425. package/dist/templates/chat/.agents/skills/actions/SKILL.md +32 -12
  426. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +10 -0
  427. package/dist/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  428. package/dist/templates/chat/.agents/skills/self-modifying-code/SKILL.md +11 -2
  429. package/dist/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  430. package/dist/templates/chat/AGENTS.md +5 -2
  431. package/dist/templates/chat/app/components/layout/Sidebar.tsx +51 -157
  432. package/dist/templates/default/.agents/skills/actions/SKILL.md +32 -12
  433. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +10 -0
  434. package/dist/templates/default/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  435. package/dist/templates/default/.agents/skills/integration-webhooks/SKILL.md +323 -0
  436. package/dist/templates/default/.agents/skills/onboarding/SKILL.md +56 -0
  437. package/dist/templates/default/.agents/skills/secrets/SKILL.md +273 -0
  438. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +11 -2
  439. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  440. package/dist/templates/default/AGENTS.md +25 -18
  441. package/dist/templates/headless/.agents/skills/actions/SKILL.md +512 -0
  442. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +10 -0
  443. package/dist/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  444. package/dist/templates/headless/.agents/skills/integration-webhooks/SKILL.md +323 -0
  445. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +273 -0
  446. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  447. package/dist/templates/headless/AGENTS.md +7 -0
  448. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -12
  449. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +10 -0
  450. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  451. package/dist/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  452. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +11 -2
  453. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  454. package/dist/templates/workspace-core/AGENTS.md +4 -0
  455. package/dist/templates/workspace-root/AGENTS.md +4 -0
  456. package/docs/AGENTS.md +7 -0
  457. package/docs/content/integrations.mdx +63 -2
  458. package/docs/design/provider-integration-authority.md +117 -0
  459. package/package.json +7 -2
  460. package/src/templates/chat/.agents/skills/actions/SKILL.md +32 -12
  461. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +10 -0
  462. package/src/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  463. package/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +11 -2
  464. package/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  465. package/src/templates/chat/AGENTS.md +5 -2
  466. package/src/templates/chat/app/components/layout/Sidebar.tsx +51 -157
  467. package/src/templates/default/.agents/skills/actions/SKILL.md +32 -12
  468. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +10 -0
  469. package/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  470. package/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +323 -0
  471. package/src/templates/default/.agents/skills/onboarding/SKILL.md +56 -0
  472. package/src/templates/default/.agents/skills/secrets/SKILL.md +273 -0
  473. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +11 -2
  474. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  475. package/src/templates/default/AGENTS.md +25 -18
  476. package/src/templates/headless/.agents/skills/actions/SKILL.md +512 -0
  477. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +10 -0
  478. package/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  479. package/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +323 -0
  480. package/src/templates/headless/.agents/skills/secrets/SKILL.md +273 -0
  481. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  482. package/src/templates/headless/AGENTS.md +7 -0
  483. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -12
  484. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +10 -0
  485. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  486. package/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  487. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +11 -2
  488. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  489. package/src/templates/workspace-core/AGENTS.md +4 -0
  490. package/src/templates/workspace-root/AGENTS.md +4 -0
@@ -1,7 +1,12 @@
1
- import crypto from "node:crypto";
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
 
4
+ import {
5
+ appendUniqueJsonLineAtomically,
6
+ withFileLockSync,
7
+ writeJsonFileAtomically,
8
+ writeTextFileAtomically,
9
+ } from "./atomic-json-file.js";
5
10
  import { codeAgentStoreRoot } from "./code-agent-runs.js";
6
11
 
7
12
  export type MultiFrontierPhase =
@@ -76,6 +81,8 @@ export interface MultiFrontierRecovery {
76
81
  export interface MultiFrontierRunState {
77
82
  schemaVersion: 1;
78
83
  collaborationId: string;
84
+ /** Opaque local workspace handle; never a filesystem path. */
85
+ workspaceId?: string;
79
86
  phase: MultiFrontierPhase;
80
87
  participants: MultiFrontierParticipantState[];
81
88
  driver: MultiFrontierDriverLease | null;
@@ -84,15 +91,19 @@ export interface MultiFrontierRunState {
84
91
  round: number;
85
92
  proposalIds: string[];
86
93
  reviewIds: string[];
94
+ /** Opt-in policy; explicit GO remains the durable default. */
95
+ autoContinueAfterAgreement: boolean;
87
96
  recovery?: MultiFrontierRecovery;
88
97
  }
89
98
 
90
99
  export interface CreateMultiFrontierRunInput {
91
100
  collaborationId: string;
101
+ workspaceId?: string;
92
102
  phase?: MultiFrontierPhase;
93
103
  participants: MultiFrontierParticipantState[];
94
104
  approval?: MultiFrontierApproval;
95
105
  checkpointIds?: string[];
106
+ autoContinueAfterAgreement?: boolean;
96
107
  }
97
108
 
98
109
  export interface RecoverMultiFrontierRunInput {
@@ -114,6 +125,88 @@ export interface PersistedMultiFrontierParticipantEvent extends MultiFrontierPar
114
125
  status?: MultiFrontierParticipantStatus;
115
126
  }
116
127
 
128
+ export type MultiFrontierArtifactKind = "proposal" | "review" | "checkpoint";
129
+
130
+ export interface MultiFrontierArtifactTestSummary {
131
+ name: string;
132
+ status: "passed" | "failed" | "skipped";
133
+ summary?: string;
134
+ }
135
+
136
+ /** A bounded, redacted projection used only for Desktop orchestrator recovery. */
137
+ export interface MultiFrontierOrchestrationArtifact {
138
+ id: string;
139
+ kind:
140
+ | "proposal"
141
+ | "cross_review"
142
+ | "revision"
143
+ | "synthesis"
144
+ | "checkpoint"
145
+ | "watchdog_review"
146
+ | "finding_disposition"
147
+ | "completion";
148
+ round: number;
149
+ participantId?: string;
150
+ text: string;
151
+ attribution: { participantIds: string[]; sourceArtifactIds: string[] };
152
+ supersedesArtifactId?: string;
153
+ metadata?: Record<string, unknown>;
154
+ }
155
+
156
+ /**
157
+ * A deliberately narrow coordination record. Full diffs, transcripts, and
158
+ * provider payloads stay in their owning runtime rather than this durable
159
+ * cross-provider index.
160
+ */
161
+ export interface PersistedMultiFrontierArtifact {
162
+ schemaVersion: 1;
163
+ id: string;
164
+ collaborationId: string;
165
+ kind: MultiFrontierArtifactKind;
166
+ createdAt: string;
167
+ participantId?: string;
168
+ title: string;
169
+ summary: string;
170
+ supersedesArtifactId?: string;
171
+ contentHash?: string;
172
+ fileRefs?: string[];
173
+ testSummary?: MultiFrontierArtifactTestSummary[];
174
+ orchestration?: MultiFrontierOrchestrationArtifact;
175
+ }
176
+
177
+ export interface AppendMultiFrontierArtifactInput {
178
+ id: string;
179
+ collaborationId: string;
180
+ kind: MultiFrontierArtifactKind;
181
+ createdAt?: string;
182
+ participantId?: string;
183
+ title: string;
184
+ summary: string;
185
+ supersedesArtifactId?: string;
186
+ contentHash?: string;
187
+ fileRefs?: string[];
188
+ testSummary?: MultiFrontierArtifactTestSummary[];
189
+ orchestration?: MultiFrontierOrchestrationArtifact;
190
+ }
191
+
192
+ export interface MultiFrontierParticipantEventRetention {
193
+ schemaVersion: 1;
194
+ retainedEventCount: number;
195
+ retainedByteCount: number;
196
+ droppedEventCount: number;
197
+ droppedByteCount: number;
198
+ truncated: boolean;
199
+ replay: {
200
+ requiresSnapshot: boolean;
201
+ firstRetainedEventId?: string;
202
+ };
203
+ }
204
+
205
+ export interface MultiFrontierParticipantEventRetentionLimits {
206
+ maxEventCount: number;
207
+ maxBytes: number;
208
+ }
209
+
117
210
  export interface AppendMultiFrontierParticipantEventInput extends MultiFrontierParticipantEvent {
118
211
  id: string;
119
212
  collaborationId: string;
@@ -126,6 +219,14 @@ export interface MultiFrontierStoredRun extends MultiFrontierRunState {
126
219
  updatedAt: string;
127
220
  }
128
221
 
222
+ /**
223
+ * The desktop main-process coordinator is the sole caller of this transition
224
+ * boundary. Its callback receives a detached snapshot, never renderer input.
225
+ */
226
+ export type MultiFrontierCoordinatorTransition = (
227
+ current: MultiFrontierStoredRun,
228
+ ) => MultiFrontierRunState | MultiFrontierStoredRun | null;
229
+
129
230
  export type AppendMultiFrontierParticipantEventResult =
130
231
  | {
131
232
  accepted: true;
@@ -144,12 +245,44 @@ export type AppendMultiFrontierParticipantEventResult =
144
245
  run: MultiFrontierStoredRun | null;
145
246
  };
146
247
 
248
+ export type AppendMultiFrontierArtifactResult =
249
+ | {
250
+ accepted: true;
251
+ deduplicated: boolean;
252
+ artifact: PersistedMultiFrontierArtifact;
253
+ run: MultiFrontierStoredRun;
254
+ }
255
+ | {
256
+ accepted: false;
257
+ reason:
258
+ | "missing-run"
259
+ | "artifact-conflict"
260
+ | "missing-participant"
261
+ | "missing-superseded-artifact"
262
+ | "artifact-limit-reached";
263
+ run: MultiFrontierStoredRun | null;
264
+ };
265
+
147
266
  const TERMINAL_PHASES = new Set<MultiFrontierPhase>([
148
267
  "completed",
149
268
  "failed",
150
269
  "canceled",
151
270
  ]);
152
271
  const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/;
272
+ const SAFE_CONTENT_HASH = /^[a-f0-9]{64}$/;
273
+ const SAFE_FILE_REF = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._/@+-]+$/;
274
+ const MAX_ARTIFACT_TITLE_BYTES = 280;
275
+ const MAX_ARTIFACT_SUMMARY_BYTES = 8 * 1024;
276
+ const MAX_ARTIFACT_FILE_REFS = 40;
277
+ const MAX_ARTIFACT_TEST_SUMMARIES = 40;
278
+ const MAX_ARTIFACT_TEST_NAME_BYTES = 280;
279
+ const MAX_ARTIFACT_TEST_SUMMARY_BYTES = 2 * 1024;
280
+ export const MAX_MULTI_FRONTIER_ARTIFACTS_PER_RUN = 200;
281
+ export const DEFAULT_MULTI_FRONTIER_PARTICIPANT_EVENT_RETENTION: MultiFrontierParticipantEventRetentionLimits =
282
+ {
283
+ maxEventCount: 2_000,
284
+ maxBytes: 1_000_000,
285
+ };
153
286
 
154
287
  /** All multi-frontier state stays alongside the existing local Code store. */
155
288
  export function multiFrontierRunsStoreRoot(): string {
@@ -164,10 +297,17 @@ export function multiFrontierParticipantEventsDir(): string {
164
297
  return path.join(multiFrontierRunsStoreRoot(), "events");
165
298
  }
166
299
 
300
+ export function multiFrontierArtifactsDir(): string {
301
+ return path.join(multiFrontierRunsStoreRoot(), "artifacts");
302
+ }
303
+
167
304
  export function createMultiFrontierRun(
168
305
  input: CreateMultiFrontierRunInput,
169
306
  ): MultiFrontierStoredRun {
170
307
  assertSafeId(input.collaborationId, "collaboration id");
308
+ if (input.workspaceId !== undefined) {
309
+ assertSafeId(input.workspaceId, "workspace id");
310
+ }
171
311
  assertParticipants(input.participants);
172
312
  if (getMultiFrontierRun(input.collaborationId)) {
173
313
  throw new Error(
@@ -178,6 +318,7 @@ export function createMultiFrontierRun(
178
318
  const state: MultiFrontierStoredRun = {
179
319
  schemaVersion: 1,
180
320
  collaborationId: input.collaborationId,
321
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
181
322
  phase: input.phase ?? "proposing",
182
323
  participants: input.participants.map((participant) => ({
183
324
  ...participant,
@@ -191,6 +332,7 @@ export function createMultiFrontierRun(
191
332
  round: 1,
192
333
  proposalIds: [],
193
334
  reviewIds: [],
335
+ autoContinueAfterAgreement: input.autoContinueAfterAgreement ?? false,
194
336
  createdAt: now,
195
337
  updatedAt: now,
196
338
  };
@@ -235,6 +377,7 @@ export function recoverMultiFrontierRun(
235
377
  phase: "paused",
236
378
  participants: state.participants.map((participant) => ({
237
379
  ...participant,
380
+ role: "watchdog",
238
381
  permission: "read_only",
239
382
  status: participant.status === "running" ? "waiting" : participant.status,
240
383
  })),
@@ -253,11 +396,86 @@ export function recoverStoredMultiFrontierRun(
253
396
  collaborationId: string,
254
397
  input: RecoverMultiFrontierRunInput,
255
398
  ): MultiFrontierStoredRun | null {
256
- const current = getMultiFrontierRun(collaborationId);
257
- if (!current) return null;
258
- const recovered = recoverMultiFrontierRun(current, input);
259
- if (recovered === current) return current;
260
- return writeNextStoredRun(current, recovered, input.now);
399
+ return mutateStoredMultiFrontierRun(collaborationId, input.now, (current) => {
400
+ const recovered = recoverMultiFrontierRun(current, input);
401
+ return recovered === current ? current : recovered;
402
+ });
403
+ }
404
+
405
+ /**
406
+ * Applies one durable coordinator-owned state transition under the run lock.
407
+ * A state-shaped result retains proposal and review references by default;
408
+ * returning a full stored record is the explicit escape hatch for a
409
+ * coordinator operation that intentionally changes those references.
410
+ */
411
+ export function transitionStoredMultiFrontierRun(
412
+ collaborationId: string,
413
+ updatedAt: string,
414
+ transition: MultiFrontierCoordinatorTransition,
415
+ ): MultiFrontierStoredRun | null {
416
+ assertSafeId(collaborationId, "collaboration id");
417
+ assertTimestamp(updatedAt, "transition time");
418
+ if (typeof transition !== "function") {
419
+ throw new Error("Invalid multi-frontier coordinator transition.");
420
+ }
421
+
422
+ return mutateStoredMultiFrontierRun(collaborationId, updatedAt, (current) => {
423
+ const candidate = transition(cloneStoredMultiFrontierRun(current));
424
+ if (candidate === null) return null;
425
+ if (isThenable(candidate)) {
426
+ throw new Error(
427
+ "Multi-frontier coordinator transitions must be synchronous.",
428
+ );
429
+ }
430
+ if (
431
+ !candidate ||
432
+ typeof candidate !== "object" ||
433
+ Array.isArray(candidate)
434
+ ) {
435
+ throw new Error("Invalid multi-frontier coordinator transition state.");
436
+ }
437
+
438
+ const isFullStoredRun = hasStoredRunTimestamps(candidate);
439
+ if (
440
+ !isFullStoredRun &&
441
+ (Object.hasOwn(candidate, "createdAt") ||
442
+ Object.hasOwn(candidate, "updatedAt"))
443
+ ) {
444
+ throw new Error("Invalid multi-frontier coordinator transition state.");
445
+ }
446
+ if (
447
+ candidate.schemaVersion !== current.schemaVersion ||
448
+ candidate.collaborationId !== current.collaborationId
449
+ ) {
450
+ throw new Error(
451
+ "Multi-frontier coordinator transitions cannot change run identity.",
452
+ );
453
+ }
454
+ if (isFullStoredRun) {
455
+ if (!isCompleteMultiFrontierStoredRun(candidate)) {
456
+ throw new Error("Invalid multi-frontier coordinator transition state.");
457
+ }
458
+ if (candidate.createdAt !== current.createdAt) {
459
+ throw new Error(
460
+ "Multi-frontier coordinator transitions cannot change creation time.",
461
+ );
462
+ }
463
+ }
464
+
465
+ const nextState: MultiFrontierRunState = {
466
+ ...candidate,
467
+ ...(isFullStoredRun
468
+ ? {}
469
+ : {
470
+ proposalIds: [...current.proposalIds],
471
+ reviewIds: [...current.reviewIds],
472
+ }),
473
+ };
474
+ if (!isCompleteMultiFrontierRunState(nextState)) {
475
+ throw new Error("Invalid multi-frontier coordinator transition state.");
476
+ }
477
+ return nextState;
478
+ });
261
479
  }
262
480
 
263
481
  /**
@@ -376,10 +594,9 @@ export function activateStoredMultiFrontierDriver(
376
594
  now = new Date().toISOString(),
377
595
  ): MultiFrontierStoredRun | null {
378
596
  assertTimestamp(now, "activation time");
379
- const current = getMultiFrontierRun(collaborationId);
380
- if (!current) return null;
381
- const activated = activateMultiFrontierDriver(current, participantId);
382
- return activated ? writeNextStoredRun(current, activated, now) : null;
597
+ return mutateStoredMultiFrontierRun(collaborationId, now, (current) =>
598
+ activateMultiFrontierDriver(current, participantId),
599
+ );
383
600
  }
384
601
 
385
602
  /** Persist an explicit driver reactivation after the human-approved resume. */
@@ -389,10 +606,9 @@ export function reactivateStoredMultiFrontierDriver(
389
606
  now = new Date().toISOString(),
390
607
  ): MultiFrontierStoredRun | null {
391
608
  assertTimestamp(now, "reactivation time");
392
- const current = getMultiFrontierRun(collaborationId);
393
- if (!current) return null;
394
- const reactivated = reactivateMultiFrontierDriver(current, participantId);
395
- return reactivated ? writeNextStoredRun(current, reactivated, now) : null;
609
+ return mutateStoredMultiFrontierRun(collaborationId, now, (current) =>
610
+ reactivateMultiFrontierDriver(current, participantId),
611
+ );
396
612
  }
397
613
 
398
614
  /**
@@ -409,67 +625,190 @@ export function appendMultiFrontierParticipantEvent(
409
625
  const createdAt = input.createdAt ?? new Date().toISOString();
410
626
  assertTimestamp(createdAt, "event time");
411
627
 
412
- const current = getMultiFrontierRun(input.collaborationId);
413
- if (!current) return { accepted: false, reason: "missing-run", run: null };
414
- const existing = listMultiFrontierParticipantEvents(
415
- input.collaborationId,
416
- ).find((event) => event.id === input.id);
417
- if (existing) {
418
- if (!isMatchingParticipantEvent(existing, input)) {
419
- return { accepted: false, reason: "event-conflict", run: current };
628
+ const runPath = multiFrontierRunPath(input.collaborationId);
629
+ return withFileLockSync(runPath, () => {
630
+ const current = readStoredRun(runPath);
631
+ if (!current) return { accepted: false, reason: "missing-run", run: null };
632
+ const existing = listMultiFrontierParticipantEvents(
633
+ input.collaborationId,
634
+ ).find((event) => event.id === input.id);
635
+ if (existing) {
636
+ if (!isMatchingParticipantEvent(existing, input)) {
637
+ return { accepted: false, reason: "event-conflict", run: current };
638
+ }
639
+ return {
640
+ accepted: true,
641
+ deduplicated: true,
642
+ event: existing,
643
+ run: current,
644
+ };
420
645
  }
421
- return {
422
- accepted: true,
423
- deduplicated: true,
424
- event: existing,
425
- run: current,
426
- };
427
- }
428
- if (
429
- !current.participants.some(
646
+ if (
647
+ !current.participants.some(
648
+ (candidate) => candidate.participantId === input.participantId,
649
+ )
650
+ ) {
651
+ return { accepted: false, reason: "missing-participant", run: current };
652
+ }
653
+ if (!canApplyMultiFrontierParticipantEvent(current, input)) {
654
+ return { accepted: false, reason: "stale-driver", run: current };
655
+ }
656
+ const participant = current.participants.find(
430
657
  (candidate) => candidate.participantId === input.participantId,
431
- )
432
- ) {
433
- return { accepted: false, reason: "missing-participant", run: current };
434
- }
435
- if (!canApplyMultiFrontierParticipantEvent(current, input)) {
436
- return { accepted: false, reason: "stale-driver", run: current };
437
- }
438
- const participant = current.participants.find(
439
- (candidate) => candidate.participantId === input.participantId,
440
- );
441
- if (
442
- input.status !== undefined &&
443
- (participant?.status === "completed" || participant?.status === "failed") &&
444
- input.status !== participant.status
445
- ) {
446
- return { accepted: false, reason: "terminal-participant", run: current };
447
- }
658
+ );
659
+ if (
660
+ input.status !== undefined &&
661
+ (participant?.status === "completed" ||
662
+ participant?.status === "failed") &&
663
+ input.status !== participant.status
664
+ ) {
665
+ return { accepted: false, reason: "terminal-participant", run: current };
666
+ }
448
667
 
449
- const event: PersistedMultiFrontierParticipantEvent = {
450
- schemaVersion: 1,
451
- id: input.id,
452
- collaborationId: input.collaborationId,
453
- participantId: input.participantId,
454
- permission: input.permission,
455
- createdAt,
456
- ...(input.generation === undefined ? {} : { generation: input.generation }),
457
- ...(input.status === undefined ? {} : { status: input.status }),
458
- };
459
- appendParticipantEvent(event);
460
- const participants = input.status
461
- ? current.participants.map((candidate) =>
462
- candidate.participantId === input.participantId
463
- ? { ...candidate, status: input.status }
464
- : candidate,
668
+ const event: PersistedMultiFrontierParticipantEvent = {
669
+ schemaVersion: 1,
670
+ id: input.id,
671
+ collaborationId: input.collaborationId,
672
+ participantId: input.participantId,
673
+ permission: input.permission,
674
+ createdAt,
675
+ ...(input.generation === undefined
676
+ ? {}
677
+ : { generation: input.generation }),
678
+ ...(input.status === undefined ? {} : { status: input.status }),
679
+ };
680
+ appendParticipantEvent(event);
681
+ compactParticipantEventJournal(input.collaborationId);
682
+ const status = input.status;
683
+ const participants = status
684
+ ? current.participants.map((candidate) =>
685
+ candidate.participantId === input.participantId
686
+ ? { ...candidate, status }
687
+ : candidate,
688
+ )
689
+ : current.participants;
690
+ const run = writeNextStoredRun(
691
+ current,
692
+ { ...current, participants },
693
+ createdAt,
694
+ );
695
+ return { accepted: true, deduplicated: false, event, run };
696
+ });
697
+ }
698
+
699
+ /**
700
+ * Appends a bounded proposal, review, or checkpoint index record. The
701
+ * coordinator owns both the artifact and its run-state reference under one
702
+ * run lock, so a reader never observes a durable reference without its record.
703
+ */
704
+ export function appendMultiFrontierArtifact(
705
+ input: AppendMultiFrontierArtifactInput,
706
+ ): AppendMultiFrontierArtifactResult {
707
+ assertArtifactInput(input);
708
+ const createdAt = input.createdAt ?? new Date().toISOString();
709
+ assertTimestamp(createdAt, "artifact time");
710
+ const artifact = toPersistedArtifact(input, createdAt);
711
+ const runPath = multiFrontierRunPath(input.collaborationId);
712
+ return withFileLockSync(runPath, () => {
713
+ const current = readStoredRun(runPath);
714
+ if (!current) return { accepted: false, reason: "missing-run", run: null };
715
+ if (
716
+ input.participantId !== undefined &&
717
+ !current.participants.some(
718
+ (participant) => participant.participantId === input.participantId,
465
719
  )
466
- : current.participants;
467
- const run = writeNextStoredRun(
468
- current,
469
- { ...current, participants },
470
- createdAt,
720
+ ) {
721
+ return { accepted: false, reason: "missing-participant", run: current };
722
+ }
723
+ if (
724
+ input.supersedesArtifactId !== undefined &&
725
+ !readStoredArtifact(
726
+ multiFrontierArtifactPath(
727
+ input.collaborationId,
728
+ input.supersedesArtifactId,
729
+ ),
730
+ )
731
+ ) {
732
+ return {
733
+ accepted: false,
734
+ reason: "missing-superseded-artifact",
735
+ run: current,
736
+ };
737
+ }
738
+
739
+ const artifactPath = multiFrontierArtifactPath(
740
+ input.collaborationId,
741
+ input.id,
742
+ );
743
+ const existing = readStoredArtifact(artifactPath);
744
+ if (existing) {
745
+ if (!isMatchingArtifact(existing, artifact)) {
746
+ return { accepted: false, reason: "artifact-conflict", run: current };
747
+ }
748
+ const ids = artifactIdsForKind(current, existing.kind);
749
+ const run = ids.includes(existing.id)
750
+ ? current
751
+ : writeNextStoredRun(
752
+ current,
753
+ withArtifactId(current, existing.kind, existing.id),
754
+ new Date().toISOString(),
755
+ );
756
+ return {
757
+ accepted: true,
758
+ deduplicated: true,
759
+ artifact: existing,
760
+ run,
761
+ };
762
+ }
763
+ if (fs.existsSync(artifactPath)) {
764
+ return { accepted: false, reason: "artifact-conflict", run: current };
765
+ }
766
+ if (
767
+ listMultiFrontierArtifacts(input.collaborationId).length >=
768
+ MAX_MULTI_FRONTIER_ARTIFACTS_PER_RUN
769
+ ) {
770
+ return {
771
+ accepted: false,
772
+ reason: "artifact-limit-reached",
773
+ run: current,
774
+ };
775
+ }
776
+
777
+ writeJsonFileAtomically(artifactPath, artifact, { mode: 0o600 });
778
+ const ids = artifactIdsForKind(current, artifact.kind);
779
+ const next = ids.includes(artifact.id)
780
+ ? current
781
+ : withArtifactId(current, artifact.kind, artifact.id);
782
+ const run = writeNextStoredRun(current, next, createdAt);
783
+ return { accepted: true, deduplicated: false, artifact, run };
784
+ });
785
+ }
786
+
787
+ export function getMultiFrontierArtifact(
788
+ collaborationId: string,
789
+ artifactId: string,
790
+ ): PersistedMultiFrontierArtifact | null {
791
+ if (!SAFE_ID.test(collaborationId) || !SAFE_ID.test(artifactId)) return null;
792
+ return readStoredArtifact(
793
+ multiFrontierArtifactPath(collaborationId, artifactId),
471
794
  );
472
- return { accepted: true, deduplicated: false, event, run };
795
+ }
796
+
797
+ export function listMultiFrontierArtifacts(
798
+ collaborationId: string,
799
+ ): PersistedMultiFrontierArtifact[] {
800
+ if (!SAFE_ID.test(collaborationId)) return [];
801
+ const directory = multiFrontierArtifactsRunDir(collaborationId);
802
+ if (!fs.existsSync(directory)) return [];
803
+ return fs
804
+ .readdirSync(directory)
805
+ .filter((file) => file.endsWith(".json"))
806
+ .map((file) => readStoredArtifact(path.join(directory, file)))
807
+ .filter(
808
+ (artifact): artifact is PersistedMultiFrontierArtifact =>
809
+ artifact !== null,
810
+ )
811
+ .sort((left, right) => left.createdAt.localeCompare(right.createdAt));
473
812
  }
474
813
 
475
814
  export function listMultiFrontierParticipantEvents(
@@ -489,30 +828,46 @@ export function listMultiFrontierParticipantEvents(
489
828
  );
490
829
  }
491
830
 
492
- function writeMultiFrontierRun(run: MultiFrontierStoredRun): void {
493
- fs.mkdirSync(multiFrontierRunsDir(), { recursive: true });
494
- writeFileAtomically(
495
- multiFrontierRunPath(run.collaborationId),
496
- `${JSON.stringify(run, null, 2)}\n`,
831
+ export function getMultiFrontierParticipantEventRetention(
832
+ collaborationId: string,
833
+ ): MultiFrontierParticipantEventRetention | null {
834
+ if (!SAFE_ID.test(collaborationId)) return null;
835
+ return readParticipantEventRetention(
836
+ multiFrontierParticipantEventRetentionPath(collaborationId),
497
837
  );
498
838
  }
499
839
 
840
+ /**
841
+ * Coordinator maintenance for a bounded event journal. It deliberately keeps
842
+ * the newest contiguous tail and a replay marker instead of deleting run state.
843
+ */
844
+ export function compactMultiFrontierParticipantEvents(
845
+ collaborationId: string,
846
+ limits = DEFAULT_MULTI_FRONTIER_PARTICIPANT_EVENT_RETENTION,
847
+ ): MultiFrontierParticipantEventRetention | null {
848
+ assertSafeId(collaborationId, "collaboration id");
849
+ assertRetentionLimits(limits);
850
+ const runPath = multiFrontierRunPath(collaborationId);
851
+ return withFileLockSync(runPath, () => {
852
+ if (!readStoredRun(runPath)) return null;
853
+ return compactParticipantEventJournal(collaborationId, limits);
854
+ });
855
+ }
856
+
857
+ function writeMultiFrontierRun(run: MultiFrontierStoredRun): void {
858
+ writeJsonFileAtomically(multiFrontierRunPath(run.collaborationId), run);
859
+ }
860
+
500
861
  function writeNewMultiFrontierRun(run: MultiFrontierStoredRun): void {
501
- fs.mkdirSync(multiFrontierRunsDir(), { recursive: true });
502
- try {
503
- fs.writeFileSync(
504
- multiFrontierRunPath(run.collaborationId),
505
- `${JSON.stringify(run, null, 2)}\n`,
506
- { encoding: "utf-8", flag: "wx" },
507
- );
508
- } catch (error) {
509
- if ((error as NodeJS.ErrnoException).code === "EEXIST") {
862
+ const filePath = multiFrontierRunPath(run.collaborationId);
863
+ withFileLockSync(filePath, () => {
864
+ if (fs.existsSync(filePath)) {
510
865
  throw new Error(
511
866
  `Multi-frontier run already exists: ${run.collaborationId}`,
512
867
  );
513
868
  }
514
- throw error;
515
- }
869
+ writeJsonFileAtomically(filePath, run);
870
+ });
516
871
  }
517
872
 
518
873
  function writeNextStoredRun(
@@ -532,13 +887,90 @@ function writeNextStoredRun(
532
887
  function appendParticipantEvent(
533
888
  event: PersistedMultiFrontierParticipantEvent,
534
889
  ): void {
535
- fs.mkdirSync(multiFrontierParticipantEventsDir(), { recursive: true });
536
- fs.appendFileSync(
890
+ appendUniqueJsonLineAtomically(
537
891
  multiFrontierParticipantEventsPath(event.collaborationId),
538
- `${JSON.stringify(event)}\n`,
892
+ event,
893
+ readParticipantEventValue,
894
+ { mode: 0o600 },
539
895
  );
540
896
  }
541
897
 
898
+ function compactParticipantEventJournal(
899
+ collaborationId: string,
900
+ limits = DEFAULT_MULTI_FRONTIER_PARTICIPANT_EVENT_RETENTION,
901
+ ): MultiFrontierParticipantEventRetention {
902
+ const eventPath = multiFrontierParticipantEventsPath(collaborationId);
903
+ return withFileLockSync(eventPath, () => {
904
+ const previous = readParticipantEventRetention(
905
+ multiFrontierParticipantEventRetentionPath(collaborationId),
906
+ );
907
+ const events = listMultiFrontierParticipantEvents(collaborationId);
908
+ const retained: PersistedMultiFrontierParticipantEvent[] = [];
909
+ let retainedByteCount = 0;
910
+ for (const event of [...events].reverse()) {
911
+ const bytes = eventLineByteLength(event);
912
+ if (
913
+ retained.length >= limits.maxEventCount ||
914
+ retainedByteCount + bytes > limits.maxBytes
915
+ ) {
916
+ break;
917
+ }
918
+ retained.push(event);
919
+ retainedByteCount += bytes;
920
+ }
921
+ retained.reverse();
922
+ const retainedIds = new Set(retained.map((event) => event.id));
923
+ const dropped = events.filter((event) => !retainedIds.has(event.id));
924
+ if (dropped.length > 0) {
925
+ writeTextFileAtomically(
926
+ eventPath,
927
+ retained.map((event) => `${JSON.stringify(event)}\n`).join(""),
928
+ );
929
+ }
930
+ const retention: MultiFrontierParticipantEventRetention = {
931
+ schemaVersion: 1,
932
+ retainedEventCount: retained.length,
933
+ retainedByteCount,
934
+ droppedEventCount: (previous?.droppedEventCount ?? 0) + dropped.length,
935
+ droppedByteCount:
936
+ (previous?.droppedByteCount ?? 0) +
937
+ dropped.reduce((total, event) => total + eventLineByteLength(event), 0),
938
+ truncated: (previous?.truncated ?? false) || dropped.length > 0,
939
+ replay: {
940
+ requiresSnapshot: (previous?.truncated ?? false) || dropped.length > 0,
941
+ ...(retained[0] ? { firstRetainedEventId: retained[0].id } : {}),
942
+ },
943
+ };
944
+ writeJsonFileAtomically(
945
+ multiFrontierParticipantEventRetentionPath(collaborationId),
946
+ retention,
947
+ { mode: 0o600 },
948
+ );
949
+ return retention;
950
+ });
951
+ }
952
+
953
+ function mutateStoredMultiFrontierRun(
954
+ collaborationId: string,
955
+ updatedAt: string,
956
+ mutate: (current: MultiFrontierStoredRun) => MultiFrontierRunState | null,
957
+ ): MultiFrontierStoredRun | null {
958
+ const filePath = multiFrontierRunPath(collaborationId);
959
+ return withFileLockSync(filePath, () => {
960
+ const current = readStoredRun(filePath);
961
+ if (!current) return null;
962
+ const nextState = mutate(current);
963
+ if (!nextState) return null;
964
+ const next: MultiFrontierStoredRun = {
965
+ ...nextState,
966
+ createdAt: current.createdAt,
967
+ updatedAt,
968
+ };
969
+ writeMultiFrontierRun(next);
970
+ return next;
971
+ });
972
+ }
973
+
542
974
  function multiFrontierRunPath(collaborationId: string): string {
543
975
  return path.join(multiFrontierRunsDir(), `${collaborationId}.json`);
544
976
  }
@@ -550,46 +982,122 @@ function multiFrontierParticipantEventsPath(collaborationId: string): string {
550
982
  );
551
983
  }
552
984
 
985
+ function multiFrontierParticipantEventRetentionPath(
986
+ collaborationId: string,
987
+ ): string {
988
+ return path.join(
989
+ multiFrontierParticipantEventsDir(),
990
+ `${collaborationId}.retention.json`,
991
+ );
992
+ }
993
+
994
+ function multiFrontierArtifactsRunDir(collaborationId: string): string {
995
+ return path.join(multiFrontierArtifactsDir(), collaborationId);
996
+ }
997
+
998
+ function multiFrontierArtifactPath(
999
+ collaborationId: string,
1000
+ artifactId: string,
1001
+ ): string {
1002
+ return path.join(
1003
+ multiFrontierArtifactsRunDir(collaborationId),
1004
+ `${artifactId}.json`,
1005
+ );
1006
+ }
1007
+
553
1008
  function readStoredRun(filePath: string): MultiFrontierStoredRun | null {
554
1009
  try {
555
1010
  const raw = JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown;
556
- if (!raw || typeof raw !== "object") return null;
557
- const run = raw as Partial<MultiFrontierStoredRun>;
558
- if (
559
- run.schemaVersion !== 1 ||
560
- typeof run.collaborationId !== "string" ||
561
- !SAFE_ID.test(run.collaborationId) ||
562
- !isPhase(run.phase) ||
563
- !isStoredRunParticipantsAndDriverValid(run.participants, run.driver) ||
564
- !isApproval(run.approval) ||
565
- !Array.isArray(run.checkpointIds) ||
566
- !run.checkpointIds.every(
567
- (checkpointId) => typeof checkpointId === "string",
568
- ) ||
569
- typeof run.round !== "number" ||
570
- !Number.isInteger(run.round) ||
571
- run.round < 1 ||
572
- !isStringArray(run.proposalIds) ||
573
- !isStringArray(run.reviewIds) ||
574
- (run.recovery !== undefined && !isRecovery(run.recovery)) ||
575
- typeof run.createdAt !== "string" ||
576
- Number.isNaN(Date.parse(run.createdAt)) ||
577
- typeof run.updatedAt !== "string" ||
578
- Number.isNaN(Date.parse(run.updatedAt))
579
- ) {
580
- return null;
581
- }
582
- return run as MultiFrontierStoredRun;
1011
+ const normalized = normalizeStoredRunDefaults(raw);
1012
+ return isCompleteMultiFrontierStoredRun(normalized) ? normalized : null;
583
1013
  } catch {
584
1014
  return null;
585
1015
  }
586
1016
  }
587
1017
 
1018
+ function isCompleteMultiFrontierStoredRun(
1019
+ value: unknown,
1020
+ ): value is MultiFrontierStoredRun {
1021
+ if (!hasStoredRunTimestamps(value)) return false;
1022
+ return (
1023
+ isCompleteMultiFrontierRunState(value) &&
1024
+ !Number.isNaN(Date.parse(value.createdAt)) &&
1025
+ !Number.isNaN(Date.parse(value.updatedAt))
1026
+ );
1027
+ }
1028
+
1029
+ function isCompleteMultiFrontierRunState(
1030
+ value: unknown,
1031
+ ): value is MultiFrontierRunState {
1032
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1033
+ const run = value as Partial<MultiFrontierRunState>;
1034
+ return (
1035
+ run.schemaVersion === 1 &&
1036
+ typeof run.collaborationId === "string" &&
1037
+ SAFE_ID.test(run.collaborationId) &&
1038
+ (run.workspaceId === undefined ||
1039
+ (typeof run.workspaceId === "string" && SAFE_ID.test(run.workspaceId))) &&
1040
+ isPhase(run.phase) &&
1041
+ isStoredRunParticipantsAndDriverValid(run.participants, run.driver) &&
1042
+ isApproval(run.approval) &&
1043
+ Array.isArray(run.checkpointIds) &&
1044
+ run.checkpointIds.every(
1045
+ (checkpointId) => typeof checkpointId === "string",
1046
+ ) &&
1047
+ typeof run.round === "number" &&
1048
+ Number.isInteger(run.round) &&
1049
+ run.round >= 1 &&
1050
+ isStringArray(run.proposalIds) &&
1051
+ isStringArray(run.reviewIds) &&
1052
+ typeof run.autoContinueAfterAgreement === "boolean" &&
1053
+ (run.recovery === undefined || isRecovery(run.recovery))
1054
+ );
1055
+ }
1056
+
1057
+ function normalizeStoredRunDefaults(value: unknown): unknown {
1058
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
1059
+ if (!Object.hasOwn(value, "autoContinueAfterAgreement")) {
1060
+ return { ...value, autoContinueAfterAgreement: false };
1061
+ }
1062
+ return value;
1063
+ }
1064
+
1065
+ function hasStoredRunTimestamps(
1066
+ value: unknown,
1067
+ ): value is MultiFrontierStoredRun {
1068
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1069
+ const run = value as Partial<MultiFrontierStoredRun>;
1070
+ return typeof run.createdAt === "string" && typeof run.updatedAt === "string";
1071
+ }
1072
+
1073
+ function cloneStoredMultiFrontierRun(
1074
+ run: MultiFrontierStoredRun,
1075
+ ): MultiFrontierStoredRun {
1076
+ return JSON.parse(JSON.stringify(run)) as MultiFrontierStoredRun;
1077
+ }
1078
+
1079
+ function isThenable(value: unknown): value is PromiseLike<unknown> {
1080
+ return (
1081
+ Boolean(value) &&
1082
+ (typeof value === "object" || typeof value === "function") &&
1083
+ typeof (value as { then?: unknown }).then === "function"
1084
+ );
1085
+ }
1086
+
588
1087
  function readParticipantEvent(
589
1088
  line: string,
590
1089
  ): PersistedMultiFrontierParticipantEvent | null {
591
1090
  try {
592
- const raw = JSON.parse(line) as unknown;
1091
+ return readParticipantEventValue(JSON.parse(line) as unknown);
1092
+ } catch {
1093
+ return null;
1094
+ }
1095
+ }
1096
+
1097
+ function readParticipantEventValue(
1098
+ raw: unknown,
1099
+ ): PersistedMultiFrontierParticipantEvent | null {
1100
+ try {
593
1101
  if (!raw || typeof raw !== "object") return null;
594
1102
  const event = raw as Partial<PersistedMultiFrontierParticipantEvent>;
595
1103
  if (
@@ -612,6 +1120,627 @@ function readParticipantEvent(
612
1120
  }
613
1121
  }
614
1122
 
1123
+ function readStoredArtifact(
1124
+ filePath: string,
1125
+ ): PersistedMultiFrontierArtifact | null {
1126
+ try {
1127
+ return readArtifactValue(
1128
+ JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown,
1129
+ );
1130
+ } catch {
1131
+ return null;
1132
+ }
1133
+ }
1134
+
1135
+ function readArtifactValue(
1136
+ raw: unknown,
1137
+ ): PersistedMultiFrontierArtifact | null {
1138
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
1139
+ const artifact = raw as Partial<PersistedMultiFrontierArtifact>;
1140
+ if (
1141
+ artifact.schemaVersion !== 1 ||
1142
+ !isSafeId(artifact.id) ||
1143
+ !isSafeId(artifact.collaborationId) ||
1144
+ !isArtifactKind(artifact.kind) ||
1145
+ typeof artifact.createdAt !== "string" ||
1146
+ Number.isNaN(Date.parse(artifact.createdAt)) ||
1147
+ (artifact.participantId !== undefined &&
1148
+ !isSafeId(artifact.participantId)) ||
1149
+ !isBoundedText(artifact.title, MAX_ARTIFACT_TITLE_BYTES) ||
1150
+ !isArtifactSummary(artifact.summary) ||
1151
+ (artifact.supersedesArtifactId !== undefined &&
1152
+ !isSafeId(artifact.supersedesArtifactId)) ||
1153
+ (artifact.contentHash !== undefined &&
1154
+ !SAFE_CONTENT_HASH.test(artifact.contentHash)) ||
1155
+ !isArtifactFileRefs(artifact.fileRefs) ||
1156
+ !isArtifactTestSummaries(artifact.testSummary) ||
1157
+ !isOrchestrationArtifact(artifact.orchestration)
1158
+ ) {
1159
+ return null;
1160
+ }
1161
+ return artifact as PersistedMultiFrontierArtifact;
1162
+ }
1163
+
1164
+ function readParticipantEventRetention(
1165
+ filePath: string,
1166
+ ): MultiFrontierParticipantEventRetention | null {
1167
+ try {
1168
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown;
1169
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
1170
+ const retention = raw as Partial<MultiFrontierParticipantEventRetention>;
1171
+ if (
1172
+ retention.schemaVersion !== 1 ||
1173
+ !isNonNegativeInteger(retention.retainedEventCount) ||
1174
+ !isNonNegativeInteger(retention.retainedByteCount) ||
1175
+ !isNonNegativeInteger(retention.droppedEventCount) ||
1176
+ !isNonNegativeInteger(retention.droppedByteCount) ||
1177
+ typeof retention.truncated !== "boolean" ||
1178
+ !retention.replay ||
1179
+ typeof retention.replay !== "object" ||
1180
+ typeof retention.replay.requiresSnapshot !== "boolean" ||
1181
+ (retention.replay.firstRetainedEventId !== undefined &&
1182
+ !isSafeId(retention.replay.firstRetainedEventId))
1183
+ ) {
1184
+ return null;
1185
+ }
1186
+ return retention as MultiFrontierParticipantEventRetention;
1187
+ } catch {
1188
+ return null;
1189
+ }
1190
+ }
1191
+
1192
+ function assertArtifactInput(input: AppendMultiFrontierArtifactInput): void {
1193
+ assertAllowlistedArtifactInput(input);
1194
+ assertSafeId(input.id, "artifact id");
1195
+ assertSafeId(input.collaborationId, "collaboration id");
1196
+ if (!isArtifactKind(input.kind))
1197
+ throw new Error("Invalid multi-frontier artifact kind.");
1198
+ if (input.participantId !== undefined) {
1199
+ assertSafeId(input.participantId, "participant id");
1200
+ }
1201
+ assertBoundedText(input.title, MAX_ARTIFACT_TITLE_BYTES, "artifact title");
1202
+ if (!isArtifactSummary(input.summary)) {
1203
+ throw new Error("Invalid multi-frontier artifact summary.");
1204
+ }
1205
+ if (input.supersedesArtifactId !== undefined) {
1206
+ assertSafeId(input.supersedesArtifactId, "superseded artifact id");
1207
+ }
1208
+ if (
1209
+ input.contentHash !== undefined &&
1210
+ !SAFE_CONTENT_HASH.test(input.contentHash)
1211
+ ) {
1212
+ throw new Error("Invalid multi-frontier artifact content hash.");
1213
+ }
1214
+ if (!isArtifactFileRefs(input.fileRefs)) {
1215
+ throw new Error("Invalid multi-frontier artifact file references.");
1216
+ }
1217
+ if (!isArtifactTestSummaries(input.testSummary)) {
1218
+ throw new Error("Invalid multi-frontier artifact test summary.");
1219
+ }
1220
+ if (!isOrchestrationArtifact(input.orchestration)) {
1221
+ throw new Error("Invalid multi-frontier orchestration artifact.");
1222
+ }
1223
+ }
1224
+
1225
+ function assertAllowlistedArtifactInput(
1226
+ input: AppendMultiFrontierArtifactInput,
1227
+ ): void {
1228
+ const allowed = new Set([
1229
+ "id",
1230
+ "collaborationId",
1231
+ "kind",
1232
+ "createdAt",
1233
+ "participantId",
1234
+ "title",
1235
+ "summary",
1236
+ "supersedesArtifactId",
1237
+ "contentHash",
1238
+ "fileRefs",
1239
+ "testSummary",
1240
+ "orchestration",
1241
+ ]);
1242
+ if (
1243
+ !input ||
1244
+ typeof input !== "object" ||
1245
+ Array.isArray(input) ||
1246
+ Object.keys(input).some((key) => !allowed.has(key))
1247
+ ) {
1248
+ throw new Error(
1249
+ "Multi-frontier artifacts only accept allowlisted summary fields.",
1250
+ );
1251
+ }
1252
+ }
1253
+
1254
+ function toPersistedArtifact(
1255
+ input: AppendMultiFrontierArtifactInput,
1256
+ createdAt: string,
1257
+ ): PersistedMultiFrontierArtifact {
1258
+ return {
1259
+ schemaVersion: 1,
1260
+ id: input.id,
1261
+ collaborationId: input.collaborationId,
1262
+ kind: input.kind,
1263
+ createdAt,
1264
+ title: input.title,
1265
+ summary: input.summary,
1266
+ ...(input.participantId === undefined
1267
+ ? {}
1268
+ : { participantId: input.participantId }),
1269
+ ...(input.supersedesArtifactId === undefined
1270
+ ? {}
1271
+ : { supersedesArtifactId: input.supersedesArtifactId }),
1272
+ ...(input.contentHash === undefined
1273
+ ? {}
1274
+ : { contentHash: input.contentHash }),
1275
+ ...(input.fileRefs === undefined ? {} : { fileRefs: [...input.fileRefs] }),
1276
+ ...(input.testSummary === undefined
1277
+ ? {}
1278
+ : {
1279
+ testSummary: input.testSummary.map((summary) => ({ ...summary })),
1280
+ }),
1281
+ ...(input.orchestration === undefined
1282
+ ? {}
1283
+ : { orchestration: cloneOrchestrationArtifact(input.orchestration) }),
1284
+ };
1285
+ }
1286
+
1287
+ function artifactIdsForKind(
1288
+ run: MultiFrontierStoredRun,
1289
+ kind: MultiFrontierArtifactKind,
1290
+ ): string[] {
1291
+ if (kind === "proposal") return run.proposalIds;
1292
+ if (kind === "review") return run.reviewIds;
1293
+ return run.checkpointIds;
1294
+ }
1295
+
1296
+ function withArtifactId(
1297
+ run: MultiFrontierStoredRun,
1298
+ kind: MultiFrontierArtifactKind,
1299
+ artifactId: string,
1300
+ ): MultiFrontierStoredRun {
1301
+ if (kind === "proposal") {
1302
+ return { ...run, proposalIds: [...run.proposalIds, artifactId] };
1303
+ }
1304
+ if (kind === "review") {
1305
+ return { ...run, reviewIds: [...run.reviewIds, artifactId] };
1306
+ }
1307
+ return { ...run, checkpointIds: [...run.checkpointIds, artifactId] };
1308
+ }
1309
+
1310
+ function isMatchingArtifact(
1311
+ existing: PersistedMultiFrontierArtifact,
1312
+ candidate: PersistedMultiFrontierArtifact,
1313
+ ): boolean {
1314
+ return JSON.stringify(existing) === JSON.stringify(candidate);
1315
+ }
1316
+
1317
+ function isArtifactKind(value: unknown): value is MultiFrontierArtifactKind {
1318
+ return value === "proposal" || value === "review" || value === "checkpoint";
1319
+ }
1320
+
1321
+ function isArtifactSummary(value: unknown): value is string {
1322
+ return (
1323
+ isBoundedText(value, MAX_ARTIFACT_SUMMARY_BYTES) &&
1324
+ !/(?:^|\n)diff --git\s|(?:^|\n)@@\s+-\d+/m.test(value)
1325
+ );
1326
+ }
1327
+
1328
+ function isArtifactFileRefs(value: unknown): value is string[] | undefined {
1329
+ return (
1330
+ value === undefined ||
1331
+ (Array.isArray(value) &&
1332
+ value.length <= MAX_ARTIFACT_FILE_REFS &&
1333
+ value.every(
1334
+ (fileRef) =>
1335
+ typeof fileRef === "string" &&
1336
+ Buffer.byteLength(fileRef, "utf-8") <= 512 &&
1337
+ SAFE_FILE_REF.test(fileRef),
1338
+ ))
1339
+ );
1340
+ }
1341
+
1342
+ function isArtifactTestSummaries(
1343
+ value: unknown,
1344
+ ): value is MultiFrontierArtifactTestSummary[] | undefined {
1345
+ return (
1346
+ value === undefined ||
1347
+ (Array.isArray(value) &&
1348
+ value.length <= MAX_ARTIFACT_TEST_SUMMARIES &&
1349
+ value.every(
1350
+ (summary) =>
1351
+ Boolean(summary) &&
1352
+ typeof summary === "object" &&
1353
+ isBoundedText(
1354
+ (summary as MultiFrontierArtifactTestSummary).name,
1355
+ MAX_ARTIFACT_TEST_NAME_BYTES,
1356
+ ) &&
1357
+ ["passed", "failed", "skipped"].includes(
1358
+ (summary as MultiFrontierArtifactTestSummary).status,
1359
+ ) &&
1360
+ ((summary as MultiFrontierArtifactTestSummary).summary ===
1361
+ undefined ||
1362
+ isBoundedText(
1363
+ (summary as MultiFrontierArtifactTestSummary).summary,
1364
+ MAX_ARTIFACT_TEST_SUMMARY_BYTES,
1365
+ )),
1366
+ ))
1367
+ );
1368
+ }
1369
+
1370
+ function isOrchestrationArtifact(
1371
+ value: unknown,
1372
+ ): value is MultiFrontierOrchestrationArtifact | undefined {
1373
+ if (value === undefined) return true;
1374
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1375
+ const artifact = value as Partial<MultiFrontierOrchestrationArtifact>;
1376
+ return (
1377
+ hasExactKeys(artifact, [
1378
+ "id",
1379
+ "kind",
1380
+ "round",
1381
+ "participantId",
1382
+ "text",
1383
+ "attribution",
1384
+ "supersedesArtifactId",
1385
+ "metadata",
1386
+ ]) &&
1387
+ isSafeId(artifact.id) &&
1388
+ [
1389
+ "proposal",
1390
+ "cross_review",
1391
+ "revision",
1392
+ "synthesis",
1393
+ "checkpoint",
1394
+ "watchdog_review",
1395
+ "finding_disposition",
1396
+ "completion",
1397
+ ].includes(artifact.kind ?? "") &&
1398
+ Number.isSafeInteger(artifact.round) &&
1399
+ artifact.round! >= 1 &&
1400
+ artifact.round! <= 3 &&
1401
+ (artifact.participantId === undefined ||
1402
+ isSafeId(artifact.participantId)) &&
1403
+ isSafeOrchestrationText(artifact.text) &&
1404
+ isOrchestrationAttribution(artifact.attribution) &&
1405
+ (artifact.supersedesArtifactId === undefined ||
1406
+ isSafeId(artifact.supersedesArtifactId)) &&
1407
+ isSafeOrchestrationMetadata(artifact.kind!, artifact.metadata)
1408
+ );
1409
+ }
1410
+
1411
+ function hasExactKeys(value: object, allowed: readonly string[]): boolean {
1412
+ return Object.keys(value).every((key) => allowed.includes(key));
1413
+ }
1414
+
1415
+ function isOrchestrationAttribution(
1416
+ value: unknown,
1417
+ ): value is MultiFrontierOrchestrationArtifact["attribution"] {
1418
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1419
+ const attribution = value as Partial<
1420
+ MultiFrontierOrchestrationArtifact["attribution"]
1421
+ >;
1422
+ return (
1423
+ hasExactKeys(attribution as Record<string, unknown>, [
1424
+ "participantIds",
1425
+ "sourceArtifactIds",
1426
+ ]) &&
1427
+ isSafeIdArray(attribution.participantIds, 2) &&
1428
+ isSafeIdArray(
1429
+ attribution.sourceArtifactIds,
1430
+ MAX_MULTI_FRONTIER_ARTIFACTS_PER_RUN,
1431
+ )
1432
+ );
1433
+ }
1434
+
1435
+ function isSafeIdArray(value: unknown, max: number): value is string[] {
1436
+ return (
1437
+ Array.isArray(value) &&
1438
+ value.length <= max &&
1439
+ value.every((item) => isSafeId(item))
1440
+ );
1441
+ }
1442
+
1443
+ function isSafeOrchestrationText(value: unknown): value is string {
1444
+ return (
1445
+ isArtifactSummary(value) &&
1446
+ !/(?:bearer\s+|api[_-]?key\s*[:=]|authorization\s*[:=]|(?:sk|rk|pk)[_-][A-Za-z0-9_-]{12,}|(?:access|refresh)[_-]?token\s*[:=])/i.test(
1447
+ value,
1448
+ )
1449
+ );
1450
+ }
1451
+
1452
+ function isSafeOrchestrationMetadata(
1453
+ kind: NonNullable<MultiFrontierOrchestrationArtifact["kind"]>,
1454
+ value: unknown,
1455
+ ): value is Record<string, unknown> | undefined {
1456
+ const allowedKeys = metadataKeysForOrchestrationKind(kind);
1457
+ if (allowedKeys.length === 0) return value === undefined;
1458
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1459
+ const entries = Object.entries(value);
1460
+ if (
1461
+ entries.length === 0 ||
1462
+ entries.some(([key]) => !allowedKeys.includes(key)) ||
1463
+ entries.some(([key]) =>
1464
+ /(token|secret|authorization|credential|password|email|organization|account)/i.test(
1465
+ key,
1466
+ ),
1467
+ )
1468
+ ) {
1469
+ return false;
1470
+ }
1471
+ try {
1472
+ return (
1473
+ isArtifactSummary(JSON.stringify(value)) &&
1474
+ isValidOrchestrationMetadata(kind, value as Record<string, unknown>)
1475
+ );
1476
+ } catch {
1477
+ return false;
1478
+ }
1479
+ }
1480
+
1481
+ function isValidOrchestrationMetadata(
1482
+ kind: NonNullable<MultiFrontierOrchestrationArtifact["kind"]>,
1483
+ metadata: Record<string, unknown>,
1484
+ ): boolean {
1485
+ if (kind === "synthesis") {
1486
+ return (
1487
+ typeof metadata.agreed === "boolean" &&
1488
+ typeof metadata.deterministicallyResolved === "boolean" &&
1489
+ (metadata.reversibleResolution === undefined ||
1490
+ isReversibleResolution(metadata.reversibleResolution))
1491
+ );
1492
+ }
1493
+ if (kind === "checkpoint") return isCheckpointBundle(metadata.bundle);
1494
+ if (kind === "cross_review" || kind === "watchdog_review") {
1495
+ return isFindingList(metadata.findings);
1496
+ }
1497
+ if (kind === "finding_disposition") {
1498
+ return isFindingDispositionList(metadata.dispositions);
1499
+ }
1500
+ if (kind === "completion") {
1501
+ return (
1502
+ isCompletionTests(metadata.tests) &&
1503
+ isSafeTextList(metadata.proofRefs, 40) &&
1504
+ isSafeTextList(metadata.remainingRisks, 40)
1505
+ );
1506
+ }
1507
+ return false;
1508
+ }
1509
+
1510
+ function isFindingList(value: unknown): boolean {
1511
+ return (
1512
+ Array.isArray(value) &&
1513
+ value.length <= 40 &&
1514
+ value.every((finding) => {
1515
+ if (!finding || typeof finding !== "object" || Array.isArray(finding))
1516
+ return false;
1517
+ const item = finding as Record<string, unknown>;
1518
+ return (
1519
+ hasExactKeys(item, [
1520
+ "id",
1521
+ "category",
1522
+ "summary",
1523
+ "rawFindingId",
1524
+ "reviewerParticipantId",
1525
+ ]) &&
1526
+ isSafeId(item.id) &&
1527
+ isSafeOrchestrationText(item.summary) &&
1528
+ isSafeId(item.rawFindingId) &&
1529
+ isSafeId(item.reviewerParticipantId) &&
1530
+ [
1531
+ "reversible_technical",
1532
+ "intent_or_scope",
1533
+ "destructive_action",
1534
+ "security_or_privacy",
1535
+ "outward_effect",
1536
+ "meaningful_cost_expansion",
1537
+ "irreversible_architecture",
1538
+ ].includes(item.category as string)
1539
+ );
1540
+ })
1541
+ );
1542
+ }
1543
+
1544
+ function isFindingDispositionList(value: unknown): boolean {
1545
+ return (
1546
+ Array.isArray(value) &&
1547
+ value.length <= 40 &&
1548
+ value.every((disposition) => {
1549
+ if (
1550
+ !disposition ||
1551
+ typeof disposition !== "object" ||
1552
+ Array.isArray(disposition)
1553
+ )
1554
+ return false;
1555
+ const item = disposition as Record<string, unknown>;
1556
+ return (
1557
+ hasExactKeys(item, ["findingId", "disposition", "reason"]) &&
1558
+ isSafeId(item.findingId) &&
1559
+ ["addressed", "rejected", "deferred"].includes(
1560
+ item.disposition as string,
1561
+ ) &&
1562
+ isSafeOrchestrationText(item.reason)
1563
+ );
1564
+ })
1565
+ );
1566
+ }
1567
+
1568
+ function isReversibleResolution(value: unknown): boolean {
1569
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1570
+ const item = value as Record<string, unknown>;
1571
+ return (
1572
+ hasExactKeys(item, [
1573
+ "alternatives",
1574
+ "comparator",
1575
+ "selected",
1576
+ "reversibility",
1577
+ ]) &&
1578
+ isSafeTextList(item.alternatives, 8) &&
1579
+ isSafeOrchestrationText(item.comparator) &&
1580
+ isSafeOrchestrationText(item.selected) &&
1581
+ isSafeOrchestrationText(item.reversibility)
1582
+ );
1583
+ }
1584
+
1585
+ function isCheckpointBundle(value: unknown): boolean {
1586
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1587
+ const item = value as Record<string, unknown>;
1588
+ return (
1589
+ hasExactKeys(item, [
1590
+ "requestSummary",
1591
+ "requestRef",
1592
+ "acceptedPlanArtifactId",
1593
+ "contentRef",
1594
+ "contentHash",
1595
+ "testOutput",
1596
+ "driverSummary",
1597
+ "openRisks",
1598
+ "unresolvedFindingIds",
1599
+ ]) &&
1600
+ isSafeOrchestrationText(item.requestSummary) &&
1601
+ (item.requestRef === undefined || isSafeId(item.requestRef)) &&
1602
+ isSafeId(item.acceptedPlanArtifactId) &&
1603
+ typeof item.contentRef === "string" &&
1604
+ /^(?!\/)(?!.*\.\.)[A-Za-z0-9._/@:-]{1,512}$/.test(item.contentRef) &&
1605
+ typeof item.contentHash === "string" &&
1606
+ SAFE_CONTENT_HASH.test(item.contentHash) &&
1607
+ isSafeOrchestrationText(item.testOutput) &&
1608
+ isSafeOrchestrationText(item.driverSummary) &&
1609
+ isSafeTextList(item.openRisks, 40) &&
1610
+ isSafeIdArray(item.unresolvedFindingIds, 40)
1611
+ );
1612
+ }
1613
+
1614
+ function isCompletionTests(value: unknown): boolean {
1615
+ return (
1616
+ Array.isArray(value) &&
1617
+ value.length <= 40 &&
1618
+ value.every((test) => {
1619
+ if (!test || typeof test !== "object" || Array.isArray(test))
1620
+ return false;
1621
+ const item = test as Record<string, unknown>;
1622
+ return (
1623
+ hasExactKeys(item, ["name", "status", "evidence"]) &&
1624
+ isSafeOrchestrationText(item.name) &&
1625
+ ["passed", "failed", "skipped"].includes(item.status as string) &&
1626
+ isSafeOrchestrationText(item.evidence)
1627
+ );
1628
+ })
1629
+ );
1630
+ }
1631
+
1632
+ function isSafeTextList(value: unknown, max: number): boolean {
1633
+ return (
1634
+ Array.isArray(value) &&
1635
+ value.length <= max &&
1636
+ value.every((item) => isSafeOrchestrationText(item))
1637
+ );
1638
+ }
1639
+
1640
+ function metadataKeysForOrchestrationKind(
1641
+ kind: NonNullable<MultiFrontierOrchestrationArtifact["kind"]>,
1642
+ ): string[] {
1643
+ if (kind === "synthesis") {
1644
+ return ["agreed", "deterministicallyResolved", "reversibleResolution"];
1645
+ }
1646
+ if (kind === "checkpoint") return ["bundle"];
1647
+ if (kind === "cross_review" || kind === "watchdog_review") {
1648
+ return ["findings"];
1649
+ }
1650
+ if (kind === "finding_disposition") return ["dispositions"];
1651
+ if (kind === "completion") return ["tests", "proofRefs", "remainingRisks"];
1652
+ return [];
1653
+ }
1654
+
1655
+ function containsUnsafeOrchestrationValue(
1656
+ value: unknown,
1657
+ depth: number,
1658
+ ): boolean {
1659
+ if (depth > 8) return true;
1660
+ if (typeof value === "string") return !isSafeOrchestrationText(value);
1661
+ if (
1662
+ value === null ||
1663
+ typeof value === "boolean" ||
1664
+ (typeof value === "number" && Number.isFinite(value))
1665
+ ) {
1666
+ return false;
1667
+ }
1668
+ if (Array.isArray(value)) {
1669
+ return (
1670
+ value.length > 40 ||
1671
+ value.some((item) => containsUnsafeOrchestrationValue(item, depth + 1))
1672
+ );
1673
+ }
1674
+ if (!value || typeof value !== "object") return true;
1675
+ const entries = Object.entries(value);
1676
+ return (
1677
+ entries.length > 40 ||
1678
+ entries.some(
1679
+ ([key, item]) =>
1680
+ !isBoundedText(key, 160) ||
1681
+ /(token|secret|authorization|credential|password|email|organization|account)/i.test(
1682
+ key,
1683
+ ) ||
1684
+ containsUnsafeOrchestrationValue(item, depth + 1),
1685
+ )
1686
+ );
1687
+ }
1688
+
1689
+ function cloneOrchestrationArtifact(
1690
+ artifact: MultiFrontierOrchestrationArtifact,
1691
+ ): MultiFrontierOrchestrationArtifact {
1692
+ return JSON.parse(
1693
+ JSON.stringify(artifact),
1694
+ ) as MultiFrontierOrchestrationArtifact;
1695
+ }
1696
+
1697
+ function isBoundedText(value: unknown, maxBytes: number): value is string {
1698
+ return (
1699
+ typeof value === "string" &&
1700
+ value.trim().length > 0 &&
1701
+ Buffer.byteLength(value, "utf-8") <= maxBytes
1702
+ );
1703
+ }
1704
+
1705
+ function assertBoundedText(
1706
+ value: unknown,
1707
+ maxBytes: number,
1708
+ label: string,
1709
+ ): void {
1710
+ if (!isBoundedText(value, maxBytes)) {
1711
+ throw new Error(`Invalid multi-frontier ${label}.`);
1712
+ }
1713
+ }
1714
+
1715
+ function assertRetentionLimits(
1716
+ limits: MultiFrontierParticipantEventRetentionLimits,
1717
+ ): void {
1718
+ if (
1719
+ !isNonNegativeInteger(limits.maxEventCount) ||
1720
+ limits.maxEventCount < 1 ||
1721
+ !isNonNegativeInteger(limits.maxBytes) ||
1722
+ limits.maxBytes < 512
1723
+ ) {
1724
+ throw new Error(
1725
+ "Invalid multi-frontier participant event retention limits.",
1726
+ );
1727
+ }
1728
+ }
1729
+
1730
+ function eventLineByteLength(
1731
+ event: PersistedMultiFrontierParticipantEvent,
1732
+ ): number {
1733
+ return Buffer.byteLength(`${JSON.stringify(event)}\n`, "utf-8");
1734
+ }
1735
+
1736
+ function isSafeId(value: unknown): value is string {
1737
+ return typeof value === "string" && SAFE_ID.test(value);
1738
+ }
1739
+
1740
+ function isNonNegativeInteger(value: unknown): value is number {
1741
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
1742
+ }
1743
+
615
1744
  function assertParticipants(
616
1745
  participants: MultiFrontierParticipantState[],
617
1746
  ): void {
@@ -821,21 +1950,3 @@ function assertTimestamp(value: string, label: string): void {
821
1950
  throw new Error(`Invalid multi-frontier ${label}.`);
822
1951
  }
823
1952
  }
824
-
825
- function writeFileAtomically(filePath: string, contents: string): void {
826
- const directory = path.dirname(filePath);
827
- const temporaryPath = path.join(
828
- directory,
829
- `.${path.basename(filePath)}.tmp-${process.pid}-${crypto.randomUUID()}`,
830
- );
831
- try {
832
- fs.writeFileSync(temporaryPath, contents, {
833
- encoding: "utf-8",
834
- flag: "wx",
835
- });
836
- fs.renameSync(temporaryPath, filePath);
837
- } catch (error) {
838
- fs.rmSync(temporaryPath, { force: true });
839
- throw error;
840
- }
841
- }