@pellux/goodvibes-tui 1.16.1 → 1.18.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 (257) hide show
  1. package/CHANGELOG.md +138 -9
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +5490 -1806
  4. package/package.json +7 -7
  5. package/src/cli/completions/generate.ts +5 -4
  6. package/src/cli/config-overrides.ts +51 -11
  7. package/src/cli/doctor.ts +4 -5
  8. package/src/cli/entrypoint.ts +13 -8
  9. package/src/cli/help.ts +7 -6
  10. package/src/cli/launch-auto-update.ts +214 -0
  11. package/src/cli/management-commands.ts +82 -15
  12. package/src/cli/management.ts +0 -2
  13. package/src/cli/parser.ts +5 -0
  14. package/src/cli/status.ts +13 -0
  15. package/src/cli/surface-command.ts +5 -5
  16. package/src/cli/tui-startup.ts +60 -97
  17. package/src/cli/types.ts +2 -0
  18. package/src/config/index.ts +1 -1
  19. package/src/config/tui-extension-settings.ts +33 -0
  20. package/src/core/composer-state.ts +1 -1
  21. package/src/core/consolidation-receipt.ts +41 -0
  22. package/src/core/conversation-line-cache.ts +3 -3
  23. package/src/core/conversation-rendering.ts +18 -9
  24. package/src/core/conversation.ts +2 -2
  25. package/src/core/long-task-notifier.ts +7 -7
  26. package/src/core/memory-provenance.ts +67 -0
  27. package/src/core/pairing-devices.ts +49 -0
  28. package/src/core/pairing-handoff.ts +101 -0
  29. package/src/core/pairing-offers.ts +83 -0
  30. package/src/core/pairing-origin.ts +78 -0
  31. package/src/core/pairing-tailscale-gateway.ts +56 -0
  32. package/src/core/power-chip-source.ts +91 -0
  33. package/src/core/power-status.ts +95 -0
  34. package/src/core/stable-host.ts +171 -0
  35. package/src/core/stream-event-wiring.ts +29 -6
  36. package/src/core/system-message-noise.ts +1 -1
  37. package/src/core/system-message-router.ts +6 -6
  38. package/src/core/turn-budget-outcome.ts +60 -0
  39. package/src/core/turn-cancellation.ts +40 -1
  40. package/src/core/turn-event-wiring.ts +14 -8
  41. package/src/daemon/cli.ts +64 -58
  42. package/src/daemon/lifecycle.ts +41 -0
  43. package/src/daemon/service-commands.ts +5 -5
  44. package/src/export/cost-utils.ts +128 -6
  45. package/src/input/autocomplete.ts +2 -2
  46. package/src/input/command-registry.ts +55 -9
  47. package/src/input/commands/acp-runtime.ts +72 -0
  48. package/src/input/commands/ci-runtime.ts +5 -1
  49. package/src/input/commands/codebase-runtime.ts +6 -6
  50. package/src/input/commands/cost-runtime.ts +1 -1
  51. package/src/input/commands/devices-runtime.ts +109 -0
  52. package/src/input/commands/git-runtime.ts +28 -4
  53. package/src/input/commands/graph-runtime.ts +45 -0
  54. package/src/input/commands/health-runtime.ts +2 -2
  55. package/src/input/commands/intelligence-runtime.ts +1 -1
  56. package/src/input/commands/local-auth-runtime.ts +1 -1
  57. package/src/input/commands/local-provider-runtime.ts +49 -10
  58. package/src/input/commands/local-runtime.ts +53 -5
  59. package/src/input/commands/memory.ts +2 -2
  60. package/src/input/commands/operator-panel-runtime.ts +5 -5
  61. package/src/input/commands/operator-rpc.ts +32 -11
  62. package/src/input/commands/permissions-provenance.ts +1 -1
  63. package/src/input/commands/permissions-runtime.ts +99 -8
  64. package/src/input/commands/planning-runtime.ts +3 -3
  65. package/src/input/commands/platform-sandbox-runtime.ts +1 -1
  66. package/src/input/commands/power-runtime.ts +56 -0
  67. package/src/input/commands/provider-accounts-runtime.ts +71 -6
  68. package/src/input/commands/provider.ts +7 -5
  69. package/src/input/commands/qrcode-runtime.ts +14 -19
  70. package/src/input/commands/queue-runtime.ts +70 -0
  71. package/src/input/commands/recall-files-git.ts +13 -51
  72. package/src/input/commands/recall-files.ts +8 -11
  73. package/src/input/commands/recall-review.ts +2 -2
  74. package/src/input/commands/relay-runtime.ts +1 -1
  75. package/src/input/commands/remote-runtime.ts +1 -1
  76. package/src/input/commands/runtime-services.ts +4 -4
  77. package/src/input/commands/schedule-runtime.ts +14 -7
  78. package/src/input/commands/services-runtime.ts +1 -1
  79. package/src/input/commands/session-workflow.ts +2 -2
  80. package/src/input/commands/settings-sync-runtime.ts +2 -2
  81. package/src/input/commands/share-runtime.ts +1 -1
  82. package/src/input/commands/shell-core.ts +46 -68
  83. package/src/input/commands/subscription-runtime.ts +1 -1
  84. package/src/input/commands/update-runtime.ts +153 -96
  85. package/src/input/commands/work-plan-runtime.ts +1 -1
  86. package/src/input/commands/workstream-attempts.ts +6 -1
  87. package/src/input/commands/workstream-runtime.ts +46 -27
  88. package/src/input/commands/worktree-runtime.ts +35 -8
  89. package/src/input/commands.ts +10 -2
  90. package/src/input/config-modal-types.ts +14 -3
  91. package/src/input/config-modal.ts +75 -13
  92. package/src/input/feature-unit-layout.ts +93 -82
  93. package/src/input/feed-context-factory.ts +2 -2
  94. package/src/input/handler-command-route.ts +2 -2
  95. package/src/input/handler-content-actions.ts +1 -1
  96. package/src/input/handler-feed-routes.ts +7 -7
  97. package/src/input/handler-feed.ts +4 -4
  98. package/src/input/handler-interactions.ts +3 -2
  99. package/src/input/handler-modal-routes.ts +12 -4
  100. package/src/input/handler-modal-token-routes.ts +1 -1
  101. package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
  102. package/src/input/handler-onboarding.ts +3 -2
  103. package/src/input/handler-picker-routes.ts +26 -13
  104. package/src/input/handler-shortcuts.ts +29 -6
  105. package/src/input/handler.ts +2 -2
  106. package/src/input/keybindings.ts +21 -3
  107. package/src/input/onboarding/onboarding-feature-units.ts +34 -27
  108. package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
  109. package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
  110. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
  111. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
  112. package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
  113. package/src/input/panel-integration-actions.ts +2 -2
  114. package/src/input/panel-mouse-geometry.ts +1 -1
  115. package/src/input/panel-paste-flood-guard.ts +1 -1
  116. package/src/input/provider-key-intake.ts +167 -0
  117. package/src/input/sandbox-exec-config.ts +2 -2
  118. package/src/input/selection-modal.ts +1 -1
  119. package/src/input/settings-modal-activation.ts +5 -0
  120. package/src/input/settings-modal-adjustment.ts +4 -31
  121. package/src/input/settings-modal-data.ts +52 -57
  122. package/src/input/settings-modal-mutations.ts +12 -68
  123. package/src/input/settings-modal-types.ts +27 -17
  124. package/src/input/settings-modal.ts +74 -55
  125. package/src/main.ts +90 -90
  126. package/src/panels/agent-inspector-shared.ts +4 -4
  127. package/src/panels/base-panel.ts +2 -2
  128. package/src/panels/builtin/agent.ts +4 -4
  129. package/src/panels/builtin/development.ts +4 -4
  130. package/src/panels/builtin/knowledge.ts +1 -1
  131. package/src/panels/builtin/operations.ts +83 -11
  132. package/src/panels/builtin/session.ts +5 -5
  133. package/src/panels/builtin/shared.ts +16 -4
  134. package/src/panels/builtin-modals.ts +59 -42
  135. package/src/panels/builtin-panels.ts +1 -1
  136. package/src/panels/cost-tracker-panel.ts +124 -7
  137. package/src/panels/diff-panel.ts +39 -6
  138. package/src/panels/eval-registry.ts +2 -2
  139. package/src/panels/fleet-acts.ts +446 -0
  140. package/src/panels/fleet-deep-link.ts +1 -1
  141. package/src/panels/fleet-gateway.ts +117 -0
  142. package/src/panels/fleet-observed-render.ts +128 -0
  143. package/src/panels/fleet-panel-format.ts +163 -11
  144. package/src/panels/fleet-panel-worktree-detail.ts +14 -0
  145. package/src/panels/fleet-panel.ts +73 -57
  146. package/src/panels/fleet-read-model.ts +84 -14
  147. package/src/panels/fleet-spawn.ts +266 -0
  148. package/src/panels/fleet-steer.ts +2 -2
  149. package/src/panels/fleet-stop.ts +41 -5
  150. package/src/panels/fleet-tabs.ts +15 -16
  151. package/src/panels/fleet-transcript.ts +5 -5
  152. package/src/panels/git-panel.ts +2 -2
  153. package/src/panels/index.ts +4 -1
  154. package/src/panels/local-auth-panel.ts +1 -1
  155. package/src/panels/memory-consolidation-gateway.ts +94 -0
  156. package/src/panels/modals/devices-modal.ts +161 -0
  157. package/src/panels/modals/hooks-modal.ts +1 -1
  158. package/src/panels/modals/keybindings-modal.ts +2 -2
  159. package/src/panels/modals/knowledge-modal.ts +1 -1
  160. package/src/panels/modals/marketplace-modal.ts +4 -4
  161. package/src/panels/modals/memory-modal.ts +153 -9
  162. package/src/panels/modals/modal-surface-helpers.ts +1 -1
  163. package/src/panels/modals/modal-theme.ts +1 -1
  164. package/src/panels/modals/pairing-modal.ts +198 -68
  165. package/src/panels/modals/planning-modal.ts +2 -2
  166. package/src/panels/modals/plugins-modal.ts +1 -1
  167. package/src/panels/modals/policy-modal.ts +1 -1
  168. package/src/panels/modals/security-modal.ts +7 -1
  169. package/src/panels/modals/skills-modal.ts +1 -1
  170. package/src/panels/modals/work-plan-modal.ts +3 -3
  171. package/src/panels/notifications-feed.ts +127 -0
  172. package/src/panels/notifications-panel.ts +160 -0
  173. package/src/panels/panel-manager.ts +9 -9
  174. package/src/panels/plugins-panel.ts +4 -4
  175. package/src/panels/project-planning-answer-actions.ts +1 -1
  176. package/src/panels/provider-health-routes.ts +2 -2
  177. package/src/panels/skills-panel.ts +4 -5
  178. package/src/panels/token-budget-panel.ts +11 -3
  179. package/src/panels/types.ts +5 -5
  180. package/src/panels/workstream-graph-render.ts +123 -0
  181. package/src/permissions/broker-approval-card.ts +201 -0
  182. package/src/permissions/hunk-selection.ts +28 -4
  183. package/src/permissions/prompt.ts +244 -29
  184. package/src/renderer/autocomplete-overlay.ts +107 -54
  185. package/src/renderer/code-block.ts +2 -2
  186. package/src/renderer/compaction-history-modal.ts +1 -1
  187. package/src/renderer/compaction-preview.ts +1 -1
  188. package/src/renderer/compaction-quality.ts +1 -1
  189. package/src/renderer/composer-fragments.ts +69 -0
  190. package/src/renderer/compositor.ts +2 -2
  191. package/src/renderer/config-modal.ts +1 -1
  192. package/src/renderer/conversation-overlays.ts +1 -1
  193. package/src/renderer/footer-tips.ts +3 -3
  194. package/src/renderer/hint-grammar.ts +1 -1
  195. package/src/renderer/modal-factory.ts +1 -1
  196. package/src/renderer/model-picker-overlay.ts +1 -1
  197. package/src/renderer/model-workspace.ts +7 -2
  198. package/src/renderer/panel-composite.ts +2 -2
  199. package/src/renderer/panel-workspace-bar.ts +1 -1
  200. package/src/renderer/process-indicator.ts +1 -1
  201. package/src/renderer/selection-modal-overlay.ts +1 -1
  202. package/src/renderer/session-picker-modal.ts +2 -2
  203. package/src/renderer/settings-modal-helpers.ts +23 -5
  204. package/src/renderer/settings-modal.ts +129 -39
  205. package/src/renderer/shell-surface.ts +6 -0
  206. package/src/renderer/terminal-bg-probe.ts +1 -1
  207. package/src/renderer/theme.ts +3 -3
  208. package/src/renderer/tool-call.ts +13 -7
  209. package/src/renderer/tool-result-summary.ts +1 -1
  210. package/src/renderer/turn-injection.ts +7 -6
  211. package/src/renderer/ui-factory.ts +38 -1
  212. package/src/runtime/bootstrap-command-context.ts +7 -2
  213. package/src/runtime/bootstrap-command-parts.ts +9 -5
  214. package/src/runtime/bootstrap-core.ts +41 -41
  215. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  216. package/src/runtime/bootstrap-shell.ts +13 -5
  217. package/src/runtime/bootstrap.ts +34 -32
  218. package/src/runtime/channel-composition.ts +46 -0
  219. package/src/runtime/code-index-services.ts +3 -4
  220. package/src/runtime/daemon-attach-notices.ts +120 -0
  221. package/src/runtime/durability-services.ts +90 -0
  222. package/src/runtime/embedded-service-factories.ts +40 -0
  223. package/src/runtime/feature-settings.ts +147 -0
  224. package/src/runtime/fleet-services.ts +41 -0
  225. package/src/runtime/idle-power-services.ts +85 -0
  226. package/src/runtime/index.ts +6 -21
  227. package/src/runtime/interaction-seams.ts +109 -0
  228. package/src/runtime/legacy-daemon-migration.ts +29 -19
  229. package/src/runtime/memory-fold.ts +1 -1
  230. package/src/runtime/notification-dispatch.ts +110 -0
  231. package/src/runtime/onboarding/apply.ts +3 -29
  232. package/src/runtime/onboarding/fast-path.ts +39 -6
  233. package/src/runtime/onboarding/provider-key-capture.ts +126 -0
  234. package/src/runtime/onboarding/snapshot.ts +0 -1
  235. package/src/runtime/onboarding/types.ts +9 -3
  236. package/src/runtime/power-keepawake-remote.ts +75 -0
  237. package/src/runtime/process-lifecycle.ts +3 -1
  238. package/src/runtime/provider-fallback.ts +55 -0
  239. package/src/runtime/provider-optimizer-wiring.ts +55 -0
  240. package/src/runtime/release-artifacts.ts +19 -103
  241. package/src/runtime/resume-notice.ts +1 -1
  242. package/src/runtime/services.ts +113 -113
  243. package/src/runtime/session-continuity-hints.ts +38 -0
  244. package/src/runtime/surface-feature-flags.ts +40 -9
  245. package/src/runtime/terminal-output-guard.ts +2 -2
  246. package/src/runtime/trust/workspace-registration.ts +10 -2
  247. package/src/runtime/trust/workspace-trust.ts +58 -31
  248. package/src/runtime/ui-services.ts +6 -1
  249. package/src/runtime/update-check.ts +15 -69
  250. package/src/runtime/workstream-services.ts +2 -2
  251. package/src/shell/blocking-input.ts +108 -132
  252. package/src/shell/recovery-input-helpers.ts +53 -166
  253. package/src/shell/ui-openers.ts +38 -6
  254. package/src/utils/format-duration.ts +1 -1
  255. package/src/verification/verification-ledger.ts +21 -4
  256. package/src/version.ts +1 -1
  257. package/src/input/commands/flags-runtime.ts +0 -328
