@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,19 +1,22 @@
1
1
  /**
2
- * Helper factories for main()'s stdin fast-path: the Ctrl+R recovery
3
- * persistence/panel-reopen callbacks and the one-key error-retry
4
- * affordance. Extracted from main.ts so the entrypoint stays under the
5
- * architecture line ceiling; main() wires these with its live services.
2
+ * Helper factories for main()'s stdin fast-path and startup: the crash-recovery
3
+ * snapshot persistence + panel-reopen callbacks, the SILENT auto-restore, and
4
+ * the one-key error-retry affordance. Extracted from main.ts so the entrypoint
5
+ * stays under the architecture line ceiling; main() wires these with its live
6
+ * services.
7
+ *
8
+ * Recovery is silent (owner ruling): there is no Ctrl+R prompt/banner and no
9
+ * dismiss/preserve dance. At startup autoRestoreRecoverySession restores the
10
+ * newest crash snapshot in place and emits a single one-line receipt. The old
11
+ * `.preserved` sibling machinery is gone — a session's exit deletes only its
12
+ * OWN snapshot (scoped by sessionId), so a concurrent session's snapshot is
13
+ * never touched.
6
14
  */
7
15
 
8
- import { copyFileSync, existsSync, readFileSync, unlinkSync } from 'node:fs';
9
16
  import type { ConversationMessageSnapshot } from '../core/conversation.ts';
10
- import type { SessionReturnContextSummary, SessionSnapshot } from '@/runtime/index.ts';
11
- import {
12
- checkRecoveryFile,
13
- deleteRecoveryFile as deleteLiveRecoveryFile,
14
- getRecoveryFilePath,
15
- loadRecoveryConversation as loadLiveRecoveryConversation,
16
- } from '@/runtime/index.ts';
17
+ import type { SessionSnapshot } from '@/runtime/index.ts';
18
+ import { autoRestoreRecovery } from '@/runtime/index.ts';
19
+ import { replayJournalForSession } from '../core/session-recovery.ts';
17
20
 
