@pellux/goodvibes-tui 2.0.21 → 2.1.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 (391) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +30 -29
  3. package/docs/README.md +8 -5
  4. package/docs/foundation-artifacts/operator-contract.json +1 -1
  5. package/package.json +28 -19
  6. package/src/cli/management.ts +5 -2
  7. package/src/cli/tui-startup.ts +34 -39
  8. package/src/core/agent-view-render.ts +157 -0
  9. package/src/core/approval-alert.ts +49 -11
  10. package/src/core/bookmark-navigation.ts +7 -1
  11. package/src/core/budget-breach-notifier.ts +27 -8
  12. package/src/core/compaction-receipt.ts +1 -1
  13. package/src/core/context-usage.ts +1 -1
  14. package/src/core/conversation-compaction-render.ts +3 -3
  15. package/src/core/conversation-display-only.ts +86 -0
  16. package/src/core/conversation-event-navigation.ts +27 -0
  17. package/src/core/conversation-line-cache.ts +216 -447
  18. package/src/core/conversation-render-context.ts +20 -27
  19. package/src/core/conversation-rendering.ts +157 -575
  20. package/src/core/conversation-splash-state.ts +6 -1
  21. package/src/core/conversation-types.ts +35 -0
  22. package/src/core/conversation-usage.ts +12 -0
  23. package/src/core/conversation.ts +130 -131
  24. package/src/core/long-task-notifier.ts +100 -31
  25. package/src/core/notices.ts +75 -0
  26. package/src/core/pairing-tailscale-gateway.ts +1 -1
  27. package/src/core/process-output-log.ts +143 -0
  28. package/src/core/rewind-receipt.ts +1 -1
  29. package/src/core/session-focus.ts +190 -0
  30. package/src/core/session-resume-core.ts +23 -81
  31. package/src/core/stream-event-wiring.ts +31 -5
  32. package/src/core/system-message-noise.ts +2 -2
  33. package/src/core/system-message-router.ts +16 -18
  34. package/src/core/turn-event-wiring.ts +272 -33
  35. package/src/core/work-tree-focus.ts +290 -0
  36. package/src/core/work-tree-fold-store.ts +143 -0
  37. package/src/core/work-tree-model.ts +799 -0
  38. package/src/core/work-tree-render.ts +384 -0
  39. package/src/core/work-tree-sources.ts +184 -0
  40. package/src/core/work-tree-wiring.ts +176 -0
  41. package/src/core/workstream-notification.ts +36 -6
  42. package/src/core/wrfc-notice-titles.ts +37 -0
  43. package/src/input/agents-modal-text.ts +119 -0
  44. package/src/input/agents-modal.ts +699 -0
  45. package/src/input/bookmark-modal.ts +33 -9
  46. package/src/input/changes-git.ts +251 -0
  47. package/src/input/changes-modal.ts +629 -0
  48. package/src/input/changes-model.ts +125 -0
  49. package/src/input/command-palette-types.ts +41 -0
  50. package/src/input/command-palette.ts +272 -0
  51. package/src/input/command-reference.ts +2 -2
  52. package/src/input/command-registry.ts +77 -35
  53. package/src/input/commands/acp-runtime.ts +19 -9
  54. package/src/input/commands/channel-runtime.ts +2 -2
  55. package/src/input/commands/ci-runtime.ts +1 -1
  56. package/src/input/commands/codebase-runtime.ts +1 -1
  57. package/src/input/commands/config.ts +7 -1
  58. package/src/input/commands/context-window.ts +25 -19
  59. package/src/input/commands/control-room-runtime.ts +18 -18
  60. package/src/input/commands/conversation-runtime.ts +0 -1
  61. package/src/input/commands/cost-runtime.ts +29 -20
  62. package/src/input/commands/diff-runtime.ts +35 -181
  63. package/src/input/commands/eval.ts +1 -1
  64. package/src/input/commands/experience-runtime.ts +3 -7
  65. package/src/input/commands/fresh-conversation.ts +86 -0
  66. package/src/input/commands/git-runtime.ts +5 -22
  67. package/src/input/commands/graph-runtime.ts +1 -1
  68. package/src/input/commands/guidance-runtime.ts +1 -1
  69. package/src/input/commands/health-runtime.ts +8 -9
  70. package/src/input/commands/hooks-runtime.ts +2 -2
  71. package/src/input/commands/hosted-runtime.ts +3 -3
  72. package/src/input/commands/incident-runtime.ts +3 -3
  73. package/src/input/commands/intelligence-runtime.ts +6 -9
  74. package/src/input/commands/knowledge.ts +2 -2
  75. package/src/input/commands/legacy-panel-command.ts +57 -0
  76. package/src/input/commands/local-auth-runtime.ts +8 -9
  77. package/src/input/commands/local-provider-runtime.ts +7 -4
  78. package/src/input/commands/local-runtime.ts +12 -15
  79. package/src/input/commands/local-setup-review.ts +1 -1
  80. package/src/input/commands/local-setup.ts +13 -19
  81. package/src/input/commands/memory.ts +1 -1
  82. package/src/input/commands/notify-runtime.ts +9 -0
  83. package/src/input/commands/operator-runtime.ts +10 -12
  84. package/src/input/commands/permissions-provenance.ts +3 -3
  85. package/src/input/commands/planning-runtime.ts +7 -7
  86. package/src/input/commands/platform-sandbox-runtime.ts +2 -2
  87. package/src/input/commands/platform-services-runtime.ts +1 -1
  88. package/src/input/commands/policy-dispatch.ts +2 -2
  89. package/src/input/commands/policy.ts +3 -3
  90. package/src/input/commands/provider.ts +1 -1
  91. package/src/input/commands/remote-runtime.ts +1 -1
  92. package/src/input/commands/review-runtime.ts +39 -94
  93. package/src/input/commands/rewind-runtime.ts +81 -108
  94. package/src/input/commands/runtime-services.ts +15 -17
  95. package/src/input/commands/services-runtime.ts +2 -2
  96. package/src/input/commands/session-content.ts +21 -5
  97. package/src/input/commands/session-workflow.ts +13 -38
  98. package/src/input/commands/settings-sync-runtime.ts +2 -2
  99. package/src/input/commands/shell-core.ts +19 -4
  100. package/src/input/commands/skills-runtime.ts +2 -2
  101. package/src/input/commands/status-runtime.ts +13 -0
  102. package/src/input/commands/subscription-runtime.ts +1 -1
  103. package/src/input/commands/tasks-runtime.ts +2 -8
  104. package/src/input/commands/work-plan-runtime.ts +4 -4
  105. package/src/input/commands/workstream-attempts.ts +24 -47
  106. package/src/input/commands/workstream-runtime.ts +2 -2
  107. package/src/input/commands/worktree-runtime.ts +3 -3
  108. package/src/input/commands.ts +3 -1
  109. package/src/input/composer-editor.ts +44 -0
  110. package/src/input/concealed-input.ts +1 -1
  111. package/src/input/config-modal-types.ts +20 -8
  112. package/src/input/config-modal.ts +106 -105
  113. package/src/input/confirm-dialog-types.ts +28 -0
  114. package/src/input/confirm-dialog.ts +70 -0
  115. package/src/input/delete-key-policy.ts +3 -3
  116. package/src/input/feed-context-factory.ts +15 -16
  117. package/src/input/handler-command-route.ts +7 -42
  118. package/src/input/handler-content-actions.ts +16 -15
  119. package/src/input/handler-feed-routes.ts +26 -211
  120. package/src/input/handler-feed.ts +68 -95
  121. package/src/input/handler-interactions.ts +5 -3
  122. package/src/input/handler-modal-routes.ts +215 -216
  123. package/src/input/handler-modal-stack.ts +81 -11
  124. package/src/input/handler-modal-token-routes.ts +4 -0
  125. package/src/input/handler-onboarding.ts +0 -1
  126. package/src/input/handler-picker-routes.ts +65 -107
  127. package/src/input/handler-session-view-route.ts +149 -0
  128. package/src/input/handler-shortcuts.ts +16 -154
  129. package/src/input/handler-types.ts +4 -2
  130. package/src/input/handler-ui-state.ts +45 -15
  131. package/src/input/handler-work-tree-route.ts +123 -0
  132. package/src/input/handler.ts +25 -70
  133. package/src/input/keybindings.ts +16 -71
  134. package/src/input/masked-entry-modal.ts +117 -0
  135. package/src/input/mcp-workspace.ts +48 -17
  136. package/src/input/model-picker-filter.ts +8 -0
  137. package/src/input/model-picker-open.ts +99 -0
  138. package/src/input/model-picker-types.ts +1 -1
  139. package/src/input/model-picker.ts +92 -9
  140. package/src/input/notifications-modal.ts +123 -0
  141. package/src/input/overlay-filter.ts +26 -0
  142. package/src/input/profile-picker-modal.ts +37 -8
  143. package/src/input/search.ts +3 -3
  144. package/src/input/selection-modal-queue.ts +2 -0
  145. package/src/input/selection-modal.ts +28 -16
  146. package/src/input/session-picker-modal.ts +33 -8
  147. package/src/input/settings-modal-activation.ts +7 -2
  148. package/src/input/settings-modal-data.ts +11 -6
  149. package/src/input/settings-modal-types.ts +1 -1
  150. package/src/input/settings-modal.ts +20 -20
  151. package/src/input/submission-intent.ts +1 -1
  152. package/src/input/submission-router.ts +4 -9
  153. package/src/input/surface-modal-host.ts +110 -0
  154. package/src/input/theme-settings-actions.ts +82 -0
  155. package/src/input/usage-modal.ts +183 -0
  156. package/src/input/views.ts +97 -0
  157. package/src/main.ts +175 -165
  158. package/src/permissions/hunk-selection.ts +1 -1
  159. package/src/permissions/prompt-card.ts +511 -0
  160. package/src/permissions/prompt.ts +59 -622
  161. package/src/renderer/agents-modal.ts +310 -0
  162. package/src/renderer/autocomplete-overlay.ts +42 -220
  163. package/src/renderer/back-to-bottom.ts +80 -0
  164. package/src/renderer/block-actions-overlay.ts +30 -48
  165. package/src/renderer/bookmark-modal.ts +48 -79
  166. package/src/renderer/changes-modal.ts +325 -0
  167. package/src/renderer/code-block.ts +184 -106
  168. package/src/renderer/command-palette.ts +93 -0
  169. package/src/renderer/composer-fragments.ts +4 -4
  170. package/src/renderer/composer.ts +146 -0
  171. package/src/renderer/compositor.ts +26 -160
  172. package/src/renderer/config-modal.ts +185 -81
  173. package/src/renderer/confirm-dialog.ts +50 -0
  174. package/src/renderer/context-inspector.ts +116 -154
  175. package/src/renderer/context-status-hint.ts +4 -4
  176. package/src/renderer/conversation-layout.ts +20 -0
  177. package/src/renderer/conversation-overlays.ts +94 -84
  178. package/src/renderer/conversation-surface.ts +19 -38
  179. package/src/renderer/diff-tint.ts +98 -0
  180. package/src/renderer/file-picker-overlay.ts +63 -90
  181. package/src/renderer/footer-targets.ts +38 -0
  182. package/src/renderer/git-status.ts +3 -4
  183. package/src/renderer/header-line.ts +140 -117
  184. package/src/renderer/help-overlay.ts +133 -219
  185. package/src/renderer/history-search-overlay.ts +15 -12
  186. package/src/renderer/lane-graph/bead.ts +800 -0
  187. package/src/renderer/lane-graph/glyphs.ts +134 -0
  188. package/src/renderer/lane-graph/layout.ts +248 -0
  189. package/src/renderer/lane-graph/paint.ts +537 -0
  190. package/src/renderer/lane-graph/semantic-memo.ts +77 -0
  191. package/src/renderer/layout-engine.ts +2 -57
  192. package/src/renderer/layout.ts +7 -13
  193. package/src/renderer/markdown-inline.ts +2 -1
  194. package/src/renderer/markdown-table.ts +17 -12
  195. package/src/renderer/markdown.ts +92 -34
  196. package/src/renderer/masked-entry-modal.ts +78 -0
  197. package/src/renderer/mcp-workspace.ts +166 -203
  198. package/src/renderer/model-workspace.ts +381 -436
  199. package/src/renderer/notifications-modal.ts +116 -0
  200. package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
  201. package/src/renderer/process-view.ts +192 -0
  202. package/src/renderer/profile-picker-modal.ts +58 -110
  203. package/src/renderer/search-overlay.ts +6 -3
  204. package/src/renderer/selection-modal-overlay.ts +87 -327
  205. package/src/renderer/semantic-diff.ts +2 -2
  206. package/src/renderer/session-chips.ts +102 -0
  207. package/src/renderer/session-picker-modal.ts +177 -297
  208. package/src/renderer/settings-modal-connections.ts +14 -23
  209. package/src/renderer/settings-modal-context.ts +367 -0
  210. package/src/renderer/settings-modal-helpers.ts +6 -11
  211. package/src/renderer/settings-modal.ts +322 -726
  212. package/src/renderer/shell-surface.ts +277 -175
  213. package/src/renderer/shortcuts-overlay.ts +217 -0
  214. package/src/renderer/status-line.ts +475 -0
  215. package/src/renderer/surface-compose.ts +174 -0
  216. package/src/renderer/surface-kit-extra.ts +303 -0
  217. package/src/renderer/surface-kit-list.ts +366 -0
  218. package/src/renderer/surface-kit-parts.ts +360 -0
  219. package/src/renderer/surface-kit.ts +568 -0
  220. package/src/renderer/syntax-highlighter.ts +280 -197
  221. package/src/renderer/syntax-theme.ts +51 -0
  222. package/src/renderer/system-message.ts +0 -1
  223. package/src/renderer/term-caps.ts +42 -1
  224. package/src/renderer/terminal-bg-probe.ts +116 -19
  225. package/src/renderer/terminal-palette-probe.ts +322 -0
  226. package/src/renderer/terminal-palette-reader.ts +183 -0
  227. package/src/renderer/terminal-palette.ts +58 -0
  228. package/src/renderer/theme-mode-config.ts +16 -1
  229. package/src/renderer/theme.ts +234 -207
  230. package/src/renderer/thinking.ts +0 -1
  231. package/src/renderer/throbber.ts +188 -0
  232. package/src/renderer/toast-center.ts +119 -0
  233. package/src/renderer/ui-factory.ts +39 -602
  234. package/src/renderer/ui-primitives.ts +8 -25
  235. package/src/renderer/usage-modal.ts +353 -0
  236. package/src/renderer/user-message.ts +58 -0
  237. package/src/renderer/voice-capture-chip.ts +53 -0
  238. package/src/runtime/bootstrap-command-context.ts +0 -5
  239. package/src/runtime/bootstrap-command-parts.ts +38 -89
  240. package/src/runtime/bootstrap-core.ts +13 -15
  241. package/src/runtime/bootstrap-hook-bridge.ts +12 -21
  242. package/src/runtime/bootstrap-notifier-sync.ts +17 -1
  243. package/src/runtime/bootstrap-shell.ts +16 -50
  244. package/src/runtime/bootstrap.ts +14 -14
  245. package/src/runtime/client/fleet-union.ts +5 -5
  246. package/src/runtime/client/hosted-exit.ts +2 -2
  247. package/src/runtime/client/hosted-sessions.ts +1 -1
  248. package/src/runtime/client/spine-adoption.ts +1 -1
  249. package/src/runtime/context-accounting-source.ts +5 -4
  250. package/src/runtime/index.ts +1 -0
  251. package/src/runtime/notification-dispatch.ts +102 -29
  252. package/src/runtime/recovery-offer-wiring.ts +4 -1
  253. package/src/runtime/recovery-prompt.ts +31 -15
  254. package/src/runtime/runtime-services-types.ts +10 -6
  255. package/src/runtime/services.ts +17 -12
  256. package/src/runtime/session-continuity-hints.ts +2 -5
  257. package/src/runtime/session-inbound-inputs.ts +1 -1
  258. package/src/runtime/session-resume-liveness-confirm.ts +14 -2
  259. package/src/runtime/startup-typeahead-gate.ts +103 -0
  260. package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
  261. package/src/runtime/store/domains/index.ts +0 -8
  262. package/src/runtime/store/selectors/index.ts +0 -28
  263. package/src/runtime/store/state.ts +0 -8
  264. package/src/runtime/ui-services.ts +4 -6
  265. package/src/runtime/usage-tracker.ts +348 -0
  266. package/src/runtime/wrfc-persistence.ts +4 -4
  267. package/src/shell/blocking-input.ts +92 -2
  268. package/src/shell/recovery-input-helpers.ts +1 -1
  269. package/src/shell/session-views.ts +539 -0
  270. package/src/shell/status-report.ts +86 -0
  271. package/src/shell/transcript-scroll.ts +68 -0
  272. package/src/shell/ui-openers.ts +84 -172
  273. package/src/shell/view-openers.ts +205 -0
  274. package/src/tools/wrfc-agent-guard.ts +22 -2
  275. package/src/utils/format-duration.ts +4 -8
  276. package/src/utils/splash-lines.ts +9 -1
  277. package/src/utils/terminal-width.ts +56 -0
  278. package/src/verification/live-verifier.ts +0 -10
  279. package/src/version.ts +1 -1
  280. package/src/{panels → views}/agent-inspector-shared.ts +2 -2
  281. package/src/{panels → views}/builtin-modals.ts +22 -21
  282. package/src/views/builtin-views.ts +135 -0
  283. package/src/{panels → views}/confirm-state.ts +9 -9
  284. package/src/{panels → views}/diff-review-model.ts +10 -3
  285. package/src/{panels → views}/eval-registry.ts +1 -1
  286. package/src/{panels → views}/fleet-acts.ts +49 -85
  287. package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
  288. package/src/{panels → views}/fleet-gateway.ts +5 -5
  289. package/src/{panels → views}/fleet-observed-render.ts +16 -16
  290. package/src/{panels → views}/fleet-read-model.ts +9 -9
  291. package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
  292. package/src/{panels → views}/fleet-spawn.ts +33 -49
  293. package/src/{panels → views}/fleet-steer.ts +12 -12
  294. package/src/{panels → views}/fleet-stop.ts +4 -4
  295. package/src/{panels → views}/fleet-transcript.ts +26 -27
  296. package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
  297. package/src/{panels → views}/hosted-session-feed.ts +6 -6
  298. package/src/views/modal-surface-registry.ts +41 -0
  299. package/src/{panels → views}/modals/devices-modal.ts +2 -2
  300. package/src/{panels → views}/modals/hooks-modal.ts +13 -13
  301. package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
  302. package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
  303. package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
  304. package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
  305. package/src/{panels → views}/modals/memory-modal.ts +8 -8
  306. package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
  307. package/src/{panels → views}/modals/pairing-modal.ts +9 -9
  308. package/src/{panels → views}/modals/planning-modal.ts +15 -20
  309. package/src/{panels → views}/modals/plugins-modal.ts +6 -6
  310. package/src/{panels → views}/modals/policy-modal.ts +13 -5
  311. package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
  312. package/src/{panels → views}/modals/remote-modal.ts +3 -3
  313. package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
  314. package/src/{panels → views}/modals/security-modal.ts +22 -16
  315. package/src/{panels → views}/modals/services-modal.ts +3 -3
  316. package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
  317. package/src/{panels → views}/modals/skills-modal.ts +9 -9
  318. package/src/{panels → views}/modals/subscription-modal.ts +3 -3
  319. package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
  320. package/src/views/notifications-feed.ts +314 -0
  321. package/src/{panels → views}/polish-core.ts +38 -36
  322. package/src/{panels → views}/polish-tables.ts +35 -26
  323. package/src/{panels → views}/polish.ts +101 -95
  324. package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
  325. package/src/{panels → views}/provider-health-routes.ts +14 -14
  326. package/src/{panels → views}/search-focus.ts +8 -8
  327. package/src/{panels → views}/session-maintenance.ts +11 -11
  328. package/src/views/skills-discovery.ts +159 -0
  329. package/src/views/view-deps.ts +80 -0
  330. package/src/views/view-panel-adapter.ts +53 -0
  331. package/src/{panels → views}/workstream-graph-render.ts +1 -1
  332. package/src/core/conversation-fold.ts +0 -101
  333. package/src/core/conversation-turn-structure.ts +0 -464
  334. package/src/core/conversation-user-receipts.ts +0 -42
  335. package/src/input/commands/operator-panel-runtime.ts +0 -186
  336. package/src/input/panel-integration-actions.ts +0 -33
  337. package/src/input/panel-mouse-geometry.ts +0 -97
  338. package/src/input/panel-paste-flood-guard.ts +0 -86
  339. package/src/panels/base-panel.ts +0 -301
  340. package/src/panels/builtin/agent.ts +0 -27
  341. package/src/panels/builtin/development.ts +0 -92
  342. package/src/panels/builtin/knowledge.ts +0 -12
  343. package/src/panels/builtin/operations.ts +0 -177
  344. package/src/panels/builtin/session.ts +0 -81
  345. package/src/panels/builtin/shared.ts +0 -357
  346. package/src/panels/builtin-panels.ts +0 -28
  347. package/src/panels/cost-tracker-panel.ts +0 -746
  348. package/src/panels/diff-panel.ts +0 -797
  349. package/src/panels/diff-review-panel.ts +0 -395
  350. package/src/panels/expandable-list-panel.ts +0 -189
  351. package/src/panels/fleet-deep-link.ts +0 -31
  352. package/src/panels/fleet-panel.ts +0 -799
  353. package/src/panels/git-panel.ts +0 -800
  354. package/src/panels/hosted-session-panel.ts +0 -221
  355. package/src/panels/index.ts +0 -21
  356. package/src/panels/local-auth-panel.ts +0 -478
  357. package/src/panels/notifications-feed.ts +0 -127
  358. package/src/panels/notifications-panel.ts +0 -160
  359. package/src/panels/panel-confirm-overlay.ts +0 -72
  360. package/src/panels/panel-manager.ts +0 -791
  361. package/src/panels/plugins-panel.ts +0 -371
  362. package/src/panels/scrollable-list-panel.ts +0 -498
  363. package/src/panels/skills-panel.ts +0 -454
  364. package/src/panels/token-budget-panel.ts +0 -688
  365. package/src/panels/types.ts +0 -191
  366. package/src/panels/wrfc-panel-format.ts +0 -133
  367. package/src/renderer/diff-view.ts +0 -134
  368. package/src/renderer/fleet-tab-strip.ts +0 -56
  369. package/src/renderer/footer-tips.ts +0 -53
  370. package/src/renderer/fullscreen-primitives.ts +0 -146
  371. package/src/renderer/fullscreen-workspace.ts +0 -194
  372. package/src/renderer/hint-grammar.ts +0 -52
  373. package/src/renderer/modal-factory.ts +0 -480
  374. package/src/renderer/overlay-box.ts +0 -154
  375. package/src/renderer/overlay-viewport.ts +0 -104
  376. package/src/renderer/panel-composite.ts +0 -145
  377. package/src/renderer/panel-workspace-bar.ts +0 -54
  378. package/src/renderer/process-indicator.ts +0 -163
  379. package/src/renderer/tab-strip.ts +0 -222
  380. package/src/renderer/text-layout.ts +0 -31
  381. package/src/renderer/tool-call.ts +0 -276
  382. package/src/renderer/tool-result-expanded-lines.ts +0 -48
  383. package/src/renderer/tool-result-summary.ts +0 -219
  384. package/src/runtime/diagnostics/panels/index.ts +0 -21
  385. package/src/runtime/diagnostics/panels/ops.ts +0 -156
  386. package/src/runtime/perf/panel-contracts.ts +0 -32
  387. package/src/runtime/store/domains/panels.ts +0 -117
  388. package/src/verification/verification-ledger.ts +0 -707
  389. /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
  390. /package/src/{panels → views}/modals/modal-theme.ts +0 -0
  391. /package/src/{panels → views}/provider-health-domains.ts +0 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * usage-tracker.ts, the session's token and cost history.
