@pellux/goodvibes-tui 1.27.0 → 2.0.1

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 (404) hide show
  1. package/CHANGELOG.md +125 -2
  2. package/README.md +7 -9
  3. package/bin/goodvibes +2 -2
  4. package/bin/launcher-support.js +8 -4
  5. package/docs/README.md +11 -2
  6. package/docs/foundation-artifacts/operator-contract.json +6403 -2347
  7. package/package.json +14 -25
  8. package/scripts/postinstall.js +35 -35
  9. package/src/audio/spoken-turn-model-routing.ts +1 -1
  10. package/src/cli/bundle-command.ts +5 -5
  11. package/src/cli/completions/generate.ts +1 -1
  12. package/src/cli/doctor.ts +3 -3
  13. package/src/cli/entrypoint.ts +12 -7
  14. package/src/cli/help.ts +4 -5
  15. package/src/cli/hooks-command.ts +1 -1
  16. package/src/cli/index.ts +1 -4
  17. package/src/cli/launch-auto-update.ts +59 -7
  18. package/src/cli/management-commands.ts +6 -6
  19. package/src/cli/management-utils.ts +5 -5
  20. package/src/cli/management.ts +7 -7
  21. package/src/cli/network-posture.ts +1 -1
  22. package/src/cli/package-verification.ts +7 -6
  23. package/src/cli/plugin-bundles-command.ts +1 -1
  24. package/src/cli/plugin-command.ts +2 -2
  25. package/src/cli/service-command.ts +2 -2
  26. package/src/cli/service-posture.ts +4 -4
  27. package/src/cli/status.ts +9 -8
  28. package/src/cli/surface-command.ts +4 -4
  29. package/src/cli/tui-startup.ts +12 -3
  30. package/src/cli-flags.ts +3 -3
  31. package/src/config/goodvibes-home-audit.ts +0 -2
  32. package/src/config/secret-config.ts +37 -5
  33. package/src/config/tui-extension-settings.ts +1 -1
  34. package/src/core/approval-alert.ts +2 -2
  35. package/src/core/budget-breach-notifier.ts +3 -3
  36. package/src/core/conversation-compaction-render.ts +1 -1
  37. package/src/core/conversation-line-cache.ts +1 -1
  38. package/src/core/conversation-render-context.ts +1 -1
  39. package/src/core/conversation-rendering.ts +2 -2
  40. package/src/core/conversation.ts +10 -10
  41. package/src/core/long-task-notifier.ts +2 -2
  42. package/src/core/memory-diagnostics-gateway.ts +1 -1
  43. package/src/core/scriptable-statusline.ts +1 -1
  44. package/src/core/session-auto-titler.ts +1 -1
  45. package/src/core/session-resume-core.ts +2 -2
  46. package/src/core/stream-event-wiring.ts +24 -44
  47. package/src/core/terminal-notifier.ts +3 -3
  48. package/src/core/turn-cancellation.ts +1 -1
  49. package/src/core/turn-event-wiring.ts +5 -5
  50. package/src/input/command-registry.ts +6 -6
  51. package/src/input/commands/channel-pairing.ts +1 -1
  52. package/src/input/commands/cluster-runtime.ts +1 -1
  53. package/src/input/commands/codebase-runtime.ts +1 -1
  54. package/src/input/commands/devices-runtime.ts +1 -1
  55. package/src/input/commands/hosted-runtime.ts +330 -0
  56. package/src/input/commands/local-setup-review.ts +1 -1
  57. package/src/input/commands/local-setup-transfer.ts +2 -2
  58. package/src/input/commands/local-setup.ts +3 -3
  59. package/src/input/commands/managed-runtime.ts +2 -2
  60. package/src/input/commands/operator-rpc.ts +24 -110
  61. package/src/input/commands/payment-card-intake.ts +1 -1
  62. package/src/input/commands/permissions-provenance.ts +2 -2
  63. package/src/input/commands/plugin-runtime.ts +1 -1
  64. package/src/input/commands/provider.ts +1 -1
  65. package/src/input/commands/relay-runtime.ts +18 -9
  66. package/src/input/commands/rewind-runtime.ts +1 -1
  67. package/src/input/commands/runtime-services.ts +1 -1
  68. package/src/input/commands/services-runtime.ts +1 -1
  69. package/src/input/commands/session-workflow.ts +1 -1
  70. package/src/input/commands/settings-sync-runtime.ts +2 -2
  71. package/src/input/commands/share-runtime.ts +1 -1
  72. package/src/input/commands/tasks-runtime.ts +145 -55
  73. package/src/input/commands/update-runtime.ts +31 -28
  74. package/src/input/commands/work-plan-runtime.ts +2 -2
  75. package/src/input/commands/workstream-attempts.ts +1 -1
  76. package/src/input/commands/workstream-runtime.ts +2 -2
  77. package/src/input/commands.ts +2 -0
  78. package/src/input/feature-unit-layout.ts +1 -1
  79. package/src/input/feed-context-factory.ts +3 -3
  80. package/src/input/handler-content-actions.ts +2 -2
  81. package/src/input/handler-feed-routes.ts +1 -1
  82. package/src/input/handler-feed.ts +3 -3
  83. package/src/input/handler-modal-token-routes.ts +1 -1
  84. package/src/input/handler-onboarding-daemon-adopt.ts +3 -2
  85. package/src/input/handler-onboarding.ts +1 -1
  86. package/src/input/handler-types.ts +2 -2
  87. package/src/input/handler-ui-state.ts +1 -1
  88. package/src/input/handler.ts +4 -4
  89. package/src/input/input-history.ts +1 -1
  90. package/src/input/onboarding/onboarding-feature-units.ts +2 -2
  91. package/src/input/onboarding/onboarding-runtime-status.ts +15 -1
  92. package/src/input/onboarding/onboarding-wizard-apply.ts +17 -8
  93. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +5 -3
  94. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +1 -1
  95. package/src/input/onboarding/onboarding-wizard-types.ts +1 -1
  96. package/src/input/payments-config.ts +6 -6
  97. package/src/input/search.ts +1 -1
  98. package/src/input/session-picker-modal.ts +46 -0
  99. package/src/input/settings-modal-activation.ts +1 -1
  100. package/src/input/settings-modal-behavior.ts +4 -4
  101. package/src/input/settings-modal-commit.ts +11 -0
  102. package/src/input/settings-modal-data.ts +13 -80
  103. package/src/input/settings-modal-mutations.ts +33 -0
  104. package/src/input/settings-modal-secrets.ts +34 -0
  105. package/src/input/settings-modal-subscriptions.ts +1 -1
  106. package/src/input/settings-modal-types.ts +15 -1
  107. package/src/input/settings-modal.ts +28 -3
  108. package/src/input/worktree-setup-config.ts +1 -1
  109. package/src/main.ts +11 -9
  110. package/src/panels/agent-inspector-shared.ts +2 -2
  111. package/src/panels/base-panel.ts +2 -2
  112. package/src/panels/builtin/operations.ts +6 -8
  113. package/src/panels/builtin/session.ts +17 -0
  114. package/src/panels/builtin/shared.ts +5 -5
  115. package/src/panels/builtin-modals.ts +3 -3
  116. package/src/panels/confirm-state.ts +1 -1
  117. package/src/panels/cost-tracker-panel.ts +4 -4
  118. package/src/panels/diff-panel.ts +2 -2
  119. package/src/panels/diff-review-panel.ts +2 -2
  120. package/src/panels/expandable-list-panel.ts +2 -2
  121. package/src/panels/fleet-acts.ts +1 -1
  122. package/src/panels/fleet-observed-render.ts +1 -1
  123. package/src/panels/fleet-panel-format.ts +1 -1
  124. package/src/panels/fleet-panel.ts +1 -1
  125. package/src/panels/fleet-spawn.ts +1 -1
  126. package/src/panels/fleet-steer.ts +1 -1
  127. package/src/panels/fleet-transcript.ts +1 -1
  128. package/src/panels/git-panel.ts +1 -1
  129. package/src/panels/hosted-session-feed.ts +352 -0
  130. package/src/panels/hosted-session-panel.ts +221 -0
  131. package/src/panels/local-auth-panel.ts +3 -3
  132. package/src/panels/modals/devices-modal.ts +1 -1
  133. package/src/panels/modals/local-auth-modal.ts +1 -1
  134. package/src/panels/modals/pairing-modal.ts +1 -1
  135. package/src/panels/modals/services-modal.ts +1 -1
  136. package/src/panels/modals/settings-sync-modal.ts +1 -1
  137. package/src/panels/modals/subscription-modal.ts +1 -1
  138. package/src/panels/modals/work-plan-modal.ts +1 -1
  139. package/src/panels/notifications-panel.ts +1 -1
  140. package/src/panels/panel-confirm-overlay.ts +1 -1
  141. package/src/panels/plugins-panel.ts +2 -2
  142. package/src/panels/polish-core.ts +2 -2
  143. package/src/panels/polish-tables.ts +1 -1
  144. package/src/panels/polish.ts +2 -2
  145. package/src/panels/scrollable-list-panel.ts +3 -3
  146. package/src/panels/skills-panel.ts +3 -3
  147. package/src/panels/token-budget-panel.ts +5 -5
  148. package/src/panels/types.ts +1 -2
  149. package/src/permissions/prompt.ts +1 -1
  150. package/src/renderer/autocomplete-overlay.ts +1 -1
  151. package/src/renderer/block-actions-overlay.ts +1 -1
  152. package/src/renderer/bookmark-modal.ts +1 -1
  153. package/src/renderer/buffer.ts +1 -1
  154. package/src/renderer/code-block.ts +1 -1
  155. package/src/renderer/composer-fragments.ts +1 -1
  156. package/src/renderer/compositor.ts +2 -2
  157. package/src/renderer/config-modal.ts +1 -1
  158. package/src/renderer/context-inspector.ts +1 -1
  159. package/src/renderer/conversation-layout.ts +2 -2
  160. package/src/renderer/conversation-overlays.ts +1 -1
  161. package/src/renderer/conversation-surface.ts +2 -2
  162. package/src/renderer/diff-view.ts +1 -1
  163. package/src/renderer/diff.ts +1 -1
  164. package/src/renderer/file-picker-overlay.ts +1 -1
  165. package/src/renderer/fleet-tab-strip.ts +1 -1
  166. package/src/renderer/fullscreen-primitives.ts +2 -2
  167. package/src/renderer/fullscreen-workspace.ts +1 -1
  168. package/src/renderer/header-line.ts +1 -1
  169. package/src/renderer/help-overlay.ts +1 -1
  170. package/src/renderer/history-search-overlay.ts +2 -2
  171. package/src/renderer/layout.ts +5 -8
  172. package/src/renderer/markdown-inline.ts +1 -1
  173. package/src/renderer/markdown-table.ts +1 -1
  174. package/src/renderer/markdown.ts +1 -1
  175. package/src/renderer/mcp-workspace.ts +1 -1
  176. package/src/renderer/modal-factory.ts +2 -2
  177. package/src/renderer/model-workspace.ts +2 -2
  178. package/src/renderer/onboarding/onboarding-wizard.ts +1 -1
  179. package/src/renderer/overlay-box.ts +1 -1
  180. package/src/renderer/panel-composite.ts +1 -1
  181. package/src/renderer/panel-workspace-bar.ts +1 -1
  182. package/src/renderer/process-indicator.ts +1 -1
  183. package/src/renderer/profile-picker-modal.ts +1 -1
  184. package/src/renderer/search-overlay.ts +2 -2
  185. package/src/renderer/selection-modal-overlay.ts +1 -1
  186. package/src/renderer/session-picker-modal.ts +124 -3
  187. package/src/renderer/settings-modal-helpers.ts +2 -0
  188. package/src/renderer/settings-modal.ts +2 -2
  189. package/src/renderer/shell-surface.ts +2 -2
  190. package/src/renderer/system-message.ts +1 -1
  191. package/src/renderer/tab-strip.ts +1 -1
  192. package/src/renderer/theme-mode-config.ts +1 -1
  193. package/src/renderer/theme.ts +11 -12
  194. package/src/renderer/thinking.ts +1 -1
  195. package/src/renderer/tool-call.ts +2 -2
  196. package/src/renderer/tool-result-expanded-lines.ts +1 -1
  197. package/src/renderer/turn-injection.ts +7 -8
  198. package/src/renderer/ui-factory.ts +3 -3
  199. package/src/runtime/bootstrap-command-context.ts +4 -4
  200. package/src/runtime/bootstrap-command-parts.ts +5 -5
  201. package/src/runtime/bootstrap-core.ts +30 -30
  202. package/src/runtime/bootstrap-shell.ts +4 -4
  203. package/src/runtime/bootstrap.ts +128 -157
  204. package/src/runtime/client/approval-updates.ts +82 -0
  205. package/src/runtime/client/build-floors.ts +167 -0
  206. package/src/runtime/client/daemon-attach-handshake.ts +115 -0
  207. package/src/runtime/client/fleet-union.ts +147 -0
  208. package/src/runtime/client/host-service-status.ts +71 -0
  209. package/src/runtime/client/hosted-exit.ts +86 -0
  210. package/src/runtime/client/hosted-roster.ts +110 -0
  211. package/src/runtime/client/hosted-session-stream.ts +178 -0
  212. package/src/runtime/client/hosted-sessions.ts +187 -0
  213. package/src/runtime/client/operator-endpoint.ts +213 -0
  214. package/src/runtime/client/spine-adoption.ts +99 -0
  215. package/src/runtime/cloudflare-control-plane.ts +1 -1
  216. package/src/runtime/context-accounting-source.ts +1 -1
  217. package/src/runtime/context.ts +2 -2
  218. package/src/runtime/conversation-rewind-port.ts +42 -14
  219. package/src/runtime/daemon-attach-notices.ts +55 -19
  220. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  221. package/src/runtime/disposal-wiring.ts +4 -24
  222. package/src/runtime/index.ts +119 -105
  223. package/src/runtime/install-self-check-startup.ts +2 -2
  224. package/src/runtime/interaction-seams.ts +1 -1
  225. package/src/runtime/knowledge-services.ts +8 -2
  226. package/src/runtime/legacy-daemon-migration.ts +15 -3
  227. package/src/runtime/legacy-daemon-reconcile.ts +3 -2
  228. package/src/runtime/memory-spine-transport.ts +37 -267
  229. package/src/runtime/notification-dispatch.ts +2 -2
  230. package/src/runtime/onboarding/apply.ts +2 -2
  231. package/src/runtime/onboarding/derivation.ts +2 -2
  232. package/src/runtime/onboarding/markers.ts +2 -2
  233. package/src/runtime/onboarding/progress.ts +2 -2
  234. package/src/runtime/onboarding/snapshot.ts +1 -1
  235. package/src/runtime/onboarding/state.ts +2 -2
  236. package/src/runtime/onboarding/types.ts +2 -2
  237. package/src/runtime/orchestrator-core-services.ts +1 -1
  238. package/src/runtime/path-shadow-startup.ts +28 -221
  239. package/src/runtime/power-keepawake-remote.ts +3 -2
  240. package/src/runtime/process-lifecycle.ts +2 -2
  241. package/src/runtime/provider-fallback.ts +10 -66
  242. package/src/runtime/recovery-autosave.ts +1 -1
  243. package/src/runtime/recovery-offer-wiring.ts +39 -23
  244. package/src/runtime/recovery-prompt.ts +31 -17
  245. package/src/runtime/relay-reachability-bridge.ts +53 -42
  246. package/src/runtime/release-artifacts.ts +2 -1
  247. package/src/runtime/runtime-services-types.ts +82 -35
  248. package/src/runtime/services.ts +305 -300
  249. package/src/runtime/session-resume-liveness-confirm.ts +1 -1
  250. package/src/runtime/ui-services.ts +7 -6
  251. package/src/runtime/update-check.ts +23 -43
  252. package/src/runtime/workspace-checkpointing.ts +1 -1
  253. package/src/runtime/wrfc-persistence.ts +2 -2
  254. package/src/shell/ui-openers.ts +30 -7
  255. package/src/utils/clipboard.ts +1 -1
  256. package/src/verification/verification-ledger.ts +10 -8
  257. package/src/version.ts +1 -1
  258. package/bin/goodvibes-daemon +0 -90
  259. package/src/cli/config-overrides.ts +0 -267
  260. package/src/cli/endpoints.ts +0 -109
  261. package/src/cli/parser.ts +0 -395
  262. package/src/cli/redaction.ts +0 -170
  263. package/src/cli/types.ts +0 -84
  264. package/src/cluster/commands.ts +0 -382
  265. package/src/cluster/remote-daemon-target.ts +0 -225
  266. package/src/cluster/render.ts +0 -268
  267. package/src/config/atomic-write.ts +0 -70
  268. package/src/config/credential-availability.ts +0 -122
  269. package/src/config/goodvibes-home.ts +0 -149
  270. package/src/config/index.ts +0 -88
  271. package/src/config/provider-model.ts +0 -23
  272. package/src/config/read-versioned.ts +0 -228
  273. package/src/core/alert-gating.ts +0 -67
  274. package/src/core/consolidation-receipt.ts +0 -41
  275. package/src/core/focus-tracker.ts +0 -41
  276. package/src/core/history.ts +0 -45
  277. package/src/core/memory-status.ts +0 -118
  278. package/src/core/orchestrator.ts +0 -7
  279. package/src/core/pairing-devices.ts +0 -49
  280. package/src/core/pairing-handoff.ts +0 -101
  281. package/src/core/pairing-offers.ts +0 -83
  282. package/src/core/pairing-origin.ts +0 -78
  283. package/src/core/rewind-turn-anchors.ts +0 -311
  284. package/src/core/session-recovery.ts +0 -210
  285. package/src/core/stable-host.ts +0 -171
  286. package/src/core/transcript-journal.ts +0 -503
  287. package/src/daemon/cli.ts +0 -641
  288. package/src/daemon/handlers/context.ts +0 -29
  289. package/src/daemon/handlers/contracts.ts +0 -77
  290. package/src/daemon/handlers/credentials.ts +0 -139
  291. package/src/daemon/handlers/drafts/draft-store.ts +0 -427
  292. package/src/daemon/handlers/drafts/index.ts +0 -17
  293. package/src/daemon/handlers/drafts/register.ts +0 -331
  294. package/src/daemon/handlers/errors.ts +0 -18
  295. package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
  296. package/src/daemon/handlers/inbox/index.ts +0 -284
  297. package/src/daemon/handlers/inbox/mapping.ts +0 -192
  298. package/src/daemon/handlers/inbox/poller.ts +0 -212
  299. package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
  300. package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
  301. package/src/daemon/handlers/inbox/providers/email.ts +0 -152
  302. package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
  303. package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
  304. package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
  305. package/src/daemon/handlers/index.ts +0 -117
  306. package/src/daemon/handlers/register.ts +0 -168
  307. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
  308. package/src/daemon/handlers/remote/backends/docker.ts +0 -79
  309. package/src/daemon/handlers/remote/backends/index.ts +0 -40
  310. package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
  311. package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
  312. package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
  313. package/src/daemon/handlers/remote/backends/types.ts +0 -97
  314. package/src/daemon/handlers/remote/dispatcher.ts +0 -181
  315. package/src/daemon/handlers/remote/index.ts +0 -119
  316. package/src/daemon/handlers/remote/peer-registry.ts +0 -357
  317. package/src/daemon/handlers/remote/service.ts +0 -191
  318. package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
  319. package/src/daemon/handlers/routing/index.ts +0 -261
  320. package/src/daemon/handlers/routing/route-store.ts +0 -319
  321. package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
  322. package/src/daemon/handlers/sqlite-store.ts +0 -278
  323. package/src/daemon/handlers/triage/index.ts +0 -57
  324. package/src/daemon/handlers/triage/integration.ts +0 -213
  325. package/src/daemon/handlers/triage/pipeline.ts +0 -274
  326. package/src/daemon/handlers/triage/scorer.ts +0 -287
  327. package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
  328. package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
  329. package/src/daemon/handlers/triage/tagger/index.ts +0 -184
  330. package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
  331. package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
  332. package/src/daemon/handlers/triage/types.ts +0 -50
  333. package/src/daemon/lifecycle.ts +0 -41
  334. package/src/daemon/provision-wake-model.ts +0 -81
  335. package/src/daemon/safe-serve.ts +0 -61
  336. package/src/daemon/send/channels.ts +0 -200
  337. package/src/daemon/send/command.ts +0 -332
  338. package/src/daemon/send/composition.ts +0 -99
  339. package/src/daemon/send/failure-text.ts +0 -93
  340. package/src/daemon/send/inert-text.ts +0 -225
  341. package/src/daemon/send/stdin.ts +0 -24
  342. package/src/daemon/service-commands.ts +0 -329
  343. package/src/export/cost-utils.ts +0 -319
  344. package/src/input/selection.ts +0 -135
  345. package/src/plugins/loader.ts +0 -15
  346. package/src/renderer/bottom-bar.ts +0 -58
  347. package/src/renderer/conversation-tree.ts +0 -228
  348. package/src/runtime/agent-graph-composition.ts +0 -78
  349. package/src/runtime/channel-composition.ts +0 -46
  350. package/src/runtime/cluster-composition.ts +0 -124
  351. package/src/runtime/cluster-group-composition.ts +0 -284
  352. package/src/runtime/code-index-services.ts +0 -167
  353. package/src/runtime/daemon-handler-composition.ts +0 -76
  354. package/src/runtime/device-posture-composition.ts +0 -110
  355. package/src/runtime/durability-housekeeping.ts +0 -183
  356. package/src/runtime/durability-services.ts +0 -155
  357. package/src/runtime/embedded-service-factories.ts +0 -58
  358. package/src/runtime/feature-settings.ts +0 -154
  359. package/src/runtime/fleet-needs-input-push.ts +0 -70
  360. package/src/runtime/fleet-services.ts +0 -41
  361. package/src/runtime/idle-power-services.ts +0 -85
  362. package/src/runtime/mail-composition.ts +0 -65
  363. package/src/runtime/memory-fold.ts +0 -55
  364. package/src/runtime/operator-token-cleanup.ts +0 -109
  365. package/src/runtime/path-shadow.ts +0 -418
  366. package/src/runtime/perf/panel-health-monitor.ts +0 -18
  367. package/src/runtime/provider-optimizer-wiring.ts +0 -55
  368. package/src/runtime/reachability-notice.ts +0 -100
  369. package/src/runtime/remote-execution-composition.ts +0 -62
  370. package/src/runtime/session-liveness-marker.ts +0 -257
  371. package/src/runtime/session-pointer-surface.ts +0 -22
  372. package/src/runtime/session-storage-services.ts +0 -41
  373. package/src/runtime/surface-feature-flags.ts +0 -96
  374. package/src/runtime/terminal-output-guard.ts +0 -235
  375. package/src/runtime/trust/workspace-registration.ts +0 -192
  376. package/src/runtime/trust/workspace-trust.ts +0 -229
  377. package/src/runtime/ui/index.ts +0 -39
  378. package/src/runtime/ui/model-picker/data-provider.ts +0 -182
  379. package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
  380. package/src/runtime/ui/model-picker/index.ts +0 -59
  381. package/src/runtime/ui/model-picker/types.ts +0 -153
  382. package/src/runtime/ui/provider-health/data-provider.ts +0 -244
  383. package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
  384. package/src/runtime/ui/provider-health/index.ts +0 -46
  385. package/src/runtime/ui/provider-health/types.ts +0 -146
  386. package/src/runtime/ui-events.ts +0 -1
  387. package/src/runtime/ui-read-model-helpers.ts +0 -1
  388. package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
  389. package/src/runtime/ui-read-models-observability-options.ts +0 -1
  390. package/src/runtime/ui-read-models-observability-remote.ts +0 -1
  391. package/src/runtime/ui-read-models-observability-security.ts +0 -1
  392. package/src/runtime/ui-read-models-observability-system.ts +0 -1
  393. package/src/runtime/ui-read-models-observability.ts +0 -1
  394. package/src/runtime/ui-service-queries.ts +0 -1
  395. package/src/runtime/voice-setup-services.ts +0 -98
  396. package/src/runtime/workstream-attempts-validation.ts +0 -77
  397. package/src/runtime/workstream-draft-edits.ts +0 -95
  398. package/src/runtime/workstream-draft-store.ts +0 -274
  399. package/src/runtime/workstream-draft-types.ts +0 -42
  400. package/src/runtime/workstream-services.ts +0 -373
  401. package/src/tools/index.ts +0 -1
  402. package/src/types/grid.ts +0 -48
  403. package/src/types/sql-js.d.ts +0 -15
  404. package/src/work-plans/work-plan-store.ts +0 -702
