@pellux/goodvibes-tui 1.16.0 → 1.17.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 (220) hide show
  1. package/CHANGELOG.md +95 -9
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +1091 -271
  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/parser.ts +5 -0
  12. package/src/cli/status.ts +13 -0
  13. package/src/cli/surface-command.ts +5 -5
  14. package/src/cli/tui-startup.ts +60 -97
  15. package/src/cli/types.ts +2 -0
  16. package/src/config/index.ts +1 -1
  17. package/src/config/tui-extension-settings.ts +33 -0
  18. package/src/core/composer-state.ts +1 -1
  19. package/src/core/conversation-line-cache.ts +3 -3
  20. package/src/core/conversation-rendering.ts +4 -4
  21. package/src/core/conversation.ts +2 -2
  22. package/src/core/long-task-notifier.ts +7 -7
  23. package/src/core/stream-event-wiring.ts +8 -6
  24. package/src/core/system-message-noise.ts +1 -1
  25. package/src/core/system-message-router.ts +6 -6
  26. package/src/core/turn-cancellation.ts +1 -1
  27. package/src/core/turn-event-wiring.ts +8 -8
  28. package/src/daemon/cli.ts +20 -5
  29. package/src/daemon/lifecycle.ts +41 -0
  30. package/src/daemon/service-commands.ts +5 -5
  31. package/src/export/cost-utils.ts +128 -6
  32. package/src/input/autocomplete.ts +2 -2
  33. package/src/input/command-registry.ts +34 -9
  34. package/src/input/commands/ci-runtime.ts +5 -1
  35. package/src/input/commands/codebase-runtime.ts +6 -6
  36. package/src/input/commands/cost-runtime.ts +1 -1
  37. package/src/input/commands/health-runtime.ts +2 -2
  38. package/src/input/commands/intelligence-runtime.ts +1 -1
  39. package/src/input/commands/local-auth-runtime.ts +1 -1
  40. package/src/input/commands/local-provider-runtime.ts +49 -10
  41. package/src/input/commands/local-runtime.ts +53 -5
  42. package/src/input/commands/memory.ts +2 -2
  43. package/src/input/commands/operator-panel-runtime.ts +5 -5
  44. package/src/input/commands/permissions-provenance.ts +1 -1
  45. package/src/input/commands/permissions-runtime.ts +99 -8
  46. package/src/input/commands/planning-runtime.ts +3 -3
  47. package/src/input/commands/platform-sandbox-runtime.ts +1 -1
  48. package/src/input/commands/provider-accounts-runtime.ts +71 -6
  49. package/src/input/commands/provider.ts +7 -5
  50. package/src/input/commands/recall-files-git.ts +13 -51
  51. package/src/input/commands/recall-files.ts +8 -11
  52. package/src/input/commands/recall-review.ts +2 -2
  53. package/src/input/commands/relay-runtime.ts +1 -1
  54. package/src/input/commands/remote-runtime.ts +1 -1
  55. package/src/input/commands/runtime-services.ts +4 -4
  56. package/src/input/commands/schedule-runtime.ts +14 -7
  57. package/src/input/commands/services-runtime.ts +1 -1
  58. package/src/input/commands/session-workflow.ts +2 -2
  59. package/src/input/commands/settings-sync-runtime.ts +2 -2
  60. package/src/input/commands/share-runtime.ts +1 -1
  61. package/src/input/commands/shell-core.ts +46 -68
  62. package/src/input/commands/subscription-runtime.ts +1 -1
  63. package/src/input/commands/update-runtime.ts +153 -96
  64. package/src/input/commands/work-plan-runtime.ts +1 -1
  65. package/src/input/commands/workstream-runtime.ts +2 -2
  66. package/src/input/commands.ts +0 -2
  67. package/src/input/config-modal-types.ts +3 -3
  68. package/src/input/config-modal.ts +10 -11
  69. package/src/input/feature-unit-layout.ts +93 -82
  70. package/src/input/feed-context-factory.ts +2 -2
  71. package/src/input/handler-command-route.ts +2 -2
  72. package/src/input/handler-content-actions.ts +1 -1
  73. package/src/input/handler-feed-routes.ts +7 -7
  74. package/src/input/handler-feed.ts +4 -4
  75. package/src/input/handler-interactions.ts +3 -2
  76. package/src/input/handler-modal-routes.ts +12 -4
  77. package/src/input/handler-modal-token-routes.ts +1 -1
  78. package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
  79. package/src/input/handler-onboarding.ts +3 -2
  80. package/src/input/handler-picker-routes.ts +26 -13
  81. package/src/input/handler-shortcuts.ts +6 -6
  82. package/src/input/handler.ts +2 -2
  83. package/src/input/keybindings.ts +2 -2
  84. package/src/input/onboarding/onboarding-feature-units.ts +34 -27
  85. package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
  86. package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
  87. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
  88. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
  89. package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
  90. package/src/input/panel-integration-actions.ts +2 -2
  91. package/src/input/panel-mouse-geometry.ts +1 -1
  92. package/src/input/panel-paste-flood-guard.ts +1 -1
  93. package/src/input/provider-key-intake.ts +167 -0
  94. package/src/input/sandbox-exec-config.ts +2 -2
  95. package/src/input/selection-modal.ts +1 -1
  96. package/src/input/settings-modal-activation.ts +5 -0
  97. package/src/input/settings-modal-adjustment.ts +4 -31
  98. package/src/input/settings-modal-data.ts +22 -21
  99. package/src/input/settings-modal-mutations.ts +12 -68
  100. package/src/input/settings-modal-types.ts +23 -16
  101. package/src/input/settings-modal.ts +74 -55
  102. package/src/main.ts +48 -48
  103. package/src/panels/agent-inspector-shared.ts +4 -4
  104. package/src/panels/base-panel.ts +2 -2
  105. package/src/panels/builtin/agent.ts +4 -4
  106. package/src/panels/builtin/development.ts +4 -4
  107. package/src/panels/builtin/knowledge.ts +1 -1
  108. package/src/panels/builtin/operations.ts +29 -10
  109. package/src/panels/builtin/session.ts +5 -5
  110. package/src/panels/builtin/shared.ts +4 -4
  111. package/src/panels/builtin-modals.ts +10 -10
  112. package/src/panels/builtin-panels.ts +1 -1
  113. package/src/panels/cost-tracker-panel.ts +124 -7
  114. package/src/panels/diff-panel.ts +5 -5
  115. package/src/panels/eval-registry.ts +2 -2
  116. package/src/panels/fleet-deep-link.ts +1 -1
  117. package/src/panels/fleet-panel-format.ts +44 -8
  118. package/src/panels/fleet-panel.ts +16 -16
  119. package/src/panels/fleet-read-model.ts +52 -13
  120. package/src/panels/fleet-steer.ts +2 -2
  121. package/src/panels/fleet-stop.ts +3 -3
  122. package/src/panels/fleet-tabs.ts +13 -14
  123. package/src/panels/fleet-transcript.ts +5 -5
  124. package/src/panels/git-panel.ts +2 -2
  125. package/src/panels/index.ts +4 -1
  126. package/src/panels/local-auth-panel.ts +1 -1
  127. package/src/panels/modals/hooks-modal.ts +1 -1
  128. package/src/panels/modals/keybindings-modal.ts +2 -2
  129. package/src/panels/modals/knowledge-modal.ts +1 -1
  130. package/src/panels/modals/marketplace-modal.ts +4 -4
  131. package/src/panels/modals/memory-modal.ts +4 -4
  132. package/src/panels/modals/modal-surface-helpers.ts +1 -1
  133. package/src/panels/modals/modal-theme.ts +1 -1
  134. package/src/panels/modals/pairing-modal.ts +2 -2
  135. package/src/panels/modals/planning-modal.ts +2 -2
  136. package/src/panels/modals/plugins-modal.ts +1 -1
  137. package/src/panels/modals/policy-modal.ts +1 -1
  138. package/src/panels/modals/security-modal.ts +1 -1
  139. package/src/panels/modals/skills-modal.ts +1 -1
  140. package/src/panels/modals/work-plan-modal.ts +3 -3
  141. package/src/panels/notifications-feed.ts +127 -0
  142. package/src/panels/notifications-panel.ts +160 -0
  143. package/src/panels/panel-manager.ts +9 -9
  144. package/src/panels/plugins-panel.ts +4 -4
  145. package/src/panels/project-planning-answer-actions.ts +1 -1
  146. package/src/panels/provider-health-routes.ts +2 -2
  147. package/src/panels/skills-panel.ts +4 -5
  148. package/src/panels/token-budget-panel.ts +11 -3
  149. package/src/panels/types.ts +5 -5
  150. package/src/permissions/broker-approval-card.ts +201 -0
  151. package/src/permissions/hunk-selection.ts +28 -4
  152. package/src/permissions/prompt.ts +244 -29
  153. package/src/renderer/autocomplete-overlay.ts +107 -54
  154. package/src/renderer/code-block.ts +2 -2
  155. package/src/renderer/compaction-history-modal.ts +1 -1
  156. package/src/renderer/compaction-preview.ts +1 -1
  157. package/src/renderer/compaction-quality.ts +1 -1
  158. package/src/renderer/compositor.ts +2 -2
  159. package/src/renderer/config-modal.ts +1 -1
  160. package/src/renderer/conversation-overlays.ts +1 -1
  161. package/src/renderer/footer-tips.ts +3 -3
  162. package/src/renderer/hint-grammar.ts +1 -1
  163. package/src/renderer/modal-factory.ts +1 -1
  164. package/src/renderer/model-picker-overlay.ts +1 -1
  165. package/src/renderer/model-workspace.ts +7 -2
  166. package/src/renderer/onboarding/onboarding-wizard.ts +22 -8
  167. package/src/renderer/panel-composite.ts +2 -2
  168. package/src/renderer/panel-workspace-bar.ts +1 -1
  169. package/src/renderer/process-indicator.ts +1 -1
  170. package/src/renderer/selection-modal-overlay.ts +102 -24
  171. package/src/renderer/session-picker-modal.ts +2 -2
  172. package/src/renderer/settings-modal-helpers.ts +14 -5
  173. package/src/renderer/settings-modal.ts +127 -39
  174. package/src/renderer/shell-surface.ts +3 -0
  175. package/src/renderer/terminal-bg-probe.ts +1 -1
  176. package/src/renderer/theme.ts +3 -3
  177. package/src/renderer/tool-call.ts +1 -1
  178. package/src/renderer/tool-result-summary.ts +1 -1
  179. package/src/renderer/turn-injection.ts +7 -6
  180. package/src/renderer/ui-factory.ts +6 -1
  181. package/src/runtime/bootstrap-command-context.ts +4 -1
  182. package/src/runtime/bootstrap-command-parts.ts +6 -4
  183. package/src/runtime/bootstrap-core.ts +35 -35
  184. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  185. package/src/runtime/bootstrap-shell.ts +6 -5
  186. package/src/runtime/bootstrap.ts +34 -32
  187. package/src/runtime/channel-composition.ts +46 -0
  188. package/src/runtime/code-index-services.ts +3 -4
  189. package/src/runtime/daemon-attach-notices.ts +120 -0
  190. package/src/runtime/durability-services.ts +55 -0
  191. package/src/runtime/embedded-service-factories.ts +40 -0
  192. package/src/runtime/feature-settings.ts +147 -0
  193. package/src/runtime/index.ts +4 -21
  194. package/src/runtime/legacy-daemon-migration.ts +29 -19
  195. package/src/runtime/memory-fold.ts +1 -1
  196. package/src/runtime/notification-dispatch.ts +110 -0
  197. package/src/runtime/onboarding/apply.ts +3 -29
  198. package/src/runtime/onboarding/fast-path.ts +39 -6
  199. package/src/runtime/onboarding/provider-key-capture.ts +126 -0
  200. package/src/runtime/onboarding/snapshot.ts +0 -1
  201. package/src/runtime/onboarding/types.ts +9 -3
  202. package/src/runtime/provider-fallback.ts +55 -0
  203. package/src/runtime/provider-optimizer-wiring.ts +55 -0
  204. package/src/runtime/release-artifacts.ts +19 -103
  205. package/src/runtime/resume-notice.ts +1 -1
  206. package/src/runtime/services.ts +81 -82
  207. package/src/runtime/session-continuity-hints.ts +38 -0
  208. package/src/runtime/surface-feature-flags.ts +40 -9
  209. package/src/runtime/terminal-output-guard.ts +2 -2
  210. package/src/runtime/trust/workspace-registration.ts +10 -2
  211. package/src/runtime/trust/workspace-trust.ts +58 -31
  212. package/src/runtime/ui-services.ts +3 -1
  213. package/src/runtime/update-check.ts +15 -69
  214. package/src/runtime/workstream-services.ts +2 -2
  215. package/src/shell/blocking-input.ts +102 -6
  216. package/src/shell/ui-openers.ts +38 -6
  217. package/src/utils/format-duration.ts +1 -1
  218. package/src/verification/verification-ledger.ts +21 -4
  219. package/src/version.ts +1 -1
  220. package/src/input/commands/flags-runtime.ts +0 -328