3
+ *
4
+ * Built once at startup and subscribed to the turn and agent event feeds for
5
+ * the life of the session, so per-turn history, the cost trend and the
6
+ * per-agent cost ledger accumulate whether or not the Usage modal has ever
7
+ * been opened. The Usage modal (input/usage-modal.ts) only reads it.
8
+ *
9
+ * Budget: the alert threshold lives in the behavior.budgetAlertUsd setting,
10
+ * the same key the background budget-breach notifier reads, so the modal, the
11
+ * `/cost budget <usd>` command and the notifier never disagree.
12
+ */
13
+
14
+ import type { AgentRecord } from '@pellux/goodvibes-sdk/platform/tools';
15
+ import {
16
+ BUDGET_ALERT_USD_CONFIG_KEY,
17
+ calcSessionCost,
18
+ computeBudgetBreach,
19
+ describePricingSource,
20
+ isModelPriced,
21
+ readBudgetAlertUsd,
22
+ writeManualModelPrice,
23
+ type BudgetAlertConfigAccess,
24
+ } from '@pellux/goodvibes-sdk/platform/providers';
25
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
26
+ import type { AgentEvent, TurnEvent } from '@pellux/goodvibes-sdk/events';
27
+ import { evaluateSessionMaintenance } from '@pellux/goodvibes-sdk/platform/runtime/operations';
28
+ import type { SessionMemoryQuery, UiEventFeed } from '@pellux/goodvibes-sdk/platform/runtime/ui';
29
+ import type { UiReadModel, UiSessionSnapshot } from './ui-read-models.ts';
30
+
31
+ /** Tokens one completed turn added. */
32
+ export interface UsageTurn {
33
+ readonly input: number;
34
+ readonly output: number;
35
+ readonly cacheRead: number;
36
+ readonly cacheWrite: number;
37
+ /** Unix ms the turn completed. */
38
+ readonly ts: number;
39
+ }
40
+
41
+ export interface UsageTotals {
42
+ readonly input: number;
43
+ readonly output: number;
44
+ readonly cacheRead: number;
45
+ readonly cacheWrite: number;
46
+ }
47
+
48
+ export type UsageAgentStatus = 'running' | 'done' | 'failed';
49
+
50
+ /** One spawned agent's tokens and cost. */
51
+ export interface UsageAgentEntry {
52
+ readonly agentId: string;
53
+ readonly task: string;
54
+ readonly model: string;
55
+ readonly inputTokens: number;
56
+ readonly outputTokens: number;
57
+ readonly cost: number;
58
+ readonly status: UsageAgentStatus;
59
+ }
60
+
61
+ /** What the session cost, and where the price came from. */
62
+ export interface UsageCost {
63
+ readonly usd: number;
64
+ /** False when the model has no known price (the figure is then not a real zero). */
65
+ readonly priced: boolean;
66
+ /** "your price", "catalog price, as of …", or null. */
67
+ readonly source: string | null;
68
+ }
69
+
70
+ export interface UsageTrackerDeps {
71
+ readonly turnEvents: UiEventFeed<TurnEvent>;
72
+ readonly agentEvents: UiEventFeed<AgentEvent>;
73
+ /** Cumulative main-session usage (the orchestrator's counters). */
74
+ readonly getUsage: () => UsageTotals & { readonly model?: string };
75
+ /** Tokens in the context window right now. */
76
+ readonly getContextTokens: () => number;
77
+ /** The current model's context window (0 when unknown). */
78
+ readonly getContextWindow: () => number;
79
+ /** The current model id (for pricing). */
80
+ readonly getModelId: () => string;
81
+ /** An agent's live record, for its usage and model. */
82
+ readonly getAgentStatus?: (agentId: string) => AgentRecord | null;
83
+ /** The fleet's priced total (every agent, chain and workflow), or null when nothing is priced. */
84
+ readonly getFleetCost?: () => number | null;
85
+ readonly configManager: Pick<ConfigManager, 'get' | 'set'>;
86
+ readonly sessionMemoryStore?: SessionMemoryQuery;
87
+ readonly sessionReadModel?: UiReadModel<UiSessionSnapshot>;
88
+ readonly requestRender?: () => void;
89
+ }
90
+
91
+ /** Turns kept in history. */
92
+ const MAX_TURNS = 100;
93
+ /** Cost-trend points kept (one per model response). */
94
+ const MAX_COST_POINTS = 32;
95
+ /** Agents kept in the cost ledger (oldest finished ones go first). */
96
+ const MAX_AGENTS = 200;
97
+
98
+ interface MutableAgent {
99
+ agentId: string;
100
+ task: string;
101
+ model: string;
102
+ inputTokens: number;
103
+ outputTokens: number;
104
+ cost: number;
105
+ status: UsageAgentStatus;
106
+ }
107
+
108
+ export class UsageTracker {
109
+ private readonly turnsList: UsageTurn[] = [];
110
+ private readonly costPoints: number[] = [];
111
+ private readonly agentMap = new Map<string, MutableAgent>();
112
+ private prevCumulative: UsageTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
113
+ private lastSessionCost = 0;
114
+ private readonly unsubs: Array<() => void> = [];
115
+ private readonly listeners = new Set<() => void>();
116
+
117
+ constructor(private readonly deps: UsageTrackerDeps) {
118
+ this.unsubs.push(deps.turnEvents.on('TURN_COMPLETED', () => this.recordTurn()));
119
+ this.unsubs.push(deps.turnEvents.on('LLM_RESPONSE_RECEIVED', () => this.recordCostPoint()));
120
+ this.unsubs.push(deps.turnEvents.on('TURN_SUBMITTED', () => this.emit()));
121
+ this.unsubs.push(deps.agentEvents.on('AGENT_SPAWNING', (payload) => {
122
+ this.agentMap.set(payload.agentId, {
123
+ agentId: payload.agentId,
124
+ task: payload.task,
125
+ model: 'unknown',
126
+ inputTokens: 0,
127
+ outputTokens: 0,
128
+ cost: 0,
129
+ status: 'running',
130
+ });
131
+ this.trimAgents();
132
+ this.emit();
133
+ }));
134
+ this.unsubs.push(deps.agentEvents.on('AGENT_COMPLETED', (payload) => {
135
+ const entry = this.agentMap.get(payload.agentId);
136
+ if (!entry) return;
137
+ entry.status = 'done';
138
+ this.readAgentUsage(entry);
139
+ this.emit();
140
+ }));
141
+ this.unsubs.push(deps.agentEvents.on('AGENT_FAILED', (payload) => {
142
+ const entry = this.agentMap.get(payload.agentId);
143
+ if (!entry) return;
144
+ entry.status = 'failed';
145
+ this.readAgentUsage(entry);
146
+ this.emit();
147
+ }));
148
+ }
149
+
150
+ dispose(): void {
151
+ for (const unsub of this.unsubs) unsub();
152
+ this.unsubs.length = 0;
153
+ this.listeners.clear();
154
+ }
155
+
156
+ /** Called on every change. Returns an unsubscribe function. */
157
+ subscribe(listener: () => void): () => void {
158
+ this.listeners.add(listener);
159
+ return () => this.listeners.delete(listener);
160
+ }
161
+
162
+ private emit(): void {
163
+ for (const listener of this.listeners) listener();
164
+ this.deps.requestRender?.();
165
+ }
166
+
167
+ // ── Recording ──────────────────────────────────────────────────────────────
168
+
169
+ /** Snapshot a completed turn (the delta since the previous one). */
170
+ recordTurn(now: number = Date.now()): void {
171
+ const cu = this.totals();
172
+ this.turnsList.push({
173
+ input: Math.max(0, cu.input - this.prevCumulative.input),
174
+ output: Math.max(0, cu.output - this.prevCumulative.output),
175
+ cacheRead: Math.max(0, cu.cacheRead - this.prevCumulative.cacheRead),
176
+ cacheWrite: Math.max(0, cu.cacheWrite - this.prevCumulative.cacheWrite),
177
+ ts: now,
178
+ });
179
+ this.prevCumulative = cu;
180
+ if (this.turnsList.length > MAX_TURNS) this.turnsList.shift();
181
+ this.recordCostPoint();
182
+ }
183
+
184
+ private recordCostPoint(): void {
185
+ const total = this.sessionCost().usd;
186
+ this.costPoints.push(Math.max(0, total - this.lastSessionCost));
187
+ this.lastSessionCost = total;
188
+ if (this.costPoints.length > MAX_COST_POINTS) this.costPoints.shift();
189
+ this.emit();
190
+ }
191
+
192
+ private readAgentUsage(entry: MutableAgent): boolean {
193
+ const rec = this.deps.getAgentStatus?.(entry.agentId);
194
+ if (!rec?.usage) return false;
195
+ const input = rec.usage.inputTokens + (rec.usage.cacheReadTokens ?? 0) + (rec.usage.cacheWriteTokens ?? 0);
196
+ const model = rec.model && rec.model !== 'unknown' ? rec.model : entry.model;
197
+ const cost = calcSessionCost(rec.usage.inputTokens, rec.usage.outputTokens, rec.usage.cacheReadTokens ?? 0, rec.usage.cacheWriteTokens ?? 0, model);
198
+ const changed = input !== entry.inputTokens || rec.usage.outputTokens !== entry.outputTokens || cost !== entry.cost || model !== entry.model;
199
+ entry.inputTokens = input;
200
+ entry.outputTokens = rec.usage.outputTokens;
201
+ entry.cost = cost;
202
+ entry.model = model;
203
+ return changed;
204
+ }
205
+
206
+ /**
207
+ * Read live usage for agents still running (their token and cost columns
208
+ * fill in as they stream instead of staying empty until they finish).
209
+ * Called while the Usage modal is open. Returns whether anything changed.
210
+ */
211
+ pollRunningAgents(): boolean {
212
+ let changed = false;
213
+ for (const entry of this.agentMap.values()) {
214
+ if (entry.status === 'running' && this.readAgentUsage(entry)) changed = true;
215
+ }
216
+ return changed;
217
+ }
218
+
219
+ private trimAgents(): void {
220
+ if (this.agentMap.size <= MAX_AGENTS) return;
221
+ for (const [id, entry] of this.agentMap) {
222
+ if (this.agentMap.size <= MAX_AGENTS) break;
223
+ if (entry.status !== 'running') this.agentMap.delete(id);
224
+ }
225
+ }
226
+
227
+ // ── Reading ────────────────────────────────────────────────────────────────
228
+
229
+ totals(): UsageTotals {
230
+ const u = this.deps.getUsage();
231
+ return { input: u.input ?? 0, output: u.output ?? 0, cacheRead: u.cacheRead ?? 0, cacheWrite: u.cacheWrite ?? 0 };
232
+ }
233
+
234
+ /** Completed turns, oldest first. */
235
+ turns(): readonly UsageTurn[] {
236
+ return this.turnsList;
237
+ }
238
+
239
+ /** Cost added per model response, oldest first. */
240
+ costTrend(): readonly number[] {
241
+ return this.costPoints;
242
+ }
243
+
244
+ /** Agents spawned this session, in spawn order. */
245
+ agents(): readonly UsageAgentEntry[] {
246
+ return [...this.agentMap.values()];
247
+ }
248
+
249
+ modelId(): string {
250
+ return this.deps.getUsage().model || this.deps.getModelId();
251
+ }
252
+
253
+ contextTokens(): number {
254
+ return Math.max(0, this.deps.getContextTokens());
255
+ }
256
+
257
+ contextWindow(): number {
258
+ return Math.max(0, this.deps.getContextWindow());
259
+ }
260
+
261
+ /**
262
+ * Where auto-compaction starts, as a fraction of the window (0 when it is
263
+ * off). The same figure the maintenance check and the compactor use.
264
+ */
265
+ compactThreshold(): number {
266
+ return this.maintenance().thresholdPct / 100;
267
+ }
268
+
269
+ maintenance(): ReturnType<typeof evaluateSessionMaintenance> {
270
+ const session = this.deps.sessionReadModel?.getSnapshot();
271
+ return evaluateSessionMaintenance({
272
+ configManager: this.deps.configManager,
273
+ currentTokens: this.contextTokens(),
274
+ contextWindow: this.contextWindow(),
275
+ messageCount: session?.totalTurns,
276
+ sessionMemoryCount: this.deps.sessionMemoryStore?.list().length ?? 0,
277
+ session: session?.session,
278
+ });
279
+ }
280
+
281
+ sessionCost(): UsageCost {
282
+ const u = this.totals();
283
+ const model = this.modelId();
284
+ const priced = isModelPriced(model);
285
+ return {
286
+ usd: calcSessionCost(u.input, u.output, u.cacheRead, u.cacheWrite, model),
287
+ priced,
288
+ source: priced ? describePricingSource(model) : null,
289
+ };
290
+ }
291
+
292
+ /** Cost of everything the fleet ran (agents, chains, workflows), or null when nothing is priced. */
293
+ fleetCost(): number | null {
294
+ const fromFleet = this.deps.getFleetCost?.() ?? null;
295
+ if (fromFleet !== null) return fromFleet;
296
+ const ledger = [...this.agentMap.values()].reduce((sum, a) => sum + a.cost, 0);
297
+ return ledger > 0 ? ledger : null;
298
+ }
299
+
300
+ /** True when some spend has no known price (so totals undercount it). */
301
+ hasUnpricedSpend(): boolean {
302
+ const u = this.totals();
303
+ if (!isModelPriced(this.modelId()) && (u.input + u.output + u.cacheRead + u.cacheWrite) > 0) return true;
304
+ return [...this.agentMap.values()].some((a) => a.inputTokens > 0 && !isModelPriced(a.model));
305
+ }
306
+
307
+ // ── Budget and price ───────────────────────────────────────────────────────
308
+
309
+ private configAccess(): BudgetAlertConfigAccess {
310
+ const config = this.deps.configManager;
311
+ return {
312
+ get: (key) => config.get(key as Parameters<typeof config.get>[0]),
313
+ set: (key, value) => config.set(key as Parameters<typeof config.set>[0], value as never),
314
+ };
315
+ }
316
+
317
+ /** The budget alert threshold in USD (0 = none). */
318
+ budget(): number {
319
+ return readBudgetAlertUsd(this.configAccess().get);
320
+ }
321
+
322
+ /** Set the budget alert threshold (0 turns it off). Returns false for a bad amount. */
323
+ setBudget(usd: number): boolean {
324
+ if (!Number.isFinite(usd) || usd < 0) return false;
325
+ this.configAccess().set(BUDGET_ALERT_USD_CONFIG_KEY, usd);
326
+ this.emit();
327
+ return true;
328
+ }
329
+
330
+ overBudget(): boolean {
331
+ return computeBudgetBreach(this.sessionCost().usd, this.budget());
332
+ }
333
+
334
+ /** True when the current model can carry a manual price (prices are keyed provider:model). */
335
+ canSetModelPrice(): boolean {
336
+ const model = this.modelId();
337
+ return model !== 'unknown' && model.includes(':');
338
+ }
339
+
340
+ /** Store your own price for the current model, USD per 1M tokens. */
341
+ setModelPrice(input: number, output: number): boolean {
342
+ if (!this.canSetModelPrice()) return false;
343
+ if (!Number.isFinite(input) || input < 0 || !Number.isFinite(output) || output < 0) return false;
344
+ writeManualModelPrice(this.configAccess(), this.modelId(), { input, output });
345
+ this.emit();
346
+ return true;
347
+ }
348
+ }
@@ -14,8 +14,8 @@
14
14
  * SystemMessageRouter is available). It reads the snapshot, identifies