18
21
  export interface PersistRecoveryDeps {
19
22
  readonly sessionManager: {
@@ -48,166 +51,50 @@ export function createReopenRecoveryPanels(deps: ReopenPanelsDeps): (snapshot: S
48
51
  };
49
52
  }
50
53
 
51
- // ---------------------------------------------------------------------------
52
- // W3 Finding 3: preserve the dismissed recovery snapshot.
53
- //
54
- // main.ts's 60s autosave (writeRecoveryFile) overwrites the single shared
55
- // recovery.jsonl with the CURRENT session's live state within a minute — so
56
- // the dismiss banner's promise ("still on disk; you will be asked again next
57
- // time") went false silently well before the next launch. Fix: the moment
58
- // the user dismisses (blocking-input.ts), copy recovery.jsonl aside to a
59
- // preserved sibling file (recovery.jsonl.preserved) so it survives this
60
- // session's later autosaves. Startup then checks BOTH the live file and the
61
- // preserved sibling and offers whichever is newer (pickNewestRecoveryInfo),
62
- // bounded to exactly one preserved file — a later dismiss replaces it, and
63
- // that replacement is reported honestly (PreserveRecoveryResult.replacedPrevious)
64
- // rather than silently dropping the older snapshot.
65
- // ---------------------------------------------------------------------------
66
-
67
- export interface RecoveryFileDeps {
54
+ export interface AutoRestoreRecoveryDeps {
55
+ readonly workingDirectory: string;
68
56
  readonly homeDirectory: string;
69
- readonly surfaceRoot?: string | undefined;
70
- }
71
-
72
- export interface PreservedRecoveryInfo {
73
- readonly title: string;
74
- readonly timestamp: number;
75
- readonly sessionId: string;
76
- readonly returnContext?: SessionReturnContextSummary | undefined;
77
- }
78
-
79
- export interface PreserveRecoveryResult {
80
- /** True if a live recovery.jsonl existed and was copied to the preserved sibling. */
81
- readonly preserved: boolean;
82
- /** True if an existing preserved sibling (from an earlier dismiss) was overwritten. */
83
- readonly replacedPrevious: boolean;
84
- }
85
-
86
- function preservedRecoveryPath(deps: RecoveryFileDeps): string {
87
- return `${getRecoveryFilePath(deps.homeDirectory, deps.surfaceRoot)}.preserved`;
88
- }
89
-
90
- function readRecoveryMetaLine(path: string): { title?: string; timestamp?: number; sessionId?: string; returnContext?: SessionReturnContextSummary } | null {
91
- try {
92
- if (!existsSync(path)) return null;
93
- const firstLine = readFileSync(path, 'utf-8').split('\n')[0];
94
- if (!firstLine) return null;
95
- return JSON.parse(firstLine) as { title?: string; timestamp?: number; sessionId?: string; returnContext?: SessionReturnContextSummary };
96
- } catch {
97
- return null;
98
- }
99
- }
100
-
101
- /** Copy the live recovery.jsonl aside to the preserved sibling. Called once, at dismiss. */
102
- export function createPreserveRecoveryFile(deps: RecoveryFileDeps): () => PreserveRecoveryResult {
103
- return () => {
104
- const live = getRecoveryFilePath(deps.homeDirectory, deps.surfaceRoot);
105
- const preserved = preservedRecoveryPath(deps);
106
- if (!existsSync(live)) return { preserved: false, replacedPrevious: false };
107
- const replacedPrevious = existsSync(preserved);
108
- try {
109
- copyFileSync(live, preserved);
110
- return { preserved: true, replacedPrevious };
111
- } catch {
112
- return { preserved: false, replacedPrevious: false };
113
- }
57
+ /** The live conversation to restore INTO (fromJSON applies the snapshot). */
58
+ readonly conversation: {
59
+ fromJSON(input: { messages: ConversationMessageSnapshot[]; title?: string; titleSource?: SessionSnapshot['titleSource'] }): void;
114
60
  };
61
+ /** Persist the post-replay snapshot so the WAL gap is durably closed. */
62
+ readonly persistSnapshot: (msgs: ConversationMessageSnapshot[]) => void;
63
+ /** Reopen the restored session's panels. */
64
+ readonly reopenPanels: (snapshot: SessionSnapshot) => void;
65
+ /** Surface the one-line receipt (the only user-visible sign of a restore). */
66
+ readonly systemMessageRouter: { high(message: string): void };
115
67
  }
116
68
 
117
69
  /**
118
- * Startup check for the preserved sibling — mirrors the SDK's
119
- * checkRecoveryFile meta read, but for the fixed preserved path. No
120
- * mtime-vs-last-clean-save gate: the preserved file is inert (never
121
- * autosaved-over) until explicitly replaced or cleared, so its mere
122
- * existence is actionable.
123
- */
124
- export function checkPreservedRecoveryFile(deps: RecoveryFileDeps): PreservedRecoveryInfo | null {
125
- const meta = readRecoveryMetaLine(preservedRecoveryPath(deps));
126
- if (!meta) return null;
127
- return { title: meta.title ?? '', timestamp: meta.timestamp ?? 0, sessionId: meta.sessionId ?? '', returnContext: meta.returnContext };
128
- }
129
-
130
- /** Load the preserved sibling's full snapshot — same JSONL shape as recovery.jsonl since it's a byte-identical copy. */
131
- export function loadPreservedRecoveryConversation(deps: RecoveryFileDeps): SessionSnapshot | null {
132
- const path = preservedRecoveryPath(deps);
133
- try {
134
- if (!existsSync(path)) return null;
135
- const lines = readFileSync(path, 'utf-8').split('\n').filter(Boolean);
136
- if (lines.length < 2) return { messages: [] };
137
- const metaLine = JSON.parse(lines[0]!) as { title?: string; titleSource?: SessionSnapshot['titleSource']; returnContext?: SessionSnapshot['returnContext'] };
138
- return {
139
- title: metaLine.title,
140
- titleSource: metaLine.titleSource,
141
- returnContext: metaLine.returnContext,
142
- messages: lines.slice(1).map((line) => {
143
- const { type: _type, ...rest } = JSON.parse(line) as { type: string } & Record<string, unknown>;
144
- return rest;
145
- }),
146
- };
147
- } catch {
148
- return null;
149
- }
150
- }
151
-
152
- /** Remove the preserved sibling — called after it has been restored or discarded. */
153
- export function deletePreservedRecoveryFile(deps: RecoveryFileDeps): void {
154
- try {
155
- unlinkSync(preservedRecoveryPath(deps));
156
- } catch {
157
- // missing file is fine
158
- }
159
- }
160
-
161
- /**
162
- * Pick the newer of the live vs. preserved recovery info for the startup
163
- * prompt, tagging which source it came from so the caller knows which
164
- * load/delete pair to use. Null when neither exists.
165
- */
166
- export function pickNewestRecoveryInfo<T extends { readonly timestamp: number }>(
167
- live: T | null,
168
- preserved: T | null,
169
- ): (T & { readonly source: 'live' | 'preserved' }) | null {
170
- if (!live && !preserved) return null;
171
- if (!live) return { ...(preserved as T), source: 'preserved' };
172
- if (!preserved) return { ...live, source: 'live' };
173
- return preserved.timestamp > live.timestamp
174
- ? { ...(preserved as T), source: 'preserved' }
175
- : { ...live, source: 'live' };
176
- }
177
-
178
- /**
179
- * Startup entry point: resolve which recovery prompt (if any) main.ts should
180
- * show — the live recovery.jsonl, or an earlier dismiss's preserved sibling,
181
- * whichever is newer. Bundles checkRecoveryFile + checkPreservedRecoveryFile
182
- * + pickNewestRecoveryInfo so main.ts's entrypoint stays a one-liner.
70
+ * Silent crash-recovery restore at startup — no prompt, no banner. Loads the
71
+ * newest crash snapshot (the SDK scopes the load AND the follow-up delete to
72
+ * that snapshot's own session id), applies it to the live conversation, replays
73
+ * any journal turns written after the snapshot, reopens its panels, and emits
74
+ * the SDK's one-line receipt. Returns true when a session was restored, false
75
+ * when there was nothing to restore.
183
76
  */
184
- export function resolveStartupRecoveryInfo(
185
- deps: RecoveryFileDeps & { readonly workingDirectory: string },
186
- ): (PreservedRecoveryInfo & { readonly source: 'live' | 'preserved' }) | null {
187
- const live = checkRecoveryFile({ workingDirectory: deps.workingDirectory, homeDirectory: deps.homeDirectory });
188
- const preserved = checkPreservedRecoveryFile(deps);
189
- return pickNewestRecoveryInfo(live, preserved);
190
- }
191
-
192
- export interface RecoveryFileOps {
193
- readonly loadRecoveryConversation: () => SessionSnapshot | null;
194
- readonly deleteRecoveryFile: () => void;
195
- readonly preserveRecoveryFile?: () => PreserveRecoveryResult;
196
- }
197
-
198
- /**
199
- * Build the three source-aware recovery callbacks main.ts's stdin handler
200
- * wires into handleBlockingShellInput. `getSource` is read fresh by
201
- * load/delete on every call (main.ts's recoverySource can change between
202
- * events); preserveRecoveryFile is only ever wired when the CURRENT prompt
203
- * came from the live file (see createPreserveRecoveryFile's doc comment).
204
- */
205
- export function createRecoveryFileOps(getSource: () => 'live' | 'preserved', deps: RecoveryFileDeps): RecoveryFileOps {
206
- return {
207
- loadRecoveryConversation: () => (getSource() === 'preserved' ? loadPreservedRecoveryConversation(deps) : loadLiveRecoveryConversation(deps)),
208
- deleteRecoveryFile: () => (getSource() === 'preserved' ? deletePreservedRecoveryFile(deps) : deleteLiveRecoveryFile(deps)),
209
- preserveRecoveryFile: getSource() === 'live' ? createPreserveRecoveryFile(deps) : undefined,
210
- };
77
+ export function autoRestoreRecoverySession(deps: AutoRestoreRecoveryDeps): boolean {
78
+ const result = autoRestoreRecovery({ workingDirectory: deps.workingDirectory, homeDirectory: deps.homeDirectory });
79
+ if (!result) return false;
80
+ const { snapshot, info, receipt } = result;
81
+ deps.conversation.fromJSON({
82
+ messages: snapshot.messages as ConversationMessageSnapshot[],
83
+ title: snapshot.title,
84
+ titleSource: snapshot.titleSource,
85
+ });
86
+ // Replay journal records that post-date the snapshot so turns written after
87
+ // the last recovery-file write (but before the crash) are not dropped.
88
+ replayJournalForSession({
89
+ homeDirectory: deps.homeDirectory,
90
+ sessionId: info.sessionId,
91
+ snapshotTimestamp: info.timestamp ?? 0,
92
+ conversation: deps.conversation as never,
93
+ persistSnapshot: deps.persistSnapshot,
94
+ });
95
+ deps.reopenPanels(snapshot);
96
+ deps.systemMessageRouter.high(`[Recovery] ${receipt}`);
97
+ return true;
211
98
  }
212
99
 
213
100
  export interface ErrorAffordanceDeps {
@@ -19,6 +19,8 @@ import { syncServiceSettingToPlatform } from './service-settings-sync.ts';
19
19
  import { setActiveThemeMode } from '../renderer/theme.ts';
20
20
  import { THEME_MODE_CONFIG_KEY, coerceThemeModeSetting } from '../renderer/theme-mode-config.ts';
21
21
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
22
+ import { buildFirstOpenItems, decodeFirstOpenChoice, selfRecordWorkspaceRegistration } from '../cli/tui-startup.ts';
23
+ import type { WorkspaceTrustLevel } from '../runtime/trust/workspace-trust.ts';
22
24
 
23
25
  type WireShellUiOpenersOptions = {
24
26
  commandContext: CommandContext;
@@ -33,13 +35,15 @@ type WireShellUiOpenersOptions = {
33
35
  subscriptionManager: SubscriptionManager;
34
36
  secretsManager?: Pick<SecretsManager, 'delete' | 'get' | 'set'>;
35
37
  serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>;
36
- /** B29: backs the model picker's 'embeddings' target and its own item-list mode. */
38
+ /** Backs the model picker's 'embeddings' target and its own item-list mode. */
37
39
  memoryEmbeddingRegistry: Pick<MemoryEmbeddingProviderRegistry, 'getDefaultProviderId' | 'setDefaultProvider' | 'status'>;
38
40
  workingDirectory: string;
39
41
  homeDirectory: string;
40
42
  getConfiguredProviderIds: () => string[];
41
43
  getPinned: () => Promise<string[]>;
42
44
  render: () => void;
45
+ /** Trust-at-consequence-time bridge — patched here with the real modal-driving implementation. */
46
+ trustPromptRef: { requestTrustDecision: () => Promise<WorkspaceTrustLevel> };
43
47
  };
44
48
 
45
49
  let commandCategoryMapCache: Map<string, string> | null = null;
@@ -123,8 +127,25 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
123
127
  getConfiguredProviderIds,
124
128
  getPinned,
125
129
  render,
130
+ trustPromptRef,
126
131
  } = options;
127
132
 
133
+ // Trust-at-consequence-time: raised by trustGatedAsk on the first non-read
134
+ // request in an undecided workspace; the answer persists via setLevel().
135
+ // Registration self-records here too when trusted (never for restricted —
136
+ // see selfRecordWorkspaceRegistration's doc).
137
+ trustPromptRef.requestTrustDecision = () =>
138
+ new Promise((resolve) => {
139
+ const { title, items } = buildFirstOpenItems();
140
+ if (!commandContext.openSelection) { resolve('restricted'); return; } // no selection surface (should not happen live)
141
+ commandContext.openSelection(title, items, { allowSearch: false, primaryVerbLabel: 'Choose' }, (result) => {
142
+ const level = decodeFirstOpenChoice(result?.item.id ?? null);
143
+ resolve(level);
144
+ if (level === 'trusted') void selfRecordWorkspaceRegistration(commandContext.workspace?.workspaceRegistrationManager);
145
+ render();
146
+ });
147
+ });
148
+
128
149
  /**
129
150
  * Pre-resolve which provider IDs have secrets-manager keys (async batch, SDK tier pattern).
130
151
  * Returns a set of provider IDs (not env var names) that are secrets-backed.
@@ -248,6 +269,13 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
248
269
  };
249
270
 
250
271
  commandContext.openModelPicker = () => {
272
+ // Picker-open re-check: re-verify each provider's live model list (TTL-
273
+ // respecting) so a freshly-opened picker reflects models the provider
274
+ // started or stopped serving. Fire-and-forget; a completed refresh re-renders
275
+ // so the list updates in place without blocking the open.
276
+ void providerRegistry.refreshLiveModelDiscovery?.().then((reports) => {
277
+ if (reports.some((report) => report.added.length > 0 || report.removed.length > 0)) render();
278
+ }).catch(() => {});
251
279
  void (async () => {
252
280
  // getSelectableModels() is catalog-driven and can list models whose
253
281
  // `provider` id (e.g. 'google', sourced from the pricing catalog) was never
@@ -358,7 +386,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
358
386
  input.modalOpened('settings');
359
387
  input.settingsModal.open(configManager, featureFlags, subscriptionManager, serviceRegistry, mcpRegistry, secretsManager, {
360
388
  onSettingApplied: (change) => {
361
- // DEBT-2: forced dark/light applies immediately (rebuild palettes + full
389
+ // forced dark/light applies immediately (rebuild palettes + full
362
390
  // repaint); auto only re-probes at startup, so it takes effect next launch.
363
391
  if (String(change.key) === THEME_MODE_CONFIG_KEY) {
364
392
  const next = coerceThemeModeSetting(change.value);
@@ -379,7 +407,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
379
407
  render();
380
408
  };
381
409
 
382
- // W6.1 (the purge): open a MIGRATE-TO-MODAL surface by name. Fed from both
410
+ // (the purge): open a MIGRATE-TO-MODAL surface by name. Fed from both
383
411
  // ctx.openModal(name) (migrated command front-doors) and PanelManager's
384
412
  // open()-time modal-redirect callback (a legacy panel id resolving to a modal
385
413
  // name). Surfaces are registered in builtin-modals.ts; a name with no
@@ -445,7 +473,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
445
473
  return;
446
474
  }
447
475
  if (panelManager.getAllOpen().length === 0) {
448
- // W6.1 (the purge): 'panel-list' (a browse-all-panels picker PANEL)
476
+ // (the purge): 'panel-list' (a browse-all-panels picker PANEL)
449
477
  // was DELETE-disposition — a picker over a handful of panels is dead
450
478
  // weight now. Its replacement is this selection MODAL, built from the
451
479
  // live registry (PanelManager.getRegisteredTypes()) rather than a
@@ -475,6 +503,10 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
475
503
  render();
476
504
  };
477
505
 
506
+ // Registry-driven surfaces built inside command handlers (/help) group by
507
+ // the same categories as the palette and the generated reference.
508
+ commandContext.getCommandCategories = () => getCommandCategoryMap();
509
+
478
510
  commandContext.openCommandPalette = () => {
479
511
  // The palette is built live from the command registry — never a hardcoded
480
512
  // list — so it can never drift from the real command set. Category labels
@@ -534,11 +566,11 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
534
566
  };
535
567
 
536
568
  commandContext.showPanel = (panelId, pane, target, opts) => {
537
- // DEBT-5: forward the deep-link target so a jumped-to panel lands on the
569
+ // forward the deep-link target so a jumped-to panel lands on the
538
570
  // right row (fleet --target); undefined for ordinary opens.
539
571
  panelManager.open(panelId, pane, target);
540
572
  panelManager.show();
541
- // UX-C focus rule 1a: every registered caller of showPanel is a slash
573
+ // focus rule 1a: every registered caller of showPanel is a slash
542
574
  // command (/panel open, /routes, /approval, /tasks, /ops-control, ...) —
543
575
  // the command path leaves focus in the composer ("the user is
544
576
  // mid-command-flow") unless the caller explicitly asks to grab it.
@@ -6,7 +6,7 @@
6
6
  * formatElapsed floors to integer seconds and lacks the null/'?ms' guards
7
7
  * that the latency panels require.
8
8
  *
9
- * W6.1 (the purge): formatShortDuration (used only by incident-review-panel
9
+ * (the purge): formatShortDuration (used only by incident-review-panel
10
10
  * and eval-panel, both removed — RETIRE-INTO-FLEET and DELETE respectively)
11
11
  * was removed as a genuinely orphaned export — no remaining caller anywhere
12
12
  * in src/.
@@ -1,7 +1,8 @@
1
1
  import { readFileSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { CONFIG_SCHEMA } from '@pellux/goodvibes-sdk/platform/config';
4
- import { FEATURE_FLAG_MAP } from '@pellux/goodvibes-sdk/platform/runtime/state';
4
+ import { FEATURE_SETTINGS } from '@pellux/goodvibes-sdk/platform/runtime/state';
5
+ import { featureEnablementWrite } from '../runtime/feature-settings.ts';
5
6
  import { CommandRegistry } from '../input/command-registry.ts';
6
7
  import { registerBuiltinCommands } from '../input/commands.ts';
7
8
 
@@ -78,8 +79,24 @@ export const FEATURE_KNOB_LOCAL_SETTINGS = [
78
79
  'runtime.toolBudget.maxCostUsd',
79
80
  ] as const;
80
81
 
81
- /** Settings with real local behavior verification: the authored baseline plus the newly persistence-tested feature-knob keys. */
82
- const SETTINGS_BEHAVIOR_VERIFIED = SETTINGS_BEHAVIOR_BASELINE + FEATURE_KNOB_LOCAL_SETTINGS.length;
82
+ /**
83
+ * Every feature's enablement settings key whose on/off writes round-trip
84
+ * through a REAL on-disk ConfigManager in
85
+ * `src/test/verification/../input/settings-modal-flag-persistence.test.ts`
86
+ * ("every feature with an off position round-trips both directions"). These
87
+ * are first-class schema keys under the dissolved feature model, so they are
88
+ * genuinely behavior-verified locally. Keys already counted in the
89
+ * feature-knob list are excluded to keep the sum honest.
90
+ */
91
+ const ENABLEMENT_KEYS_BEHAVIOR_VERIFIED = new Set(
92
+ FEATURE_SETTINGS
93
+ .filter((feature) => featureEnablementWrite(feature.id, true) !== null && featureEnablementWrite(feature.id, false) !== null)
94
+ .map((feature) => feature.enablement.key)
95
+ .filter((key) => !(FEATURE_KNOB_LOCAL_SETTINGS as readonly string[]).includes(key)),
96
+ ).size;
97
+
98
+ /** Settings with real local behavior verification: the authored baseline plus the persistence-tested feature-knob and enablement keys. */
99
+ const SETTINGS_BEHAVIOR_VERIFIED = SETTINGS_BEHAVIOR_BASELINE + FEATURE_KNOB_LOCAL_SETTINGS.length + ENABLEMENT_KEYS_BEHAVIOR_VERIFIED;
83
100
 
84
101
  const EXTERNAL_SLASH_COMMANDS = new Set([
85
102
  'auth',
@@ -181,7 +198,7 @@ export function buildVerificationLedger(root: string): VerificationLedger {
181
198
  const slashCommands = slashCommandNames.length;
182
199
  const panels = countBuiltinPanels(root);
183
200
  const cliCommands = cliCommandNames.length;
184
- const featureFlags = FEATURE_FLAG_MAP.size;
201
+ const featureFlags = FEATURE_SETTINGS.length;
185
202
  const settings = CONFIG_SCHEMA.length;
186
203
  const externalSlashCommands = slashCommandNames.filter((command) => EXTERNAL_SLASH_COMMANDS.has(command)).length;
187
204
  const externalCliCommands = cliCommandNames.filter((command) => EXTERNAL_CLI_COMMANDS.has(command)).length;
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '1.16.1';
9
+ let _version = '1.18.0';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;