@@ -1,8 +1,11 @@
1
1
  /**
2
- * `/update` — a real self-update path for binary installs, mirroring
3
- * scripts/install.sh's download-verify-swap semantics (that file is the
4
- * tested reference for release-asset naming and checksum verification; keep
5
- * this in lockstep with it).
2
+ * `/update` — a real self-update path for binary installs. The
3
+ * download-verify-swap mechanics are the SDK's canonical update policy
4
+ * module (platform/runtime/self-update — hoisted from this file's
5
+ * semantics), the same mechanism the daemon's hourly loop and
6
+ * scripts/install.sh follow: one update mechanism everywhere. This file owns
7
+ * only the /update UX: install-kind gating, target selection, and the
8
+ * printed report.
6
9
  *
7
10
  * Subcommands:
8
11
  * /update [check] — resolve the latest release tag and report
@@ -12,9 +15,17 @@
12
15
  * app and daemon binaries, and refresh the
13
16
  * sqlite-vec native addon in lockstep so the
14
17
  * vector index never goes stale beside a new
15
- * binary. For any other install kind, prints the
16
- * exact command to run instead — it never
17
- * attempts a swap it can't do safely.
18
+ * binary. Every swap parks the outgoing file
19
+ * at `<path>.previous`, so the replaced
20
+ * version is always kept. For any other
21
+ * install kind, prints the exact command to
22
+ * run instead — it never attempts a swap it
23
+ * can't do safely.
24
+ * /update rollback — exchange each installed file with its kept
25
+ * `.previous` counterpart: one command back to
26
+ * the version that ran before the last update
27
+ * (and, being an exchange, one more command
28
+ * forward again).
18
29
  * /update review — install/subscription/sandbox posture,
19
30
  * unrelated to the update mechanics above.
20
31
  * /update bundle export|inspect <path> — portable posture bundle, as before.
@@ -26,9 +37,16 @@
26
37
  * (scripts/release.ts) does not publish separate stable/preview channels,
27
38
  * so there is no real channel selection to wire it to.
28
39
  */
