@pellux/goodvibes-tui 1.16.1 → 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 (219) hide show
  1. package/CHANGELOG.md +89 -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/panel-composite.ts +2 -2
  167. package/src/renderer/panel-workspace-bar.ts +1 -1
  168. package/src/renderer/process-indicator.ts +1 -1
  169. package/src/renderer/selection-modal-overlay.ts +1 -1
  170. package/src/renderer/session-picker-modal.ts +2 -2
  171. package/src/renderer/settings-modal-helpers.ts +14 -5
  172. package/src/renderer/settings-modal.ts +127 -39
  173. package/src/renderer/shell-surface.ts +3 -0
  174. package/src/renderer/terminal-bg-probe.ts +1 -1
  175. package/src/renderer/theme.ts +3 -3
  176. package/src/renderer/tool-call.ts +1 -1
  177. package/src/renderer/tool-result-summary.ts +1 -1
  178. package/src/renderer/turn-injection.ts +7 -6
  179. package/src/renderer/ui-factory.ts +6 -1
  180. package/src/runtime/bootstrap-command-context.ts +4 -1
  181. package/src/runtime/bootstrap-command-parts.ts +6 -4
  182. package/src/runtime/bootstrap-core.ts +35 -35
  183. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  184. package/src/runtime/bootstrap-shell.ts +6 -5
  185. package/src/runtime/bootstrap.ts +34 -32
  186. package/src/runtime/channel-composition.ts +46 -0
  187. package/src/runtime/code-index-services.ts +3 -4
  188. package/src/runtime/daemon-attach-notices.ts +120 -0
  189. package/src/runtime/durability-services.ts +55 -0
  190. package/src/runtime/embedded-service-factories.ts +40 -0
  191. package/src/runtime/feature-settings.ts +147 -0
  192. package/src/runtime/index.ts +4 -21
  193. package/src/runtime/legacy-daemon-migration.ts +29 -19
  194. package/src/runtime/memory-fold.ts +1 -1
  195. package/src/runtime/notification-dispatch.ts +110 -0
  196. package/src/runtime/onboarding/apply.ts +3 -29
  197. package/src/runtime/onboarding/fast-path.ts +39 -6
  198. package/src/runtime/onboarding/provider-key-capture.ts +126 -0
  199. package/src/runtime/onboarding/snapshot.ts +0 -1
  200. package/src/runtime/onboarding/types.ts +9 -3
  201. package/src/runtime/provider-fallback.ts +55 -0
  202. package/src/runtime/provider-optimizer-wiring.ts +55 -0
  203. package/src/runtime/release-artifacts.ts +19 -103
  204. package/src/runtime/resume-notice.ts +1 -1
  205. package/src/runtime/services.ts +81 -82
  206. package/src/runtime/session-continuity-hints.ts +38 -0
  207. package/src/runtime/surface-feature-flags.ts +40 -9
  208. package/src/runtime/terminal-output-guard.ts +2 -2
  209. package/src/runtime/trust/workspace-registration.ts +10 -2
  210. package/src/runtime/trust/workspace-trust.ts +58 -31
  211. package/src/runtime/ui-services.ts +3 -1
  212. package/src/runtime/update-check.ts +15 -69
  213. package/src/runtime/workstream-services.ts +2 -2
  214. package/src/shell/blocking-input.ts +102 -6
  215. package/src/shell/ui-openers.ts +38 -6
  216. package/src/utils/format-duration.ts +1 -1
  217. package/src/verification/verification-ledger.ts +21 -4
  218. package/src/version.ts +1 -1
  219. package/src/input/commands/flags-runtime.ts +0 -328
@@ -7,15 +7,24 @@
7
7
  * - If a provider API key is already configured (env / secrets), skip
8
8
  * onboarding entirely — write the check marker and drop straight into a
9
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.
10
+ * - Otherwise ask exactly one question: start now on the default model, or
11
+ * run the full guided setup. Dismissing (Esc) starts now — so it is
12
+ * always skippable. The start-now copy is GENERATED from the default
13
+ * model's registered auth state (buildOnboardingModelCopy), so a
14
+ * "no API key needed" promise can only render when the provider is
15
+ * genuinely keyless-ready.
13
16
  *
14
17
  * The full wizard remains one keystroke away (the "Full guided setup" choice,
15
18
  * or `/onboarding` at any time). If the command context can't answer whether a
16
19
  * provider is configured (e.g. a headless/test surface), we fall back to the