@@ -1,11 +1,12 @@
1
1
  import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
2
2
  import type { ProviderAuthFreshness, ProviderAuthRoute } from '@/runtime/index.ts';
3
- import type { FeatureFlag, FlagState } from '@/runtime/index.ts';
3
+ import type { FeatureFlag, FeatureSetting, FlagState } from '@/runtime/index.ts';
4
4
 
5
5
  export type SettingsCategory =
6
6
  | 'display'
7
7
  | 'ui'
8
8
  | 'provider'
9
+ | 'pricing'
9
10
  | 'subscriptions'
10
11
  | 'behavior'
11
12
  | 'storage'
@@ -43,8 +44,8 @@ export type SettingsCategory =
43
44
  | 'integrations'
44
45
  | 'policy'
45
46
  | 'notifications'
46
- | 'flags'
47
47
  | 'release'
48
+ | 'update'
48
49
  | 'danger';
49
50
 
50
51
  export type SettingsFocusPane = 'categories' | 'settings';
@@ -54,12 +55,12 @@ export const SETTINGS_CATEGORY_GROUPS: ReadonlyArray<{
54
55
  readonly categories: readonly SettingsCategory[];
55
56
  }> = [
56
57
  { label: 'Interface', categories: ['display', 'ui', 'behavior', 'notifications', 'permissions', 'policy', 'security'] },
57
- { label: 'AI Routing', categories: ['provider', 'subscriptions', 'helper', 'tools', 'tts'] },
58
+ { label: 'AI Routing', categories: ['provider', 'pricing', 'subscriptions', 'helper', 'tools', 'tts'] },
58
59
  { label: 'Service & Network', categories: ['service', 'daemon', 'network', 'controlPlane', 'httpListener', 'web', 'relay'] },
59
60
  { label: 'Surfaces & Cloud', categories: ['surfaces', 'integrations', 'mcp', 'cloudflare'] },
60
61
  { label: 'Automation', categories: ['batch', 'automation', 'checkin', 'watchers', 'orchestration', 'planner', 'wrfc'] },
61
62
  { label: 'Runtime & Data', categories: ['storage', 'atRest', 'sandbox', 'fetch', 'agents', 'runtime', 'cache', 'telemetry', 'diagnostics', 'learning'] },
62
- { label: 'Advanced', categories: ['flags', 'release', 'danger'] },
63
+ { label: 'Advanced', categories: ['release', 'update', 'danger'] },
63
64
  ];
