@ours.network/fleet 1.2.0-nightly.9 → 2.0.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 (429) hide show
  1. package/README.md +280 -68
  2. package/dist/account-origin.d.ts +5 -0
  3. package/dist/account-origin.js +31 -0
  4. package/dist/agent-ours/bridge.js +26 -9
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/file-rpc.d.ts +12 -0
  8. package/dist/agent-ours/file-rpc.js +18 -4
  9. package/dist/agent-ours/harness.js +9 -1
  10. package/dist/agent-ours/mcp-endpoint.d.ts +22 -0
  11. package/dist/agent-ours/mcp-endpoint.js +101 -25
  12. package/dist/agent-ours/runtime.d.ts +9 -1
  13. package/dist/agent-ours/runtime.js +50 -2
  14. package/dist/agent-ours/service.d.ts +27 -1
  15. package/dist/agent-ours/service.js +107 -13
  16. package/dist/application/codex-models.d.ts +15 -0
  17. package/dist/application/codex-models.js +56 -0
  18. package/dist/application/conversation-history.d.ts +31 -0
  19. package/dist/application/conversation-history.js +339 -0
  20. package/dist/application/conversation-role-observer.d.ts +62 -0
  21. package/dist/application/conversation-role-observer.js +206 -0
  22. package/dist/application/fleet-query-service.d.ts +1 -1
  23. package/dist/application/fleet-query-service.js +3 -3
  24. package/dist/application/log-service.js +8 -8
  25. package/dist/application/model-catalog.d.ts +1 -1
  26. package/dist/application/model-catalog.js +5 -2
  27. package/dist/application/preset-provenance.d.ts +19 -0
  28. package/dist/application/preset-provenance.js +59 -0
  29. package/dist/application/resource-observer.d.ts +43 -0
  30. package/dist/application/resource-observer.js +323 -0
  31. package/dist/application/role-creation-service.js +3 -2
  32. package/dist/application/role-removal-service.js +17 -3
  33. package/dist/application/role-repository.d.ts +3 -1
  34. package/dist/application/role-repository.js +89 -54
  35. package/dist/application/room-layout-definitions.d.ts +45 -0
  36. package/dist/application/room-layout-definitions.js +130 -0
  37. package/dist/application/session-control.d.ts +63 -17
  38. package/dist/application/session-control.js +221 -23
  39. package/dist/application/supervisor-ours-tools.js +5 -11
  40. package/dist/application/task-layouts.d.ts +91 -0
  41. package/dist/application/task-layouts.js +261 -0
  42. package/dist/application/task-room-service.d.ts +101 -3
  43. package/dist/application/task-room-service.js +286 -33
  44. package/dist/application/types.d.ts +2 -0
  45. package/dist/atomic-file.d.ts +34 -1
  46. package/dist/atomic-file.js +161 -18
  47. package/dist/briefing.d.ts +3 -1
  48. package/dist/briefing.js +120 -31
  49. package/dist/build-info.json +10 -5
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +391 -38
  53. package/dist/client-profile.d.ts +5 -17
  54. package/dist/client-profile.js +4 -127
  55. package/dist/config.d.ts +45 -0
  56. package/dist/config.js +57 -6
  57. package/dist/creation.d.ts +1 -1
  58. package/dist/creation.js +4 -4
  59. package/dist/daemon-recovery.d.ts +14 -9
  60. package/dist/daemon-recovery.js +124 -115
  61. package/dist/docs.d.ts +1 -1
  62. package/dist/docs.js +214 -20
  63. package/dist/doctor.d.ts +4 -1
  64. package/dist/doctor.js +75 -60
  65. package/dist/erased-resources.d.ts +9 -0
  66. package/dist/erased-resources.js +87 -0
  67. package/dist/file-delivery/reader.d.ts +15 -0
  68. package/dist/file-delivery/reader.js +63 -0
  69. package/dist/file-delivery/store.d.ts +15 -0
  70. package/dist/file-delivery/store.js +124 -0
  71. package/dist/file-delivery/types.d.ts +19 -0
  72. package/dist/file-delivery/types.js +1 -0
  73. package/dist/fleet-command-audit.d.ts +13 -0
  74. package/dist/fleet-command-audit.js +31 -9
  75. package/dist/harness/agent-session.d.ts +4 -0
  76. package/dist/harness/claude-code-session.js +1 -1
  77. package/dist/harness/claude-code.js +21 -11
  78. package/dist/harness/claude-runtime.d.ts +12 -0
  79. package/dist/harness/claude-runtime.js +43 -0
  80. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  81. package/dist/harness/codex-app-server-proxy.js +41 -5
  82. package/dist/harness/codex-reasoning.d.ts +5 -0
  83. package/dist/harness/codex-reasoning.js +12 -0
  84. package/dist/harness/codex-runtime.d.ts +11 -1
  85. package/dist/harness/codex-runtime.js +31 -4
  86. package/dist/harness/codex-session.js +2 -2
  87. package/dist/harness/codex.js +24 -12
  88. package/dist/init-wizard.d.ts +2 -0
  89. package/dist/init-wizard.js +38 -19
  90. package/dist/isolation/policy.js +6 -1
  91. package/dist/isolation/types.d.ts +5 -0
  92. package/dist/managed-cli.d.ts +253 -0
  93. package/dist/managed-cli.js +923 -0
  94. package/dist/managed-recovery.d.ts +13 -0
  95. package/dist/managed-recovery.js +31 -0
  96. package/dist/minimal-setup.d.ts +2 -0
  97. package/dist/minimal-setup.js +44 -0
  98. package/dist/monitor.d.ts +9 -34
  99. package/dist/monitor.js +90 -153
  100. package/dist/notifications/fleet-producer.d.ts +31 -0
  101. package/dist/notifications/fleet-producer.js +351 -0
  102. package/dist/notifications/outbox.d.ts +59 -0
  103. package/dist/notifications/outbox.js +199 -0
  104. package/dist/notifications/target-cleanup.d.ts +16 -0
  105. package/dist/notifications/target-cleanup.js +75 -0
  106. package/dist/notifications/task-producer.d.ts +20 -0
  107. package/dist/notifications/task-producer.js +100 -0
  108. package/dist/ops.d.ts +2 -0
  109. package/dist/ops.js +20 -3
  110. package/dist/owner-channel/channel.js +17 -6
  111. package/dist/owner-channel/commands.d.ts +4 -0
  112. package/dist/owner-channel/commands.js +8 -0
  113. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  114. package/dist/owner-channel/ours-client.js +2 -17
  115. package/dist/paths.d.ts +5 -0
  116. package/dist/paths.js +5 -0
  117. package/dist/preset-migration.js +55 -0
  118. package/dist/process-incarnation.d.ts +23 -0
  119. package/dist/process-incarnation.js +42 -0
  120. package/dist/reminders/manager.d.ts +75 -0
  121. package/dist/reminders/manager.js +278 -0
  122. package/dist/reminders/runtime.d.ts +27 -0
  123. package/dist/reminders/runtime.js +32 -0
  124. package/dist/reminders/schedule.d.ts +37 -0
  125. package/dist/reminders/schedule.js +104 -0
  126. package/dist/reminders/store.d.ts +341 -0
  127. package/dist/reminders/store.js +762 -0
  128. package/dist/reminders/tools.d.ts +20 -0
  129. package/dist/reminders/tools.js +114 -0
  130. package/dist/resolved-plan.js +1 -0
  131. package/dist/rooms-tasks/add-member.d.ts +48 -0
  132. package/dist/rooms-tasks/add-member.js +84 -0
  133. package/dist/rooms-tasks/archived-absence.d.ts +4 -0
  134. package/dist/rooms-tasks/archived-absence.js +11 -0
  135. package/dist/rooms-tasks/cli.js +182 -5
  136. package/dist/rooms-tasks/close.d.ts +5 -2
  137. package/dist/rooms-tasks/close.js +224 -36
  138. package/dist/rooms-tasks/config.d.ts +1 -1
  139. package/dist/rooms-tasks/config.js +11 -4
  140. package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
  141. package/dist/rooms-tasks/cowork-adapter.js +15 -121
  142. package/dist/rooms-tasks/cowork-http.js +56 -6
  143. package/dist/rooms-tasks/deletion.d.ts +1 -1
  144. package/dist/rooms-tasks/deletion.js +66 -10
  145. package/dist/rooms-tasks/erasure.d.ts +6 -0
  146. package/dist/rooms-tasks/erasure.js +173 -0
  147. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  148. package/dist/rooms-tasks/external-worker.js +3 -4
  149. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  150. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  151. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  152. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  153. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  154. package/dist/rooms-tasks/layout-cli.js +107 -0
  155. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  156. package/dist/rooms-tasks/layout-config.js +89 -0
  157. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  158. package/dist/rooms-tasks/layout-control.js +52 -0
  159. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  160. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  161. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  162. package/dist/rooms-tasks/layout-service.js +203 -0
  163. package/dist/rooms-tasks/layout.d.ts +121 -0
  164. package/dist/rooms-tasks/layout.js +286 -0
  165. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  166. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  167. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  168. package/dist/rooms-tasks/live-readiness.js +85 -0
  169. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  170. package/dist/rooms-tasks/member-ownership.js +40 -0
  171. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  172. package/dist/rooms-tasks/member-startup.js +2 -0
  173. package/dist/rooms-tasks/provision.d.ts +3 -0
  174. package/dist/rooms-tasks/provision.js +101 -49
  175. package/dist/rooms-tasks/recovery.d.ts +19 -0
  176. package/dist/rooms-tasks/recovery.js +110 -0
  177. package/dist/rooms-tasks/room-state.d.ts +4 -2
  178. package/dist/rooms-tasks/room-state.js +47 -8
  179. package/dist/rooms-tasks/task-review.d.ts +113 -0
  180. package/dist/rooms-tasks/task-review.js +561 -0
  181. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  182. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  183. package/dist/rooms-tasks/task-state.d.ts +51 -5
  184. package/dist/rooms-tasks/task-state.js +358 -50
  185. package/dist/rooms-tasks/task-tools.d.ts +35 -0
  186. package/dist/rooms-tasks/task-tools.js +134 -0
  187. package/dist/rooms-tasks/types.d.ts +78 -0
  188. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  189. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  190. package/dist/rooms-tasks/workspace.d.ts +35 -0
  191. package/dist/rooms-tasks/workspace.js +325 -0
  192. package/dist/runner.d.ts +23 -4
  193. package/dist/runner.js +409 -61
  194. package/dist/session/acp.d.ts +75 -2
  195. package/dist/session/acp.js +345 -82
  196. package/dist/session/arbiter.d.ts +98 -0
  197. package/dist/session/arbiter.js +272 -3
  198. package/dist/session/codex-app-server.d.ts +45 -1
  199. package/dist/session/codex-app-server.js +225 -32
  200. package/dist/session/control.d.ts +17 -3
  201. package/dist/session/control.js +219 -23
  202. package/dist/session/conversation-normalizer.d.ts +2 -1
  203. package/dist/session/conversation-normalizer.js +1 -1
  204. package/dist/session/conversation-observation.d.ts +25 -0
  205. package/dist/session/conversation-observation.js +100 -0
  206. package/dist/session/conversation-replay.d.ts +50 -0
  207. package/dist/session/conversation-replay.js +203 -0
  208. package/dist/session/conversation-store.d.ts +15 -1
  209. package/dist/session/conversation-store.js +213 -26
  210. package/dist/session/conversation-types.d.ts +57 -4
  211. package/dist/session/prompt-batch.d.ts +79 -0
  212. package/dist/session/prompt-batch.js +120 -0
  213. package/dist/session/task-notice.d.ts +14 -0
  214. package/dist/session/task-notice.js +13 -0
  215. package/dist/session/types.d.ts +42 -1
  216. package/dist/setup-tunnel-preflight.d.ts +34 -0
  217. package/dist/setup-tunnel-preflight.js +62 -0
  218. package/dist/socket-path.d.ts +2 -0
  219. package/dist/socket-path.js +22 -0
  220. package/dist/spawn.d.ts +5 -1
  221. package/dist/spawn.js +40 -4
  222. package/dist/startup-prompt.d.ts +15 -0
  223. package/dist/startup-prompt.js +71 -0
  224. package/dist/startup-readiness.d.ts +20 -1
  225. package/dist/startup-readiness.js +66 -3
  226. package/dist/startup-telemetry.d.ts +16 -0
  227. package/dist/startup-telemetry.js +46 -0
  228. package/dist/subscriptions/cli.d.ts +91 -0
  229. package/dist/subscriptions/cli.js +223 -0
  230. package/dist/subscriptions/launch.d.ts +37 -0
  231. package/dist/subscriptions/launch.js +93 -0
  232. package/dist/subscriptions/login.d.ts +77 -0
  233. package/dist/subscriptions/login.js +215 -0
  234. package/dist/subscriptions/observed.d.ts +7 -0
  235. package/dist/subscriptions/observed.js +44 -0
  236. package/dist/subscriptions/probe.d.ts +10 -0
  237. package/dist/subscriptions/probe.js +73 -0
  238. package/dist/subscriptions/service.d.ts +102 -0
  239. package/dist/subscriptions/service.js +364 -0
  240. package/dist/subscriptions/store.d.ts +71 -0
  241. package/dist/subscriptions/store.js +143 -0
  242. package/dist/supervisor/adoption.d.ts +8 -0
  243. package/dist/supervisor/adoption.js +78 -0
  244. package/dist/supervisor/catalog.d.ts +62 -0
  245. package/dist/supervisor/catalog.js +254 -0
  246. package/dist/supervisor/fleet.d.ts +8 -0
  247. package/dist/supervisor/fleet.js +89 -0
  248. package/dist/supervisor/index.js +3 -6
  249. package/dist/supervisor/launchd.d.ts +1 -1
  250. package/dist/supervisor/launchd.js +26 -13
  251. package/dist/supervisor/legacy.d.ts +35 -0
  252. package/dist/supervisor/legacy.js +524 -0
  253. package/dist/supervisor/manager.d.ts +15 -0
  254. package/dist/supervisor/manager.js +270 -0
  255. package/dist/supervisor/registration-refresh.d.ts +45 -0
  256. package/dist/supervisor/registration-refresh.js +266 -0
  257. package/dist/supervisor/scope.d.ts +4 -0
  258. package/dist/supervisor/scope.js +10 -0
  259. package/dist/supervisor/systemd.d.ts +2 -2
  260. package/dist/supervisor/systemd.js +18 -37
  261. package/dist/task-supervision.d.ts +9 -0
  262. package/dist/task-supervision.js +145 -0
  263. package/dist/temp-idle.d.ts +17 -0
  264. package/dist/temp-idle.js +42 -0
  265. package/dist/temp-lifecycle.d.ts +23 -8
  266. package/dist/temp-lifecycle.js +123 -66
  267. package/dist/temp-supervisor-recovery.d.ts +1 -1
  268. package/dist/temp-supervisor-recovery.js +2 -2
  269. package/dist/tunnel-setup-completion.d.ts +7 -0
  270. package/dist/tunnel-setup-completion.js +13 -0
  271. package/dist/tunnel-setup-input.d.ts +19 -0
  272. package/dist/tunnel-setup-input.js +130 -0
  273. package/dist/watchdog/query.d.ts +3 -1
  274. package/dist/watchdog/query.js +2 -1
  275. package/dist/web/agent-attachments.d.ts +29 -0
  276. package/dist/web/agent-attachments.js +162 -0
  277. package/dist/web/auth.d.ts +65 -2
  278. package/dist/web/auth.js +201 -11
  279. package/dist/web/available-port.d.ts +2 -0
  280. package/dist/web/available-port.js +23 -0
  281. package/dist/web/control.d.ts +3 -3
  282. package/dist/web/control.js +10 -8
  283. package/dist/web/conversation-stream-codec.d.ts +23 -0
  284. package/dist/web/conversation-stream-codec.js +110 -0
  285. package/dist/web/conversation-stream.d.ts +26 -0
  286. package/dist/web/conversation-stream.js +239 -0
  287. package/dist/web/events.d.ts +2 -0
  288. package/dist/web/events.js +9 -0
  289. package/dist/web/fleet-config-service.d.ts +7 -1
  290. package/dist/web/fleet-config-service.js +61 -16
  291. package/dist/web/prefix-gateway.d.ts +32 -0
  292. package/dist/web/prefix-gateway.js +248 -0
  293. package/dist/web/resource-views.d.ts +138 -0
  294. package/dist/web/resource-views.js +519 -0
  295. package/dist/web/runtime.d.ts +3 -1
  296. package/dist/web/runtime.js +132 -34
  297. package/dist/web/server.d.ts +19 -0
  298. package/dist/web/server.js +893 -71
  299. package/dist/web/service.d.ts +11 -2
  300. package/dist/web/service.js +30 -4
  301. package/dist/web/workspace-devices.d.ts +107 -0
  302. package/dist/web/workspace-devices.js +277 -0
  303. package/dist/web/workspace-tunnel.d.ts +1 -0
  304. package/dist/web/workspace-tunnel.js +34 -0
  305. package/dist/workspace-enrollment.d.ts +83 -0
  306. package/dist/workspace-enrollment.js +605 -0
  307. package/dist/workspace-replacement.d.ts +66 -0
  308. package/dist/workspace-replacement.js +238 -0
  309. package/docs/acp-files.md +146 -0
  310. package/docs/conversation-stream.md +216 -0
  311. package/docs/fleet-migration.md +54 -0
  312. package/docs/live-voice.md +13 -0
  313. package/docs/room-layouts.md +227 -0
  314. package/docs/self-reminders.md +374 -0
  315. package/docs/task-progress-review.md +143 -0
  316. package/docs/task-workspaces.md +225 -0
  317. package/docs/validation/managed-cli-permissions.md +421 -0
  318. package/examples/room-layouts/fresh.yaml +27 -0
  319. package/examples/room-layouts/scoped.yaml +21 -0
  320. package/examples/room-layouts/shared.yaml +19 -0
  321. package/package.json +25 -20
  322. package/presets/README.md +78 -0
  323. package/presets/brain-catalog.json +8 -2
  324. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  325. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  326. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  327. package/presets/fleet/agent_templates/LocalCoordinator.yaml +6 -3
  328. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  329. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  330. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  331. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  332. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  333. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  334. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  335. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  336. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  337. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  338. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  339. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  340. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  341. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  342. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  343. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  344. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  345. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  351. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  352. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  353. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  354. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  384. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  385. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  386. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  387. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  390. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  391. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  392. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  393. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  394. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  396. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  397. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  398. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  399. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  400. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  406. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  407. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  408. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  409. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  410. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  411. package/presets/fleet/roles/Coordinator.yaml +21 -13
  412. package/presets/fleet/roles/Critic.yaml +24 -0
  413. package/presets/fleet/roles/Developer.yaml +32 -0
  414. package/presets/fleet/roles/Engineer.yaml +72 -0
  415. package/presets/fleet/roles/LocalCoordinator.yaml +17 -0
  416. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  417. package/presets/fleet/room_layouts/pair.yaml +24 -0
  418. package/presets/fleet/room_layouts/single.yaml +17 -0
  419. package/presets/fleet/room_layouts/team.yaml +30 -0
  420. package/presets/fleet/room_templates/engineering.yaml +19 -0
  421. package/presets/manifest.json +2 -2
  422. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  423. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  424. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  425. package/dist/web-app/icons/ours-fleet.svg +0 -4
  426. package/dist/web-app/index.html +0 -17
  427. package/dist/web-app/manifest.webmanifest +0 -15
  428. package/dist/web-app/offline.html +0 -18
  429. package/dist/web-app/sw.js +0 -51