17
20
  * classic behavior of opening the wizard, so nothing regresses.
18
21
  */
22
+ import {
23
+ buildOnboardingModelCopy,
24
+ resolveDefaultModelReadiness,
25
+ type ModelProviderSource,
26
+ type OnboardingModelCopy,
27
+ } from '@pellux/goodvibes-sdk/platform/providers';
19
28
  import type { CommandContext } from '../../input/command-registry.ts';
20
29
  import type { SelectionItem } from '../../input/selection-modal.ts';
21
30
  import { writeOnboardingCheckMarker } from './markers.ts';
@@ -43,6 +52,27 @@ function readConfiguredProviderIds(commandContext: CommandContext): string[] | n
43
52
 
44
53
  const MARKER_OPTIONS: WriteOnboardingCheckMarkerOptions = { source: 'command' };
45
54
 
55
+ /**
56
+ * Copy for the "start now" choice, GENERATED from the default model's live
57
+ * registration state (never hand-written): a "no API key needed" promise can
58
+ * only appear when the provider's own auth state proves keyless readiness.
59
+ * Surfaces that cannot answer (no registry / no getForModel) resolve to the
60
+ * honest 'unresolvable' copy rather than a fabricated keyless claim.
61
+ */
62
+ function resolveStartCopy(commandContext: CommandContext): OnboardingModelCopy {
63
+ const modelKey = String(commandContext.platform?.config?.provider?.model ?? '').trim();
64
+ const registry = commandContext.provider?.providerRegistry;
65
+ const source: ModelProviderSource =
66
+ registry && typeof (registry as Partial<ModelProviderSource>).getForModel === 'function'
67
+ ? registry
68
+ : {
69
+ getForModel: () => {
70
+ throw new Error('provider registry unavailable on this surface');
71
+ },
72
+ };
73
+ return buildOnboardingModelCopy(resolveDefaultModelReadiness(source, modelKey));
74
+ }
75
+
46
76
  /**
47
77
  * Run the fast-path first-run flow. Returns nothing; side effects (wizard open,
48
78
  * marker write, modal) happen through the injected deps.
@@ -68,8 +98,9 @@ export function startOnboardingFastPath(deps: OnboardingFastPathDeps): void {
68
98
  return;
69
99
  }
70
100
 
101
+ const startCopy = resolveStartCopy(commandContext);
71
102
  const items: SelectionItem[] = [
72
- { id: 'start-now', label: 'Start now', detail: 'Use the free default model — no API key needed', primaryAction: 'select' },
103
+ { id: 'start-now', label: startCopy.headline, detail: startCopy.detail, primaryAction: 'select' },
73
104
  { id: 'full-setup', label: 'Full guided setup', detail: 'Choose provider, model, channels, and more', primaryAction: 'select' },
74
105
  ];
75
106
  commandContext.openSelection(
@@ -82,10 +113,12 @@ export function startOnboardingFastPath(deps: OnboardingFastPathDeps): void {
82
113
  return;
83
114
  }
84
115
  // Start now (or dismissed): mark first-run done and drop into a working
85
- // session on the free default model.
116
+ // session on the default model. The status line reuses the generated
117
+ // copy so it can never promise keyless readiness the registration
118
+ // state does not prove.
86
119
  writeOnboardingCheckMarker(shellPaths, MARKER_OPTIONS);
87
120
  commandContext.print?.(
88
- 'You\'re on the free default model. Add a provider key anytime with /provider, or run /onboarding for full setup.',
121
+ `${startCopy.detail}. Add a provider key anytime with /provider, or run /onboarding for full setup.`,
89
122
  );
90
123
  render();
91
124
  },
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Provider-agnostic key capture for onboarding.
3
+ *
4
+ * The onboarding wizard offers an API-key field for every provider the registry
5
+ * knows accepts a key — derived from each provider's registration-time auth
6
+ * state (describeAuthState), never a hardcoded vendor list. This module holds
7
+ * the two shared pieces that keep the wizard step and the apply step in lockstep:
8
+ *
9
+ * 1. enrichProviderAccountsSnapshot — stamps each provider-account record with
10
+ * the secrets-store key it reads its API key from (its declared auth env
11
+ * var), read live from the provider registry.
12
+ * 2. deriveProviderKeyCaptureTargets / providerKeyFieldId — the single source
13
+ * of truth for which providers get a key field and how those fields are
14
+ * named, used by both the field builder and the apply writer.
15
+ */
16
+
17
+ import type { LLMProvider } from '@pellux/goodvibes-sdk/platform/providers';
18
+ import type {
19
+ OnboardingProviderAccountRecord,
20
+ OnboardingProviderAccountsSnapshot,
21
+ } from './types.ts';
22
+
23
+ /** Minimal provider lookup the enrichment needs (structural for testing). */
24
+ export interface ProviderKeyRegistryLike {
25
+ tryGet(name: string): LLMProvider | undefined;
26
+ }
27
+
28
+ /** Read a provider's declared API-key env var, or undefined when it accepts none. */
29
+ export function readProviderApiKeyEnvVar(
30
+ registry: ProviderKeyRegistryLike,
31
+ providerId: string,
32
+ ): string | undefined {
33
+ let provider: LLMProvider | undefined;
34
+ try {
35
+ provider = registry.tryGet(providerId);
36
+ } catch {
37
+ provider = undefined;
38
+ }
39
+ const envVars = provider?.describeAuthState?.().authEnvVars;
40
+ return envVars && envVars.length > 0 ? envVars[0] : undefined;
41
+ }
42
+
43
+ /**
44
+ * Return an equivalent snapshot with each provider record stamped with the
45
+ * secrets-store key it accepts an API key under (from live registration truth).
46
+ * Records for keyless / subscription-only providers keep apiKeyEnvVar absent.
47
+ */
48
+ export function enrichProviderAccountsSnapshot(
49
+ snapshot: OnboardingProviderAccountsSnapshot,
50
+ registry: ProviderKeyRegistryLike,
51
+ ): OnboardingProviderAccountsSnapshot {
52
+ return {
53
+ ...snapshot,
54
+ providers: snapshot.providers.map((record) => {
55
+ const apiKeyEnvVar = readProviderApiKeyEnvVar(registry, record.providerId);
56
+ return apiKeyEnvVar ? { ...record, apiKeyEnvVar } : record;
57
+ }),
58
+ };
59
+ }
60
+
61
+ /** A provider that onboarding can offer an API-key field for. */
62
+ export interface ProviderKeyCaptureTarget {
63
+ readonly providerId: string;
64
+ readonly apiKeyEnvVar: string;
65
+ readonly configured: boolean;
66
+ }
67
+
68
+ /**
69
+ * The providers onboarding offers an API-key field for: every registered
70
+ * provider that declares an api-key route AND carries a resolved auth env var.
71
+ * Ordered by provider id so the wizard renders deterministically.
72
+ */
73
+ export function deriveProviderKeyCaptureTargets(
74
+ providers: readonly OnboardingProviderAccountRecord[] | undefined,
75
+ ): ProviderKeyCaptureTarget[] {
76
+ if (!providers) return [];
77
+ return providers
78
+ .filter((record) => record.apiKeyEnvVar && record.availableRoutes.includes('api-key'))
79
+ .map((record) => ({
80
+ providerId: record.providerId,
81
+ apiKeyEnvVar: record.apiKeyEnvVar as string,
82
+ configured: record.configured,
83
+ }))
84
+ .sort((a, b) => a.providerId.localeCompare(b.providerId));
85
+ }
86
+
87
+ /** The wizard field id that captures a given provider's API key. */
88
+ export function providerKeyFieldId(providerId: string): string {
89
+ return `providers.api-key.${providerId}`;
90
+ }
91
+
92
+ /** A subscription-capable provider and its current subscription posture. */
93
+ export interface SubscriptionStatusTarget {
94
+ readonly providerId: string;
95
+ readonly active: boolean;
96
+ readonly pending: boolean;
97
+ }
98
+
99
+ /**
100
+ * The providers onboarding shows a subscription-status row for: every provider
101
+ * that declares a subscription route (registration truth), plus any provider
102
+ * with an active or pending subscription session — so a live session is never
103
+ * hidden just because its account record was absent. Ordered by provider id.
104
+ */
105
+ export function deriveSubscriptionStatusTargets(
106
+ providers: readonly OnboardingProviderAccountRecord[] | undefined,
107
+ activeProviderIds: readonly string[],
108
+ pendingProviderIds: readonly string[],
109
+ ): SubscriptionStatusTarget[] {
110
+ const active = new Set(activeProviderIds);
111
+ const pending = new Set(pendingProviderIds);
112
+ const ids = new Set<string>();
113
+ for (const record of providers ?? []) {
114
+ if (record.availableRoutes.includes('subscription')) ids.add(record.providerId);
115
+ }
116
+ for (const id of active) ids.add(id);
117
+ for (const id of pending) ids.add(id);
118
+ return [...ids]
119
+ .sort((a, b) => a.localeCompare(b))
120
+ .map((providerId) => ({ providerId, active: active.has(providerId), pending: pending.has(providerId) }));
121
+ }
122
+
123
+ /** The wizard field id of a provider's subscription-status row. */
124
+ export function subscriptionStatusFieldId(providerId: string): string {
125
+ return `providers.subscription.${providerId}`;
126
+ }
@@ -43,7 +43,6 @@ function buildConfigSnapshot(
43
43
  network: config.getCategory('network'),
44
44
  surfaces: config.getCategory('surfaces'),
45
45
  service: config.getCategory('service'),
46
- featureFlags: config.getCategory('featureFlags'),
47
46
  batch: config.getCategory('batch'),
48
47
  cloudflare: config.getCategory('cloudflare'),
49
48
  };