29
- import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
40
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
30
41
  import { dirname, join } from 'node:path';
31
42
  import { spawnSync } from 'node:child_process';
43
+ import {
44
+ applyVerifiedUpdate,
45
+ realUpdateFileIo,
46
+ rollbackKeptPrevious,
47
+ type UpdateFileIo,
48
+ type UpdateTarget,
49
+ } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
32
50
  import type { CommandRegistry } from '../command-registry.ts';
33
51
  import { VERSION } from '../../version.ts';
34
52
  import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
@@ -37,8 +55,6 @@ import {
37
55
  parseChecksumFile,
38
56
  resolveArtifactNames,
39
57
  resolveSqliteVecAsset,
40
- sha256,
41
- verifyChecksum,
42
58
  } from '../../runtime/release-artifacts.ts';
43
59
  import {
44
60
  compareVersions,
@@ -66,47 +82,13 @@ async function downloadText(fetchImpl: UpdateFetchLike, url: string): Promise<st
66
82
  return await response.text();
67
83
  }
68
84
 
69
- async function downloadBuffer(fetchImpl: UpdateFetchLike, url: string): Promise<Buffer> {
70
- const response = await fetchImpl(url);
71
- if (!response.ok) {
72
- throw new Error(`download failed (${response.status}) for ${url}`);
73
- }
74
- return Buffer.from(await response.arrayBuffer());
75
- }
76
-
77
85
  /**
78
- * Writes the new binary next to the target, then renames over it — an
79
- * atomic replace on the same filesystem, so a currently-running process
80
- * that already opened the old file keeps its old inode (POSIX unlink
81
- * semantics) instead of executing a half-written file. Same approach as
82
- * scripts/install.sh's `mv -f "$WORKDIR/$artifact" "$target"`.
86
+ * Suffix under which every swap keeps the file it replaced — re-exported
87
+ * from the SDK's canonical update policy module so rollback and swap share
88
+ * one definition everywhere.
83
89
  */
