@pellux/goodvibes-agent 1.9.1 → 1.10.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 (65) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +12 -1
  3. package/dist/package/main.js +36712 -28803
  4. package/dist/package/{web-tree-sitter-jbz042ba.wasm → web-tree-sitter-e011xaqr.wasm} +0 -0
  5. package/docs/README.md +1 -1
  6. package/docs/connected-host.md +1 -1
  7. package/docs/getting-started.md +1 -1
  8. package/docs/tools-and-commands.md +1 -0
  9. package/package.json +3 -3
  10. package/release/live-verification/live-verification.json +11 -11
  11. package/release/live-verification/live-verification.md +12 -12
  12. package/src/agent/email/email-service.ts +1 -1
  13. package/src/agent/email/imap-client.ts +4 -4
  14. package/src/agent/email/smtp-client.ts +5 -5
  15. package/src/cli/config-overrides.ts +29 -14
  16. package/src/cli/entrypoint.ts +12 -4
  17. package/src/cli/launch-auto-update.ts +218 -0
  18. package/src/cli/relay-command.ts +4 -4
  19. package/src/cli/service-posture.ts +2 -2
  20. package/src/cli/tui-startup.ts +31 -0
  21. package/src/cli/workspaces-command.ts +5 -1
  22. package/src/cli-flags.ts +1 -1
  23. package/src/config/index.ts +1 -1
  24. package/src/config/update-settings.ts +45 -0
  25. package/src/config/workspace-registration.ts +214 -15
  26. package/src/input/commands/runtime-services.ts +0 -5
  27. package/src/input/commands/update-runtime.ts +313 -0
  28. package/src/input/commands.ts +2 -0
  29. package/src/input/feed-context-factory.ts +2 -2
  30. package/src/input/handler-feed.ts +8 -8
  31. package/src/input/handler.ts +1 -1
  32. package/src/input/mcp-workspace.ts +5 -1
  33. package/src/input/panel-paste-flood-guard.ts +1 -1
  34. package/src/input/settings-modal-types.ts +11 -5
  35. package/src/input/settings-modal.ts +56 -61
  36. package/src/main.ts +19 -20
  37. package/src/renderer/activity-sidebar.ts +53 -4
  38. package/src/renderer/settings-modal-helpers.ts +2 -0
  39. package/src/renderer/settings-modal.ts +37 -25
  40. package/src/runtime/bootstrap-core.ts +16 -5
  41. package/src/runtime/bootstrap-external-services.ts +107 -11
  42. package/src/runtime/bootstrap-hook-bridge.ts +2 -2
  43. package/src/runtime/bootstrap-shell.ts +4 -4
  44. package/src/runtime/bootstrap.ts +34 -12
  45. package/src/runtime/connected-host-autostart.ts +269 -0
  46. package/src/runtime/daemon-receipts.ts +66 -0
  47. package/src/runtime/diagnostics/panels/index.ts +0 -2
  48. package/src/runtime/feature-enablement.ts +176 -0
  49. package/src/runtime/index.ts +12 -29
  50. package/src/runtime/memory-spine-adoption.ts +18 -0
  51. package/src/runtime/onboarding/apply.ts +50 -37
  52. package/src/runtime/onboarding/types.ts +2 -2
  53. package/src/runtime/onboarding/verify.ts +22 -4
  54. package/src/runtime/release-artifacts.ts +113 -0
  55. package/src/runtime/services.ts +228 -19
  56. package/src/runtime/session-spine-rest-transport.ts +84 -4
  57. package/src/runtime/ui-services.ts +1 -1
  58. package/src/runtime/update-check.ts +64 -0
  59. package/src/shell/ui-openers.ts +8 -0
  60. package/src/tools/agent-harness-metadata.ts +8 -0
  61. package/src/tools/agent-harness-setup-connected-host.ts +1 -1
  62. package/src/tools/agent-harness-setup-posture.ts +1 -1
  63. package/src/version.ts +1 -1
  64. package/src/runtime/diagnostics/panels/ops.ts +0 -156
  65. package/src/runtime/surface-feature-flags.ts +0 -100