@@ -1,187 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Triage tagger — Discord provider.
3
- //
4
- // Two paths, in priority order:
5
- // 1. REAL thread tags: when the item targets a forum/media-channel thread and
6
- // a forum-tag mapping resolves >=1 tag id, PATCH the thread's applied_tags
7
- // (read-then-merge — never blind overwrite). Exact fidelity to the
8
- // contract's "Discord thread tags".
9
- // 2. Reaction analog: Discord has no arbitrary per-message tags, so otherwise
10
- // add a unicode reaction (PUT .../reactions/{emoji}/@me).
11
- //
12
- // The bot token is resolved per-apply from the daemon credential store and is
13
- // never logged or returned.
14
- // ---------------------------------------------------------------------------
15
-
16
- import type { HandlerContext } from '../../context.ts';
17
- import type { DaemonCredentialStore } from '../../credentials.ts';
18
- import { HandlerError } from '../../errors.ts';
19
- import type { InboundChannelItem } from '../types.ts';
20
- import type { ApplyTagsResult, TaggerProviderConfig } from './shared.ts';
21
- import { discordEmojiForTag, stringMeta } from './shared.ts';
22
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
23
-
24
- export async function applyDiscord(
25
- item: InboundChannelItem,
26
- tags: string[],
27
- providers: TaggerProviderConfig,
28
- credentials: DaemonCredentialStore,
29
- fetchImpl: typeof fetch,
30
- ctx: HandlerContext,
31
- base: ApplyTagsResult,
32
- ): Promise<ApplyTagsResult> {
33
- const cfg = providers.discord;
34
- if (!cfg) return { ...base, reason: 'discord-not-configured' };
35
- const channelId = stringMeta(item, 'channelId') ?? item.conversationId;
36
- const messageId = stringMeta(item, 'messageId') ?? item.id;
37
- if (!channelId || !messageId) return { ...base, reason: 'discord-missing-target' };
38
- if (tags.length === 0) return { ...base, reason: 'no-tags' };
39
-
40
- const token = await credentials.resolveConfigSecret(cfg.tokenConfigKey);
41
- if (!token) return { ...base, reason: 'discord-no-credentials' };
42
-
43
- // Exact-fidelity path: when the item targets a forum/media-channel thread and
44
- // a forum-tag mapping resolves at least one tag, apply REAL Discord thread
45
- // tags (PATCH the thread's applied_tags). A forum post's thread id is the
46
- // thread's own channel id; accept an explicit metadata.threadId or fall back
47
- // to channelId for that case.
48
- const threadId = stringMeta(item, 'threadId') ?? channelId;
49
- const tagIds = resolveDiscordTagIds(tags, cfg.forumTagIds);
50
- if (tagIds.length > 0) {
51
- return applyDiscordThreadTags(item, threadId, tags, tagIds, token, fetchImpl, ctx);
52
- }
53
-
54
- // Analog fallback: Discord has no arbitrary per-message tags, so apply a
55
- // unicode reaction on the source message instead.
56
- const applied: string[] = [];
57
- for (const tag of tags) {
58
- const emoji = encodeURIComponent(discordEmojiForTag(tag));
59
- const url = `https://discord.com/api/v10/channels/${channelId}/messages/${messageId}/reactions/${emoji}/@me`;
60
- const response = await fetchImpl(url, {
61
- method: 'PUT',
62
- headers: {
63
- Authorization: `Bot ${token}`,
64
- 'Content-Length': '0',
65
- },
66
- });
67
- // 204 No Content is the success case for adding a reaction.
68
- if (response.status !== 204 && response.status !== 200) {
69
- const detail = await response.text().catch(() => '');
70
- ctx.logger.warn('triage: discord reaction rejected', { status: response.status });
71
- throw new HandlerError(
72
- `Discord reaction HTTP ${response.status}${detail ? `: ${detail.slice(0, 120)}` : ''}`,
73
- 'TRIAGE_DISCORD_TAG_FAILED',
74
- 502,
75
- );
76
- }
77
- applied.push(tag);
78
- }
79
- return { surface: item.surface, itemId: item.id, appliedTags: applied, skipped: false };
80
- }
81
-
82
- /**
83
- * Map GoodVibes triage tags to configured Discord forum-tag snowflake ids,
84
- * preserving order and dropping unmapped tags. De-duplicates ids.
85
- */
86
- function resolveDiscordTagIds(
87
- tags: readonly string[],
88
- forumTagIds: Record<string, string> | undefined,
89
- ): string[] {
90
- if (!forumTagIds) return [];
91
- const ids: string[] = [];
92
- for (const tag of tags) {
93
- const id = forumTagIds[tag];
94
- if (typeof id === 'string' && id.length > 0 && !ids.includes(id)) ids.push(id);
95
- }
96
- return ids;
97
- }
98
-
99
- /**
100
- * Apply real Discord thread tags by merging the resolved forum-tag ids into the
101
- * thread's existing applied_tags via PATCH /channels/{thread.id}. Idempotent:
102
- * already-present tag ids are kept and not duplicated.
103
- */
104
- async function applyDiscordThreadTags(
105
- item: InboundChannelItem,
106
- threadId: string,
107
- tags: readonly string[],
108
- tagIds: readonly string[],
109
- token: string,
110
- fetchImpl: typeof fetch,
111
- ctx: HandlerContext,
112
- ): Promise<ApplyTagsResult> {
113
- const url = `https://discord.com/api/v10/channels/${threadId}`;
114
- // DATA-LOSS GUARD: we PATCH the thread's full applied_tags array, so we must
115
- // first read the EXISTING tags and merge. If that read fails (network error
116
- // OR a non-ok HTTP status), we have no idea what tags are currently on the
117
- // thread — PATCHing with only our new ids would silently destroy whatever
118
- // forum tags were already applied. Abort instead of overwriting.
119
- const existing = await fetchDiscordAppliedTags(url, token, fetchImpl, ctx);
120
- if (existing === null) {
121
- throw new HandlerError(
122
- 'Discord thread tag read failed; aborting to avoid overwriting existing applied_tags.',
123
- 'TRIAGE_DISCORD_TAG_FAILED',
124
- 502,
125
- );
126
- }
127
- const merged = [...new Set([...existing, ...tagIds])];
128
- const response = await fetchImpl(url, {
129
- method: 'PATCH',
130
- headers: {
131
- Authorization: `Bot ${token}`,
132
- 'Content-Type': 'application/json',
133
- },
134
- body: JSON.stringify({ applied_tags: merged }),
135
- });
136
- if (response.status !== 200 && response.status !== 204) {
137
- const detail = await response.text().catch(() => '');
138
- ctx.logger.warn('triage: discord thread tag rejected', { status: response.status });
139
- throw new HandlerError(
140
- `Discord thread tag HTTP ${response.status}${detail ? `: ${detail.slice(0, 120)}` : ''}`,
141
- 'TRIAGE_DISCORD_TAG_FAILED',
142
- 502,
143
- );
144
- }
145
- return { surface: item.surface, itemId: item.id, appliedTags: [...tags], skipped: false };
146
- }
147
-
148
- /**
149
- * Read a thread's current applied_tags.
150
- *
151
- * Returns:
152
- * - string[] -> the read SUCCEEDED; the array is the current applied_tags
153
- * (possibly empty when the thread genuinely has no tags, or
154
- * when applied_tags is absent/malformed in a 2xx body).
155
- * - null -> the read FAILED (thrown error OR non-ok HTTP status). The
156
- * caller MUST NOT proceed with a PATCH in this case, because a
157
- * failed read means the existing tag set is unknown and a
158
- * PATCH would overwrite it (data loss).
159
- */
160
- async function fetchDiscordAppliedTags(
161
- url: string,
162
- token: string,
163
- fetchImpl: typeof fetch,
164
- ctx: HandlerContext,
165
- ): Promise<string[] | null> {
166
- try {
167
- const response = await fetchImpl(url, {
168
- method: 'GET',
169
- headers: { Authorization: `Bot ${token}` },
170
- });
171
- if (!response.ok) {
172
- ctx.logger.warn('triage: discord thread tag read failed', { status: response.status });
173
- return null;
174
- }
175
- const payload = (await response.json().catch(() => null)) as
176
- | { applied_tags?: unknown }
177
- | null;
178
- const current = payload?.applied_tags;
179
- if (!Array.isArray(current)) return [];
180
- return current.filter((t): t is string => typeof t === 'string');
181
- } catch (error) {
182
- ctx.logger.warn('triage: discord thread tag read errored', {
183
- message: summarizeError(error),
184
- });
185
- return null;
186
- }
187
- }
@@ -1,384 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Triage tagger — IMAP provider.
3
- //
4
- // Applies a triage label as an IMAP keyword flag (STORE +FLAGS) over a minimal
5
- // IMAP4rev1-over-TLS client. Credentials are resolved per-apply from the daemon
6
- // credential store and are NEVER logged or returned. CRLF/control characters in
7
- // any interpolated command value are rejected (CRLF-injection guard). Transient
8
- // network failures are retried with bounded exponential backoff; protocol-level
9
- // NO/BAD rejections are deterministic and never retried.
10
- // ---------------------------------------------------------------------------
11
-
12
- import { connect as tlsConnect } from 'node:tls';
13
- import type { DaemonCredentialStore } from '../../credentials.ts';
14
- import type { InboundChannelItem } from '../types.ts';
15
- import type { ApplyTagsResult, TaggerProviderConfig } from './shared.ts';
16
- import { imapKeywordForTag } from './shared.ts';
17
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
18
-
19
- export interface ImapStoreArgs {
20
- host: string;
21
- port: number;
22
- user: string;
23
- password: string;
24
- mailbox: string;
25
- uid: string;
26
- flag: string;
27
- }
28
-
29
- export interface ImapRetryOptions {
30
- /** Total attempts including the first (default 3). Values < 1 disable retry. */
31
- maxAttempts?: number;
32
- /** Base backoff in ms for the first retry (default 250). */
33
- baseDelayMs?: number;
34
- /** Cap on any single backoff delay in ms (default 2000). */
35
- maxDelayMs?: number;
36
- /** Injectable sleep (tests). Defaults to a real timer. */
37
- sleep?: (ms: number) => Promise<void>;
38
- }
39
-
40
- export type ImapStoreFlag = (args: ImapStoreArgs) => Promise<void>;
41
-
42
- /**
43
- * The minimal subset of a node:tls TLSSocket that imapStoreFlagOverTls drives.
44
- * Declaring it explicitly (rather than depending on the full TLSSocket surface)
45
- * gives the data-pump a precise injection seam: tests can supply an in-memory
46
- * duplex that exercises the protocol state machine without a real network
47
- * socket, while the production path passes the real tlsConnect result.
48
- */
49
- export interface ImapSocketLike {
50
- setEncoding(encoding: string): void;
51
- setTimeout(ms: number, callback: () => void): void;
52
- write(data: string): void;
53
- on(event: 'data', listener: (chunk: string) => void): void;
54
- on(event: 'error', listener: (err: Error) => void): void;
55
- on(event: 'close', listener: () => void): void;
56
- destroy(): void;
57
- }
58
-
59
- /** Connector seam: opens an ImapSocketLike for the given host/port. */
60
- export type ImapConnect = (opts: {
61
- host: string;
62
- port: number;
63
- servername: string;
64
- }) => ImapSocketLike;
65
-
66
- /** Production connector — a real IMAP4rev1-over-TLS socket via node:tls. */
67
- export const tlsImapConnect: ImapConnect = (opts) =>
68
- tlsConnect(opts, () => {
69
- /* greeting handled in the data pump */
70
- }) as unknown as ImapSocketLike;
71
-
72
- export async function applyImap(
73
- item: InboundChannelItem,
74
- tags: string[],
75
- providers: TaggerProviderConfig,
76
- credentials: DaemonCredentialStore,
77
- storeFlag: ImapStoreFlag,
78
- base: ApplyTagsResult,
79
- ): Promise<ApplyTagsResult> {
80
- const cfg = providers.imap;
81
- if (!cfg) return { ...base, reason: 'imap-not-configured' };
82
- const uid = imapUidFromItem(item);
83
- if (!uid) return { ...base, reason: 'imap-missing-uid' };
84
- if (tags.length === 0) return { ...base, reason: 'no-tags' };
85
-
86
- const password = await credentials.resolveConfigSecret(cfg.passwordConfigKey);
87
- if (!password) return { ...base, reason: 'imap-no-credentials' };
88
-
89
- const applied: string[] = [];
90
- for (const tag of tags) {
91
- await storeFlag({
92
- host: cfg.host,
93
- port: cfg.port ?? 993,
94
- user: cfg.user,
95
- password,
96
- mailbox: cfg.mailbox ?? 'INBOX',
97
- uid,
98
- flag: imapKeywordForTag(tag),
99
- });
100
- applied.push(tag);
101
- }
102
- return { surface: item.surface, itemId: item.id, appliedTags: applied, skipped: false };
103
- }
104
-
105
- function imapUidFromItem(item: InboundChannelItem): string | null {
106
- const meta = item.metadata ?? {};
107
- const uid = meta.imapUid ?? meta.uid;
108
- if (typeof uid === 'string' && uid.length > 0) return uid;
109
- if (typeof uid === 'number' && Number.isFinite(uid)) return String(uid);
110
- return null;
111
- }
112
-
113
- /**
114
- * Error subclass carrying whether a failure is transient (worth retrying) or a
115
- * deterministic protocol rejection (NO/BAD) that must not be retried.
116
- */
117
- export class ImapStoreError extends Error {
118
- readonly transient: boolean;
119
- constructor(message: string, transient: boolean) {
120
- super(message);
121
- this.name = 'ImapStoreError';
122
- this.transient = transient;
123
- }
124
- }
125
-
126
- /**
127
- * Reject CR, LF, NUL and any other ASCII control character (0x00-0x1F, 0x7F)
128
- * in a value destined for an IMAP command line. CR/LF are the dangerous ones:
129
- * an unescaped CRLF would terminate the current command and let an attacker
130
- * inject a second IMAP command (CRLF injection). IMAP's quoted-string syntax
131
- * has no escape for these control chars — backslash only escapes `\` and `"` —
132
- * so the only safe handling is to refuse the value outright.
133
- */
134
- function assertImapSafe(value: string, field: string): void {
135
- // eslint-disable-next-line no-control-regex -- intentional control-char guard
136
- if (/[\x00-\x1F\x7F]/.test(value)) {
137
- throw new ImapStoreError(
138
- `IMAP ${field} contains illegal control characters (possible CRLF injection)`,
139
- false,
140
- );
141
- }
142
- }
143
-
144
- /**
145
- * Wrap a value as an IMAP quoted string. Backslash and double-quote are escaped
146
- * per RFC 3501; control characters are NOT escapable in the quoted-string
147
- * grammar, so callers MUST validate with assertImapSafe first. quoteImap
148
- * re-asserts as defense-in-depth so no future caller can bypass the guard.
149
- */
150
- function quoteImap(value: string): string {
151
- assertImapSafe(value, 'value');
152
- return `"${value.replace(/([\\"])/g, '\\$1')}"`;
153
- }
154
-
155
- // A concrete UID sequence-set: one or more comma-separated single UIDs or
156
- // numeric ranges (`12`, `3:9`, `1,4,7:9`). RFC 3501 also permits `*` (the
157
- // largest UID in the mailbox) and ranges to `*`, but a wildcard would let a
158
- // single value like `1:*` apply the flag to the ENTIRE mailbox — never the
159
- // intent when tagging one triaged item — so `*` is rejected outright.
160
- const IMAP_UID_SET = /^[0-9]+(:[0-9]+)?(,[0-9]+(:[0-9]+)?)*$/;
161
-
162
- // An IMAP flag: an optional leading `\` (system flag, e.g. `\Seen`) followed by
163
- // atom characters only. The atom grammar excludes SP and the list/quoting
164
- // specials `(){%*"\` and `]`, so a flag cannot contain a space (which would
165
- // otherwise inject a second flag atom into the `+FLAGS (...)` list) or close
166
- // the parenthesised list early.
167
- // eslint-disable-next-line no-control-regex -- atom grammar excludes controls
168
- const IMAP_FLAG = /^\\?[^\s(){%*"\\\]\x00-\x1F\x7F]+$/;
169
-
170
- /**
171
- * Validate an IMAP UID value as a concrete sequence-set before it is
172
- * interpolated UNQUOTED into `UID STORE`. assertImapSafe only blocks control
173
- * chars; a control-char-free wildcard like `1:*` would still pass that guard
174
- * and mutate the whole mailbox. This is the boundary check that prevents it.
175
- */
176
- function assertImapUid(value: string): void {
177
- if (!IMAP_UID_SET.test(value)) {
178
- throw new ImapStoreError(
179
- `IMAP uid is not a valid numeric sequence-set: ${JSON.stringify(value)}`,
180
- false,
181
- );
182
- }
183
- }
184
-
185
- /**
186
- * Validate an IMAP flag against the flag/atom grammar before it is interpolated
187
- * UNQUOTED into the `+FLAGS (...)` list. assertImapSafe only blocks control
188
- * chars; a flag containing a space (e.g. `\Seen Junk`) would still pass that
189
- * guard and inject a second flag atom. This is the boundary check that
190
- * prevents it — independent of any upstream normalizer.
191
- */
192
- function assertImapFlag(value: string): void {
193
- if (!IMAP_FLAG.test(value)) {
194
- throw new ImapStoreError(
195
- `IMAP flag is not a valid flag-keyword atom: ${JSON.stringify(value)}`,
196
- false,
197
- );
198
- }
199
- }
200
-
201
- /**
202
- * Minimal IMAP4rev1 client: connect over TLS, LOGIN, SELECT, UID STORE +FLAGS,
203
- * LOGOUT. Uses only node:tls (Bun-compatible).
204
- *
205
- * Tagged-command sequencing uses an explicit completion flag rather than a line
206
- * heuristic: the LOGOUT step is tracked by reference, so its tagged OK (or an
207
- * untagged `* BYE`) cleanly completes the operation and tells the `close`
208
- * handler the disconnect was expected. Connection/timeout/socket failures are
209
- * surfaced as transient ImapStoreError; NO/BAD protocol responses as
210
- * non-transient.
211
- */
212
- export function imapStoreFlagOverTls(
213
- args: ImapStoreArgs,
214
- connect: ImapConnect = tlsImapConnect,
215
- ): Promise<void> {
216
- return new Promise<void>((resolve, reject) => {
217
- try {
218
- assertImapSafe(args.user, 'user');
219
- assertImapSafe(args.password, 'password');
220
- assertImapSafe(args.mailbox, 'mailbox');
221
- assertImapSafe(args.uid, 'uid');
222
- assertImapSafe(args.flag, 'flag');
223
- // uid and flag are interpolated UNQUOTED below; the control-char guard
224
- // alone is insufficient, so enforce the structural grammar here too.
225
- assertImapUid(args.uid);
226
- assertImapFlag(args.flag);
227
- } catch (err) {
228
- reject(
229
- err instanceof ImapStoreError
230
- ? err
231
- : new ImapStoreError(summarizeError(err), false),
232
- );
233
- return;
234
- }
235
- type Step = { tag: string; isLogout: boolean; resolve: () => void; reject: (e: Error) => void };
236
-
237
- let done = false;
238
- let completed = false; // LOGOUT acknowledged (or BYE seen) — close is expected.
239
- let buffer = '';
240
- let pending: Step | null = null;
241
- let counter = 0;
242
- let greeted = false;
243
-
244
- const finish = (err?: Error): void => {
245
- if (done) return;
246
- done = true;
247
- try {
248
- socket.destroy();
249
- } catch {
250
- /* ignore */
251
- }
252
- if (err) reject(err);
253
- else resolve();
254
- };
255
-
256
- const socket: ImapSocketLike = connect({
257
- host: args.host,
258
- port: args.port,
259
- servername: args.host,
260
- });
261
- socket.setEncoding('utf-8');
262
- socket.setTimeout(20_000, () =>
263
- finish(new ImapStoreError('IMAP connection timed out', true)),
264
- );
265
-
266
- const send = (command: string, isLogout = false): Promise<void> =>
267
- new Promise<void>((res, rej) => {
268
- counter += 1;
269
- const tag = `A${counter}`;
270
- pending = { tag, isLogout, resolve: res, reject: rej };
271
- socket.write(`${tag} ${command}\r\n`);
272
- });
273
-
274
- const runSequence = async (): Promise<void> => {
275
- await send(`LOGIN ${quoteImap(args.user)} ${quoteImap(args.password)}`);
276
- await send(`SELECT ${quoteImap(args.mailbox)}`);
277
- await send(`UID STORE ${args.uid} +FLAGS (${args.flag})`);
278
- await send('LOGOUT', true);
279
- };
280
-
281
- const handleLine = (line: string): void => {
282
- if (!greeted) {
283
- greeted = true;
284
- if (!/^\* (OK|PREAUTH)/i.test(line)) {
285
- finish(new ImapStoreError(`IMAP server rejected connection: ${line}`, true));
286
- return;
287
- }
288
- runSequence().catch((err) =>
289
- finish(
290
- err instanceof ImapStoreError
291
- ? err
292
- : new ImapStoreError(summarizeError(err), false),
293
- ),
294
- );
295
- return;
296
- }
297
-
298
- // An untagged BYE during LOGOUT is the server announcing a clean close.
299
- if (/^\* BYE/i.test(line) && pending?.isLogout) {
300
- completed = true;
301
- return;
302
- }
303
-
304
- if (!pending) return;
305
- if (!line.startsWith(`${pending.tag} `)) return; // untagged data line
306
- const status = line.slice(pending.tag.length + 1);
307
- const current = pending;
308
- pending = null;
309
- if (/^OK/i.test(status)) {
310
- current.resolve();
311
- if (current.isLogout) {
312
- completed = true;
313
- finish();
314
- }
315
- } else {
316
- // NO/BAD: deterministic protocol rejection — do not retry.
317
- current.reject(new ImapStoreError(`IMAP command failed: ${status}`, false));
318
- }
319
- };
320
-
321
- socket.on('error', (err) =>
322
- finish(new ImapStoreError(summarizeError(err), true)),
323
- );
324
- socket.on('close', () => {
325
- if (completed) finish();
326
- else finish(new ImapStoreError('IMAP connection closed unexpectedly', true));
327
- });
328
- socket.on('data', (chunk: string) => {
329
- buffer += chunk;
330
- let newlineIdx = buffer.indexOf('\n');
331
- while (newlineIdx !== -1) {
332
- const line = buffer.slice(0, newlineIdx).replace(/\r$/, '');
333
- buffer = buffer.slice(newlineIdx + 1);
334
- handleLine(line);
335
- newlineIdx = buffer.indexOf('\n');
336
- }
337
- });
338
- });
339
- }
340
-
341
- /** True when an error is worth retrying (network/connection, not protocol). */
342
- function isTransientImapError(error: unknown): boolean {
343
- if (error instanceof ImapStoreError) return error.transient;
344
- const code = (error as { code?: unknown })?.code;
345
- if (typeof code === 'string') {
346
- return ['ECONNRESET', 'ETIMEDOUT', 'EPIPE', 'ECONNREFUSED', 'EHOSTUNREACH', 'ENETUNREACH', 'EAI_AGAIN']
347
- .includes(code);
348
- }
349
- return false;
350
- }
351
-
352
- const defaultSleep = (ms: number): Promise<void> =>
353
- new Promise((res) => setTimeout(res, ms));
354
-
355
- /**
356
- * Wrap an IMAP store function with bounded exponential-backoff retry on
357
- * transient failures only. Protocol-level (NO/BAD) errors are surfaced on the
358
- * first attempt without retry.
359
- */
360
- export function makeRetryingImapStoreFlag(
361
- inner: ImapStoreFlag,
362
- retry: ImapRetryOptions = {},
363
- ): ImapStoreFlag {
364
- const maxAttempts = Math.max(1, retry.maxAttempts ?? 3);
365
- const baseDelayMs = Math.max(0, retry.baseDelayMs ?? 250);
366
- const maxDelayMs = Math.max(baseDelayMs, retry.maxDelayMs ?? 2_000);
367
- const sleep = retry.sleep ?? defaultSleep;
368
-
369
- return async (args: ImapStoreArgs): Promise<void> => {
370
- let lastError: unknown;
371
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
372
- try {
373
- await inner(args);
374
- return;
375
- } catch (error) {
376
- lastError = error;
377
- if (attempt >= maxAttempts || !isTransientImapError(error)) throw error;
378
- const delay = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));
379
- await sleep(delay);
380
- }
381
- }
382
- throw lastError;
383
- };
384
- }