@@ -0,0 +1,75 @@
1
+ import type { ReminderAttempt, UrgentReminderHooks } from '../session/arbiter.js';
2
+ import type { PromptOrigin } from '../session/types.js';
3
+ import type { ReminderEvent, ReminderRecord, ReminderStore } from './store.js';
4
+ /** The admission seam: the role's RoleTurnArbiter in production. */
5
+ export interface ReminderAdmitter {
6
+ tryReminder(text: string, origin: Extract<PromptOrigin, {
7
+ kind: 'self-reminder';
8
+ }>, beforeQueue: () => boolean, releaseGuard: () => boolean): Promise<ReminderAttempt>;
9
+ /** Urgent admission (see `RoleTurnArbiter.tryUrgentReminder`). Absent: urgent reminders are idle-only. */
10
+ tryUrgentReminder?(origin: Extract<PromptOrigin, {
11
+ kind: 'self-reminder';
12
+ }>, hooks: UrgentReminderHooks): Promise<ReminderAttempt>;
13
+ }
14
+ export interface ReminderManagerDeps {
15
+ now(): number;
16
+ setTimer(callback: () => void, ms: number): unknown;
17
+ clearTimer(timer: unknown): void;
18
+ log(line: string): void;
19
+ }
20
+ /**
21
+ * The dispatcher never sleeps longer than this while reminders are open, so a
22
+ * wall-clock jump (host suspend, NTP step) is noticed within a minute. With
23
+ * nothing open it arms no timer at all: an idle agent costs nothing.
24
+ */
25
+ export declare const REMINDER_POLL_CEILING_MS = 60000;
26
+ export interface ReminderManagerHandle {
27
+ start(): void;
28
+ stop(): void;
29
+ /** Called after an agent tool changed the schedule so the timer reflects it. */
30
+ changed(): void;
31
+ }
32
+ /**
33
+ * Turns due self-reminders into wakes for the agent that owns them.
34
+ *
35
+ * Runs inside that agent's runner, beside the role's turn arbiter. It starts
36
+ * only after the role's startup turn has succeeded and stops before the runner
37
+ * releases its runtime binding. Admission is idle-only by default: a wake
38
+ * never interrupts or queues behind work, and the session's release guard
39
+ * makes the durable `dispatched` claim at the moment the prompt reaches the
40
+ * model. A reminder created with `interrupt: true` is considered first and may
41
+ * ask an interruptible busy turn to stop once (`tryUrgentReminder`); the
42
+ * release guard is still the only path to the model.
43
+ */
44
+ export declare class ReminderManager implements ReminderManagerHandle {
45
+ private readonly role;
46
+ private readonly store;
47
+ private readonly admitter;
48
+ private readonly deps;
49
+ private timer?;
50
+ private started;
51
+ private stopping;
52
+ private polling;
53
+ private busyStreak;
54
+ /** Consecutive re-arms with work already overdue; drives the spin backoff. */
55
+ private overdueStreak;
56
+ private readonly inFlight;
57
+ constructor(role: string, store: ReminderStore, admitter: ReminderAdmitter, deps: ReminderManagerDeps);
58
+ start(): void;
59
+ /** Synchronous: after this returns no timer is armed and every release guard refuses. */
60
+ stop(): void;
61
+ get isStopping(): boolean;
62
+ changed(): void;
63
+ /** Public fake-clock seam; the timer calls the same transition. */
64
+ poll(): Promise<void>;
65
+ private admit;
66
+ private guard;
67
+ private retryAt;
68
+ private schedule;
69
+ }
70
+ /**
71
+ * The wake text. It carries the agent's own title so the wake is legible, but
72
+ * the note is only returned by `fleet_reminder_inspect`, which also re-checks
73
+ * eligibility at the moment the agent acts.
74
+ */
75
+ export declare function wakePrompt(event: ReminderEvent, reminder: ReminderRecord, now: number, retry: boolean): string;
@@ -0,0 +1,278 @@
1
+ /**
2
+ * The dispatcher never sleeps longer than this while reminders are open, so a
3
+ * wall-clock jump (host suspend, NTP step) is noticed within a minute. With
4
+ * nothing open it arms no timer at all: an idle agent costs nothing.
5
+ */
6
+ export const REMINDER_POLL_CEILING_MS = 60_000;
7
+ /** Busy role: re-check at the next idle boundary within these bounds. */
8
+ const BUSY_RETRY_MIN_MS = 2_000;
9
+ const BUSY_RETRY_MAX_MS = 30_000;
10
+ /** Admission refused before reaching the model: bounded backoff, inside the late window. */
11
+ const UNAVAILABLE_RETRY_MS = [5_000, 15_000, 30_000, 60_000];
12
+ /**
13
+ * Turns due self-reminders into wakes for the agent that owns them.
14
+ *
15
+ * Runs inside that agent's runner, beside the role's turn arbiter. It starts
16
+ * only after the role's startup turn has succeeded and stops before the runner
17
+ * releases its runtime binding. Admission is idle-only by default: a wake
18
+ * never interrupts or queues behind work, and the session's release guard
19
+ * makes the durable `dispatched` claim at the moment the prompt reaches the
20
+ * model. A reminder created with `interrupt: true` is considered first and may
21
+ * ask an interruptible busy turn to stop once (`tryUrgentReminder`); the
22
+ * release guard is still the only path to the model.
23
+ */
24
+ export class ReminderManager {
25
+ role;
26
+ store;
27
+ admitter;
28
+ deps;
29
+ timer;
30
+ started = false;
31
+ stopping = false;
32
+ polling = false;
33
+ busyStreak = 0;
34
+ /** Consecutive re-arms with work already overdue; drives the spin backoff. */
35
+ overdueStreak = 0;
36
+ inFlight = new Set();
37
+ constructor(role, store, admitter, deps) {
38
+ this.role = role;
39
+ this.store = store;
40
+ this.admitter = admitter;
41
+ this.deps = deps;
42
+ }
43
+ start() {
44
+ if (this.started || this.stopping)
45
+ return;
46
+ this.started = true;
47
+ this.schedule(0);
48
+ }
49
+ /** Synchronous: after this returns no timer is armed and every release guard refuses. */
50
+ stop() {
51
+ this.stopping = true;
52
+ if (this.timer !== undefined)
53
+ this.deps.clearTimer(this.timer);
54
+ this.timer = undefined;
55
+ }
56
+ get isStopping() { return this.stopping; }
57
+ changed() {
58
+ if (this.started && !this.stopping && !this.polling)
59
+ this.schedule();
60
+ }
61
+ /** Public fake-clock seam; the timer calls the same transition. */
62
+ async poll() {
63
+ if (!this.started || this.stopping || this.polling)
64
+ return;
65
+ this.polling = true;
66
+ let delay;
67
+ try {
68
+ // Urgent events that may still interrupt come first, so neither a busy
69
+ // ordinary event nor an urgent one whose single interruption is spent
70
+ // (now idle-only) can hide them. Earliest-due order is kept within each
71
+ // group; ordinary and spent events keep the idle-only default.
72
+ const ready = this.store.sweep(this.inFlight);
73
+ const rank = (id) => {
74
+ const summary = this.store.eventSummary(id);
75
+ return summary?.reminder.interrupt === true && !summary.event.interrupt ? 0 : 1;
76
+ };
77
+ const ranks = new Map(ready.map(event => [event.id, rank(event.id)]));
78
+ ready.sort((a, b) => ranks.get(a.id) - ranks.get(b.id));
79
+ for (const event of ready) {
80
+ if (this.stopping)
81
+ return;
82
+ const outcome = await this.admit(event);
83
+ if (outcome === 'busy') {
84
+ this.busyStreak++;
85
+ delay = Math.min(BUSY_RETRY_MAX_MS, BUSY_RETRY_MIN_MS * 2 ** Math.min(this.busyStreak - 1, 4));
86
+ break;
87
+ }
88
+ this.busyStreak = 0;
89
+ // One wake per turn boundary: the next due reminder waits for the
90
+ // following poll, by which time this one's turn has claimed the role.
91
+ if (outcome === 'admitted') {
92
+ delay = 0;
93
+ break;
94
+ }
95
+ }
96
+ }
97
+ catch (error) {
98
+ this.deps.log(`[${this.role}] self-reminder dispatch failed: ${error?.name ?? 'Error'}; retrying`);
99
+ delay = UNAVAILABLE_RETRY_MS[0];
100
+ }
101
+ finally {
102
+ this.polling = false;
103
+ }
104
+ this.schedule(delay);
105
+ }
106
+ async admit(event) {
107
+ const summary = this.store.eventSummary(event.id);
108
+ if (!summary)
109
+ return 'skipped';
110
+ const { reminder } = summary;
111
+ const origin = {
112
+ kind: 'self-reminder', eventId: event.id, reminderId: reminder.id, revision: event.revision,
113
+ };
114
+ const text = () => {
115
+ const current = this.store.eventSummary(event.id)?.event ?? event;
116
+ return wakePrompt(current, reminder, this.deps.now(), event.attempts > 0);
117
+ };
118
+ const beforeQueue = () => !this.stopping && this.store.markAdmitting(event.id);
119
+ const releaseGuard = () => this.guard(event.id);
120
+ this.inFlight.add(event.id);
121
+ let attempt;
122
+ try {
123
+ attempt = reminder.interrupt === true && this.admitter.tryUrgentReminder
124
+ ? await this.admitter.tryUrgentReminder(origin, {
125
+ beforeQueue, releaseGuard, text,
126
+ mayInterrupt: () => !this.stopping && this.store.mayInterrupt(event.id),
127
+ markRequested: target => !this.stopping && this.store.markInterruptRequested(event.id, target),
128
+ recordOutcome: report => {
129
+ this.store.recordInterruptOutcome(event.id, report.outcome, report.reason);
130
+ this.deps.log(`[${this.role}] self-reminder ${event.id.slice(0, 12)} interrupt ${report.outcome}`
131
+ + ` (${report.target.source} turn)`);
132
+ },
133
+ deferred: reason => this.store.recordInterruptDeferred(event.id, reason),
134
+ stillAdmitting: () => !this.stopping && this.store.stillAdmitting(event.id),
135
+ })
136
+ : await this.admitter.tryReminder(text(), origin, beforeQueue, releaseGuard);
137
+ }
138
+ catch (error) {
139
+ this.inFlight.delete(event.id);
140
+ this.store.releaseUndispatched(event.id, this.retryAt(event));
141
+ throw error;
142
+ }
143
+ if (attempt.state === 'busy') {
144
+ this.inFlight.delete(event.id);
145
+ // An urgent attempt may have claimed the event before finding the role
146
+ // busy again (owner input won the boundary after an interruption).
147
+ this.store.releaseUndispatched(event.id, this.deps.now());
148
+ return 'busy';
149
+ }
150
+ if (attempt.state === 'unavailable') {
151
+ this.inFlight.delete(event.id);
152
+ this.store.releaseUndispatched(event.id, this.retryAt(event));
153
+ this.deps.log(`[${this.role}] self-reminder ${event.id.slice(0, 12)} not admitted; will retry within its window`);
154
+ return 'skipped';
155
+ }
156
+ // Own the completion before anything else can throw: the queued prompt
157
+ // ends this admission whatever happens to the receipt write below.
158
+ const settled = () => {
159
+ this.inFlight.delete(event.id);
160
+ // Settled without the guard dispatching it (offline session, refused at
161
+ // release while stopping): still undispatched, so it may be retried.
162
+ // A failed write leaves it admitting with no owner; sweep recovers that.
163
+ try {
164
+ this.store.releaseUndispatched(event.id, this.retryAt(event));
165
+ }
166
+ catch { /* recovered by sweep */ }
167
+ this.changed();
168
+ };
169
+ void attempt.queued.completion.then(settled, settled);
170
+ // The receipt is diagnostic only; dispatch state is the release guard's.
171
+ try {
172
+ this.store.recordReceipt(event.id, attempt.queued.promptId);
173
+ }
174
+ catch {
175
+ this.deps.log(`[${this.role}] self-reminder ${event.id.slice(0, 12)} receipt not recorded`);
176
+ }
177
+ this.deps.log(`[${this.role}] self-reminder ${event.id.slice(0, 12)} admitted`);
178
+ return 'admitted';
179
+ }
180
+ guard(eventId) {
181
+ const result = this.store.claimDispatch(eventId, this.stopping);
182
+ if (!result.dispatch)
183
+ this.deps.log(`[${this.role}] self-reminder ${eventId.slice(0, 12)} not dispatched at release (${result.reason})`);
184
+ return result.dispatch;
185
+ }
186
+ retryAt(event) {
187
+ const step = UNAVAILABLE_RETRY_MS[Math.min(event.attempts, UNAVAILABLE_RETRY_MS.length - 1)];
188
+ return this.deps.now() + step;
189
+ }
190
+ schedule(delayMs) {
191
+ if (this.timer !== undefined)
192
+ this.deps.clearTimer(this.timer);
193
+ this.timer = undefined;
194
+ if (this.stopping)
195
+ return;
196
+ let delay = delayMs;
197
+ if (delay === undefined) {
198
+ const next = this.store.nextWorkAt(this.inFlight);
199
+ if (next === undefined) {
200
+ this.overdueStreak = 0;
201
+ return;
202
+ }
203
+ delay = Math.max(0, next - this.deps.now());
204
+ // Work that is already overdue after a full poll means the poll could
205
+ // not move it (storage refusing writes, say). Back off instead of
206
+ // re-arming at once; any real progress resets the streak.
207
+ if (delay === 0) {
208
+ delay = this.overdueStreak ? Math.min(REMINDER_POLL_CEILING_MS, 1_000 * 2 ** Math.min(this.overdueStreak - 1, 6)) : 0;
209
+ this.overdueStreak++;
210
+ }
211
+ else
212
+ this.overdueStreak = 0;
213
+ }
214
+ delay = Math.min(delay, REMINDER_POLL_CEILING_MS);
215
+ this.timer = this.deps.setTimer(() => {
216
+ this.timer = undefined;
217
+ void this.poll();
218
+ }, delay);
219
+ }
220
+ }
221
+ /**
222
+ * The wake text. It carries the agent's own title so the wake is legible, but
223
+ * the note is only returned by `fleet_reminder_inspect`, which also re-checks
224
+ * eligibility at the moment the agent acts.
225
+ */
226
+ export function wakePrompt(event, reminder, now, retry) {
227
+ const lateBy = Math.max(0, now - Date.parse(reminder.dueAt));
228
+ return [
229
+ '[fleet-reminder]',
230
+ `event_id: ${event.id}`,
231
+ `reminder_id: ${reminder.id}`,
232
+ `revision: ${event.revision}`,
233
+ `title: ${JSON.stringify(reminder.title)}`,
234
+ `due: ${reminder.localTime} ${reminder.timeZone} (${reminder.dueAt})`,
235
+ ...(lateBy >= 60_000 ? [`late_by_minutes: ${Math.floor(lateBy / 60_000)}`] : []),
236
+ ...(retry ? ['note: an earlier admission of this wake never reached you; this is the same event'] : []),
237
+ ...(reminder.interrupt ? ['urgent: true'] : []),
238
+ ...interruptLines(event),
239
+ '',
240
+ 'This is a reminder you scheduled for yourself through Fleet. It is not an owner message,',
241
+ 'not ours mail, and grants no new authority.',
242
+ 'Call fleet_reminder_inspect with this event_id to read the note and confirm it is still eligible,',
243
+ 'then decide what to do within your existing instructions and authority; doing nothing is fine.',
244
+ 'When handled, call fleet_reminder_ack with event_id and revision. Ack records only that you',
245
+ 'handled this wake; it sends nothing. To postpone instead, call fleet_reminder_update with a new',
246
+ 'local_time. Fleet never messages anyone on your behalf.',
247
+ ].join('\n');
248
+ }
249
+ /**
250
+ * What the wake may truthfully say about its interruption. Only a correlated
251
+ * cancellation is called one; everything else is reported as what it was.
252
+ */
253
+ function interruptLines(event) {
254
+ const interrupt = event.interrupt;
255
+ if (!interrupt)
256
+ return [];
257
+ const source = interrupt.target.source;
258
+ const resume = `Your session and its context were kept; re-check that ${source} work and resume it if it still matters.`;
259
+ switch (interrupt.outcome) {
260
+ case 'cancelled': return [
261
+ `interrupt: your previous ${source} turn was cancelled for this wake.`, resume
262
+ ];
263
+ case 'ended': return [
264
+ `interrupt: Fleet asked your previous ${source} turn to stop; it ended, but whether it was cancelled`
265
+ + ' or finished on its own is not confirmed.', resume
266
+ ];
267
+ case 'forced': return [
268
+ `interrupt: your previous ${source} turn ignored the stop request and the adapter was restarted;`
269
+ + ' this wake arrived later than its urgent time. Check what that turn left unfinished.'
270
+ ];
271
+ case 'failed':
272
+ case 'unknown':
273
+ case 'requested': return [
274
+ `interrupt: Fleet asked your previous ${source} turn to stop but the outcome is unknown (${interrupt.outcome});`
275
+ + ' this wake was delivered at the next idle boundary instead.'
276
+ ];
277
+ }
278
+ }
@@ -0,0 +1,27 @@
1
+ import { ReminderManager, type ReminderAdmitter, type ReminderManagerDeps } from './manager.js';
2
+ import type { ReminderToolBackend } from './tools.js';
3
+ export interface RoleReminderBinding {
4
+ role: string;
5
+ stateDir: string;
6
+ /** The managed runtime's own record; nothing here comes from the agent. */
7
+ runtime: {
8
+ instance?: string;
9
+ cid?: string;
10
+ action?: string;
11
+ lifetime?: 'permanent' | 'temporary';
12
+ };
13
+ /** Whether this session backend accepts supervisor-submitted prompts. */
14
+ promptInput: boolean;
15
+ admitter: ReminderAdmitter;
16
+ service: {
17
+ setReminders?(backend: ReminderToolBackend | undefined): void;
18
+ };
19
+ deps: ReminderManagerDeps;
20
+ }
21
+ /**
22
+ * Bind self-reminders for one runner attempt: load the store for exactly this
23
+ * runtime instance and identity, expose it behind the agent's own MCP tools,
24
+ * and return the (not yet started) dispatcher. Any doubt leaves reminders
25
+ * unavailable rather than guessing an owner.
26
+ */
27
+ export declare function bindRoleReminders(binding: RoleReminderBinding): ReminderManager | undefined;
@@ -0,0 +1,32 @@
1
+ import { ReminderManager } from './manager.js';
2
+ import { ReminderStore } from './store.js';
3
+ /**
4
+ * Bind self-reminders for one runner attempt: load the store for exactly this
5
+ * runtime instance and identity, expose it behind the agent's own MCP tools,
6
+ * and return the (not yet started) dispatcher. Any doubt leaves reminders
7
+ * unavailable rather than guessing an owner.
8
+ */
9
+ export function bindRoleReminders(binding) {
10
+ const { role, runtime, deps } = binding;
11
+ if (!binding.promptInput || !binding.service.setReminders)
12
+ return undefined;
13
+ if (!runtime.instance || !runtime.action || !/^[A-Fa-f0-9]{64}$/.test(runtime.cid ?? '')
14
+ || (runtime.lifetime !== 'permanent' && runtime.lifetime !== 'temporary')) {
15
+ deps.log(`[${role}] self-reminders unavailable: managed runtime identity is not established`);
16
+ return undefined;
17
+ }
18
+ const owner = {
19
+ role, lifetime: runtime.lifetime, instance: runtime.instance, cid: runtime.cid, action: runtime.action,
20
+ };
21
+ let store;
22
+ try {
23
+ store = new ReminderStore(binding.stateDir, owner, deps.now, deps.log);
24
+ }
25
+ catch (error) {
26
+ deps.log(`[${role}] self-reminders unavailable: state could not be opened (${error?.name ?? 'Error'})`);
27
+ return undefined;
28
+ }
29
+ const manager = new ReminderManager(role, store, binding.admitter, deps);
30
+ binding.service.setReminders({ store, changed: () => manager.changed() });
31
+ return manager;
32
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Wall-clock schedule resolution for self-reminders.
3
+ *
4
+ * A reminder is stated the way a person states it — a calendar date, a time of
5
+ * day and the IANA zone it is meant in — and stored as the one UTC instant that
6
+ * statement denotes. Nothing is inferred from the host's own zone: a time with
7
+ * an offset, a missing date or an unknown zone is refused rather than guessed.
8
+ *
9
+ * Daylight-saving transitions are the two places a wall time is not one
10
+ * instant. A time skipped by a spring-forward gap does not exist and is
11
+ * refused. A time repeated by a fall-back overlap exists twice, and the caller
12
+ * must say which with `fold` (0 = the first occurrence, 1 = the second), the
13
+ * same convention as Python's `datetime.fold`.
14
+ */
15
+ export declare class ReminderScheduleError extends Error {
16
+ readonly code: string;
17
+ constructor(code: string, message: string);
18
+ }
19
+ /** Canonical IANA zone name, or a typed refusal. Host defaults never apply. */
20
+ export declare function canonicalTimeZone(raw: unknown): string;
21
+ export interface ResolvedSchedule {
22
+ /** Canonical `YYYY-MM-DDTHH:MM:SS`, as the caller meant it. */
23
+ localTime: string;
24
+ timeZone: string;
25
+ /** Present only when the wall time was ambiguous and the caller chose. */
26
+ fold?: 0 | 1;
27
+ dueAt: number;
28
+ }
29
+ /**
30
+ * Resolve a wall time in a zone to its instant. Every offset the zone uses in
31
+ * the surrounding two days is a candidate; a candidate survives only if it
32
+ * reads back as the requested wall time. Zero survivors is a gap, two is an
33
+ * overlap. Zones change offset at most once in any such window in practice,
34
+ * and a reading that does not round-trip is never accepted, so the answer is
35
+ * never a guess.
36
+ */
37
+ export declare function resolveSchedule(localTime: unknown, timeZone: unknown, fold?: unknown): ResolvedSchedule;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Wall-clock schedule resolution for self-reminders.
3
+ *
4
+ * A reminder is stated the way a person states it — a calendar date, a time of
5
+ * day and the IANA zone it is meant in — and stored as the one UTC instant that
6
+ * statement denotes. Nothing is inferred from the host's own zone: a time with
7
+ * an offset, a missing date or an unknown zone is refused rather than guessed.
8
+ *
9
+ * Daylight-saving transitions are the two places a wall time is not one
10
+ * instant. A time skipped by a spring-forward gap does not exist and is
11
+ * refused. A time repeated by a fall-back overlap exists twice, and the caller
12
+ * must say which with `fold` (0 = the first occurrence, 1 = the second), the
13
+ * same convention as Python's `datetime.fold`.
14
+ */
15
+ export class ReminderScheduleError extends Error {
16
+ code;
17
+ constructor(code, message) {
18
+ super(message);
19
+ this.code = code;
20
+ this.name = 'ReminderScheduleError';
21
+ }
22
+ }
23
+ const LOCAL_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/;
24
+ const HOUR_MS = 60 * 60 * 1000;
25
+ const formatters = new Map();
26
+ function formatter(timeZone) {
27
+ let value = formatters.get(timeZone);
28
+ if (!value) {
29
+ value = new Intl.DateTimeFormat('en-US', {
30
+ timeZone, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit',
31
+ hour: '2-digit', minute: '2-digit', second: '2-digit', era: 'short',
32
+ });
33
+ formatters.set(timeZone, value);
34
+ }
35
+ return value;
36
+ }
37
+ /** Canonical IANA zone name, or a typed refusal. Host defaults never apply. */
38
+ export function canonicalTimeZone(raw) {
39
+ if (typeof raw !== 'string' || !raw.trim() || raw.length > 64 || !/^[A-Za-z0-9_+\-/]+$/.test(raw))
40
+ throw new ReminderScheduleError('invalid_time_zone', 'time_zone must be an IANA zone name such as Europe/London or UTC');
41
+ try {
42
+ return new Intl.DateTimeFormat('en-US', { timeZone: raw }).resolvedOptions().timeZone;
43
+ }
44
+ catch {
45
+ throw new ReminderScheduleError('invalid_time_zone', `unknown IANA time zone '${raw}'`);
46
+ }
47
+ }
48
+ function parseLocalTime(raw) {
49
+ const match = typeof raw === 'string' ? LOCAL_TIME_RE.exec(raw) : null;
50
+ if (!match)
51
+ throw new ReminderScheduleError('invalid_local_time', 'local_time must be a calendar date and time without an offset, e.g. 2027-01-20T16:00 or 2027-01-20T16:00:00');
52
+ const [year, month, day, hour, minute, second] = match.slice(1).map(part => Number(part ?? 0));
53
+ const wall = { year: year, month: month, day: day, hour: hour, minute: minute, second: second };
54
+ // Date.UTC normalizes overflow (Feb 30 -> Mar 2); a round trip rejects it.
55
+ const probe = new Date(wallMs(wall));
56
+ if (wall.year < 1970 || wall.year > 9999 || probe.getUTCFullYear() !== wall.year
57
+ || probe.getUTCMonth() + 1 !== wall.month || probe.getUTCDate() !== wall.day
58
+ || wall.hour > 23 || wall.minute > 59 || wall.second > 59)
59
+ throw new ReminderScheduleError('invalid_local_time', `local_time '${String(raw)}' is not a real calendar date and time`);
60
+ return wall;
61
+ }
62
+ function wallMs(wall) {
63
+ return Date.UTC(wall.year, wall.month - 1, wall.day, wall.hour, wall.minute, wall.second);
64
+ }
65
+ /** The wall-clock reading of `instant` in `timeZone`, expressed as naive UTC milliseconds. */
66
+ function wallAt(instant, timeZone) {
67
+ const parts = Object.fromEntries(formatter(timeZone).formatToParts(new Date(instant))
68
+ .map(part => [part.type, part.value]));
69
+ if (parts.era !== 'AD')
70
+ throw new ReminderScheduleError('invalid_local_time', 'dates before the common era are not supported');
71
+ return Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour), Number(parts.minute), Number(parts.second));
72
+ }
73
+ /**
74
+ * Resolve a wall time in a zone to its instant. Every offset the zone uses in
75
+ * the surrounding two days is a candidate; a candidate survives only if it
76
+ * reads back as the requested wall time. Zero survivors is a gap, two is an
77
+ * overlap. Zones change offset at most once in any such window in practice,
78
+ * and a reading that does not round-trip is never accepted, so the answer is
79
+ * never a guess.
80
+ */
81
+ export function resolveSchedule(localTime, timeZone, fold) {
82
+ const zone = canonicalTimeZone(timeZone);
83
+ const wall = parseLocalTime(localTime);
84
+ if (fold !== undefined && fold !== null && fold !== 0 && fold !== 1)
85
+ throw new ReminderScheduleError('invalid_fold', 'fold must be 0 (first occurrence) or 1 (second occurrence)');
86
+ const naive = wallMs(wall);
87
+ const offsets = new Set();
88
+ for (let probe = -48; probe <= 48; probe += 6) {
89
+ const instant = naive + probe * HOUR_MS;
90
+ offsets.add(wallAt(instant, zone) - instant);
91
+ }
92
+ const matches = [...new Set([...offsets].map(offset => naive - offset))]
93
+ .filter(instant => wallAt(instant, zone) === naive)
94
+ .sort((a, b) => a - b);
95
+ const canonical = new Date(naive).toISOString().slice(0, 19);
96
+ if (!matches.length)
97
+ throw new ReminderScheduleError('nonexistent_local_time', `${canonical} does not exist in ${zone} (skipped by a daylight-saving change); choose another time`);
98
+ if (matches.length > 1) {
99
+ if (fold !== 0 && fold !== 1)
100
+ throw new ReminderScheduleError('ambiguous_local_time', `${canonical} occurs twice in ${zone}; pass fold 0 for the first occurrence or 1 for the second`);
101
+ return { localTime: canonical, timeZone: zone, fold, dueAt: matches[fold] };
102
+ }
103
+ return { localTime: canonical, timeZone: zone, dueAt: matches[0] };
104
+ }