64
65
 
65
66
  export const SETTINGS_CATEGORIES: SettingsCategory[] = SETTINGS_CATEGORY_GROUPS.flatMap(group => group.categories);
@@ -74,34 +75,40 @@ export interface SettingEntry {
74
75
  sourceLabel?: string;
75
76
  lockReason?: string;
76
77
  /**
77
- * Present when this row is a feature-unit toggle header (a feature flag
78
- * rendered as one unit with its config keys beneath it). The row's
79
- * setting.key is `featureFlags.<id>` and setting.type is 'boolean'; toggling
80
- * routes to the feature-flag manager, not a plain config write.
78
+ * Present when this row is a feature-unit header (a platform capability
79
+ * rendered as one unit with the settings that tune it beneath). The row IS
80
+ * the real config row for the feature's enablement key — boolean headers
81
+ * toggle and enum headers cycle their mode choices through the ordinary
82
+ * settings write path (a plain config write; the settings bridge keeps the
83
+ * runtime gate in sync).
81
84
  */
82
85
  flag?: FlagEntry;
83
86
  /**
84
- * Present when this row is a config sub-option OWNED by a feature unit — the
85
- * id of the owning flag. Drives the indented rendering beneath its header and
86
- * marks the row as claimed so it does not double-list as an orphan.
87
+ * Present when this row is a settings sub-option OWNED by a feature unit —
88
+ * the id of the owning feature. Drives the indented rendering beneath its
89
+ * header and marks the row as claimed so it does not double-list as an
90
+ * orphan.
87
91
  */
88
92
  ownerFlagId?: string;
89
93
  }
90
94
 
91
95
  export interface FlagEntry {
96
+ /** The per-feature settings metadata (domain, enablement shape, settings keys, real description). */
97
+ feature: FeatureSetting;
98
+ /** The internal gate declaration (kill state/reason live here). */
92
99
  flag: FeatureFlag;
93
100
  /** Live effective state as the runtime currently sees it. */
94
101
  state: FlagState;
95
102
  /**
96
103
  * Last known config-layer (persisted) value. Equals `state` unless a
97
- * startup-gated flag was toggled this session — then it holds the value that
98
- * will take effect on the next launch.
104
+ * startup-gated feature was changed this session — then it holds the value
105
+ * that will take effect on the next launch.
99
106
  */
100
107
  persistedState: FlagState;
101
108
  /**
102
- * True when a startup-gated flag's persisted value differs from its effective
103
- * state, i.e. a restart is required before the change takes effect. Sourced
104
- * from FeatureFlagManager.getAll(), never guessed.
109
+ * True when a startup-gated feature's persisted value differs from its
110
+ * effective state, i.e. a restart is required before the change takes
111
+ * effect. Sourced from FeatureFlagManager.getAll(), never guessed.
105
112
  */
106
113
  pendingRestart: boolean;
107
114
  }
@@ -5,9 +5,12 @@
5
5
  * - Active category (Tab to cycle)
6
6
  * - Selected setting index within category (↑↓)
7
7
  * - Editing mode for inline string/number input