15
15
  * chains that were in a non-terminal state at last write, emits a
16
16
  * high-priority 'wrfc' system message per interrupted chain, and re-imports
17
- * each interrupted chain into the WrfcController so it reappears as a panel
18
- * row and can be resumed by the operator. The `interruptedChains` accessor
17
+ * each interrupted chain into the WrfcController so it reappears as a row in the Agents modal
18
+ * and can be resumed by the operator. The `interruptedChains` accessor
19
19
  * additionally exposes the recovered set for inspection.
20
20
  *
21
21
  * A chain that looked interrupted (non-terminal) in the old snapshot can be
@@ -88,7 +88,7 @@ export interface WrfcControllerReader {
88
88
  listChains(): WrfcChain[];
89
89
  /**
90
90
  * Re-import a chain recovered from a previous process so it reappears in the
91
- * controller's in-memory map and becomes selectable/resumable from the panel.
91
+ * controller's in-memory map and becomes selectable/resumable from the Agents modal.
92
92
  * Optional so read-only test doubles can omit it; the real WrfcController
93
93
  * provides it. `force` is left at its default, on a fresh start the map is
94
94
  * empty so importing never clobbers a live chain.
@@ -204,7 +204,7 @@ class WrfcPersistenceImpl implements WrfcPersistence {
204
204
  const candidateInterrupted = snapshot.chains.filter((c) => isNonTerminal(c.state));
205
205
  for (const chain of candidateInterrupted) {
206
206
  // Re-import so the chain reappears in the controller's in-memory map and
207
- // becomes selectable/resumable from the panel. On a fresh process start
207
+ // becomes selectable/resumable from the Agents modal. On a fresh process start
208
208
  // the map is empty, so importChain (force=false) never clobbers a live
209
209
  // chain. The accessor is optional for read-only test doubles.
210
210
  //
@@ -1,5 +1,6 @@
1
1
  import type { PermissionRequest, RememberTier } from '@pellux/goodvibes-sdk/platform/permissions';
2
2
  import { applyHunkKey, buildModifiedEditArgs, type HunkSelectionState } from '../permissions/hunk-selection.ts';
3
+ import { permissionButtons } from '../permissions/prompt-card.ts';
3
4
 
4
5
  export type PendingPermissionState = PermissionRequest & {
5
6
  resolve: (approved: boolean, remember?: boolean, modifiedArgs?: Record<string, unknown>, extras?: { rememberTier?: RememberTier; reason?: string }) => void;
@@ -25,8 +26,29 @@ export type PendingPermissionState = PermissionRequest & {
25
26
  openedAt?: number;
26
27
  /** Requester attribution ("session abc12345" / a named agent) shown on the prompt; absent when unknown. */
27
28
  requestedBy?: string;
29
+ /** The chosen button (index into permissionButtons); arrows move it, Enter presses it. */
30
+ choice?: number;
31
+ /** The "Allow for session ▸" remember-tier submenu is open, and which tier is highlighted. */
32
+ submenuOpen?: boolean;
33
+ submenuIndex?: number;
34
+ /** First card-body row shown when the card is taller than the screen. */
35
+ scroll?: number;
28
36
  };