@@ -16,7 +16,6 @@ import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
16
16
  import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
17
17
  import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
18
18
  import type { OperatorClient } from '@/runtime/index.ts';
19
- import type { OpsApi } from '@/runtime/index.ts';
20
19
  import type { PeerClient } from '@/runtime/index.ts';
21
20
  import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
22
21
  import type { ProviderApi } from '@pellux/goodvibes-sdk/platform/providers';
@@ -239,10 +238,6 @@ export function requireMcpApi(context: CommandContext): McpApi {
239
238
  return requireContextValue(context.clients?.mcpApi, 'clients.mcpApi');
240
239
  }
241
240
 
242
- export function requireOpsApi(context: CommandContext): OpsApi {
243
- return requireContextValue(context.clients?.opsApi, 'clients.opsApi');
244
- }
245
-
246
241
  export function requireDirectTransport(context: CommandContext): DirectTransport {
247
242
  return requireContextValue(context.clients?.transport, 'clients.transport');
248
243
  }
@@ -0,0 +1,313 @@
1
+ /**
2
+ * `/update` — a real self-update path for binary installs. The
3
+ * download-verify-swap mechanics are the SDK's canonical update policy module
4
+ * (platform/runtime/self-update), the same mechanism the TUI's `/update` and
5
+ * the daemon's hourly loop follow: one update mechanism everywhere. This file
6
+ * owns only the agent's /update UX: install-kind gating, this repo's release
7
+ * asset layout, and the printed report.
8
+ *
9
+ * Subcommands:
10
+ * /update [check] — resolve the latest release tag and report whether this
11
+ * build is already current.
12
+ * /update apply — for a binary install (a directly-downloaded release
13
+ * binary), download + verify + atomically swap the agent
14
+ * binary, and refresh the sqlite-vec native addon in
15
+ * lockstep so the vector index never goes stale beside a
16
+ * new binary. Every swap parks the outgoing file at
17
+ * `<path>.previous`, so the replaced version is always
18
+ * kept. For any other install kind, prints the exact
19
+ * command to run instead — it never attempts a swap it
20
+ * can't do safely.
21
+ * /update rollback — exchange each installed file with its kept `.previous`
22
+ * counterpart: one command back to the version that ran
23
+ * before the last update (and, being an exchange, one
24
+ * more command forward again).
25
+ *
26
+ * The agent ships ONE binary and no daemon binary, and its addon travels as a
27
+ * tar.gz ARCHIVE asset (see release-artifacts.ts) rather than a bare file —
28
+ * so the addon bytes are downloaded and checksum-verified as the archive,
29
+ * extracted in memory, and only then swapped with the same keep-previous
30
+ * mechanics as the binary. Verification order is strict: the addon archive is
31
+ * fully verified and extracted BEFORE the binary swap begins, so a corrupted
32
+ * addon download can never leave a new binary beside a stale (or half-written)
33
+ * addon.
34
+ */
35
+ import { dirname, join } from 'node:path';
36
+ import {
37
+ applyVerifiedUpdate,
38
+ realUpdateFileIo,
39
+ rollbackKeptPrevious,
40
+ sha256,
41
+ swapFileAtomically,
42
+ verifyChecksum,
43
+ type UpdateFileIo,
44
+ } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
45
+ import type { CommandRegistry } from '../command-registry.ts';
46
+ import { VERSION } from '../../version.ts';
47
+ import {
48
+ CHECKSUM_MANIFEST_NAME,
49
+ extractTarGzEntry,
50
+ parseChecksumFile,
51
+ resolveAgentBinaryAssetName,
52
+ resolveSqliteVecArchive,
53
+ } from '../../runtime/release-artifacts.ts';
54
+ import {
55
+ compareVersions,
56
+ detectInstallKind,
57
+ fallbackUpdateCommand,
58
+ normalizeVersion,
59
+ resolveLatestReleaseTag,
60
+ type InstallKind,
61
+ type UpdateFetchLike,
62
+ } from '../../runtime/update-check.ts';
63
+
64
+ const REPO_RELEASES_LATEST_URL = 'https://github.com/mgd34msu/goodvibes-agent/releases/latest';
65
+
66
+ function releaseDownloadBaseUrl(tag: string): string {
67
+ return `https://github.com/mgd34msu/goodvibes-agent/releases/download/${tag}`;
68
+ }
69
+
70
+ /**
71
+ * Suffix under which every swap keeps the file it replaced — re-exported from
72
+ * the SDK's canonical update policy module so rollback and swap share one
73
+ * definition everywhere.
74
+ */
75
+ export { PREVIOUS_FILE_SUFFIX } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
76
+ import { PREVIOUS_FILE_SUFFIX } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
77
+
78
+ async function downloadBytes(fetchImpl: UpdateFetchLike, url: string): Promise<Buffer> {
79
+ const response = await fetchImpl(url);
80
+ if (!response.ok) {
81
+ throw new Error(`download failed (${response.status}) for ${url}`);
82
+ }
83
+ return Buffer.from(await response.arrayBuffer());
84
+ }
85
+
86
+ export interface CheckForUpdateResult {
87
+ readonly latestTag: string;
88
+ readonly isCurrent: boolean;
89
+ }
90
+
91
+ export async function checkForUpdate(fetchImpl: UpdateFetchLike, currentVersion: string): Promise<CheckForUpdateResult> {
92
+ const latestTag = await resolveLatestReleaseTag(fetchImpl, REPO_RELEASES_LATEST_URL);
93
+ const isCurrent = compareVersions(currentVersion, latestTag) >= 0;
94
+ return { latestTag, isCurrent };
95
+ }
96
+
97
+ export interface ApplyUpdateOptions {
98
+ readonly fetchImpl: UpdateFetchLike;
99
+ readonly execPath: string;
100
+ readonly platform: NodeJS.Platform;
101
+ readonly arch: string;
102
+ readonly currentVersion: string;
103
+ readonly print: (line: string) => void;
104
+ /** Injectable filesystem seam (the SDK's UpdateFileIo) so tests observe swaps in memory. */
105
+ readonly io?: UpdateFileIo;
106
+ }
107
+
108
+ /**
109
+ * The real self-update path, delegating the download-verify-swap mechanics to
110
+ * the SDK's canonical update policy module. For a binary install: resolve the
111
+ * latest tag, compare to the running version, and if newer, verify and stage
112
+ * the addon archive first (bytes in memory, no writes), run the binary
113
+ * download-verify-swap, then swap the extracted addon file — each swap keeps
114
+ * the outgoing file at `<path>.previous`. For any other install kind, never
115
+ * attempts a swap — it prints the exact command for that install method
116
+ * instead.
117
+ */
118
+ export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
119
+ const installKind: InstallKind = detectInstallKind(options.execPath);
120
+ if (installKind !== 'binary') {
121
+ options.print(
122
+ [
123
+ `This install is not a self-updatable binary install (detected: ${installKind === 'bun-global-package' ? 'bun/npm package install' : 'running from source'}).`,
124
+ `Update with: ${fallbackUpdateCommand(installKind)}`,
125
+ ].join('\n'),
126
+ );
127
+ return;
128
+ }
129
+
130
+ const latestTag = await resolveLatestReleaseTag(options.fetchImpl, REPO_RELEASES_LATEST_URL);
131
+ if (compareVersions(options.currentVersion, latestTag) >= 0) {
132
+ options.print(`Already current: running v${normalizeVersion(options.currentVersion)}, latest release is ${latestTag}.`);
133
+ return;
134
+ }
135
+
136
+ const binaryAsset = resolveAgentBinaryAssetName(options.platform, options.arch);
137
+ if (!binaryAsset) {
138
+ options.print(`No prebuilt binaries are published for ${options.platform}-${options.arch}; cannot self-update.`);
139
+ return;
140
+ }
141
+
142
+ options.print(`Update available: ${latestTag} (running v${normalizeVersion(options.currentVersion)}). Downloading and verifying...`);
143
+
144
+ const baseUrl = releaseDownloadBaseUrl(latestTag);
145
+ const io = options.io ?? realUpdateFileIo;
146
+ const appBinaryPath = options.execPath;
147
+
148
+ // The sqlite-vec native addon travels with the binary as a tar.gz archive
149
+ // asset: refresh it in the same update so /update never leaves a new binary
150
+ // beside a stale addon. The manifest entry decides whether the target
151
+ // release ships it — an entry that IS present makes the download, checksum,
152
+ // and extraction mandatory (any failure is fatal, verified before the
153
+ // binary swap begins), while an absent entry means the target predates the
154
+ // addon archives and is skipped rather than blocking an otherwise-valid
155
+ // binary update.
156
+ const manifestBytes = await downloadBytes(options.fetchImpl, `${baseUrl}/${CHECKSUM_MANIFEST_NAME}`);
157
+ const checksums = parseChecksumFile(manifestBytes.toString('utf-8'));
158
+ const addon = resolveSqliteVecArchive(options.platform, options.arch);
159
+ const addonIncluded = addon !== null && checksums.get(addon.assetName) !== undefined;
160
+ let addonFileBytes: Buffer | null = null;
161
+ let addonTargetPath: string | null = null;
162
+ if (addon && addonIncluded) {
163
+ const archiveBytes = await downloadBytes(options.fetchImpl, `${baseUrl}/${addon.assetName}`);
164
+ verifyChecksum(addon.assetName, sha256(archiveBytes), checksums.get(addon.assetName));
165
+ addonFileBytes = extractTarGzEntry(archiveBytes, addon.entryPath);
166
+ if (addonFileBytes === null) {
167
+ throw new Error(`addon archive ${addon.assetName} verified but holds no ${addon.entryPath} — refusing a partial update`);
168
+ }
169
+ addonTargetPath = join(dirname(appBinaryPath), 'lib', addon.dirName, addon.fileName);
170
+ }
171
+
172
+ // One mechanism everywhere: downloads + verifies the binary against the
173
+ // same manifest, then swaps it atomically with the outgoing file kept at
174
+ // `<path>.previous`. The addon bytes above are already verified, so the
175
+ // addon swap after this cannot fail on bad data.
176
+ await applyVerifiedUpdate({
177
+ fetchImpl: options.fetchImpl,
178
+ downloadBaseUrl: baseUrl,
179
+ targets: [{ label: 'agent binary', path: appBinaryPath, assetName: binaryAsset, executable: true }],
180
+ io,
181
+ platform: options.platform,
182
+ });
183
+ if (addonFileBytes !== null && addonTargetPath !== null) {
184
+ swapFileAtomically(addonTargetPath, addonFileBytes, { executable: false, io, platform: options.platform });
185
+ }
186
+
187
+ options.print(
188
+ [
189
+ `Updated to ${latestTag}.`,
190
+ ` agent binary: ${appBinaryPath}`,
191
+ ...(addonTargetPath
192
+ ? [` vector addon: ${addonTargetPath}`]
193
+ : [` vector addon: the ${latestTag} release ships no ${addon?.assetName ?? 'addon archive'} for this platform — left untouched`]),
194
+ '',
195
+ 'Restart goodvibes-agent to run the new version.',
196
+ ].join('\n'),
197
+ );
198
+ }
199
+
200
+ export interface RollbackUpdateOptions {
201
+ readonly execPath: string;
202
+ readonly platform: NodeJS.Platform;
203
+ readonly arch: string;
204
+ readonly print: (line: string) => void;
205
+ /** Injectable filesystem seam (the SDK's UpdateFileIo) so tests observe renames in memory. */
206
+ readonly io?: UpdateFileIo;
207
+ }
208
+
209
+ /**
210
+ * One-command rollback to the version that ran before the last update,
211
+ * delegating the exchange mechanics to the SDK's rollbackKeptPrevious (the
212
+ * same module the swap uses): every installed file (agent binary, vector
213
+ * addon) that has a kept `.previous` counterpart is EXCHANGED with it — the
214
+ * previous version becomes live, and the version being rolled back is itself
215
+ * kept at `.previous`, so a second `/update rollback` rolls forward again.
216
+ * Files without a kept counterpart are reported and left untouched; nothing
217
+ * is downloaded.
218
+ */
219
+ export function rollbackUpdate(options: RollbackUpdateOptions): void {
220
+ const installKind: InstallKind = detectInstallKind(options.execPath);
221
+ if (installKind !== 'binary') {
222
+ options.print(
223
+ [
224
+ `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.`,
225
+ `Install a specific version with your package manager instead, e.g.: ${fallbackUpdateCommand(installKind)}`,
226
+ ].join('\n'),
227
+ );
228
+ return;
229
+ }
230
+
231
+ const io = options.io ?? realUpdateFileIo;
232
+ const addon = resolveSqliteVecArchive(options.platform, options.arch);
233
+ const targets = [
234
+ { label: 'agent binary', path: options.execPath },
235
+ ...(addon ? [{ label: 'vector addon', path: join(dirname(options.execPath), 'lib', addon.dirName, addon.fileName) }] : []),
236
+ ];
237
+
238
+ const result = rollbackKeptPrevious(targets, io);
239
+ if (result.restored.length === 0) {
240
+ options.print(
241
+ `No previous version is kept beside this install (nothing at ${options.execPath}${PREVIOUS_FILE_SUFFIX}). ` +
242
+ 'The previous version is kept from the next update onward.',
243
+ );
244
+ return;
245
+ }
246
+
247
+ options.print(
248
+ [
249
+ 'Rolled back to the previously installed version.',
250
+ ...result.restored.map((target) => ` ${target.label}: ${target.path} (the replaced version is kept at ${target.path}${PREVIOUS_FILE_SUFFIX})`),
251
+ '',
252
+ 'Restart goodvibes-agent to run the restored version.',
253
+ ].join('\n'),
254
+ );
255
+ }
256
+
257
+ export function registerUpdateCommand(registry: CommandRegistry): void {
258
+ registry.register({
259
+ name: 'update',
260
+ aliases: ['upgrade'],
261
+ description: 'Check for a newer GoodVibes Agent release and, for binary installs, download/verify/apply it or roll back to the kept previous version',
262
+ usage: '[check|apply|rollback]',
263
+ async handler(args, ctx) {
264
+ const sub = args[0] ?? 'check';
265
+
266
+ if (sub === 'check') {
267
+ try {
268
+ const result = await checkForUpdate(fetch as UpdateFetchLike, VERSION);
269
+ ctx.print(
270
+ result.isCurrent
271
+ ? `Already current: running v${normalizeVersion(VERSION)} (latest release is ${result.latestTag}).`
272
+ : `Update available: ${result.latestTag} (running v${normalizeVersion(VERSION)}). Run /update apply to install it.`,
273
+ );
274
+ } catch (error) {
275
+ ctx.print(`Could not check for updates: ${error instanceof Error ? error.message : String(error)}`);
276
+ }
277
+ return;
278
+ }
279
+
280
+ if (sub === 'apply') {
281
+ try {
282
+ await applyUpdate({
283
+ fetchImpl: fetch as UpdateFetchLike,
284
+ execPath: process.execPath,
285
+ platform: process.platform,
286
+ arch: process.arch,
287
+ currentVersion: VERSION,
288
+ print: ctx.print,
289
+ });
290
+ } catch (error) {
291
+ ctx.print(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
292
+ }
293
+ return;
294
+ }
295
+
296
+ if (sub === 'rollback') {
297
+ try {
298
+ rollbackUpdate({
299
+ execPath: process.execPath,
300
+ platform: process.platform,
301
+ arch: process.arch,
302
+ print: ctx.print,
303
+ });
304
+ } catch (error) {
305
+ ctx.print(`Rollback failed: ${error instanceof Error ? error.message : String(error)}`);
306
+ }
307
+ return;
308
+ }
309
+
310
+ ctx.print('Usage: /update [check|apply|rollback]');
311
+ },
312
+ });
313
+ }
@@ -41,6 +41,7 @@ import { registerOperatorActionRuntimeCommands } from './commands/operator-actio
41
41
  import { registerConnectedHostAdminCommands } from './commands/connected-host-admin-runtime.ts';
42
42
  import { registerEmailRuntimeCommands } from './commands/email-runtime.ts';
43
43
  import { registerCalendarRuntimeCommands } from './commands/calendar-runtime.ts';
44
+ import { registerUpdateCommand } from './commands/update-runtime.ts';
44
45
 
45
46
  function registerAgentMemoryCommand(registry: CommandRegistry): void {
46
47
  registry.register({
@@ -90,6 +91,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
90
91
  registerLocalProviderRuntimeCommands(registry);
91
92
  registerNetworkScanRuntimeCommands(registry);
92
93
  registerHealthRuntimeCommands(registry);
94
+ registerUpdateCommand(registry);
93
95
  registerProviderAccountsRuntimeCommands(registry);
94
96
  registerConversationRuntimeCommands(registry);
95
97
  registerQrcodeRuntimeCommands(registry);
@@ -95,9 +95,9 @@ export interface FeedContextStableRefs {
95
95
  selection: SelectionManager;
96
96
  pasteRegistry: Map<string, string>;
97
97
  imageRegistry: Map<string, { data: string; mediaType: string }>;
98
- /** Ported from goodvibes-tui's DEBT-5 item 5; mutated in place, never reallocated. */
98
+ /** Ported from goodvibes-tui's unbracketed-paste-flood guard; mutated in place, never reallocated. */
99
99
  burstGuard: PanelBurstGuardState;
100
- /** OS-level terminal focus tracker, ported from goodvibes-tui's W2.3. */
100
+ /** OS-level terminal focus tracker, ported from goodvibes-tui's core/focus-tracker.ts. */
101
101
  focusTracker: FocusTracker;
102
102
  projectRoot: string;
103
103
  selectionModal: SelectionModal;
@@ -64,13 +64,13 @@ import type { FocusTracker } from '../core/focus-tracker.ts';
64
64
  * - `inputHistory`, `conversationManager` — late-wired service handles; synced at
65
65
  * feed() entry only since no in-feed action rewires them
66
66
  * - `pasteRegistry`, `imageRegistry` — owned Maps, never replaced
67
- * - `burstGuard` (ported from goodvibes-tui's DEBT-5 item 5) — the
67
+ * - `burstGuard` (ported from goodvibes-tui's panel-paste-flood-guard.ts) — the
68
68
  * unbracketed-paste-flood guard's sliding-window state, mutated in place
69
69
  * across tokens by trackPanelPasteFloodGuard (see panel-paste-flood-guard.ts).
70
70
  * Never reallocated. `burstSuppressedCount` is this wiring layer's own
71
71
  * bookkeeping (not part of the ported module) for the honest resolution
72
72
  * notice — see feedInputTokens below.
73
- * - `focusTracker` (ported from goodvibes-tui's W2.3) — tracks OS-level
73
+ * - `focusTracker` (ported from goodvibes-tui's core/focus-tracker.ts) — tracks OS-level
74
74
  * terminal focus from `\x1b[I`/`\x1b[O` tokens (DECSET ?1004h, enabled in
75
75
  * main.ts). Shared instance from RuntimeServices, threaded via
76
76
  * uiServices.platform.focusTracker (mirrors the TUI's own wiring).
@@ -105,11 +105,11 @@ export interface InputFeedContext {
105
105
  contentWidth: number;
106
106
  readonly pasteRegistry: Map<string, string>;
107
107
  readonly imageRegistry: Map<string, { data: string; mediaType: string }>;
108
- /** Ported from goodvibes-tui DEBT-5 item 5 — mutated in place, never reallocated. */
108
+ /** Ported from goodvibes-tui's paste-flood guard — mutated in place, never reallocated. */
109
109
  readonly burstGuard: PanelBurstGuardState;
110
110
  /** Wiring-layer bookkeeping (not part of the ported module) for the honest suppressed-count notice. */
111
111
  burstSuppressedCount: number;
112
- /** Ported from goodvibes-tui W2.3 — OS-level terminal focus, fed from 'focus' tokens below. */
112
+ /** Ported from goodvibes-tui's focus tracker — OS-level terminal focus, fed from 'focus' tokens below. */
113
113
  readonly focusTracker: FocusTracker;
114
114
  readonly projectRoot: string;
115
115
  readonly selection: SelectionManager;
@@ -178,11 +178,11 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
178
178
  const keybindings = context.keybindingsManager;
179
179
  // One `now` per feed() call (not per token) — a genuine unbracketed-paste
180
180
  // flood delivers many tokens in one drain, and they should all measure as
181
- // arriving "at once" (mirrors goodvibes-tui's handler-feed.ts DEBT-5 item 5 doc).
181
+ // arriving "at once" (mirrors the same doc note in goodvibes-tui's handler-feed.ts).
182
182
  const now = Date.now();
183
183
 
184
184
  for (const token of tokens) {
185
- // Ported from goodvibes-tui W2.3: focus-reporting tokens (CSI ?1004h)
185
+ // Ported from goodvibes-tui's focus tracking: focus-reporting tokens (CSI ?1004h)
186
186
  // never reach the composer or any modal route — consumed here, first,
187
187
  // unconditionally. No render needed.
188
188
  if (token.type === 'focus') {
@@ -294,8 +294,8 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
294
294
  }
295
295
  }
296
296
 
297
- // Ported from goodvibes-tui's panel-paste-flood-guard.ts, DEBT-5 item
298
- // 5: guards command-mode's key-driven dispatch (handleCommandModeToken,
297
+ // Ported from goodvibes-tui's panel-paste-flood-guard.ts: guards
298
+ // command-mode's key-driven dispatch (handleCommandModeToken,
299
299
  // below) from an unbracketed-paste-replay or control-character-injection
300
300
  // burst.
301
301
  //
@@ -94,7 +94,7 @@ export class InputHandler {
94
94
  public pasteRegistry = new Map<string, string>();
95
95
  public nextPasteId = 1;
96
96
  public lastCtrlCTime = 0;
97
- /** Ported from goodvibes-tui DEBT-5 item 5 — unbracketed-paste-flood guard state, mutated in place. */
97
+ /** Ported from goodvibes-tui — unbracketed-paste-flood guard state, mutated in place. */
98
98
  public burstGuard: PanelBurstGuardState = { timestamps: [], suspended: false, hintShown: false };
99
99
  /** Long-lived feed context — reused across every feed() call to avoid per-keystroke allocation. */
100
100
  public feedContext!: import('./handler-feed.ts').InputFeedContext;
@@ -150,7 +150,11 @@ function serverConfigToForm(server?: McpServerConfig): McpWorkspaceForm {
150
150
  };
151
151
  }
152
152
 
153
- function formToServerConfig(form: McpWorkspaceForm): McpServerConfig {
153
+ // The workspace form edits stdio (command-launched) servers only, so the
154
+ // converted config always carries a command; streamable-HTTP (url) servers
155
+ // from the SDK's widened McpServerConfig appear in the list but are managed
156
+ // through the /mcp command line.
157
+ function formToServerConfig(form: McpWorkspaceForm): McpServerConfig & { command: string } {
154
158
  const name = form.name.trim();
155
159
  const command = form.command.trim();
156
160
  if (!name) throw new Error('Server name is required.');
@@ -1,6 +1,6 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // panel-paste-flood-guard.ts — ported from goodvibes-tui (commit 90eb3a26,
3
- // DEBT-5 item 5).
3
+ // src/input/panel-paste-flood-guard.ts).
4
4
  //
5
5
  // A terminal WITHOUT bracketed paste delivers a pasted block as a burst of
6
6
  // discrete 1-char 'text' tokens (isPasteToken stays false for every one of
@@ -1,7 +1,7 @@
1
1
  import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
2
2
  import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
3
3
  import type { ProviderAuthFreshness, ProviderAuthRoute } from '@/runtime/index.ts';
4
- import type { FeatureFlag, FlagState } from '@/runtime/index.ts';
4
+ import type { FeatureSetting, FlagState } from '@/runtime/index.ts';
5
5
 
6
6
  export interface SettingsModalChange {
7
7
  readonly key: ConfigKey;
@@ -61,7 +61,9 @@ export type SettingsCategory =
61
61
  | 'policy'
62
62
  | 'fetch'
63
63
  | 'security'
64
- | 'integrations';
64
+ | 'integrations'
65
+ | 'update'
66
+ | 'pricing';
65
67
 
66
68
  export type SettingsFocusPane = 'categories' | 'settings';
67
69
 
@@ -70,10 +72,10 @@ export const SETTINGS_CATEGORY_GROUPS: ReadonlyArray<{
70
72
  readonly categories: readonly SettingsCategory[];
71
73
  }> = [
72
74
  { label: 'Agent Experience', categories: ['display', 'ui', 'behavior', 'agents', 'notifications', 'permissions', 'policy', 'fetch', 'diagnostics'] },
73
- { label: 'Models and Providers', categories: ['provider', 'subscriptions', 'helper', 'tools', 'tts'] },
75
+ { label: 'Models and Providers', categories: ['provider', 'subscriptions', 'helper', 'tools', 'tts', 'pricing'] },
74
76
  { label: 'Agent-local state', categories: ['storage', 'cache', 'telemetry', 'atRest', 'security', 'learning'] },
75
77
  { label: 'Channels and Tools', categories: ['surfaces', 'mcp', 'automation', 'checkin', 'integrations'] },
76
- { label: 'Daemon Runtime', categories: ['daemon', 'service', 'controlPlane', 'httpListener', 'web', 'watchers', 'network', 'relay'] },
78
+ { label: 'Daemon Runtime', categories: ['daemon', 'service', 'controlPlane', 'httpListener', 'web', 'watchers', 'network', 'relay', 'update'] },
77
79
  { label: 'Advanced Runtime', categories: ['orchestration', 'planner', 'runtime', 'sandbox', 'batch', 'cloudflare', 'wrfc'] },
78
80
  { label: 'Advanced', categories: ['flags', 'release'] },
79
81
  ];
@@ -92,8 +94,12 @@ export interface SettingEntry {
92
94
  }
93
95
 
94
96
  export interface FlagEntry {
95
- flag: FeatureFlag;
97
+ /** The feature as FEATURE_SETTINGS describes it: domain, enablement shape, settings keys, real description. */
98
+ feature: FeatureSetting;
99
+ /** Live gate state from the manager (kill-switch aware); config-derived when no manager is attached. */
96
100
  state: FlagState;
101
+ /** The enablement settings key's current config value, rendered on the row. */
102
+ enablementValue: string;
97
103
  }
98
104
 
99
105
  export interface McpEntry {