@@ -1,6 +1,5 @@
1
1
  import type { ConfigManager, ConfigKey, GoodVibesConfig } from '../../config/index.ts';
2
2
  import type { SecretsManager, SecretRecord, SecretStorageReview } from '../../config/secrets.ts';
3
- import type { FeatureFlagConfigKey } from '../surface-feature-flags.ts';
4
3
  import type { LocalAuthSnapshot, UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
5
4
  import type { ShellPathService } from '@/runtime/index.ts';
6
5
  import type {
@@ -46,7 +45,6 @@ export interface OnboardingConfigSnapshot {
46
45
  readonly network: GoodVibesConfig['network'];
47
46
  readonly surfaces: GoodVibesConfig['surfaces'];
48
47
  readonly service: GoodVibesConfig['service'];
49
- readonly featureFlags: GoodVibesConfig['featureFlags'];
50
48
  readonly batch: GoodVibesConfig['batch'];
51
49
  readonly cloudflare: GoodVibesConfig['cloudflare'];
52
50
  }
@@ -157,6 +155,14 @@ export interface OnboardingProviderAccountRecord {
157
155
  readonly availableRoutes: readonly string[];
158
156
  readonly activeRoute: string;
159
157
  readonly authFreshness: string;
158
+ /**
159
+ * The secrets-store key this provider reads its API key from — its declared
160
+ * auth env var, read from the live provider's registration-time auth state.
161
+ * Present only for providers that accept an API key; absent for keyless /
162
+ * subscription-only providers. Used to offer provider-agnostic key entry in
163
+ * onboarding, and as the storage key when the entered value is saved.
164
+ */
165
+ readonly apiKeyEnvVar?: string;
160
166
  }
161
167
 
162
168
  export interface OnboardingProviderAccountsSnapshot {
@@ -267,7 +273,7 @@ export interface OnboardingStepDerivationState {
267
273
  export type OnboardingApplyOperation =
268
274
  | {
269
275
  readonly kind: 'set-config';
270
- readonly key: ConfigKey | FeatureFlagConfigKey;
276
+ readonly key: ConfigKey;
271
277
  readonly value: unknown;
272
278
  readonly scope?: 'global' | 'project';
273
279
  }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * provider-fallback.ts — the pre-catalog fallback registration for the
3
+ * configured model, extracted verbatim from services.ts (file-size hygiene):
4
+ * before the model catalog cache has loaded, the configured provider:model is
5
+ * registered with SDK family-aware context-window inference so the meter and
6
+ * compaction denominator agree with the post-catalog window.
7
+ */
8
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
9
+ import { inferFallbackContextWindow, type ModelDefinition, type ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
10
+
11
+ function buildFallbackModelDefinition(provider: string, modelId: string): ModelDefinition {
12
+ const providerLower = provider.toLowerCase();
13
+ const isReasoningProvider = providerLower.includes('openai')
14
+ || providerLower.includes('anthropic')
15
+ || providerLower.includes('gemini')
16
+ || providerLower.includes('google');
17
+
18
+ return {
19
+ id: modelId,
20
+ provider,
21
+ registryKey: `${provider}:${modelId}`,
22
+ displayName: modelId,
23
+ description: 'Configured model available before the model catalog cache has loaded.',
24
+ capabilities: {
25
+ toolCalling: true,
26
+ codeEditing: true,
27
+ reasoning: isReasoningProvider,
28
+ multimodal: isReasoningProvider,
29
+ },
30
+ contextWindow: inferFallbackContextWindow(provider, modelId),
31
+ contextWindowProvenance: 'fallback',
32
+ selectable: true,
33
+ tier: 'standard',
34
+ ...(isReasoningProvider ? { reasoningEffort: ['instant', 'low', 'medium', 'high'] } : {}),
35
+ };
36
+ }
37
+
38
+ export function ensureConfiguredModelIsRoutable(providerRegistry: ProviderRegistry, configManager: ConfigManager): void {
39
+ const configuredModel = String(configManager.get('provider.model') ?? '').trim();
40
+ if (!configuredModel.includes(':')) return;
41
+ if (providerRegistry.listModels().some((model) => model.registryKey === configuredModel)) return;
42
+
43
+ const [providerId, ...modelParts] = configuredModel.split(':');
44
+ const modelId = modelParts.join(':').trim();
45
+ if (!providerId || !modelId) return;
46
+
47
+ const provider = providerRegistry.tryGet(providerId);
48
+ if (!provider) return;
49
+
50
+ providerRegistry.registerRuntimeProvider({
51
+ provider,
52
+ replace: true,
53
+ models: [buildFallbackModelDefinition(providerId, modelId)],
54
+ });
55
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * provider-optimizer-wiring — bind the provider optimizer to its capability
3
+ * gate and seed its persisted routing mode at startup.
4
+ *
5
+ * Mirrors the SDK composition root's provider-optimizer-wiring module (which
6
+ * the SDK does not export through a public seam) so this repo's composition
7
+ * fork cannot drift from it silently: the optimizer is constructed dark, the
8
+ * gate subscription flips it live, and provider.optimizerMode seeds the mode.
9
+ */
10
+
11
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
12
+ import type { ProviderOptimizer } from '@pellux/goodvibes-sdk/platform/providers';
13
+ import type { FeatureFlagManager } from '@/runtime/index.ts';
14
+
15
+ export function bindProviderOptimizerFeatureFlag(
16
+ featureFlags: Pick<FeatureFlagManager, 'isEnabled' | 'subscribe'>,
17
+ providerOptimizer: Pick<ProviderOptimizer, 'setEnabled'>,
18
+ ): () => void {
19
+ providerOptimizer.setEnabled(featureFlags.isEnabled('provider-optimizer'));
20
+ return featureFlags.subscribe((flagId, state) => {
21
+ if (flagId === 'provider-optimizer') {
22
+ providerOptimizer.setEnabled(state === 'enabled');
23
+ }
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Apply the persistent provider-optimizer routing mode from config at startup.
29
+ * provider.optimizerMode 'off' keeps the optimizer inactive (its gate derives
30
+ * from the same key); this only seeds the mode/pin so an operator can persist
31
+ * "auto" or a pinned model without re-issuing a /provider command each
32
+ * session. Runtime pin/unpin/setMode still override for the live session.
33
+ */
34
+ export function applyProviderOptimizerConfigMode(
35
+ configManager: Pick<ConfigManager, 'get'>,
36
+ providerOptimizer: Pick<ProviderOptimizer, 'setMode' | 'pin'>,
37
+ ): void {
38
+ const mode = configManager.get('provider.optimizerMode');
39
+ if (mode === 'off') {
40
+ providerOptimizer.setMode('manual'); // inert baseline while inactive
41
+ return;
42
+ }
43
+ if (mode === 'pinned') {
44
+ const pinned = configManager.get('provider.optimizerPinnedModel').trim();
45
+ const sep = pinned.indexOf(':');
46
+ if (sep > 0 && sep < pinned.length - 1) {
47
+ providerOptimizer.pin(pinned.slice(0, sep), pinned.slice(sep + 1));
48
+ } else {
49
+ // Pinned mode requested without a valid provider-qualified model — stay manual.
50
+ providerOptimizer.setMode('manual');
51
+ }
52
+ } else {
53
+ providerOptimizer.setMode(mode);
54
+ }
55
+ }
@@ -1,109 +1,25 @@
1
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:
2
+ * Release-artifact naming and checksum verification — re-exported from the
3
+ * SDK's canonical update policy module (platform/runtime/self-update), which
4
+ * was hoisted from this file's semantics. One mechanism everywhere: the
5
+ * asset naming convention (`goodvibes[-daemon]-{platform}-{arch}`), the
6
+ * sqlite-vec addon naming, and the "no manifest entry is as fatal as a
7
+ * mismatch" verification rule now have a single owner. Consumers that must
8
+ * stay in lockstep:
8
9
  * - 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.
10
+ * - src/input/commands/update-runtime.ts (the /update self-update)
11
+ * - scripts/install.sh (the shell installer; mirrors the same names)
14
12
  */
15
- import { createHash } from 'node:crypto';
16
-
17
- export const CHECKSUM_MANIFEST_NAME = 'SHA256SUMS.txt';
13
+ export {
14
+ CHECKSUM_MANIFEST_NAME,
15
+ parseChecksumFile,
16
+ resolveArtifactNames,
17
+ resolveSqliteVecAsset,
18
+ sha256,
19
+ verifyChecksum,
20
+ type ReleaseArtifactNames,
21
+ type SqliteVecAsset,
22
+ } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
18
23
 
19
24
  export type SupportedPlatform = 'linux' | 'darwin';
20
25
  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 interface SqliteVecAsset {
46
- /** Release asset filename, e.g. `sqlite-vec-linux-x64.so`. */
47
- readonly assetName: string;
48
- /** Directory name the SDK loader resolves, e.g. `sqlite-vec-linux-x64`. */
49
- readonly dirName: string;
50
- /** File the loader opens inside that directory, e.g. `vec0.so`. */
51
- readonly fileName: string;
52
- }
53
-
54
- /**
55
- * Names the sqlite-vec native addon for a platform/arch. Unlike the binaries
56
- * (whose release tag maps darwin→"macos"), the addon keeps the Node-style
57
- * platform tag ("linux" | "darwin") because that is exactly what the SDK's
58
- * loader resolves at `<execDir>/lib/sqlite-vec-<process.platform>-<process.arch>/vec0.<suffix>`.
59
- * The scripts/install.sh installer and the release workflow name the asset the
60
- * same way; all three must stay in lockstep.
61
- */
62
- export function resolveSqliteVecAsset(platform: string, arch: string): SqliteVecAsset | null {
63
- if ((platform !== 'linux' && platform !== 'darwin') || (arch !== 'x64' && arch !== 'arm64')) {
64
- return null;
65
- }
66
- const suffix = platform === 'darwin' ? 'dylib' : 'so';
67
- const dirName = `sqlite-vec-${platform}-${arch}`;
68
- return {
69
- assetName: `${dirName}.${suffix}`,
70
- dirName,
71
- fileName: `vec0.${suffix}`,
72
- };
73
- }
74
-
75
- export function sha256(buffer: Buffer | Uint8Array): string {
76
- return createHash('sha256').update(buffer).digest('hex');
77
- }
78
-
79
- export function parseChecksumFile(contents: string): Map<string, string> {
80
- const checksums = new Map<string, string>();
81
- for (const rawLine of contents.split(/\r?\n/)) {
82
- const line = rawLine.trim();
83
- if (!line) continue;
84
- const match = line.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
85
- if (!match) continue;
86
- checksums.set(match[2], match[1].toLowerCase());
87
- }
88
- return checksums;
89
- }
90
-
91
- /**
92
- * Verify a downloaded artifact's checksum against the parsed manifest.
93
- * An artifact with no entry in the manifest is a hard failure, identical
94
- * in severity to a mismatching entry — never treated as "unverifiable, so
95
- * skip the check". Throws naming the artifact and the manifest.
96
- */
97
- export function verifyChecksum(
98
- artifactName: string,
99
- actual: string,
100
- expected: string | undefined,
101
- manifestName: string = CHECKSUM_MANIFEST_NAME,
102
- ): void {
103
- if (expected === undefined) {
104
- throw new Error(`no checksum entry for ${artifactName} in ${manifestName} — refusing to install an unverified binary`);
105
- }
106
- if (expected !== actual) {
107
- throw new Error(`checksum mismatch for ${artifactName}: expected ${expected}, got ${actual}`);
108
- }
109
- }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * resume-notice.ts — the boot-time "previous session found" transcript notice.
3
3
  *
4
- * UX-D item 1: a supervision-journey audit of 1.7.0 found that the TUI
4
+ * item 1: a supervision-journey audit of 1.7.0 found that the TUI
5
5
  * accumulates rich resumable state on disk (a saved conversation, workspace
6
6
  * checkpoints, WRFC chain history) but never surfaces any of it at startup —
7
7
  * an operator has no way to know it exists short of already knowing the