84
- function swapBinaryAtomically(targetPath: string, buffer: Buffer): void {
85
- const tempPath = `${targetPath}.update-download`;
86
- writeFileSync(tempPath, buffer);
87
- if (process.platform !== 'win32') {
88
- chmodSync(tempPath, 0o755);
89
- }
90
- renameSync(tempPath, targetPath);
91
- }
92
-
93
- /**
94
- * Writes the sqlite-vec native addon into place with the same temp-write +
95
- * atomic-rename discipline as the binaries, so a running process never
96
- * dlopen()s a half-written extension. The addon's parent directory
97
- * (`<execDir>/lib/sqlite-vec-<os>-<arch>/`) is created first because a fresh
98
- * install may not have a `lib/` tree yet. A shared library needs no execute
99
- * bit, so this uses 0o644 rather than the binaries' 0o755.
100
- */
101
- function writeAddonAtomically(targetPath: string, buffer: Buffer): void {
102
- mkdirSync(dirname(targetPath), { recursive: true });
103
- const tempPath = `${targetPath}.update-download`;
104
- writeFileSync(tempPath, buffer);
105
- if (process.platform !== 'win32') {
106
- chmodSync(tempPath, 0o644);
107
- }
108
- renameSync(tempPath, targetPath);
109
- }
90
+ export { PREVIOUS_FILE_SUFFIX } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
91
+ import { PREVIOUS_FILE_SUFFIX } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
110
92
 
111
93
  export interface DaemonServiceRestartInfo {
112
94
  readonly managed: boolean;
@@ -169,19 +151,19 @@ export interface ApplyUpdateOptions {
169
151
  readonly print: (line: string) => void;
170
152
  readonly configManager: { get(key: string): unknown };
171
153
  readonly runCommand?: RunCommand;
172
- /** Injectable so tests can observe/skip the actual filesystem swap. */
173
- readonly swap?: (targetPath: string, buffer: Buffer) => void;
174
- /** Injectable so tests can observe/skip writing the sqlite-vec addon. */
175
- readonly writeAddon?: (targetPath: string, buffer: Buffer) => void;
176
- readonly fileExists?: (path: string) => boolean;
154
+ /** Injectable filesystem seam (the SDK's UpdateFileIo) so tests observe swaps in memory. */
155
+ readonly io?: UpdateFileIo;
177
156
  }
178
157
 
179
158
  /**
180
- * The real self-update path. For a binary install: resolve the latest tag,
181
- * compare to the running version, and if newer, download + verify both
182
- * artifacts BEFORE swapping either one (so a checksum failure on the second
183
- * artifact never leaves a mismatched pair installed), then atomically swap
184
- * each in place. For any other install kind, never attempts a swap — it
159
+ * The real self-update path, delegating the download-verify-swap mechanics
160
+ * to the SDK's canonical update policy module (applyVerifiedUpdate — the
161
+ * same mechanism the daemon's hourly loop uses). For a binary install:
162
+ * resolve the latest tag, compare to the running version, and if newer,
163
+ * download + checksum-verify EVERY artifact before swapping any one (so a
164
+ * checksum failure never leaves a mismatched pair installed), then
165
+ * atomically swap each in place with the outgoing file kept at
166
+ * `<path>.previous`. For any other install kind, never attempts a swap — it
185
167
  * prints the exact command for that install method instead.
186
168
  */
187
169
  export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
@@ -211,22 +193,10 @@ export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
211
193
  options.print(`Update available: ${latestTag} (running v${normalizeVersion(options.currentVersion)}). Downloading and verifying...`);
212
194
 
213
195
  const baseUrl = releaseDownloadBaseUrl(latestTag);
214
- const checksumText = await downloadText(options.fetchImpl, `${baseUrl}/${CHECKSUM_MANIFEST_NAME}`);
215
- const checksums = parseChecksumFile(checksumText);
216
-
196
+ const io = options.io ?? realUpdateFileIo;
217
197
  const appBinaryPath = options.execPath;
218
198
  const daemonBinaryPath = join(dirname(appBinaryPath), 'goodvibes-daemon');
219
- const fileExists = options.fileExists ?? existsSync;
220
- const daemonBinaryPresent = fileExists(daemonBinaryPath);
221
-
222
- const appBuffer = await downloadBuffer(options.fetchImpl, `${baseUrl}/${artifacts.app}`);
223
- verifyChecksum(artifacts.app, sha256(appBuffer), checksums.get(artifacts.app));
224
-
225
- let daemonBuffer: Buffer | null = null;
226
- if (daemonBinaryPresent) {
227
- daemonBuffer = await downloadBuffer(options.fetchImpl, `${baseUrl}/${artifacts.daemon}`);
228
- verifyChecksum(artifacts.daemon, sha256(daemonBuffer), checksums.get(artifacts.daemon));
229
- }
199
+ const daemonBinaryPresent = io.exists(daemonBinaryPath);
230
200
 
231
201
  // The sqlite-vec native addon travels with the binaries: refresh it in the
232
202
  // same download-verify-swap pass so /update never leaves a new binary beside a
@@ -237,28 +207,36 @@ export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
237
207
  // swap), while an absent entry means the target predates the addon and is
238
208
  // skipped rather than blocking an otherwise-valid binary update. On macOS the
239
209
  // file is refreshed for consistency even though the platform blocks extension
240
- // loading.
210
+ // loading. This inclusion decision needs one manifest pre-read; the swap
211
+ // itself re-verifies every included artifact inside applyVerifiedUpdate.
212
+ const checksumText = await downloadText(options.fetchImpl, `${baseUrl}/${CHECKSUM_MANIFEST_NAME}`);
213
+ const checksums = parseChecksumFile(checksumText);
241
214
  const addon = resolveSqliteVecAsset(options.platform, options.arch);
242
- const addonExpected = addon ? checksums.get(addon.assetName) : undefined;
243
- let addonBuffer: Buffer | null = null;
244
- if (addon && addonExpected !== undefined) {
245
- addonBuffer = await downloadBuffer(options.fetchImpl, `${baseUrl}/${addon.assetName}`);
246
- verifyChecksum(addon.assetName, sha256(addonBuffer), addonExpected);
247
- }
248
-
249
- // All downloads verified before any write — an update must not apply partially.
250
- const swap = options.swap ?? swapBinaryAtomically;
251
- swap(appBinaryPath, appBuffer);
252
- if (daemonBuffer) {
253
- swap(daemonBinaryPath, daemonBuffer);
254
- }
255
- const addonTargetPath = addon && addonBuffer
215
+ const addonIncluded = addon !== null && checksums.get(addon.assetName) !== undefined;
216
+ const addonTargetPath = addon && addonIncluded
256
217
  ? join(dirname(appBinaryPath), 'lib', addon.dirName, addon.fileName)
257
218
  : null;
258
- if (addonBuffer && addonTargetPath) {
259
- const writeAddon = options.writeAddon ?? writeAddonAtomically;
260
- writeAddon(addonTargetPath, addonBuffer);
261
- }
219
+
220
+ const targets: UpdateTarget[] = [
221
+ { label: 'app binary', path: appBinaryPath, assetName: artifacts.app, executable: true },
222
+ ...(daemonBinaryPresent
223
+ ? [{ label: 'daemon binary', path: daemonBinaryPath, assetName: artifacts.daemon, executable: true }]
224
+ : []),
225
+ ...(addon && addonTargetPath
226
+ ? [{ label: 'vector addon', path: addonTargetPath, assetName: addon.assetName, executable: false }]
227
+ : []),
228
+ ];
229
+
230
+ // One mechanism everywhere: downloads + verifies ALL targets before any
231
+ // write, then swaps each atomically with the outgoing file kept at
232
+ // `<path>.previous`.
233
+ await applyVerifiedUpdate({
234
+ fetchImpl: options.fetchImpl,
235
+ downloadBaseUrl: baseUrl,
236
+ targets,
237
+ io,
238
+ platform: options.platform,
239
+ });
262
240
 
263
241
  const serviceInfo = detectDaemonServiceManaged(options.platform, options.configManager, options.runCommand);
264
242
 
@@ -279,6 +257,70 @@ export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
279
257
  );
