@pellux/goodvibes-tui 1.13.1 → 1.14.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 (158) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +6 -6
  3. package/bin/goodvibes +6 -0
  4. package/bin/goodvibes-daemon +6 -0
  5. package/bin/launcher-support.js +34 -1
  6. package/docs/foundation-artifacts/operator-contract.json +8076 -2047
  7. package/package.json +15 -11
  8. package/scripts/postinstall.js +73 -48
  9. package/src/cli/bundle-command.ts +5 -5
  10. package/src/cli/completion.ts +1 -1
  11. package/src/cli/completions/generate.ts +15 -3
  12. package/src/cli/doctor.ts +385 -0
  13. package/src/cli/entrypoint.ts +72 -1
  14. package/src/cli/help.ts +29 -9
  15. package/src/cli/hooks-command.ts +74 -0
  16. package/src/cli/hooks-report.ts +167 -0
  17. package/src/cli/index.ts +3 -0
  18. package/src/cli/management.ts +13 -1
  19. package/src/cli/parser.ts +10 -2
  20. package/src/cli/plugin-bundles-command.ts +228 -0
  21. package/src/cli/plugin-command.ts +179 -0
  22. package/src/cli/service-posture.ts +10 -0
  23. package/src/cli/status.ts +275 -2
  24. package/src/cli/tui-startup.ts +119 -1
  25. package/src/cli/types.ts +3 -1
  26. package/src/config/index.ts +16 -0
  27. package/src/config/tui-extension-settings.ts +169 -0
  28. package/src/core/approval-alert.ts +15 -2
  29. package/src/core/compaction-receipt.ts +127 -0
  30. package/src/core/model-routing-chip.ts +82 -0
  31. package/src/core/permission-mode.ts +89 -0
  32. package/src/core/rewind-receipt.ts +83 -0
  33. package/src/core/rewind-turn-anchors.ts +160 -0
  34. package/src/core/scriptable-statusline.ts +141 -0
  35. package/src/core/session-auto-titler.ts +110 -0
  36. package/src/core/stream-event-wiring.ts +20 -0
  37. package/src/core/system-message-router.ts +1 -1
  38. package/src/core/terminal-notifier.ts +102 -0
  39. package/src/core/turn-event-wiring.ts +80 -1
  40. package/src/input/command-reference.ts +96 -0
  41. package/src/input/command-registry.ts +25 -0
  42. package/src/input/commands/channel-pairing.ts +245 -0
  43. package/src/input/commands/channel-runtime.ts +152 -6
  44. package/src/input/commands/checkin-runtime.ts +89 -0
  45. package/src/input/commands/checkpoint-runtime.ts +7 -179
  46. package/src/input/commands/ci-runtime.ts +207 -0
  47. package/src/input/commands/cost-attribution-format.ts +52 -0
  48. package/src/input/commands/cost-runtime.ts +44 -4
  49. package/src/input/commands/editor-runtime.ts +22 -0
  50. package/src/input/commands/flags-runtime.ts +328 -0
  51. package/src/input/commands/health-metrics-format.ts +67 -0
  52. package/src/input/commands/health-runtime.ts +55 -1
  53. package/src/input/commands/memory.ts +23 -0
  54. package/src/input/commands/operator-rpc.ts +95 -0
  55. package/src/input/commands/palette-runtime.ts +22 -0
  56. package/src/input/commands/permissions-provenance.ts +264 -0
  57. package/src/input/commands/permissions-runtime.ts +21 -0
  58. package/src/input/commands/planning-runtime.ts +28 -1
  59. package/src/input/commands/platform-access-runtime.ts +2 -97
  60. package/src/input/commands/principals-runtime.ts +212 -0
  61. package/src/input/commands/product-runtime.ts +43 -3
  62. package/src/input/commands/recall-files-config.ts +54 -0
  63. package/src/input/commands/recall-files-git.ts +57 -0
  64. package/src/input/commands/recall-files.ts +185 -0
  65. package/src/input/commands/recall-query.ts +8 -3
  66. package/src/input/commands/recall-review.ts +2 -2
  67. package/src/input/commands/recall-shared.ts +13 -0
  68. package/src/input/commands/relay-runtime.ts +86 -0
  69. package/src/input/commands/review-runtime.ts +160 -0
  70. package/src/input/commands/rewind-runtime.ts +362 -0
  71. package/src/input/commands/schedule-nl.ts +147 -0
  72. package/src/input/commands/schedule-runtime.ts +29 -10
  73. package/src/input/commands/secret-runtime.ts +55 -0
  74. package/src/input/commands/session-content.ts +19 -8
  75. package/src/input/commands/session-workflow.ts +10 -0
  76. package/src/input/commands/update-runtime.ts +363 -0
  77. package/src/input/commands/workstream-attempt-dependents.ts +103 -0
  78. package/src/input/commands/workstream-attempts.ts +244 -0
  79. package/src/input/commands/workstream-runtime.ts +164 -26
  80. package/src/input/commands/worktree-runtime.ts +56 -2
  81. package/src/input/commands.ts +135 -80
  82. package/src/input/composer-capture.ts +67 -0
  83. package/src/input/composer-editor.ts +134 -0
  84. package/src/input/concealed-input.ts +94 -0
  85. package/src/input/exec-env-scrub-config.ts +84 -0
  86. package/src/input/feature-unit-layout.ts +142 -0
  87. package/src/input/feed-context-factory.ts +2 -0
  88. package/src/input/handler-feed-routes.ts +17 -0
  89. package/src/input/handler-feed.ts +3 -0
  90. package/src/input/handler-modal-routes.ts +5 -2
  91. package/src/input/handler-shortcuts.ts +37 -0
  92. package/src/input/handler.ts +24 -2
  93. package/src/input/keybindings.ts +11 -2
  94. package/src/input/onboarding/onboarding-feature-units.ts +536 -0
  95. package/src/input/onboarding/onboarding-wizard-apply.ts +40 -6
  96. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +324 -629
  97. package/src/input/onboarding/onboarding-wizard-steps.ts +5 -5
  98. package/src/input/onboarding/onboarding-wizard-types.ts +6 -0
  99. package/src/input/relay-settings-descriptions.ts +35 -0
  100. package/src/input/sandbox-exec-config.ts +107 -0
  101. package/src/input/settings-modal-adjustment.ts +4 -7
  102. package/src/input/settings-modal-data.ts +178 -8
  103. package/src/input/settings-modal-mutations.ts +41 -13
  104. package/src/input/settings-modal-types.ts +42 -5
  105. package/src/input/settings-modal.ts +67 -15
  106. package/src/input/submission-router.ts +4 -1
  107. package/src/input/worktree-setup-config.ts +93 -0
  108. package/src/main.ts +43 -43
  109. package/src/panels/builtin/development.ts +14 -0
  110. package/src/panels/diff-review-model.ts +189 -0
  111. package/src/panels/diff-review-panel.ts +395 -0
  112. package/src/panels/fleet-panel-format.ts +114 -2
  113. package/src/panels/fleet-panel.ts +65 -78
  114. package/src/panels/fleet-read-model.ts +91 -11
  115. package/src/panels/fleet-stop.ts +19 -4
  116. package/src/panels/modals/memory-modal.ts +11 -2
  117. package/src/permissions/hunk-selection.ts +86 -5
  118. package/src/permissions/prompt.ts +108 -7
  119. package/src/permissions/sandbox-exec-gate.ts +162 -0
  120. package/src/renderer/settings-modal-helpers.ts +21 -1
  121. package/src/renderer/settings-modal.ts +70 -44
  122. package/src/renderer/shell-surface.ts +23 -1
  123. package/src/renderer/system-message.ts +4 -0
  124. package/src/renderer/tool-result-summary.ts +95 -5
  125. package/src/renderer/ui-factory.ts +22 -0
  126. package/src/runtime/bootstrap-command-context.ts +15 -1
  127. package/src/runtime/bootstrap-command-parts.ts +14 -2
  128. package/src/runtime/bootstrap-core.ts +38 -38
  129. package/src/runtime/bootstrap-notifier-sync.ts +41 -0
  130. package/src/runtime/bootstrap-shell.ts +8 -0
  131. package/src/runtime/bootstrap.ts +21 -23
  132. package/src/runtime/context-accounting-source.ts +103 -0
  133. package/src/runtime/conversation-rewind-port.ts +130 -0
  134. package/src/runtime/fleet-needs-input-push.ts +70 -0
  135. package/src/runtime/install-self-check-startup.ts +36 -0
  136. package/src/runtime/install-self-check.ts +149 -0
  137. package/src/runtime/onboarding/fast-path.ts +93 -0
  138. package/src/runtime/process-lifecycle.ts +32 -24
  139. package/src/runtime/relay-reachability-bridge.ts +53 -0
  140. package/src/runtime/release-artifacts.ts +79 -0
  141. package/src/runtime/services.ts +33 -33
  142. package/src/runtime/trust/workspace-registration.ts +184 -0
  143. package/src/runtime/trust/workspace-trust.ts +202 -0
  144. package/src/runtime/ui-services.ts +2 -1
  145. package/src/runtime/update-check.ts +113 -0
  146. package/src/runtime/workspace-checkpointing.ts +32 -0
  147. package/src/runtime/workstream-attempts-validation.ts +77 -0
  148. package/src/runtime/workstream-draft-edits.ts +95 -0
  149. package/src/runtime/workstream-draft-store.ts +101 -0
  150. package/src/runtime/workstream-draft-types.ts +42 -0
  151. package/src/runtime/workstream-services.ts +70 -39
  152. package/src/shell/blocking-input.ts +29 -0
  153. package/src/shell/ui-openers.ts +65 -0
  154. package/src/verification/verification-ledger.ts +56 -2
  155. package/src/version.ts +1 -1
  156. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +0 -130
  157. package/src/runtime/gateway-verbs.ts +0 -47
  158. package/src/runtime/render-scheduler.ts +0 -90