8
- * - Feature flags tab with runtime toggle support
8
+ * - Feature-unit headers (each capability's real enablement row in its domain)
9
9
  *
10
- * Saves changes via configManager.set(key, value) or featureFlagManager methods.
10
+ * Every save is a plain configManager.set(key, value) on a domain settings
11
+ * key; the SDK settings bridge keeps the runtime gate manager in sync, and
12
+ * this modal re-reads the manager after enablement writes so pending-restart
13
+ * markers are shown honestly at the point of change.
11
14
  *
12
15
  * Data assembly delegates to settings-modal-data.ts (buildSettingGroups, etc.).
13
16
  * Mutation logic delegates to settings-modal-mutations.ts (applySettingValue, etc.).
@@ -41,7 +44,6 @@ import {
41
44
  } from './settings-modal-types.ts';
42
45
  import {
43
46
  buildSettingGroups,
44
- buildFlagEntries,
45
47
  buildMcpEntries,
46
48
  buildSubscriptionEntries,
47
49
  buildNetworkFilteredItems,
@@ -51,9 +53,10 @@ import {
51
53
  import { getSettingLabel } from '../renderer/settings-modal-helpers.ts';
52
54
  import {
53
55
  applySettingValue,
54
- applyFlagState,
56
+ syncFlagEntryFromManager,
55
57
  type SettingAppliedCallback,
56
58
  } from './settings-modal-mutations.ts';
59
+ import { featureEnablementWrite, isFeatureValueEnabled } from '../runtime/feature-settings.ts';
57
60
  import {
58
61
  activateSelected as _activateSelected,
59
62
  handleSubscriptionLogoutKey as _handleSubscriptionLogoutKey,
@@ -117,6 +120,13 @@ export class SettingsModal {
117
120
 
118
121
  /** Current value of the inline edit buffer. */
119
122
  public editBuffer = '';
123
+ /**
124
+ * Scroll offset (in wrapped lines) of the documentation pane for the
125
+ * selected row. The renderer clamps it to the pane's real content height
126
+ * and shows honest more-above/below markers — long feature documentation
127
+ * scrolls, it never clips. Reset whenever the selection changes.
128
+ */
129
+ public contextScroll = 0;
120
130
  /** Server awaiting explicit allow-all confirmation, if any. */
121
131
  public mcpAllowAllConfirmationTarget: string | null = null;
122
132
  /**
@@ -140,8 +150,6 @@ export class SettingsModal {
140
150
  /** Settings grouped by category. */
141
151
  public groups: Map<SettingsCategory, SettingEntry[]> = new Map();
142
152
 
143
- /** Feature flag entries (populated when flags tab is active). */
144
- public flagEntries: FlagEntry[] = [];
145
153
  /** MCP server trust entries (populated when mcp tab is active). */
146
154
  public mcpEntries: McpEntry[] = [];
147
155
  /** Provider subscription entries (populated when subscriptions tab is active). */
@@ -207,7 +215,6 @@ export class SettingsModal {
207
215
  this.mcpRegistry = mcpRegistry ?? null;
208
216
  this.onSettingApplied = options?.onSettingApplied ?? null;
209
217
  this.groups = buildSettingGroups(configManager, featureFlagManager);
210
- this.flagEntries = buildFlagEntries(featureFlagManager);
211
218
  this.mcpEntries = buildMcpEntries(this.mcpRegistry);
212
219
  this.subscriptionEntries = buildSubscriptionEntries(subscriptionManager, serviceRegistry);
213
220
  this.categoryIndex = 0;
@@ -225,9 +232,15 @@ export class SettingsModal {
225
232
  this.searchQuery = '';
226
233
  this.searchResults = [];
227
234
  this.searchFocused = false;
235
+ this.contextScroll = 0;
228
236
  this.active = true;
229
237
  }
230
238
 
239
+ /** Scroll the documentation pane by `delta` wrapped lines (renderer clamps the top end). */
240
+ scrollContext(delta: number): void {
241
+ this.contextScroll = Math.max(0, this.contextScroll + delta);
242
+ }
243
+
231
244
  close(): void {
232
245
  this.active = false;
233
246
  this.editingMode = false;
@@ -242,6 +255,7 @@ export class SettingsModal {
242
255
  this.searchQuery = '';
243
256
  this.searchResults = [];
244
257
  this.searchFocused = false;
258
+ this.contextScroll = 0;
245
259
  this.serviceRegistry = null;
246
260
  this.secretsManager = null;
247
261
  this.onSettingApplied = null;
@@ -266,6 +280,7 @@ export class SettingsModal {
266
280
  setSearchQuery(query: string): void {
267
281
  this.searchQuery = query;
268
282
  this.searchFocused = true;
283
+ this.contextScroll = 0;
269
284
  if (query.trim().length === 0) {
270
285
  this.searchResults = [];
271
286
  } else {
@@ -286,6 +301,7 @@ export class SettingsModal {
286
301
  if (this.editingMode) return;
287
302
  this.categoryIndex = (this.categoryIndex + 1) % SETTINGS_CATEGORIES.length;
288
303
  this.selectedIndex = 0;
304
+ this.contextScroll = 0;
289
305
  this.subscriptionLogoutConfirmationTarget = null;
290
306
  this._reloadTabEntries();
291
307
  }
@@ -295,6 +311,7 @@ export class SettingsModal {
295
311
  if (this.editingMode) return;
296
312
  this.categoryIndex = (this.categoryIndex - 1 + SETTINGS_CATEGORIES.length) % SETTINGS_CATEGORIES.length;
297
313
  this.selectedIndex = 0;
314
+ this.contextScroll = 0;
298
315
  this.subscriptionLogoutConfirmationTarget = null;
299
316
  this._reloadTabEntries();
300
317
  }
@@ -326,6 +343,7 @@ export class SettingsModal {
326
343
 
327
344
  moveUp(): void {
328
345
  if (this.editingMode) return;
346
+ this.contextScroll = 0;
329
347
  if (this.searchFocused) {
330
348
  if (this.searchResults.length > 0) {
331
349
  this.selectedIndex = (this.selectedIndex - 1 + this.searchResults.length) % this.searchResults.length;
@@ -334,9 +352,7 @@ export class SettingsModal {
334
352
  }
335
353
  const items = this._currentItems();
336
354
  if (items.length === 0) {
337
- if (this.currentCategory === 'flags' && this.flagEntries.length > 0) {
338
- this.selectedIndex = (this.selectedIndex - 1 + this.flagEntries.length) % this.flagEntries.length;
339
- } else if (this.currentCategory === 'mcp' && this.mcpEntries.length > 0) {
355
+ if (this.currentCategory === 'mcp' && this.mcpEntries.length > 0) {
340
356
  this.selectedIndex = (this.selectedIndex - 1 + this.mcpEntries.length) % this.mcpEntries.length;
341
357
  } else if (this.currentCategory === 'subscriptions' && this.subscriptionEntries.length > 0) {
342
358
  this.selectedIndex = (this.selectedIndex - 1 + this.subscriptionEntries.length) % this.subscriptionEntries.length;
@@ -350,6 +366,7 @@ export class SettingsModal {
350
366
 
351
367
  moveDown(): void {
352
368
  if (this.editingMode) return;
369
+ this.contextScroll = 0;
353
370
  if (this.searchFocused) {
354
371
  if (this.searchResults.length > 0) {
355
372
  this.selectedIndex = (this.selectedIndex + 1) % this.searchResults.length;
@@ -358,9 +375,7 @@ export class SettingsModal {
358
375
  }
359
376
  const items = this._currentItems();
360
377
  if (items.length === 0) {
361
- if (this.currentCategory === 'flags' && this.flagEntries.length > 0) {
362
- this.selectedIndex = (this.selectedIndex + 1) % this.flagEntries.length;
363
- } else if (this.currentCategory === 'mcp' && this.mcpEntries.length > 0) {
378
+ if (this.currentCategory === 'mcp' && this.mcpEntries.length > 0) {
364
379
  this.selectedIndex = (this.selectedIndex + 1) % this.mcpEntries.length;
365
380
  } else if (this.currentCategory === 'subscriptions' && this.subscriptionEntries.length > 0) {
366
381
  this.selectedIndex = (this.selectedIndex + 1) % this.subscriptionEntries.length;
@@ -414,6 +429,7 @@ export class SettingsModal {
414
429
  selectTarget(target?: string): void {
415
430
  const normalized = target?.trim();
416
431
  if (!normalized) return;
432
+ this.contextScroll = 0;
417
433
 
418
434
  const categoryIndex = SETTINGS_CATEGORIES.indexOf(normalized as SettingsCategory);
419
435
  if (categoryIndex >= 0) {
@@ -493,38 +509,21 @@ export class SettingsModal {
493
509
  }
494
510
 
495
511
  /**
496
- * Toggle the currently selected feature flag.
497
- *
498
- * Killed flags cannot be toggled. Non-runtimeToggleable flags toggle in config
499
- * only (require restart). runtimeToggleable flags toggle immediately.
512
+ * Toggle the currently selected feature unit through its real domain
513
+ * settings key: boolean/constant enablements flip the key, enum enablements
514
+ * jump between the feature's stock active mode and its off mode. Killed
515
+ * features cannot be toggled; constant capabilities with no off position
516
+ * (non-boolean keys) are left to their domain settings.
500
517
  */
501
518
  toggleSelectedFlag(): void {
502
519
  const entry = this.getSelected();
503
520
  if (!entry?.flag) return;
504
521
  if (entry.flag.state === 'killed') return;
505
- this._toggleFlagValue(entry.setting.key as ConfigKey, entry.flag.state !== 'enabled');
506
- }
507
-
508
- /**
509
- * Toggle the feature flag behind a `featureFlags.<id>` header row. Finds the
510
- * header entry, applies the desired state through the flag manager (runtime +
511
- * persisted override), and refreshes the header row's value/default marker in
512
- * place. Killed flags and no-op toggles are ignored.
513
- */
514
- private _toggleFlagValue(key: ConfigKey, value: unknown): void {
515
- if (!this.featureFlagManager || !this.configManager) return;
516
- let target: SettingEntry | null = null;
517
- for (const entries of this.groups.values()) {
518
- const candidate = entries.find((e) => e.setting.key === key && e.flag);
519
- if (candidate) { target = candidate; break; }
520
- }
521
- if (!target?.flag) return;
522
- if (target.flag.state === 'killed') return;
523
- const desired = value ? 'enabled' : 'disabled';
524
- if (target.flag.state === desired) return;
525
- applyFlagState(target.flag, desired, this.featureFlagManager, this.configManager);
526
- target.currentValue = target.flag.state === 'enabled';
527
- target.isDefault = target.flag.state === target.flag.flag.defaultState;
522
+ const feature = entry.flag.feature;
523
+ const currentlyOn = isFeatureValueEnabled(feature, entry.currentValue);
524
+ const write = featureEnablementWrite(feature.id, !currentlyOn);
525
+ if (!write) return;
526
+ this._setValue(write.key, write.value);
528
527
  }
529
528
 
530
529
  /**
@@ -586,6 +585,18 @@ export class SettingsModal {
586
585
  }
587
586
  }
588
587
 
588
+ if (setting.type === 'object') {
589
+ // Object-typed keys (e.g. pricing.modelPrices) edit as JSON; the
590
+ // schema's validate() below still rules on the parsed shape.
591
+ try {
592
+ parsed = JSON.parse(this.editBuffer);
593
+ } catch {
594
+ this.editingMode = false;
595
+ this.editBuffer = '';
596
+ return false;
597
+ }
598
+ }
599
+
589
600
  if (setting.validate && !setting.validate(parsed)) {
590
601
  this.editingMode = false;
591
602
  this.editBuffer = '';
@@ -685,11 +696,9 @@ export class SettingsModal {
685
696
 
686
697
  // ── Private helpers ────────────────────────────────────────────────────────────────
687
698
 
688
- /** Reload flag/mcp/subscription entries when the active tab changes. */
699
+ /** Reload mcp/subscription entries when the active tab changes. */
689
700
  private _reloadTabEntries(): void {
690
- if (this.currentCategory === 'flags') {
691
- this.flagEntries = buildFlagEntries(this.featureFlagManager);
692
- } else if (this.currentCategory === 'mcp') {
701
+ if (this.currentCategory === 'mcp') {
693
702
  this.mcpEntries = buildMcpEntries(this.mcpRegistry);
694
703
  } else if (this.currentCategory === 'subscriptions') {
695
704
  this.subscriptionEntries = buildSubscriptionEntries(this.subscriptionManager, this.serviceRegistry);
@@ -698,8 +707,8 @@ export class SettingsModal {
698
707
 
699
708
  /**
700
709
  * Returns [] for the mcp/subscriptions categories (which render their own
701
- * entry types). The 'flags' category now flows through the normal group path
702
- * — it holds the no-config feature-unit toggles (Advanced Features).
710
+ * entry types). Every other category flows through the normal group path,
711
+ * feature-unit headers included.
703
712
  */
704
713
  private _currentItems(): SettingEntry[] {
705
714
  if (
@@ -716,15 +725,6 @@ export class SettingsModal {
716
725
  private _setValue(key: ConfigKey, value: unknown): void {
717
726
  if (!this.configManager) return;
718
727
 
719
- // Feature-unit toggle headers write to `featureFlags.<id>`; route those to
720
- // the feature-flag manager (runtime toggle + persisted override) instead of
721
- // a plain config write. Reset/adjust/activate all funnel a boolean value
722
- // here (true = enabled), so this is the single flag-toggle chokepoint.
723
- if (typeof key === 'string' && key.startsWith('featureFlags.')) {
724
- this._toggleFlagValue(key, value);
725
- return;
726
- }
727
-
728
728
  const callback: SettingAppliedCallback | null = this.onSettingApplied
729
729
  ? (change) => this.onSettingApplied!(change)
730
730
  : null;
@@ -740,6 +740,13 @@ export class SettingsModal {
740
740
  },
741
741
  });
742
742
 
743
+ // Feature-unit headers write plain domain keys; the SDK settings bridge
744
+ // has already applied the change to the gate manager (live flip for
745
+ // runtime-toggleable features, pending-restart marker for startup-gated
746
+ // ones). Re-read the manager for every header bound to this key so the
747
+ // rows and the context pane show the honest state at the point of change.
748
+ this._syncFeatureHeadersForKey(key);
749
+
743
750
  if (result.restartDomain !== null) {
744
751
  this.lastSaveTriggeredRestart = result.restartDomain;
745
752
  }
@@ -749,4 +756,16 @@ export class SettingsModal {
749
756
  // No-op (result.changed === false, effectMessage === null): leave lastSettingEffectMessage untouched.
750
757
  }
751
758
 
759
+ /** Refresh live/persisted/pending state for every feature header bound to `key`. */
760
+ private _syncFeatureHeadersForKey(key: ConfigKey): void {
761
+ if (!this.featureFlagManager) return;
762
+ for (const entries of this.groups.values()) {
763
+ for (const entry of entries) {
764
+ if (entry.flag && entry.flag.feature.enablement.key === key) {
765
+ syncFlagEntryFromManager(entry.flag, this.featureFlagManager);
766
+ }
767
+ }
768
+ }
769
+ }
770
+
752
771
  }
package/src/main.ts CHANGED
@@ -13,6 +13,7 @@ import { FileUndoManager } from '@pellux/goodvibes-sdk/platform/state';
13
13
  import { PermissionManager } from '@pellux/goodvibes-sdk/platform/permissions';
14
14
  import { AcpManager } from '@pellux/goodvibes-sdk/platform/acp';
15
15
  import { PermissionPromptUI, buildPendingPermissionExtras } from './permissions/prompt.ts';
16
+ import { handleBrokerApprovalChange, buildFixSessionAffordance, buildFixSessionErrorNotice, handleFixSessionAttachKey, refreshFixSessionsFromApprovals } from './permissions/broker-approval-card.ts';
16
17
  import { CommandRegistry } from './input/command-registry.ts';
17
18
  import type { CommandContext } from './input/command-registry.ts';
18
19
  import { renderProcessIndicator } from './renderer/process-indicator.ts';
@@ -32,8 +33,10 @@ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
32
33
  import { registerBuiltinPanels } from './panels/builtin-panels.ts';
33
34
  import { bootstrapRuntime } from './runtime/bootstrap.ts';
34
35
  import type { BootstrapContext } from './runtime/bootstrap.ts';
36
+ import { selfUpdateAtLaunch } from './cli/launch-auto-update.ts';
35
37
  import { buildSharedOrchestratorCoreServices, refreshMemoryRecallSnapshot } from './runtime/orchestrator-core-services.ts';
36
- import type { HITLMode } from '@pellux/goodvibes-sdk/platform/state';
38
+ import { createSessionContinuityHintsBuilder } from './runtime/session-continuity-hints.ts';
39
+ import { resolveWebSurfaceUrl } from '@pellux/goodvibes-sdk/platform/runtime/feature-announcements';
37
40
  import { readLastSessionPointer, writeRecoveryFile } from '@/runtime/index.ts';
38
41
  import { handleBlockingShellInput, type PendingPermissionState } from './shell/blocking-input.ts';
39
42
  import { createPersistRecoverySnapshot, createRecoveryFileOps, createReopenRecoveryPanels, handleErrorAffordanceKey, resolveStartupRecoveryInfo } from './shell/recovery-input-helpers.ts';
@@ -43,7 +46,7 @@ import { buildPersistedSessionContext, formatReturnContextForDisplay, getReturnC
43
46
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
44
47
  import { prepareShellCliRuntime } from './cli/entrypoint.ts';
45
48
  import { applyInitialTuiCliState } from './cli/tui-startup.ts';
46
- import { applyRuntimeConfigDefault, applyRuntimeConfigValue } from './cli/config-overrides.ts';
49
+ import { applyConfiguredHitlMode, applyRuntimeConfigValue, applyTuiRuntimeConfigDefaults } from './cli/config-overrides.ts';
47
50
  import { renderToolCallBlock } from './renderer/tool-call.ts';
48
51
  import { wireSpokenTurnRuntime } from './audio/spoken-turn-wiring.ts';
49
52
  import { attachSpokenTurnModelRouting, createSpokenTurnInputOptions } from './audio/spoken-turn-model-routing.ts';
@@ -79,6 +82,10 @@ async function main() {
79
82
  homeDirectory: homedir(),
80
83
  }, 'goodvibes');
81
84
 
85
+ // Launch-time self-update, before any bootstrap or terminal mode change; on
86
+ // an installed update this restarts onto the swapped binary and never returns.
87
+ const launchUpdateLines = await selfUpdateAtLaunch({ configManager, stdout });
88
+
82
89
  const ctx: BootstrapContext = await bootstrapRuntime(stdout, {
83
90
  configManager,
84
91
  workingDir: bootstrapWorkingDir,
@@ -102,7 +109,7 @@ async function main() {
102
109
  agentStatusIntervalRef,
103
110
  orchestratorRefs,
104
111
  setRenderRequest,
105
- permissionPromptRef,
112
+ permissionPromptRef, trustPromptRef,
106
113
  _writeLastSessionPointer: writeLastSessionPointer,
107
114
  systemMessageRouter,
108
115
  } = ctx;
@@ -111,7 +118,7 @@ async function main() {
111
118
  const { approvalBroker, agentManager, modeManager, processManager, providerRegistry, secretsManager, subscriptionManager } = ctx.services;
112
119
  conversation.setSessionMemoryStore(ctx.services.sessionMemoryStore);
113
120
  conversation.setSessionLineageTracker(ctx.services.sessionLineageTracker);
114
- // Shared payload (single source of truth, includes wo805's memoryRegistry —
121
+ // Shared payload (single source of truth, includes the memoryRegistry —
115
122
  // see orchestrator-core-services.ts) plus this site's favoritesStore.
116
123
  orchestrator.setCoreServices({
117
124
  ...buildSharedOrchestratorCoreServices({ services: ctx.services, configManager, providerRegistry }),
@@ -120,49 +127,37 @@ async function main() {
120
127
  ctx.services.wrfcController.setPlanManager(ctx.services.planManager);
121
128
  let activeConversationWidth = stdout.columns || 80;
122
129
  conversation.setWidthProvider(() => activeConversationWidth);
123
- {
124
- const hitlMode = configManager.get('behavior.hitlMode') as HITLMode | undefined;
125
- if (hitlMode && (hitlMode === 'quiet' || hitlMode === 'balanced' || hitlMode === 'operator')) {
126
- modeManager.setHITLMode(hitlMode);
127
- }
128
- }
130
+ // Persisted HITL mode + TUI-side config defaults (doc'd at their definitions).
131
+ applyConfiguredHitlMode(configManager, modeManager);
132
+ applyTuiRuntimeConfigDefaults(configManager);
129
133
 
130
- // TUI default: show token speed ON (SDK schema default is false). applyRuntimeConfigDefault
131
- // reads the global + project settings files and only applies the default in-memory when the
132
- // key is absent from both (e.g. a new install); an explicit user value is respected. No disk write.
133
- applyRuntimeConfigDefault(configManager, 'display.showTokenSpeed', true);
134
+ // Re-surface pre-TUI launch-update lines in-session (the alt screen wipes stdout).
135
+ for (const line of launchUpdateLines) systemMessageRouter.high(`[Update] ${line}`);
134
136
 
135
137
  const panelManager = ctx.services.panelManager;
136
- const buildSessionContinuityHints = () => {
137
- const sessionSnapshot = uiServices.readModels.session.getSnapshot();
138
- const tasksSnapshot = uiServices.readModels.tasks.getSnapshot();
139
- const remoteSnapshot = uiServices.readModels.remote.getSnapshot();
140
- const worktreeSnapshot = uiServices.readModels.worktrees.getSnapshot();
141
- return {
142
- pendingApprovals: sessionSnapshot.pendingApproval ? 1 : 0,
143
- activeTasks: tasksSnapshot.tasks.filter((task) => task.status === 'running' || task.status === 'queued').length,
144
- blockedTasks: tasksSnapshot.tasks.filter((task) => task.status === 'blocked').length,
145
- remoteContracts: remoteSnapshot.contracts.length,
146
- remoteRunners: remoteSnapshot.contracts.slice(0, 4).map((contract) => contract.runnerId),
147
- worktreeCount: worktreeSnapshot.records.length,
148
- worktreePaths: worktreeSnapshot.records.slice(0, 3).map((record) => record.path),
149
- openPanels: panelManager.getAllOpen().map((panel) => panel.id),
150
- };
151
- };
138
+ const buildSessionContinuityHints = createSessionContinuityHintsBuilder({ readModels: uiServices.readModels, panelManager });
152
139
 
153
140
  let pendingPermission: PendingPermissionState | null = null;
154
- approvalBroker.subscribe((approval) => {
155
- if (!pendingPermission) return;
156
- if (pendingPermission.callId !== approval.callId) return;
157
- if (approval.status === 'pending' || approval.status === 'claimed') return;
158
- pendingPermission = null;
159
- render();
141
+ // One-key jump/attach to a spawned CI fix-session: the affordance ARMS the id; the next 'j' runs the resume so the user never retypes it.
142
+ let fixSessionAttachArmed: string | null = null;
143
+ const attachToFixSession = (fixSessionId: string) => { void commandContext.executeCommand?.('session', ['resume', fixSessionId]); };
144
+ const onFixSessionStarted = buildFixSessionAffordance({
145
+ notify: (message) => { systemMessageRouter.high(message); render(); },
146
+ arm: (fixSessionId) => { fixSessionAttachArmed = fixSessionId; },
160
147
  });
148
+ // A failed fix-session spawn stamps an error instead of a dead id: render it honestly, no jump armed.
149
+ const onFixSessionError = buildFixSessionErrorNotice((message) => { systemMessageRouter.high(message); render(); });
150
+ commandContext.armFixSessionAttach = onFixSessionStarted;
151
+ approvalBroker.subscribe((approval) => handleBrokerApprovalChange({
152
+ approval, broker: approvalBroker, render, onFixSessionStarted, onFixSessionError,
153
+ getPending: () => pendingPermission,
154
+ setPending: (next) => { pendingPermission = next; },
155
+ }));
156
+ refreshFixSessionsFromApprovals(() => approvalBroker.listApprovals(), onFixSessionStarted, onFixSessionError); // catch pre-subscription stamps
161
157
 
162
158
  let scrollTop = 0;
163
159
  let scrollLocked = true;
164
- // Cached from the overlay-aware clamp the renderer computes (conversation-layout) each
165
- // frame so scroll() clamps against exactly what is displayed, not a footer estimate.
160
+ // Cached from the overlay-aware clamp the renderer computes (conversation-layout) each frame so scroll() clamps against exactly what is displayed, not a footer estimate.
166
161
  let lastMaxScroll: number | null = null;
167
162
  // Stream and tool-timer state; mutated by wireStreamEventMetrics handlers, read during render.
168
163
  const streamMetrics: StreamMetrics = {
@@ -185,8 +180,7 @@ async function main() {
185
180
  const currentModel = providerRegistry.getCurrentModel();
186
181
  const contextWindow = providerRegistry.getContextWindowForModel(currentModel);
187
182
  const rows = stdout.rows || 24;
188
- // Compact threshold must match buildShellFooter's `compact: height < 30` posture below,
189
- // else estimateShellFooterHeight's cached-height fast path answers with the wrong mode.
183
+ // Compact threshold must match buildShellFooter's `compact: height < 30` posture below, else estimateShellFooterHeight's cached-height fast path answers with the wrong mode.
190
184
  return rows - 2 - estimateShellFooterHeight(promptLines, contextWindow, rows < 30);
191
185
  };
192
186
 
@@ -414,7 +408,7 @@ async function main() {
414
408
  input.setConversationManager(conversation);
415
409
  input.setContentWidth(getPromptContentWidth());
416
410
  input.filePicker.setOnUpdate(() => render());
417
- // W6.1 retirement: agentDetailModal/processModal setOnRefresh wiring removed —
411
+ // retirement: agentDetailModal/processModal setOnRefresh wiring removed —
418
412
  // those modals were deleted (Fleet subsumes the live process tree via F2).
419
413
 
420
414
  // Model picker callback is handled in bootstrap.ts — do not duplicate here.
@@ -501,6 +495,7 @@ async function main() {
501
495
  hitlMode: modeManager.getHITLMode(),
502
496
  // Cross-surface spine posture segment (adopted-daemon mode only).
503
497
  sessionSpineStatus: (() => { const s = uiServices.platform.externalServices?.inspect(); return s?.sessionSpineActive && s.sessionSpineStatus && s.sessionSpineStatus !== 'unknown' ? s.sessionSpineStatus : undefined; })(), runningAgentCount, runningProcessCount,
498
+ webSurfaceUrl: configManager.get('web.enabled') ? resolveWebSurfaceUrl(configManager) : undefined,
504
499
  // Composer must not read as focused while the panel/process indicator owns keyboard focus.
505
500
  promptFocused: !input.panelFocused && !input.indicatorFocused,
506
501
  indicatorFocused: input.indicatorFocused,
@@ -547,7 +542,7 @@ async function main() {
547
542
  // Calculate how many rows are consumed by overlays (thinking, permissions, queue, file picker)
548
543
  let overlayRows = 0;
549
544
  if (orchestrator.isThinking) overlayRows += 2; // spinner + blank
550
- if (pendingPermission) overlayRows += PermissionPromptUI.getPromptHeight(pendingPermission, pendingPermission.hunkState, pendingPermission.detailsExpanded, pendingPermission.requestedBy);
545
+ if (pendingPermission) overlayRows += PermissionPromptUI.getPromptHeight(pendingPermission, pendingPermission.hunkState, pendingPermission.detailsExpanded, pendingPermission.requestedBy, PermissionPromptUI.promptViewState(pendingPermission, conversationWidth, approvalBroker));
551
546
  overlayRows += orchestrator.messageQueue.length * 3; // queued messages
552
547
  // File picker and model picker overlay rows computed from actual rendered line count below
553
548
  // Selection modal overlay rows are computed from actual rendered line count below
@@ -597,7 +592,7 @@ async function main() {
597
592
  }
598
593
 
599
594
  if (pendingPermission) {
600
- viewport.push(...PermissionPromptUI.createPromptLines(conversationWidth, pendingPermission, pendingPermission.hunkState, pendingPermission.detailsExpanded, pendingPermission.requestedBy));
595
+ viewport.push(...PermissionPromptUI.createPromptLines(conversationWidth, pendingPermission, pendingPermission.hunkState, pendingPermission.detailsExpanded, pendingPermission.requestedBy, PermissionPromptUI.promptViewState(pendingPermission, conversationWidth, approvalBroker)));
601
596
  }
602
597
 
603
598
  orchestrator.messageQueue.forEach(msg => {
@@ -643,8 +638,8 @@ async function main() {
643
638
  panelWidth: panelComposite.panelWidth,
644
639
  });
645
640
  };
646
- const renderScheduler = createRenderScheduler(renderNow, undefined, () => lifecycle.isTerminalRestored()); // WO-208 coalescer; no frames after terminal restore
647
- const render = (): void => renderScheduler.schedule(); // captured direct writes → activity log + quiet /debug counter, not repeated transcript lines (UX-B 1a)
641
+ const renderScheduler = createRenderScheduler(renderNow, undefined, () => lifecycle.isTerminalRestored()); // coalescer; no frames after terminal restore
642
+ const render = (): void => renderScheduler.schedule(); // captured direct writes → activity log + quiet /debug counter, not repeated transcript lines (1a)
648
643
  const terminalOutputGuard = installTuiTerminalOutputGuard({ stdout, stderr: process.stderr, onCapture: (total) => { commandContext.session.runtime.terminalWritesIntercepted = total; render(); } });
649
644
 
650
645
  setRenderRequest(() => renderScheduler.flushNow()); // bootstrap's 16ms coalescer composites via the (restore-gated) scheduler
@@ -669,7 +664,7 @@ async function main() {
669
664
  homeDirectory,
670
665
  getConfiguredProviderIds: ctx._getConfiguredProviderIds,
671
666
  getPinned: ctx._getPinned,
672
- render,
667
+ render, trustPromptRef,
673
668
  });
674
669
 
675
670
  const { refreshGit, unsubs: turnUnsubs } = wireTurnEventHandlers({
@@ -731,7 +726,7 @@ async function main() {
731
726
  stdin.resume();
732
727
  stdin.setEncoding('utf8');
733
728
  allowTerminalWrite(() => stdout.write((cli.flags.noAltScreen ? '' : ALT_SCREEN_ENTER) + CLEAR_SCREEN + CURSOR_HIDE + MOUSE_ENABLE + KEYBOARD_EXT_ENABLE + PASTE_ENABLE + FOCUS_ENABLE));
734
- // DEBT-2: forced dark/light applies before first paint; auto (TTY only) fires the
729
+ // forced dark/light applies before first paint; auto (TTY only) fires the
735
730
  // OSC 11 probe and repaints once if light wins. filterInput strips the reply from stdin.
736
731
  const themeProbe = installBackgroundThemeProbe({ configManager, isTTY: Boolean(stdout.isTTY), env: process.env, writeQuery: (b) => allowTerminalWrite(() => stdout.write(b)), requestRepaint: () => { compositor.resetDiff(); render(); } });
737
732
 
@@ -752,12 +747,17 @@ async function main() {
752
747
  if (blocking.handled) {
753
748
  return;
754
749
  }
755
-
756
750
  // One-key retry affordance: armed after a user-visible TURN_ERROR; any key other than r/m dismisses it and routes normally.
757
751
  if (errorAffordanceActive) {
758
752
  errorAffordanceActive = false;
759
753
  if (handleErrorAffordanceKey(data, { retryArmed: retryCtx !== null, retry: retryTurn, openModelPicker: () => commandContext.openModelPicker?.(), render })) return;
760
754
  }
755
+ // One-key jump to a spawned CI fix-session: 'j' attaches, any other key dismisses and routes normally.
756
+ if (fixSessionAttachArmed !== null) {
757
+ const armedFixSessionId = fixSessionAttachArmed;
758
+ fixSessionAttachArmed = null;
759
+ if (handleFixSessionAttachKey(data, { armedFixSessionId, attach: attachToFixSession, render })) return;
760
+ }
761
761
 
762
762
  input.feed(data);
763
763
  });
@@ -8,7 +8,7 @@ export type AgentInspectorEntryKind = 'user' | 'assistant' | 'tool_call' | 'tool
8
8
  // ---------------------------------------------------------------------------
9
9
  // Shared agent status / stall constants
10
10
  // Used by the Fleet panel's agent views and read-model consumers (formerly also
11
- // AgentDetailModal, which was retired in W6.1).
11
+ // AgentDetailModal, which was retired in).
12
12
  // ---------------------------------------------------------------------------
13
13
 
14
14
  /** Terminal statuses — cancel not offered; stall check skipped. */
@@ -127,7 +127,7 @@ export function agentKindStyle(kind: AgentInspectorEntryKind, colors: Record<str
127
127
  }
128
128
 
129
129
  // ---------------------------------------------------------------------------
130
- // Inspector filter modes (ported from the merged agent-logs console — WO-110)
130
+ // Inspector filter modes (ported from the merged agent-logs console —)
131
131
  // ---------------------------------------------------------------------------
132
132
 
133
133
  export type AgentInspectorFilterType = 'all' | 'assistant' | 'tool' | 'error';
@@ -188,7 +188,7 @@ interface AgentUsageLike {
188
188
 
189
189
  /**
190
190
  * True when a usage object carries actual reported token data rather than
191
- * being present-but-empty. W0.9: the SDK's AgentRecord.usage is initialised
191
+ * being present-but-empty.: the SDK's AgentRecord.usage is initialised
192
192
  * to an all-zero object at agent spawn (platform/tools/agent/manager.ts) and
193
193
  * — in the currently pinned SDK version — is never updated past that, so a
194
194
  * plain truthiness check on `rec.usage` treats every agent, including ones
@@ -256,7 +256,7 @@ export function buildWrfcCostSegments(
256
256
  }
257
257
 
258
258
  // ---------------------------------------------------------------------------
259
- // Agent lifecycle event subscriptions (ported from agent-logs — WO-110)
259
+ // Agent lifecycle event subscriptions (ported from agent-logs —)
260
260
  // ---------------------------------------------------------------------------
261
261
 
262
262
  export interface AgentActivityHooks {
@@ -227,10 +227,10 @@ export abstract class BasePanel implements Panel {
227
227
  return consumed;
228
228
  }
229
229
 
230
- /** R2: Mark this panel dirty — it will be re-rendered on the next compositor frame. */
230
+ /** Mark this panel dirty — it will be re-rendered on the next compositor frame. */
231
231
  public invalidate(): void { this.needsRender = true; }
232
232
 
233
- /** R2: Called by the compositor after a successful render to clear the dirty flag. */
233
+ /** Called by the compositor after a successful render to clear the dirty flag. */
234
234
  public markRendered(): void { this.needsRender = false; }
235
235
 
236
236
  protected markDirty(): void {