280
258
  }
281
259
 
260
+ export interface RollbackUpdateOptions {
261
+ readonly execPath: string;
262
+ readonly platform: NodeJS.Platform;
263
+ readonly arch: string;
264
+ readonly print: (line: string) => void;
265
+ readonly configManager: { get(key: string): unknown };
266
+ readonly runCommand?: RunCommand;
267
+ /** Injectable filesystem seam (the SDK's UpdateFileIo) so tests observe renames in memory. */
268
+ readonly io?: UpdateFileIo;
269
+ }
270
+
271
+ /**
272
+ * One-command rollback to the version that ran before the last update,
273
+ * delegating the exchange mechanics to the SDK's rollbackKeptPrevious (the
274
+ * same module the swap uses): every installed file (app binary, daemon
275
+ * binary, vector addon) that has a kept `.previous` counterpart is EXCHANGED
276
+ * with it — the previous version becomes live, and the version being rolled
277
+ * back is itself kept at `.previous`, so a second `/update rollback` rolls
278
+ * forward again. Files without a kept counterpart are reported and left
279
+ * untouched; nothing is downloaded.
280
+ */
281
+ export function rollbackUpdate(options: RollbackUpdateOptions): void {
282
+ const installKind: InstallKind = detectInstallKind(options.execPath);
283
+ if (installKind !== 'binary') {
284
+ options.print(
285
+ [
286
+ `This install is not a self-updatable binary install (detected: ${installKind === 'bun-global-package' ? 'bun/npm package install' : 'running from source'}), so there is no kept previous binary to roll back to.`,
287
+ `Install a specific version with your package manager instead, e.g.: ${fallbackUpdateCommand(installKind)}`,
288
+ ].join('\n'),
289
+ );
290
+ return;
291
+ }
292
+
293
+ const io = options.io ?? realUpdateFileIo;
294
+ const addon = resolveSqliteVecAsset(options.platform, options.arch);
295
+ const targets = [
296
+ { label: 'app binary', path: options.execPath },
297
+ { label: 'daemon binary', path: join(dirname(options.execPath), 'goodvibes-daemon') },
298
+ ...(addon ? [{ label: 'vector addon', path: join(dirname(options.execPath), 'lib', addon.dirName, addon.fileName) }] : []),
299
+ ];
300
+
301
+ const result = rollbackKeptPrevious(targets, io);
302
+ if (result.restored.length === 0) {
303
+ options.print(
304
+ `No previous version is kept beside this install (nothing at ${options.execPath}${PREVIOUS_FILE_SUFFIX}). ` +
305
+ 'The previous version is kept from the next update onward.',
306
+ );
307
+ return;
308
+ }
309
+
310
+ const serviceInfo = detectDaemonServiceManaged(options.platform, options.configManager, options.runCommand);
311
+ options.print(
312
+ [
313
+ 'Rolled back to the previously installed version.',
314
+ ...result.restored.map((target) => ` ${target.label}: ${target.path} (the replaced version is kept at ${target.path}${PREVIOUS_FILE_SUFFIX})`),
315
+ '',
316
+ 'Restart goodvibes to run the restored version.',
317
+ serviceInfo.managed
318
+ ? `The daemon is managed by systemd — restart it with: ${serviceInfo.restartCommand}`
319
+ : 'The daemon restarts automatically the next time goodvibes launches.',
320
+ ].join('\n'),
321
+ );
322
+ }
323
+
282
324
  interface UpdateBundle {
283
325
  readonly version: 1;
284
326
  readonly exportedAt: number;
@@ -304,8 +346,8 @@ export function registerUpdateCommand(registry: CommandRegistry): void {
304
346
  registry.register({
305
347
  name: 'update',
306
348
  aliases: ['upgrade'],
307
- description: 'Check for a newer GoodVibes release and, for binary installs, download/verify/apply it',
308
- usage: '[check|apply|review|bundle export <path>|bundle inspect <path>]',
349
+ description: 'Check for a newer GoodVibes release and, for binary installs, download/verify/apply it or roll back to the kept previous version',
350
+ usage: '[check|apply|rollback|review|bundle export <path>|bundle inspect <path>]',
309
351
  async handler(args, ctx) {
310
352
  const sub = args[0] ?? 'check';
311
353
 
@@ -340,6 +382,21 @@ export function registerUpdateCommand(registry: CommandRegistry): void {
340
382
  return;
341
383
  }
342
384
 
385
+ if (sub === 'rollback') {
386
+ try {
387
+ rollbackUpdate({
388
+ execPath: process.execPath,
389
+ platform: process.platform,
390
+ arch: process.arch,
391
+ print: ctx.print,
392
+ configManager: ctx.platform.configManager,
393
+ });
394
+ } catch (error) {
395
+ ctx.print(`Rollback failed: ${error instanceof Error ? error.message : String(error)}`);
396
+ }
397
+ return;
398
+ }
399
+
343
400
  if (sub === 'review') {
344
401
  const installKind = detectInstallKind(process.execPath);
345
402
  const subscriptions = requireSubscriptionManager(ctx);
@@ -358,7 +415,7 @@ export function registerUpdateCommand(registry: CommandRegistry): void {
358
415
  ` built-in subscription providers: ${builtinProviders.length}${builtinProviders.length > 0 ? ` (${builtinProviders.join(', ')})` : ''}`,
359
416
  ` active subscriptions: ${activeSubscriptions.length}${activeSubscriptions.length > 0 ? ` (${activeSubscriptions.join(', ')})` : ''}`,
360
417
  ` sandbox profile: ${sandboxProfile}`,
361
- ' use /update check to look for a newer release, /update apply to install it',
418
+ ' use /update check to look for a newer release, /update apply to install it, /update rollback to return to the kept previous version',
362
419
  ].join('\n'),
363
420
  );
364
421
  return;
@@ -406,7 +463,7 @@ export function registerUpdateCommand(registry: CommandRegistry): void {
406
463
  }
407
464
  }
408
465
 
409
- ctx.print('Usage: /update [check|apply|review|bundle export <path>|bundle inspect <path>]');
466
+ ctx.print('Usage: /update [check|apply|rollback|review|bundle export <path>|bundle inspect <path>]');
410
467
  },
411
468
  });
412
469
  }