29
37
 
38
+ const KEY_LEFT = '\x1b[D';
39
+ const KEY_RIGHT = '\x1b[C';
40
+ const KEY_UP = '\x1b[A';
41
+ const KEY_DOWN = '\x1b[B';
42
+ const KEY_PAGE_UP = '\x1b[5~';
43
+ const KEY_PAGE_DOWN = '\x1b[6~';
44
+
45
+ /** Move the chosen button by delta (wrapping). */
46
+ function withChoice(req: PendingPermissionState, delta: number): PendingPermissionState {
47
+ const count = permissionButtons(req, req).length;
48
+ if (count === 0) return req;
49
+ return { ...req, choice: ((req.choice ?? 0) + delta + count) % count };
50
+ }
51
+
30
52
  /**
31
53
  * Settle window after a permission prompt appears during which buffered
32
54
  * keystrokes are ignored rather than interpreted as the response. 350ms is long
@@ -73,6 +95,17 @@ export function handleBlockingShellInput(
73
95
  }
74
96
 
75
97
  if (req.hunkState) {
98
+ // Buttons (Apply selected / Deny): arrows and tab choose, Enter presses.
99
+ if (data === KEY_LEFT || data === KEY_RIGHT || data === '\t') {
100
+ render();
101
+ return { handled: true, pendingPermission: withChoice(req, data === KEY_LEFT ? -1 : 1) };
102
+ }
103
+ if ((data === '\r' || data === '\n') && permissionButtons(req, req)[req.choice ?? 0]?.id === 'deny') {
104
+ req.resolve(false, false);
105
+ abortTurn();
106
+ render();
107
+ return { handled: true, pendingPermission: null };
108
+ }
76
109
  const { state, commit } = applyHunkKey(req.hunkState, data);
77
110
  if (commit === 'apply') {
78
111
  req.resolve(true, false, buildModifiedEditArgs(req, state));
@@ -143,8 +176,65 @@ export function handleBlockingShellInput(
143
176
  return { handled: true, pendingPermission };
144
177
  }
145
178
 
146
- // Scroll, mouse, PageUp/Down, arrow, and panel-navigation keys, plus a
147
- // bare Esc, are not card answers. Pass them through to the normal input
179
+ // The remember-tier submenu is a sub-level of the card: arrows pick a
180
+ // tier, Enter approves and remembers at it, Esc closes just the submenu.
181
+ if (req.submenuOpen) {
182
+ const tiers = req.rememberOptions ?? [];
183
+ const index = req.submenuIndex ?? 0;
184
+ if (data === '\x1b') {
185
+ render();
186
+ return { handled: true, pendingPermission: { ...req, submenuOpen: false } };
187
+ }
188
+ if (data === KEY_UP || data === KEY_DOWN) {
189
+ const next = tiers.length === 0 ? 0 : (index + (data === KEY_UP ? -1 : 1) + tiers.length) % tiers.length;
190
+ render();
191
+ return { handled: true, pendingPermission: { ...req, submenuIndex: next } };
192
+ }
193
+ if (data === '\r' || data === '\n') {
194
+ const option = tiers[index];
195
+ if (option) {
196
+ req.resolve(true, option.tier === 'session', undefined, { rememberTier: option.tier });
197
+ render();
198
+ return { handled: true, pendingPermission: null };
199
+ }
200
+ }
201
+ }
202
+
203
+ // Button choice and the body scroll are card keys.
204
+ if (data === KEY_LEFT || data === KEY_RIGHT || data === '\t') {
205
+ render();
206
+ return { handled: true, pendingPermission: withChoice(req, data === KEY_LEFT ? -1 : 1) };
207
+ }
208
+ if (data === KEY_UP || data === KEY_DOWN || data === KEY_PAGE_UP || data === KEY_PAGE_DOWN) {
209
+ const step = data === KEY_UP ? -1 : data === KEY_DOWN ? 1 : data === KEY_PAGE_UP ? -5 : 5;
210
+ render();
211
+ return { handled: true, pendingPermission: { ...req, scroll: Math.max(0, (req.scroll ?? 0) + step) } };
212
+ }
213
+ if (data === '\r' || data === '\n') {
214
+ const pressed = permissionButtons(req, req)[req.choice ?? 0];
215
+ if (pressed?.id === 'allow') {
216
+ req.resolve(true, false);
217
+ render();
218
+ return { handled: true, pendingPermission: null };
219
+ }
220
+ if (pressed?.id === 'session') {
221
+ req.resolve(true, true, undefined, { rememberTier: 'session' });
222
+ render();
223
+ return { handled: true, pendingPermission: null };
224
+ }
225
+ if (pressed?.id === 'remember') {
226
+ render();
227
+ return { handled: true, pendingPermission: { ...req, submenuOpen: true, submenuIndex: req.submenuIndex ?? 0 } };
228
+ }
229
+ if (pressed?.id === 'deny') {
230
+ req.resolve(false, false);
231
+ render();
232
+ return { handled: true, pendingPermission: null };
233
+ }
234
+ }
235
+
236
+ // Mouse, view-navigation and other escape sequences, plus a bare Esc,
237
+ // are not card answers. Pass them through to the normal input
148
238
  // handler so the transcript stays scrollable and Esc only drops focus. The
149
239
  // request stays pending (answer it with y/n or a remember tier when ready);
150
240
  // Ctrl+C above is still the hard abort, and 'n' still denies.
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * The silent startup auto-restore that used to live here
7
7
  * (autoRestoreRecoverySession, plus its createPersistRecoverySnapshot /
8
- * createReopenRecoveryPanels factories) has been removed: state restores
8
+ * createReopenRecoveryViews factories) has been removed: state restores
9
9
  * happen ONLY when the user explicitly asks (a CLI flag, a slash command, or
10
10
  * a prompt), never automatically at bare launch (owner ruling). A live
11
11
  * recovery snapshot is now surfaced, never applied, by the boot resume