@@ -0,0 +1,93 @@
1
+ /**
2
+ * fast-path.ts — minimum-steps first run.
3
+ *
4
+ * The full onboarding wizard is comprehensive but long (5+ steps for a brand
5
+ * new user). The fast path gets a new user to a working session in the fewest
6
+ * possible steps:
7
+ * - If a provider API key is already configured (env / secrets), skip
8
+ * onboarding entirely — write the check marker and drop straight into a
9
+ * working session. ZERO steps.
10
+ * - Otherwise ask exactly one question: start now on the free default model,
11
+ * or run the full guided setup. Dismissing (Esc) starts now — so it is
12
+ * always skippable.
13
+ *
14
+ * The full wizard remains one keystroke away (the "Full guided setup" choice,
15
+ * or `/onboarding` at any time). If the command context can't answer whether a
16
+ * provider is configured (e.g. a headless/test surface), we fall back to the
17
+ * classic behavior of opening the wizard, so nothing regresses.
18
+ */
19
+ import type { CommandContext } from '../../input/command-registry.ts';
20
+ import type { SelectionItem } from '../../input/selection-modal.ts';
21
+ import { writeOnboardingCheckMarker } from './markers.ts';
22
+ import type { WriteOnboardingCheckMarkerOptions } from './types.ts';
23
+
24
+ type OnboardingMarkerShellPaths = Parameters<typeof writeOnboardingCheckMarker>[0];
25
+
26
+ export interface OnboardingFastPathDeps {
27
+ readonly input: { openOnboardingWizard: (options: { mode: 'new'; reset: boolean }) => void };
28
+ readonly commandContext: CommandContext;
29
+ readonly shellPaths: OnboardingMarkerShellPaths;
30
+ readonly render: () => void;
31
+ }
32
+
33
+ /** Read configured provider ids defensively — returns [] if the surface can't answer. */
34
+ function readConfiguredProviderIds(commandContext: CommandContext): string[] | null {
35
+ const registry = commandContext.provider?.providerRegistry;
36
+ if (!registry || typeof registry.getConfiguredProviderIds !== 'function') return null;
37
+ try {
38
+ return registry.getConfiguredProviderIds();
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ const MARKER_OPTIONS: WriteOnboardingCheckMarkerOptions = { source: 'command' };
45
+
46
+ /**
47
+ * Run the fast-path first-run flow. Returns nothing; side effects (wizard open,
48
+ * marker write, modal) happen through the injected deps.
49
+ */
50
+ export function startOnboardingFastPath(deps: OnboardingFastPathDeps): void {
51
+ const { input, commandContext, shellPaths, render } = deps;
52
+ const openWizard = (): void => input.openOnboardingWizard({ mode: 'new', reset: true });
53
+
54
+ const configured = readConfiguredProviderIds(commandContext);
55
+ // Fall back to the classic wizard if we can't detect providers or can't prompt.
56
+ if (configured === null || !commandContext.openSelection) {
57
+ openWizard();
58
+ return;
59
+ }
60
+
61
+ if (configured.length > 0) {
62
+ // A provider key already exists — nothing to ask. Straight to a working session.
63
+ writeOnboardingCheckMarker(shellPaths, MARKER_OPTIONS);
64
+ commandContext.print?.(
65
+ `Ready — provider configured (${configured.join(', ')}). Run /onboarding anytime for full setup.`,
66
+ );
67
+ render();
68
+ return;
69
+ }
70
+
71
+ const items: SelectionItem[] = [
72
+ { id: 'start-now', label: 'Start now', detail: 'Use the free default model — no API key needed', primaryAction: 'select' },
73
+ { id: 'full-setup', label: 'Full guided setup', detail: 'Choose provider, model, channels, and more', primaryAction: 'select' },
74
+ ];
75
+ commandContext.openSelection(
76
+ 'Welcome to GoodVibes — get started',
77
+ items,
78
+ { allowSearch: false, primaryVerbLabel: 'Choose' },
79
+ (result) => {
80
+ if (result?.item.id === 'full-setup') {
81
+ openWizard();
82
+ return;
83
+ }
84
+ // Start now (or dismissed): mark first-run done and drop into a working
85
+ // session on the free default model.
86
+ writeOnboardingCheckMarker(shellPaths, MARKER_OPTIONS);
87
+ commandContext.print?.(
88
+ 'You\'re on the free default model. Add a provider key anytime with /provider, or run /onboarding for full setup.',
89
+ );
90
+ render();
91
+ },
92
+ );
93
+ }
@@ -19,6 +19,7 @@
19
19
  * `unsubs` registry is shared by reference and drained on exit.
20
20
  */
21
21
  import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
22
+ import { createTerminalLifecycle, TERMINAL_ESCAPES } from '@pellux/goodvibes-terminal-shell';
22
23
  import { formatUserFacingErrorLine } from '../core/format-user-error.ts';
23
24
  import { allowTerminalWrite } from './terminal-output-guard.ts';
24
25
  import { buildPersistedSessionContext, deleteRecoveryFile } from '@/runtime/index.ts';
@@ -129,30 +130,37 @@ export function installProcessLifecycle(deps: ProcessLifecycleDeps): ProcessLife
129
130
  render();
130
131
  };
131
132
 
132
- let terminalRestored = false;
133
+ // Terminal enter/restore sequencing is the byte-identical seam both daemon
134
+ // front-ends share, so it lives in @pellux/goodvibes-terminal-shell and this
135
+ // wiring delegates to it. Only the restore path is used here (the TUI's
136
+ // startup enter sequence stays in main.ts); the graceful shutdown below
137
+ // (draining services, persisting sessions, exit codes) is TUI-specific and
138
+ // stays local, calling restoreTerminal() for the synchronous hand-back.
139
+ //
140
+ // The injected `ansi` restore sequences are mapped onto the package's escape
141
+ // set so the exact bytes the TUI has always emitted are preserved. The no-alt
142
+ // exit deliberately uses the package's CLEAR_VIEWPORT_HOME ('\x1b[2J\x1b[H',
143
+ // no ESC[3J) — never ansi.CLEAR_SCREEN, which carries the scrollback-wiping 3J.
144
+ const terminalLifecycle = createTerminalLifecycle({
145
+ write: (data) => { stdout.write(data); },
146
+ noAltScreen,
147
+ guardedWrite: allowTerminalWrite,
148
+ disposeOutputGuard: () => getTerminalOutputGuard().dispose(),
149
+ setRawMode: (enabled) => stdin.setRawMode(enabled),
150
+ escapes: {
151
+ ...TERMINAL_ESCAPES,
152
+ ALT_SCREEN_EXIT: ansi.ALT_SCREEN_EXIT,
153
+ PASTE_DISABLE: ansi.PASTE_DISABLE,
154
+ KEYBOARD_EXT_DISABLE: ansi.KEYBOARD_EXT_DISABLE,
155
+ MOUSE_DISABLE: ansi.MOUSE_DISABLE,
156
+ CURSOR_SHOW: ansi.CURSOR_SHOW,
157
+ FOCUS_DISABLE: ansi.FOCUS_DISABLE,
158
+ },
159
+ });
133
160
  // Idempotent, synchronous-only terminal restore. Safe to call from process.on('exit'),
134
- // signal handlers, uncaughtException, and exitApp. Disposes the output guard FIRST so a
135
- // crash stack reaches the real stderr instead of being suppressed by the guard.
136
- const restoreTerminal = (): void => {
137
- if (terminalRestored) return;
138
- terminalRestored = true;
139
- // Alt-screen path: just leave the alt screen — 1049l restores the primary
140
- // screen and cursor exactly as they were at launch. Clearing first is
141
- // pointless (the alt screen is discarded) and actively harmful: the old
142
- // CLEAR_SCREEN included ESC[3J, which wipes the PRIMARY scrollback on
143
- // several emulators even when issued from the alt screen.
144
- // No-alt path: the compositor painted over the primary screen, so clear
145
- // the viewport and home the cursor — but WITHOUT 3J, the user's scrollback
146
- // is theirs. CURSOR_SHOW goes AFTER the screen switch so visibility
147
- // applies to the screen the shell prompt lands on.
148
- const exitScreen = noAltScreen ? '\x1b[2J\x1b[H' : ansi.ALT_SCREEN_EXIT;
149
- allowTerminalWrite(() => stdout.write(
150
- ansi.PASTE_DISABLE + ansi.KEYBOARD_EXT_DISABLE + ansi.MOUSE_DISABLE + ansi.FOCUS_DISABLE
151
- + exitScreen + ansi.CURSOR_SHOW,
152
- ));
153
- getTerminalOutputGuard().dispose();
154
- try { stdin.setRawMode(false); } catch { /* stdin may not be a TTY */ }
155
- };
161
+ // signal handlers, uncaughtException, and exitApp. Disposes the output guard AFTER the
162
+ // restore write so a crash stack reaches the real stderr instead of being suppressed.
163
+ const restoreTerminal = terminalLifecycle.restoreTerminal;
156
164
 
157
165
  const uncaughtExceptionHandler = (err: Error): void => {
158
166
  restoreTerminal();
@@ -220,7 +228,7 @@ export function installProcessLifecycle(deps: ProcessLifecycleDeps): ProcessLife
220
228
  return {
221
229
  exitApp,
222
230
  restoreTerminal,
223
- isTerminalRestored: () => terminalRestored,
231
+ isTerminalRestored: terminalLifecycle.isTerminalRestored,
224
232
  resizeHandler,
225
233
  sigintHandler,
226
234
  unhandledRejectionHandler,
@@ -0,0 +1,53 @@
1
+ /**
2
+ * relay-reachability-bridge.ts — reads the LIVE outbound-relay reachability
3
+ * state off the running embedded/standalone DaemonServer.
4
+ *
5
+ * The SDK's `DaemonServer` composes and owns a `RelayReachability` controller
6
+ * internally (gated by `relay.enabled` + the `relay-connect` feature flag) and
7
+ * exposes it via `getRelayReachability(): RelayReachability | null` — but that
8
+ * accessor is not on the narrow `DaemonService` interface `HostServicesHandle`
9
+ * types its `daemonServer` field with (see
10
+ * `@pellux/goodvibes-sdk/platform/runtime/bootstrap-services`), so the field
11
+ * needs a defensive structural check before use — same pattern
12
+ * `waitForConfigDrivenRestarts` in bootstrap.ts already uses for
13
+ * `waitForRestart`. `platform/relay` itself has no public export subpath
14
+ * (daemon-internal), so the RelayReachability shape is described locally as a
15
+ * minimal structural type rather than imported by name.
16
+ */
17
+
18
+ import type { RelayRegistrationStatus } from '@pellux/goodvibes-daemon-sdk';
19
+ import { encodeRelayPairingString, type RelayPairingPayload } from '@pellux/goodvibes-transport-core/relay';
20
+
21
+ /** Structural subset of the SDK's `RelayReachability` this bridge needs. */
22
+ interface RelayReachabilityLike {
23
+ readonly status: RelayRegistrationStatus | 'disabled';
24
+ mintPairing(): Promise<RelayPairingPayload | null>;
25
+ }
26
+
27
+ /** Structural subset of a live daemon that may expose relay reachability. */
28
+ interface RelayCapableDaemonService {
29
+ getRelayReachability?(): RelayReachabilityLike | null;
30
+ }
31
+
32
+ function getRelay(daemonServer: unknown): RelayReachabilityLike | null {
33
+ const candidate = daemonServer as RelayCapableDaemonService | null;
34
+ if (!candidate || typeof candidate.getRelayReachability !== 'function') return null;
35
+ return candidate.getRelayReachability() ?? null;
36
+ }
37
+
38
+ /** Two live-relay accessors, spread into `externalServices` from bootstrap.ts. */
39
+ export function buildRelayExternalServiceMethods(getDaemonServer: () => unknown): {
40
+ relayStatus(): RelayRegistrationStatus | 'disabled';
41
+ mintRelayPairing(): Promise<{ readonly payload: RelayPairingPayload; readonly encoded: string } | null>;
42
+ } {
43
+ return {
44
+ relayStatus: () => getRelay(getDaemonServer())?.status ?? 'disabled',
45
+ mintRelayPairing: async () => {
46
+ const relay = getRelay(getDaemonServer());
47
+ if (!relay) return null;
48
+ const payload = await relay.mintPairing();
49
+ if (!payload) return null;
50
+ return { payload, encoded: encodeRelayPairingString(payload) };
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Shared release-artifact naming and checksum-verification logic.
3
+ *
4
+ * This is the one place that knows the release asset naming convention
5
+ * (`goodvibes[-daemon]-{platform}-{arch}`) and how to verify a downloaded
6
+ * artifact against SHA256SUMS.txt. Two callers share it and must stay in
7
+ * lockstep:
8
+ * - scripts/postinstall.js (npm install-time binary download)
9
+ * - src/input/commands/update-runtime.ts (the /update command's self-update)
10
+ *
11
+ * A downloaded artifact with NO entry in the checksum manifest is just as
12
+ * unverified as one with a mismatching entry — verifyChecksum() throws in
13
+ * both cases, never silently skips.
14
+ */
15
+ import { createHash } from 'node:crypto';
16
+
17
+ export const CHECKSUM_MANIFEST_NAME = 'SHA256SUMS.txt';
18
+
19
+ export type SupportedPlatform = 'linux' | 'darwin';
20
+ export type SupportedArch = 'x64' | 'arm64';
21
+
22
+ export interface ReleaseArtifactNames {
23
+ readonly app: string;
24
+ readonly daemon: string;
25
+ }
26
+
27
+ /** Release-asset platform tag as used in artifact filenames ("linux" | "macos"). */
28
+ const PLATFORM_TAGS: Record<SupportedPlatform, string> = {
29
+ linux: 'linux',
30
+ darwin: 'macos',
31
+ };
32
+
33
+ export function resolveArtifactNames(platform: string, arch: string): ReleaseArtifactNames | null {
34
+ const platformTag = PLATFORM_TAGS[platform as SupportedPlatform];
35
+ if (!platformTag || (arch !== 'x64' && arch !== 'arm64')) {
36
+ return null;
37
+ }
38
+ const suffix = `${platformTag}-${arch}`;
39
+ return {
40
+ app: `goodvibes-${suffix}`,
41
+ daemon: `goodvibes-daemon-${suffix}`,
42
+ };
43
+ }
44
+
45
+ export function sha256(buffer: Buffer | Uint8Array): string {
46
+ return createHash('sha256').update(buffer).digest('hex');
47
+ }
48
+
49
+ export function parseChecksumFile(contents: string): Map<string, string> {
50
+ const checksums = new Map<string, string>();
51
+ for (const rawLine of contents.split(/\r?\n/)) {
52
+ const line = rawLine.trim();
53
+ if (!line) continue;
54
+ const match = line.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
55
+ if (!match) continue;
56
+ checksums.set(match[2], match[1].toLowerCase());
57
+ }
58
+ return checksums;
59
+ }
60
+
61
+ /**
62
+ * Verify a downloaded artifact's checksum against the parsed manifest.
63
+ * An artifact with no entry in the manifest is a hard failure, identical
64
+ * in severity to a mismatching entry — never treated as "unverifiable, so
65
+ * skip the check". Throws naming the artifact and the manifest.
66
+ */
67
+ export function verifyChecksum(
68
+ artifactName: string,
69
+ actual: string,
70
+ expected: string | undefined,
71
+ manifestName: string = CHECKSUM_MANIFEST_NAME,
72
+ ): void {
73
+ if (expected === undefined) {
74
+ throw new Error(`no checksum entry for ${artifactName} in ${manifestName} — refusing to install an unverified binary`);
75
+ }
76
+ if (expected !== actual) {
77
+ throw new Error(`checksum mismatch for ${artifactName}: expected ${expected}, got ${actual}`);
78
+ }
79
+ }
@@ -5,7 +5,8 @@ import { SecretsManager } from '../config/secrets.ts';
5
5
  import { AutomationDeliveryManager, AutomationManager, AutomationRouteStore } from '@pellux/goodvibes-sdk/platform/automation';
6
6
  import { ChannelDeliveryRouter, ChannelPluginRegistry, ChannelPolicyManager, RouteBindingManager, SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
7
7
  import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
8
- import { attachWsOnlyGatewayVerbHandlers, createArchivableFleetRegistry } from './gateway-verbs.ts';
8
+ import { StepUpService } from '@pellux/goodvibes-sdk/daemon';
9
+ import { attachWsOnlyGatewayVerbHandlers, createArchivableFleetRegistry } from '@pellux/goodvibes-terminal-shell';
9
10
  import { WatcherRegistry } from '@pellux/goodvibes-sdk/platform/watchers';
10
11
  import { ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts';
11
12
  import {
@@ -23,16 +24,19 @@ import {
23
24
  import { MediaProviderRegistry, ensureBuiltinMediaProviders } from '@pellux/goodvibes-sdk/platform/media';
24
25
  import { MultimodalService } from '@pellux/goodvibes-sdk/platform/multimodal';
25
26
  import { AgentMessageBus, AgentOrchestrator, ArchetypeLoader, WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
26
- import { AgentManager, OverflowHandler, ProcessManager, createWorkflowServices, type WorkflowServices } from '@pellux/goodvibes-sdk/platform/tools';
27
+ import { AgentManager, ContextAccountingHolder, OverflowHandler, ProcessManager, createWorkflowServices, type WorkflowServices } from '@pellux/goodvibes-sdk/platform/tools';
27
28
  import { FileStateCache, FileUndoManager, MemoryEmbeddingProviderRegistry, MemoryRegistry, MemoryStore, ModeManager, ProjectIndex, resolveCanonicalMemoryDbPath, type CodeIndexStore, type CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
28
29
  import { MemorySpineClient, createLocalMemoryAccess } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
29
30
  import { WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
30
- import type { RuntimeEventBus } from '@/runtime/index.ts';
31
+ import { createWorkspaceCheckpointing } from './workspace-checkpointing.ts';
32
+ import { createSessionConversationRewindPort } from './conversation-rewind-port.ts';
31
33
  import { createDomainDispatch } from './store/index.ts';
32
34
  import type { DomainDispatch, RuntimeStore } from './store/index.ts';
33
- import { DistributedRuntimeManager } from '@/runtime/index.ts';
34
- import { RemoteRunnerRegistry, RemoteSupervisor } from '@/runtime/index.ts';
35
- import { IntegrationHelperService } from '@/runtime/index.ts';
35
+ import {
36
+ type RuntimeEventBus, DistributedRuntimeManager, RemoteRunnerRegistry, RemoteSupervisor, IntegrationHelperService,
37
+ IdempotencyStore, ComponentHealthMonitor, WorktreeRegistry, SandboxSessionRegistry, createShellPathService,
38
+ type ShellPathService, type FeatureFlagManager, createFeatureFlagManager, PolicyRuntimeState,
39
+ } from '@/runtime/index.ts';
36
40
  import { VoiceProviderRegistry, VoiceService, ensureBuiltinVoiceProviders } from '@pellux/goodvibes-sdk/platform/voice';
37
41
  import { WebSearchProviderRegistry, WebSearchService } from '@pellux/goodvibes-sdk/platform/web-search';
38
42
  import { PanelManager } from '../panels/panel-manager.ts';
@@ -48,18 +52,11 @@ import { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp';
48
52
  import { BenchmarkStore, CacheHitTracker, FavoritesStore, inferFallbackContextWindow, type ModelDefinition, ModelLimitsService, ProviderCapabilityRegistry, ProviderOptimizer, ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
49
53
  import { KeybindingsManager } from '../input/keybindings.ts';
50
54
  import { AdaptivePlanner, DeterministicReplayEngine, ExecutionPlanManager, SessionLineageTracker, SessionMemoryStore } from '@pellux/goodvibes-sdk/platform/core';
51
- import { IdempotencyStore } from '@/runtime/index.ts';
52
- import { ComponentHealthMonitor } from '@/runtime/index.ts';
53
- import { WorktreeRegistry } from '@/runtime/index.ts';
54
- import { SandboxSessionRegistry } from '@/runtime/index.ts';
55
- import { createShellPathService, type ShellPathService } from '@/runtime/index.ts';
56
55
  import { isFeatureFlagEnabled } from './surface-feature-flags.ts';
57
- import type { FeatureFlagManager } from '@/runtime/index.ts';
58
- import { createFeatureFlagManager } from '@/runtime/index.ts';
59
- import { PolicyRuntimeState } from '@/runtime/index.ts';
60
56
  import { type ArchivableProcessRegistry } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
61
57
  import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
62
58
  import { createWorkstreamServices, type OrchestrationEngine, type WorkstreamCommandService } from './workstream-services.ts';
59
+ import { wireFleetNeedsInputPush } from './fleet-needs-input-push.ts';
63
60
  import { codeIndexDbPath, createCodeIndexServices, isCodeInjectionSettingEnabled } from './code-index-services.ts';
64
61
  import { WorkPlanStore } from '../work-plans/work-plan-store.ts';
65
62
  import {
@@ -75,6 +72,7 @@ import { registerDraftMethods } from '../daemon/handlers/drafts/index.ts';
75
72
  import { registerCalendar } from '../daemon/handlers/calendar/index.ts';
76
73
  import { registerEmailMethods } from '../daemon/handlers/email/index.ts';
77
74
  import { registerRemoteSurface } from '../daemon/handlers/remote/index.ts';
75
+ import { WorkspaceTrustManager } from './trust/workspace-trust.ts';
78
76
 
79
77
  const REGULAR_KNOWLEDGE_DB_FILE = 'knowledge-wiki.sqlite';
80
78
  const HOME_GRAPH_KNOWLEDGE_DB_FILE = 'knowledge-home-graph.sqlite';
@@ -136,6 +134,8 @@ export interface RuntimeServicesOptions {
136
134
  readonly getConversationTitle?: () => string | undefined;
137
135
  readonly workingDir: string;
138
136
  readonly homeDirectory: string;
137
+ /** Prior GoodVibes runtime state existed at startup — sampled before any write; seeds the trust gate's grandfathering. */
138
+ readonly workspaceHadPriorState?: boolean;
139
139
  }
140
140
 
141
141
  export interface RuntimeServices {
@@ -172,6 +172,7 @@ export interface RuntimeServices {
172
172
  readonly memorySpine: MemorySpineClient;
173
173
  readonly serviceRegistry: ServiceRegistry;
174
174
  readonly secretsManager: SecretsManager;
175
+ readonly stepUpService: StepUpService;
175
176
  readonly subscriptionManager: SubscriptionManager;
176
177
  readonly localUserAuthManager: UserAuthManager;
177
178
  readonly profileManager: ProfileManager;
@@ -224,6 +225,7 @@ export interface RuntimeServices {
224
225
  readonly agentManager: AgentManager;
225
226
  readonly agentMessageBus: AgentMessageBus;
226
227
  readonly agentOrchestrator: AgentOrchestrator;
228
+ readonly contextAccountingHolder: ContextAccountingHolder; // bound at bootstrap.ts; see context-accounting-source.ts
227
229
  readonly wrfcController: WrfcController;
228
230
  readonly processManager: ProcessManager;
229
231
  /** The phase/work-item orchestration engine — see runtime/workstream-services.ts. */
@@ -237,6 +239,8 @@ export interface RuntimeServices {
237
239
  readonly modeManager: ModeManager;
238
240
  readonly fileUndoManager: FileUndoManager;
239
241
  readonly workspaceCheckpointManager: WorkspaceCheckpointManager;
242
+ /** Per-workspace trust gate — restricts write/execute/delegate tools until the workspace is trusted. */
243
+ readonly workspaceTrustManager: WorkspaceTrustManager;
240
244
  readonly integrationHelpers: IntegrationHelperService;
241
245
  /** Re-root path-bound stores (MemoryStore, ProjectIndex) to a new working directory, called by WorkspaceSwapManager after verification; stores needing a process restart just warn-log and keep serving the old path until the daemon restarts with the new --working-dir. */
242
246
  rerootStores(newWorkingDir: string): Promise<void>;
@@ -249,6 +253,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
249
253
  workingDirectory,
250
254
  homeDirectory,
251
255
  });
256
+ const workspaceTrustManager = new WorkspaceTrustManager({ shellPaths, hadPriorState: options.workspaceHadPriorState ?? false });
252
257
  const configManager = options.configManager;
253
258
  const featureFlags = options.featureFlags ?? createFeatureFlagManager();
254
259
  const runtimeDispatch = createDomainDispatch(options.runtimeStore);
@@ -273,9 +278,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
273
278
  globalHome: homeDirectory,
274
279
  configManager,
275
280
  });
276
- const subscriptionManager = new SubscriptionManager(
277
- shellPaths.resolveUserPath('tui', 'subscriptions.json'),
278
- );
281
+ // Step-up (WebAuthn) ceremony service, shared between the ceremony gateway verbs and the relay gate's verifier.
282
+ const stepUpService = new StepUpService({ secrets: secretsManager });
283
+ const subscriptionManager = new SubscriptionManager(shellPaths.resolveUserPath('tui', 'subscriptions.json'));
279
284
  const serviceRegistry = new ServiceRegistry(shellPaths.resolveProjectPath('tui', 'services.json'), {
280
285
  secretsManager,
281
286
  subscriptionManager,
@@ -337,6 +342,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
337
342
  executor: agentOrchestrator,
338
343
  configManager,
339
344
  });
345
+ const contextAccountingHolder = new ContextAccountingHolder();
340
346
  agentOrchestrator.setConversationSink({ // Conversation-snapshot bridge (mirrors the SDK's own createRuntimeServices)
341
347
  register: (agentId, source) => agentManager.registerConversationSource(agentId, source),
342
348
  release: (agentId) => agentManager.releaseConversationSource(agentId),
@@ -629,23 +635,13 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
629
635
  });
630
636
  const modeManager = new ModeManager();
631
637
  const fileUndoManager = new FileUndoManager();
632
- const workspaceCheckpointManager = new WorkspaceCheckpointManager({
633
- workspaceRoot: workingDirectory,
634
- runtimeBus: options.runtimeBus,
635
- });
636
- // Fire-and-forget: subscriptions go live immediately if init() succeeds.
637
- // If it rejects, WorkspaceCheckpointManager caches that rejection on
638
- // `initPromise` and never clears it, so every later call (create/list/
639
- // diff/restore — each awaits init() first) re-throws the same error
640
- // forever: checkpointing becomes entirely unavailable for the rest of the
641
- // session, not "degraded to manual-only". The `.catch(() => {})` here only
642
- // exists to prevent an unhandled rejection at startup; the checkpoint
643
- // commands (checkpoint-runtime.ts) are what actually catch and report the
644
- // failure to the user, on first use.
645
- void workspaceCheckpointManager.init().catch(() => {});
638
+ const workspaceCheckpointManager = createWorkspaceCheckpointing({ workspaceRoot: workingDirectory, runtimeBus: options.runtimeBus, configManager });
646
639
 
647
- // ws-only verbs (fleet/checkpoints/search/push) 501 without this — see gateway-verbs.ts.
648
- attachWsOnlyGatewayVerbHandlers(gatewayMethods, { processRegistry, workspaceCheckpointManager, sessionBroker, secretsManager, approvalBroker, shellPaths });
640
+ // Shared terminal-shell wrapper over the SDK's registerGatewayVerbGroups (see gateway-verbs.ts): 501s the ws-only
641
+ // family without it. principals.*/channels.profiles.*/ci.* register unconditionally; checkin.* only with
642
+ // channelDeliveryRouter/providerRegistry/automationManager/sessionLister ALL present; fleet needs-input push gated by
643
+ // runtimeBus/sessionPresence (fleet-needs-input-push.ts). conversationRewindPort serves conversation-scope rewind live.
644
+ attachWsOnlyGatewayVerbHandlers(gatewayMethods, { processRegistry, workspaceCheckpointManager, conversationRewindPort: createSessionConversationRewindPort(), sessionBroker, secretsManager, stepUpService, approvalBroker, shellPaths, configManager, runtimeStore: options.runtimeStore, channelDeliveryRouter, providerRegistry, automationManager, sessionLister: sessionBroker, ...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }) });
649
645
  const integrationHelpers = new IntegrationHelperService({
650
646
  workingDirectory,
651
647
  homeDirectory,
@@ -692,12 +688,14 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
692
688
  overflowHandler,
693
689
  sandboxSessionRegistry,
694
690
  workflowServices: workflow,
691
+ contextAccountingHolder,
695
692
  });
696
693
 
697
694
  return {
698
695
  workingDirectory,
699
696
  homeDirectory,
700
697
  shellPaths,
698
+ workspaceTrustManager,
701
699
  configManager,
702
700
  featureFlags,
703
701
  runtimeBus: options.runtimeBus,
@@ -727,6 +725,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
727
725
  memorySpine,
728
726
  serviceRegistry,
729
727
  secretsManager,
728
+ stepUpService,
730
729
  subscriptionManager,
731
730
  localUserAuthManager,
732
731
  profileManager,
@@ -778,6 +777,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
778
777
  agentManager,
779
778
  agentMessageBus,
780
779
  agentOrchestrator,
780
+ contextAccountingHolder,
781
781
  wrfcController,
782
782
  processManager,
783
783
  orchestrationEngine,