@@ -25,7 +25,7 @@ function getStore(ctx: import('../command-registry.ts').CommandContext): WorkPla
25
25
  }
26
26
 
27
27
  function openPanel(ctx: import('../command-registry.ts').CommandContext): void {
28
- // W6.1: work-plan migrated to the 'work-plan' modal — open it via the seam.
28
+ // work-plan migrated to the 'work-plan' modal — open it via the seam.
29
29
  openModalCommand(ctx, 'work-plan-modal');
30
30
  }
31
31
 
@@ -25,6 +25,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
25
25
  import type { CommandContext } from '../command-registry.ts';
26
26
  import type { WorkstreamCommandService } from '../../runtime/workstream-services.ts';
27
27
  import { requirePanelManager } from './runtime-services.ts';
28
+ import { describeFailureReason, isTurnBudgetReason } from '../../core/turn-budget-outcome.ts';
28
29
  import { type AttemptGraphItem, renderDependentHoldLines } from './workstream-attempt-dependents.ts';
29
30
 
30
31
  /** Resolve a workstream's items for dependent-hold display; null-safe against a stubbed engine. */
@@ -58,7 +59,11 @@ function resolveCandidate(group: HeldMergeGroup, ref: string): AttemptCandidate
58
59
 
59
60
  function candidateLine(c: AttemptCandidate): string {
60
61
  const files = c.diff ? `${c.diff.files.length} file(s)` : 'no diff';
61
- const state = c.state === 'held-merge' ? 'held (pick-ready)' : `failed${c.failureReason ? `: ${c.failureReason}` : ''}`;
62
+ const state = c.state === 'held-merge'
63
+ ? 'held (pick-ready)'
64
+ : isTurnBudgetReason(c.failureReason)
65
+ ? describeFailureReason(c.failureReason) // a spent budget, not an infrastructure failure
66
+ : `failed${c.failureReason ? `: ${c.failureReason}` : ''}`;
62
67
  return ` ${c.attemptIndex + 1}. [${state}] ${c.title} — ${files}, item ${shortId(c.itemId)}`;
63
68
  }
64
69
 
@@ -2,7 +2,7 @@
2
2
  // workstream-runtime.ts — /workstream
3
3
  //
4
4
  // UX over the OrchestrationEngine (@pellux/goodvibes-sdk/platform/orchestration,
5
- // wo701/W4.1) via its command-facing facade wired onto RuntimeServices as
5
+ //) via its command-facing facade wired onto RuntimeServices as
6
6
  // `workstreamCommands` (src/runtime/workstream-services.ts) and threaded
7
7
  // through CommandContext.session.workstreamEngine exactly like wrfcController
8
8
  // already is (bootstrap-command-context.ts).
@@ -21,7 +21,7 @@
21
21
  // pre-creation "draft" concept, so approval happens on a TUI-held draft
22
22
  // (workstream-services.ts's WorkstreamDraft) before anything is created in
23
23
  // the engine at all — see that module's header doc for the full reality-wins
24
- // divergence from the wo703 design brief.
24
+ // divergence from the design brief.
25
25
  // ---------------------------------------------------------------------------
26
26
 
27
27
  import { AdaptivePlanner } from '@pellux/goodvibes-sdk/platform/core';
@@ -102,6 +102,33 @@ async function checkFanoutQuotaWarning(ctx: CommandContext, draft: WorkstreamDra
102
102
  ].join('\n');
103
103
  }
104
104
 
105
+ /**
106
+ * Launch an already-approved draft through its gates (best-of-N attempt
107
+ * validation, then the fan-out quota warning unless `--force`), and print the
108
+ * outcome. Shared by `launch` and by `approve` (which launches in the same act
109
+ * — the one confirmed step that replaces the old approve-then-retype ceremony).
110
+ */
111
+ async function launchApprovedDraft(ctx: CommandContext, service: WorkstreamCommandService, id: string, draft: WorkstreamDraft, force: boolean): Promise<void> {
112
+ const attemptsCheck = validateAttempts(draft.spec);
113
+ if (attemptsCheck.violations.length > 0) {
114
+ ctx.print(`Cannot launch ${id} — best-of-N plan constraints are violated:\n${attemptsCheck.violations.map((v) => ` - ${v}`).join('\n')}\nFix the plan (or drop the attempts) and re-approve.`);
115
+ return;
116
+ }
117
+ if (!force) {
118
+ const quotaWarning = await checkFanoutQuotaWarning(ctx, draft);
119
+ if (quotaWarning) {
120
+ ctx.print(quotaWarning);
121
+ return;
122
+ }
123
+ }
124
+ const result = service.launchDraft(id);
125
+ if (!result) {
126
+ ctx.print(`Could not launch ${id}.`);
127
+ return;
128
+ }
129
+ ctx.print(`Launched workstream ${result.workstreamId} — track it with /workstream status ${result.workstreamId} or the Fleet panel.`);
130
+ }
131
+
105
132
  /**
106
133
  * phase.role for a 'custom' phase is the free-text description passed to
107
134
  * `/workstream insert-phase` — it is purely COSMETIC. Neither
@@ -377,7 +404,7 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
377
404
  registry.register({
378
405
  name: 'workstream',
379
406
  description: 'Author and oversee multi-phase agent workstreams (orchestration engine)',
380
- usage: 'create [--isolation shared|worktree] <task...> | list | status [id] | insert-phase <id> <description...> | edit-item <id> <item#> <brief...> | remove-item <id> <item#> | move-item <id> <item#> <pos> | approve <id> | edit <id> [--isolation shared|worktree] <task...> | launch <id> [--force] | cancel <id> | attempts list|diff|judge|pick',
407
+ usage: 'create [--isolation shared|worktree] <task...> | list | status [id] | insert-phase <id> <description...> | edit-item <id> <item#> <brief...> | remove-item <id> <item#> | move-item <id> <item#> <pos> | approve <id> [--no-launch] [--force] | edit <id> [--isolation shared|worktree] <task...> | launch <id> [--force] | cancel <id> | attempts list|diff|judge|pick',
381
408
  argsHint: 'create [--isolation worktree] <task> | list | status [id] | edit-item | remove-item | move-item | approve | edit | launch | cancel | attempts',
382
409
  handler: async (args, ctx: CommandContext) => {
383
410
  const service = ctx.session.workstreamEngine;
@@ -470,9 +497,14 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
470
497
  }
471
498
 
472
499
  if (sub === 'approve') {
473
- const id = args[1];
500
+ // Approve IS the one confirmed act: it approves AND launches (typing the
501
+ // verb with an explicit id is the confirmation). --no-launch keeps the
502
+ // old split for scripts; --force skips the fan-out quota warning.
503
+ const noLaunch = args.includes('--no-launch');
504
+ const force = args.includes('--force');
505
+ const id = args.find((arg, index) => index > 0 && arg !== '--no-launch' && arg !== '--force');
474
506
  if (!id) {
475
- ctx.print('Usage: /workstream approve <id>');
507
+ ctx.print('Usage: /workstream approve <id> [--no-launch] [--force]');
476
508
  return;
477
509
  }
478
510
  const draft = service.approveDraft(id);
@@ -480,7 +512,11 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
480
512
  ctx.print(draftNotFoundMessage(service, id));
481
513
  return;
482
514
  }
483
- ctx.print(`Approved ${id}. Launch with: /workstream launch ${id}`);
515
+ if (noLaunch) {
516
+ ctx.print(`Approved ${id} (held, not launched). Launch it with /workstream launch ${id}.`);
517
+ return;
518
+ }
519
+ await launchApprovedDraft(ctx, service, id, draft, force);
484
520
  return;
485
521
  }
486
522
 
@@ -554,27 +590,10 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
554
590
  return;
555
591
  }
556
592
  if (!draft.approved) {
557
- ctx.print(`Proposal ${id} is not approved yet. Run /workstream approve ${id} first.`);
558
- return;
559
- }
560
- const attemptsCheck = validateAttempts(draft.spec);
561
- if (attemptsCheck.violations.length > 0) {
562
- ctx.print(`Cannot launch ${id} — best-of-N plan constraints are violated:\n${attemptsCheck.violations.map((v) => ` - ${v}`).join('\n')}\nFix the plan (or drop the attempts) and re-approve.`);
563
- return;
564
- }
565
- if (!force) {
566
- const quotaWarning = await checkFanoutQuotaWarning(ctx, draft);
567
- if (quotaWarning) {
568
- ctx.print(quotaWarning);
569
- return;
570
- }
571
- }
572
- const result = service.launchDraft(id);
573
- if (!result) {
574
- ctx.print(`Could not launch ${id}.`);
593
+ ctx.print(`Proposal ${id} is not approved yet. Run /workstream approve ${id} first (or approve launches it directly).`);
575
594
  return;
576
595
  }
577
- ctx.print(`Launched workstream ${result.workstreamId} — track it with /workstream status ${result.workstreamId} or the Fleet panel.`);
596
+ await launchApprovedDraft(ctx, service, id, draft, force);
578
597
  return;
579
598
  }
580
599
 
@@ -616,9 +635,9 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
616
635
  + ' /workstream edit-item <id> <item#> <new brief...>\n'
617
636
  + ' /workstream remove-item <id> <item#>\n'
618
637
  + ' /workstream move-item <id> <item#> <new-position#>\n'
619
- + ' /workstream approve <id>\n'
638
+ + ' /workstream approve <id> [--no-launch] [--force] (approves AND launches in one act)\n'
620
639
  + ' /workstream edit <id> [--isolation shared|worktree] <new task...>\n'
621
- + ' /workstream launch <id> [--force]\n'
640
+ + ' /workstream launch <id> [--force] (for a --no-launch-held or re-launched draft)\n'
622
641
  + ' /workstream cancel <id>',
623
642
  );
624
643
  },
@@ -145,19 +145,46 @@ export function registerWorktreeRuntimeCommands(registry: CommandRegistry): void
145
145
  : `${header}: ${targetKind} ${targetId}\n No attached worktrees tracked.`);
146
146
  return;
147
147
  }
148
- if (sub === 'pause' || sub === 'resume' || sub === 'keep' || sub === 'discard') {
148
+ if (sub === 'discard') {
149
+ // Discard is a real destructive act, not a metadata flip: route it
150
+ // through the worktrees.discard operator verb (preservation commit,
151
+ // git worktree removal, honest receipt) — the SAME behavior as the
152
+ // Fleet panel's D key. Typing the verb with an explicit path is the
153
+ // confirmation.
154
+ const path = args[1];
155
+ if (!path) {
156
+ ctx.print('Usage: /worktree discard <path>');
157
+ return;
158
+ }
159
+ const rpc = getOperatorRpc(ctx);
160
+ if (!rpc.available) {
161
+ ctx.print(`[worktree discard] ${rpc.reason}`);
162
+ return;
163
+ }
164
+ try {
165
+ const receipt = await rpc.sdk.operator.invoke('worktrees.discard', { path });
166
+ if (receipt.ok) {
167
+ ctx.print([
168
+ `[worktree discard] Discarded ${receipt.path}`,
169
+ ` branch kept: ${receipt.branch || '(unknown)'}`,
170
+ ` preservation commit: ${receipt.preservedCommit || '(none — nothing to preserve)'}`,
171
+ ` ${receipt.detail}`,
172
+ ].join('\n'));
173
+ } else {
174
+ ctx.print(`[worktree discard] refused for ${receipt.path}: ${receipt.detail}`);
175
+ }
176
+ } catch (error) {
177
+ ctx.print(`[worktree discard] round-trip request failed: ${describeOperatorRpcError(error)}`);
178
+ }
179
+ return;
180
+ }
181
+ if (sub === 'pause' || sub === 'resume' || sub === 'keep') {
149
182
  const path = args[1];
150
183
  if (!path) {
151
184
  ctx.print(`Usage: /worktree ${sub} <path>`);
152
185
  return;
153
186
  }
154
- const nextState = sub === 'resume'
155
- ? 'active'
156
- : sub === 'pause'
157
- ? 'paused'
158
- : sub === 'keep'
159
- ? 'kept'
160
- : 'discard';
187
+ const nextState = sub === 'resume' ? 'active' : sub === 'pause' ? 'paused' : 'kept';
161
188
  runtime.setState(path, nextState);
162
189
  ctx.print(`Updated ${path} to state ${nextState}.`);
163
190
  return;
@@ -17,6 +17,9 @@ import { registerOperatorRuntimeCommands } from './commands/operator-runtime.ts'
17
17
  import { registerPluginRuntimeCommands } from './commands/plugin-runtime.ts';
18
18
  import { registerDiffRuntimeCommands } from './commands/diff-runtime.ts';
19
19
  import { registerGitRuntimeCommands } from './commands/git-runtime.ts';
20
+ import { registerQueueRuntimeCommands } from './commands/queue-runtime.ts';
21
+ import { registerPowerRuntimeCommands } from './commands/power-runtime.ts';
22
+ import { registerGraphRuntimeCommands } from './commands/graph-runtime.ts';
20
23
  import { registerTestRuntimeCommands } from './commands/test-runtime.ts';
21
24
  import { registerNotifyRuntimeCommands } from './commands/notify-runtime.ts';
22
25
  import { registerReplayRuntimeCommands } from './commands/replay-runtime.ts';
@@ -36,7 +39,6 @@ import { registerRemoteRuntimeCommands } from './commands/remote-runtime.ts';
36
39
  import { registerTeleportRuntimeCommands } from './commands/teleport-runtime.ts';
37
40
  import { registerSubscriptionRuntimeCommands } from './commands/subscription-runtime.ts';
38
41
  import { registerHooksRuntimeCommands } from './commands/hooks-runtime.ts';
39
- import { registerFlagsRuntimeCommands } from './commands/flags-runtime.ts';
40
42
  import { registerEditorRuntimeCommands } from './commands/editor-runtime.ts';
41
43
  import { registerControlRoomRuntimeCommands } from './commands/control-room-runtime.ts';
42
44
  import { registerMcpRuntimeCommands } from './commands/mcp-runtime.ts';
@@ -56,11 +58,13 @@ import { registerLocalProviderRuntimeCommands } from './commands/local-provider-
56
58
  import { registerHealthRuntimeCommands } from './commands/health-runtime.ts';
57
59
  import { registerSettingsSyncRuntimeCommands } from './commands/settings-sync-runtime.ts';
58
60
  import { registerWorktreeRuntimeCommands } from './commands/worktree-runtime.ts';
61
+ import { registerAcpRuntimeCommands } from './commands/acp-runtime.ts';
59
62
  import { registerProviderAccountsRuntimeCommands } from './commands/provider-accounts-runtime.ts';
60
63
  import { registerLocalAuthRuntimeCommands } from './commands/local-auth-runtime.ts';
61
64
  import { registerIntelligenceRuntimeCommands } from './commands/intelligence-runtime.ts';
62
65
  import { registerConversationRuntimeCommands } from './commands/conversation-runtime.ts';
63
66
  import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
67
+ import { registerDevicesRuntimeCommands } from './commands/devices-runtime.ts';
64
68
  import { registerRelayRuntimeCommands } from './commands/relay-runtime.ts';
65
69
  import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
66
70
  import { registerTtsRuntimeCommands } from './commands/tts-runtime.ts';
@@ -102,6 +106,9 @@ export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
102
106
  { category: 'Plugins', register: registerPluginRuntimeCommands },
103
107
  { category: 'Diff & Review', register: registerDiffRuntimeCommands },
104
108
  { category: 'Git', register: registerGitRuntimeCommands },
109
+ { category: 'Shell & Session', register: registerQueueRuntimeCommands },
110
+ { category: 'Shell & Session', register: registerPowerRuntimeCommands },
111
+ { category: 'Automation', register: registerGraphRuntimeCommands },
105
112
  { category: 'Testing', register: registerTestRuntimeCommands },
106
113
  { category: 'Notifications', register: registerNotifyRuntimeCommands },
107
114
  { category: 'Sessions & Replay', register: registerReplayRuntimeCommands },
@@ -121,7 +128,6 @@ export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
121
128
  { category: 'Remote', register: registerTeleportRuntimeCommands },
122
129
  { category: 'Subscriptions', register: registerSubscriptionRuntimeCommands },
123
130
  { category: 'Hooks', register: registerHooksRuntimeCommands },
124
- { category: 'Feature Flags', register: registerFlagsRuntimeCommands },
125
131
  { category: 'Editor', register: registerEditorRuntimeCommands },
126
132
  { category: 'Control Room', register: registerControlRoomRuntimeCommands },
127
133
  { category: 'MCP', register: registerMcpRuntimeCommands },
@@ -135,11 +141,13 @@ export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
135
141
  { category: 'Health', register: registerHealthRuntimeCommands },
136
142
  { category: 'Settings Sync', register: registerSettingsSyncRuntimeCommands },
137
143
  { category: 'Worktrees', register: registerWorktreeRuntimeCommands },
144
+ { category: 'Fleet', register: registerAcpRuntimeCommands },
138
145
  { category: 'Provider Accounts', register: registerProviderAccountsRuntimeCommands },
139
146
  { category: 'Local Auth', register: registerLocalAuthRuntimeCommands },
140
147
  { category: 'Intelligence', register: registerIntelligenceRuntimeCommands },
141
148
  { category: 'Conversation', register: registerConversationRuntimeCommands },
142
149
  { category: 'QR Codes', register: registerQrcodeRuntimeCommands },
150
+ { category: 'Security', register: registerDevicesRuntimeCommands },
143
151
  { category: 'Relay', register: registerRelayRuntimeCommands },
144
152
  { category: 'Onboarding', register: registerOnboardingRuntimeCommands },
145
153
  { category: 'Voice & TTS', register: registerTtsRuntimeCommands },