@pellux/goodvibes-tui 1.1.0 → 1.9.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.
- package/CHANGELOG.md +85 -21
- package/README.md +28 -18
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +90 -14
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +104 -51
- package/src/core/turn-cancellation.ts +25 -0
- package/src/core/turn-event-wiring.ts +72 -3
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/export/cost-utils.ts +36 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +84 -5
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +232 -0
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +10 -2
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +73 -35
- package/src/input/commands/operator-panel-runtime.ts +71 -31
- package/src/input/commands/planning-runtime.ts +116 -4
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +26 -6
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +16 -4
- package/src/input/commands/shell-core.ts +25 -8
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +488 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +175 -0
- package/src/input/config-modal.ts +592 -0
- package/src/input/feed-context-factory.ts +10 -8
- package/src/input/handler-command-route.ts +37 -20
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +74 -114
- package/src/input/handler-feed.ts +79 -27
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +136 -6
- package/src/input/handler-modal-stack.ts +14 -7
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +26 -102
- package/src/input/handler-shortcuts.ts +74 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +12 -29
- package/src/input/keybindings.ts +23 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +77 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +146 -2
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +49 -51
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +18 -46
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +758 -0
- package/src/panels/fleet-read-model.ts +520 -0
- package/src/panels/fleet-steer.ts +125 -0
- package/src/panels/fleet-stop.ts +153 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +166 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +42 -0
- package/src/panels/modals/pairing-modal.ts +163 -0
- package/src/panels/modals/planning-modal.ts +304 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +177 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +123 -9
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -11
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +37 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +216 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +95 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +15 -3
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +43 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +62 -4
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +133 -0
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +27 -1
- package/src/runtime/bootstrap-command-parts.ts +41 -10
- package/src/runtime/bootstrap-core.ts +47 -15
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +48 -17
- package/src/runtime/bootstrap.ts +129 -22
- package/src/runtime/code-index-services.ts +135 -0
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +26 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +59 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +98 -46
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +27 -3
- package/src/runtime/workstream-services.ts +327 -0
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +68 -4
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +171 -26
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -36,12 +36,17 @@ import {
|
|
|
36
36
|
import { scheduleBackgroundMcpDiscovery, startBackgroundProviderRegistration } from '@/runtime/index.ts';
|
|
37
37
|
import { restoreSavedModel } from '@/runtime/index.ts';
|
|
38
38
|
import { startExternalServices, type ExternalServicesHandle, type HostServiceStatus } from '@/runtime/index.ts';
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
39
|
+
import { createHttpTransport } from '@/runtime/index.ts';
|
|
40
|
+
import { foldLegacySpineStore, deriveSpineFooterStatus } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
41
|
+
import { createTuiSpineTransport, type SpineSessionsClient } from './session-spine-transport.ts';
|
|
42
|
+
import { pruneStaleOperatorTokens } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
43
|
+
import { resolveDaemonCompanionToken, workspaceOperatorTokenCandidates } from './operator-token-cleanup.ts';
|
|
41
44
|
import type { UiRuntimeServices } from './ui-services.ts';
|
|
42
45
|
import { createDeferredStartupCoordinator } from '@/runtime/index.ts';
|
|
43
46
|
import { initializeBootstrapCore } from './bootstrap-core.ts';
|
|
44
47
|
import { createBootstrapShell } from './bootstrap-shell.ts';
|
|
48
|
+
import { announceResumeState } from './resume-notice.ts';
|
|
49
|
+
import { buildSharedOrchestratorCoreServices } from './orchestrator-core-services.ts';
|
|
45
50
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
46
51
|
import { DaemonServer } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
47
52
|
import { HttpListener } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
@@ -110,19 +115,15 @@ export type BootstrapContext = RuntimeContext & {
|
|
|
110
115
|
/** Command registry used by InputHandler. main.ts needs this to wire input. */
|
|
111
116
|
commandRegistry: import('../input/command-registry.ts').CommandRegistry;
|
|
112
117
|
/**
|
|
113
|
-
* System message router instantiated at startup, wired to conversation
|
|
118
|
+
* System message router instantiated at startup, wired to conversation.
|
|
114
119
|
*
|
|
115
120
|
* @remarks
|
|
116
121
|
* Route operational messages through this rather than calling
|
|
117
|
-
* conversation.addSystemMessage() directly so
|
|
118
|
-
*
|
|
122
|
+
* conversation.addSystemMessage() directly so routing-target config
|
|
123
|
+
* (panel/conversation/both) and the forced-inline critical prefixes stay
|
|
124
|
+
* centralized in one place.
|
|
119
125
|
*/
|
|
120
126
|
systemMessageRouter: SystemMessageRouter;
|
|
121
|
-
/**
|
|
122
|
-
* Wire the agent detail modal opener after InputHandler is constructed in main.ts.
|
|
123
|
-
* Call with `(id) => input.agentDetailModal.open(id)` once the InputHandler is ready.
|
|
124
|
-
*/
|
|
125
|
-
setOpenAgentDetail: (fn: (agentId: string) => void) => void;
|
|
126
127
|
};
|
|
127
128
|
|
|
128
129
|
// ── Bootstrap function ────────────────────────────────────────────────────
|
|
@@ -186,6 +187,9 @@ export async function bootstrapRuntime(
|
|
|
186
187
|
setRenderRequest,
|
|
187
188
|
runtimeSessionIdRef,
|
|
188
189
|
wrfcPersistence,
|
|
190
|
+
sessionSpine,
|
|
191
|
+
sessionInboundInputs,
|
|
192
|
+
sessionUnionCache,
|
|
189
193
|
} = await initializeBootstrapCore(stdout, options, (limit) => controlPlaneRecentEventsRef.value(limit));
|
|
190
194
|
const providerRegistry = services.providerRegistry;
|
|
191
195
|
const {
|
|
@@ -195,6 +199,11 @@ export async function bootstrapRuntime(
|
|
|
195
199
|
pluginManager,
|
|
196
200
|
} = services;
|
|
197
201
|
|
|
202
|
+
// D4b: a liveness flip is only PAINTED once something calls requestRender();
|
|
203
|
+
// without this, the footer's spine segment sat correct-but-undrawn until
|
|
204
|
+
// incidental activity redrew it (minutes, during an idle stretch).
|
|
205
|
+
sessionUnionCache.setOnTransition(() => requestRender());
|
|
206
|
+
|
|
198
207
|
// ── Phase 6: Orchestrator + AcpManager ───────────────────────────────────
|
|
199
208
|
|
|
200
209
|
// Mutable function refs so main.ts can patch these after constructing the scroll/viewport state.
|
|
@@ -235,15 +244,11 @@ export async function bootstrapRuntime(
|
|
|
235
244
|
logger.debug('companion handleUserInput safety catch', { error: String(err) });
|
|
236
245
|
});
|
|
237
246
|
};
|
|
247
|
+
// Shared payload (single source of truth, includes wo805's memoryRegistry —
|
|
248
|
+
// see orchestrator-core-services.ts) plus this site's cacheHitTracker.
|
|
238
249
|
orchestrator.setCoreServices({
|
|
239
|
-
configManager,
|
|
240
|
-
providerRegistry,
|
|
250
|
+
...buildSharedOrchestratorCoreServices({ services, configManager, providerRegistry }),
|
|
241
251
|
cacheHitTracker: services.cacheHitTracker,
|
|
242
|
-
planManager: services.planManager,
|
|
243
|
-
adaptivePlanner: services.adaptivePlanner,
|
|
244
|
-
sessionMemoryStore: services.sessionMemoryStore,
|
|
245
|
-
sessionLineageTracker: services.sessionLineageTracker,
|
|
246
|
-
idempotencyStore: services.idempotencyStore,
|
|
247
252
|
});
|
|
248
253
|
conversation.setSessionLineageTracker(services.sessionLineageTracker);
|
|
249
254
|
|
|
@@ -269,6 +274,7 @@ export async function bootstrapRuntime(
|
|
|
269
274
|
runtimeBus,
|
|
270
275
|
runtimeStore: store,
|
|
271
276
|
services,
|
|
277
|
+
sessionSpine,
|
|
272
278
|
conversation,
|
|
273
279
|
runtime,
|
|
274
280
|
orchestrator,
|
|
@@ -294,10 +300,31 @@ export async function bootstrapRuntime(
|
|
|
294
300
|
wrfcPersistence.rehydrate();
|
|
295
301
|
const commandRegistry = shell.commandRegistry;
|
|
296
302
|
const commandContext = shell.commandContext;
|
|
303
|
+
// Boot resume notice (UX-D item 1): after rehydrate() so chain history is
|
|
304
|
+
// ready, before the operator can type anything. Fire-and-forget — the
|
|
305
|
+
// checkpoint-count lookup is async (WorkspaceCheckpointManager.list() awaits
|
|
306
|
+
// its own init()), and this must not block the rest of bootstrap the way
|
|
307
|
+
// main.ts's `void workspaceCheckpointManager.init().catch(() => {})`
|
|
308
|
+
// deliberately doesn't either. Local file I/O only; resolves well before a
|
|
309
|
+
// human can react to the first rendered frame.
|
|
310
|
+
void announceResumeState({
|
|
311
|
+
workingDirectory: services.workingDirectory,
|
|
312
|
+
homeDirectory: services.homeDirectory,
|
|
313
|
+
surfaceRoot: 'tui',
|
|
314
|
+
sessionManager: services.sessionManager,
|
|
315
|
+
checkpointManager: services.workspaceCheckpointManager,
|
|
316
|
+
chainHistory: wrfcPersistence.knownChains,
|
|
317
|
+
memoryAvailable: Boolean(commandContext.clients?.knowledgeApi?.memory),
|
|
318
|
+
router: systemMessageRouter,
|
|
319
|
+
}).catch(() => {
|
|
320
|
+
// Best-effort — never let the resume notice block or crash boot.
|
|
321
|
+
});
|
|
297
322
|
const gitStatusProvider = shell.gitStatusProvider;
|
|
298
323
|
const inputHistory = shell.inputHistory;
|
|
299
324
|
const lastGitInfoRef = shell.lastGitInfoRef;
|
|
300
|
-
|
|
325
|
+
// W1.6 FIX 2: dispose the header's live-repo-state poll (git-status.ts
|
|
326
|
+
// startPolling) on shutdown, same pattern as acpTaskSyncInterval above.
|
|
327
|
+
bootstrapUnsubs.push(() => gitStatusProvider.stopPolling());
|
|
301
328
|
const pluginCommandRegistry = {
|
|
302
329
|
register(command: {
|
|
303
330
|
readonly name: string;
|
|
@@ -348,7 +375,70 @@ export async function bootstrapRuntime(
|
|
|
348
375
|
|
|
349
376
|
const hostServiceIsActive = (status: HostServiceStatus): boolean => status.mode === 'embedded' || status.mode === 'external';
|
|
350
377
|
|
|
351
|
-
|
|
378
|
+
// 'blocked' (occupied by an unverified process) and 'incompatible' (occupied
|
|
379
|
+
// by a GoodVibes daemon we refused to adopt on a wire-version mismatch) both
|
|
380
|
+
// mean the configured port is held and unusable by this TUI instance.
|
|
381
|
+
const hostServiceIsBlocked = (status: HostServiceStatus): boolean => status.mode === 'blocked' || status.mode === 'incompatible';
|
|
382
|
+
|
|
383
|
+
// PERMANENT DESIGN (docs/decisions/2026-07-06-session-spine-mode-branch-is-permanent.md):
|
|
384
|
+
// 'embedded'/'external' are two distinct, both-supported daemon topologies, not
|
|
385
|
+
// migration stages — the ONE client-selection point for the session spine, driven by
|
|
386
|
+
// the SAME authoritative HostServiceMode adopt-or-start already computed above.
|
|
387
|
+
// 'embedded' (daemon.embedInProcess=true) means THIS process's own SharedSessionBroker
|
|
388
|
+
// already IS the daemon's broker — nothing to mirror TO, so the spine stays
|
|
389
|
+
// permanently dormant. Every other mode ('disabled'/'blocked'/'incompatible'/
|
|
390
|
+
// 'unavailable') also stays local-only and honest. Only 'external' (a separately-
|
|
391
|
+
// running daemon this TUI adopted) activates the wire mirror.
|
|
392
|
+
let spineActiveForBaseUrl: string | null = null;
|
|
393
|
+
const syncSessionSpineToHostStatus = (daemonStatus: HostServiceStatus, sharedDaemonToken: string): void => {
|
|
394
|
+
if (daemonStatus.mode !== 'external') {
|
|
395
|
+
if (spineActiveForBaseUrl !== null) {
|
|
396
|
+
sessionSpine.deactivate(`daemon mode changed to '${daemonStatus.mode}'`);
|
|
397
|
+
sessionInboundInputs.deactivate(`daemon mode changed to '${daemonStatus.mode}'`);
|
|
398
|
+
spineActiveForBaseUrl = null;
|
|
399
|
+
}
|
|
400
|
+
// Keep the read facade honest per topology (permanent, not staged): 'embedded'
|
|
401
|
+
// means this process's broker IS the daemon's broker (local reads are the whole
|
|
402
|
+
// truth); every other non-external mode is local-only/dormant.
|
|
403
|
+
if (daemonStatus.mode === 'embedded') sessionUnionCache.markEmbedded();
|
|
404
|
+
else sessionUnionCache.deactivate(`daemon mode '${daemonStatus.mode}'`);
|
|
405
|
+
logger.info(`[bootstrap] session spine: daemon mode '${daemonStatus.mode}' — local-only (no spine mirror)`);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const baseUrl = daemonStatus.baseUrl;
|
|
409
|
+
if (spineActiveForBaseUrl === baseUrl) return; // already wired to this exact adopted daemon
|
|
410
|
+
const httpTransport = createHttpTransport({ baseUrl, authToken: sharedDaemonToken });
|
|
411
|
+
const sessionsClient: SpineSessionsClient = {
|
|
412
|
+
register: (input) => httpTransport.operator.sessions.register(input),
|
|
413
|
+
close: (sessionId) => httpTransport.operator.sessions.close(sessionId),
|
|
414
|
+
};
|
|
415
|
+
sessionSpine.activate(createTuiSpineTransport(sessionsClient));
|
|
416
|
+
// D3: adopt the same daemon's wire for the INBOUND steer path — collect
|
|
417
|
+
// steer/follow-up inputs another live surface queued for this session and
|
|
418
|
+
// inject them into the turn machinery (acking delivery on the wire).
|
|
419
|
+
sessionInboundInputs.activate({
|
|
420
|
+
listInputs: async (sessionId, opts) => ({
|
|
421
|
+
inputs: await httpTransport.operator.sessions.inputs(sessionId, opts.limit, { state: opts.state, since: opts.since }),
|
|
422
|
+
}),
|
|
423
|
+
deliverInput: (sessionId, inputId, opts) => httpTransport.operator.sessions.deliverInput(sessionId, inputId, opts),
|
|
424
|
+
});
|
|
425
|
+
// 'external' only: adopt the same daemon's wire as the read facade's cross-surface
|
|
426
|
+
// union source (interval-refreshed; served synchronously to panels).
|
|
427
|
+
sessionUnionCache.activate({ list: (limit) => httpTransport.operator.sessions.list(limit) });
|
|
428
|
+
spineActiveForBaseUrl = baseUrl;
|
|
429
|
+
logger.info(`[bootstrap] session spine: adopted external daemon at ${baseUrl} — mirroring session identity`);
|
|
430
|
+
// Legacy fold: one-time (marker-guarded) import of this project's own
|
|
431
|
+
// pre-spine control-plane sessions.json into the now-adopted daemon.
|
|
432
|
+
const legacyStorePath = services.shellPaths.resolveProjectPath('tui', 'control-plane', 'sessions.json');
|
|
433
|
+
const fold = foldLegacySpineStore(sessionSpine, {
|
|
434
|
+
storePath: legacyStorePath,
|
|
435
|
+
markerPath: `${legacyStorePath}.spine-migrated`,
|
|
436
|
+
project: services.workingDirectory,
|
|
437
|
+
});
|
|
438
|
+
if (fold.folded > 0) {
|
|
439
|
+
logger.info(`[bootstrap] session spine: folded ${fold.folded} legacy local session(s) into the adopted daemon`);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
352
442
|
|
|
353
443
|
const inspectExternalServices = () => {
|
|
354
444
|
const daemonStatus = externalServices.daemonStatus;
|
|
@@ -360,6 +450,16 @@ export async function bootstrapRuntime(
|
|
|
360
450
|
httpListenerPortInUse: hostServiceIsBlocked(httpListenerStatus),
|
|
361
451
|
daemonStatus,
|
|
362
452
|
httpListenerStatus,
|
|
453
|
+
// Honest session-spine posture, independent of daemonRunning —
|
|
454
|
+
// 'external'-adopted-but-currently-unreachable degrades to 'offline' here
|
|
455
|
+
// even though daemonRunning might still read true from a stale handle.
|
|
456
|
+
// D4: sessionSpine.status() alone is ACTIVITY-gated (only updates on a
|
|
457
|
+
// register/heartbeat/close), so after the daemon dies mid-idle it would
|
|
458
|
+
// keep reading 'online'. Derive the footer status from the union cache's
|
|
459
|
+
// 5s liveness probe too — one signal, no new timer — so offline surfaces
|
|
460
|
+
// within one refresh interval of the daemon dying.
|
|
461
|
+
sessionSpineActive: sessionSpine.active,
|
|
462
|
+
sessionSpineStatus: deriveSpineFooterStatus(sessionSpine.status(), sessionUnionCache.crossSurfaceView),
|
|
363
463
|
};
|
|
364
464
|
};
|
|
365
465
|
|
|
@@ -418,7 +518,7 @@ export async function bootstrapRuntime(
|
|
|
418
518
|
await waitForConfigDrivenRestarts(externalServices);
|
|
419
519
|
await externalServices.stop();
|
|
420
520
|
const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
|
|
421
|
-
const companionTokenRecord =
|
|
521
|
+
const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir);
|
|
422
522
|
externalServicesPromise = startExternalServices(
|
|
423
523
|
configManager,
|
|
424
524
|
runtimeBus,
|
|
@@ -428,6 +528,7 @@ export async function bootstrapRuntime(
|
|
|
428
528
|
);
|
|
429
529
|
externalServices = await externalServicesPromise;
|
|
430
530
|
controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
|
|
531
|
+
syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
|
|
431
532
|
requestRender();
|
|
432
533
|
return inspectExternalServices();
|
|
433
534
|
},
|
|
@@ -466,7 +567,7 @@ export async function bootstrapRuntime(
|
|
|
466
567
|
// bearer, so tokens scanned from the /qrcode panel's QR actually
|
|
467
568
|
// authenticate against the embedded daemon this surface starts.
|
|
468
569
|
const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
|
|
469
|
-
const companionTokenRecord =
|
|
570
|
+
const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir);
|
|
470
571
|
// F3 resolution (TUI 0.19.20): remove stale pre-0.21.28 workspace-scoped operator
|
|
471
572
|
// token files so only the canonical <daemonHomeDir>/operator-tokens.json survives.
|
|
472
573
|
// The prune is best-effort — it silently skips missing files, no-ops when tokens
|
|
@@ -491,6 +592,7 @@ export async function bootstrapRuntime(
|
|
|
491
592
|
);
|
|
492
593
|
externalServices = await externalServicesPromise;
|
|
493
594
|
controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
|
|
595
|
+
syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
|
|
494
596
|
requestRender();
|
|
495
597
|
},
|
|
496
598
|
onError: (error) => {
|
|
@@ -628,7 +730,6 @@ export async function bootstrapRuntime(
|
|
|
628
730
|
_getConfiguredProviderIds: () => services.providerRegistry.getConfiguredProviderIds(),
|
|
629
731
|
commandRegistry,
|
|
630
732
|
systemMessageRouter,
|
|
631
|
-
setOpenAgentDetail,
|
|
632
733
|
shutdown: async (sessionData) => {
|
|
633
734
|
// Clear bootstrap-owned subscriptions
|
|
634
735
|
bootstrapUnsubs.forEach(fn => fn());
|
|
@@ -636,6 +737,12 @@ export async function bootstrapRuntime(
|
|
|
636
737
|
runtimeUnsubs.forEach((fn) => fn());
|
|
637
738
|
runtimeUnsubs.length = 0;
|
|
638
739
|
forensicsCollector.dispose();
|
|
740
|
+
// Honest close on exit — fire-and-forget (never blocks shutdown);
|
|
741
|
+
// a no-op when the spine was never activated (embedded/local-only topology).
|
|
742
|
+
sessionSpine.close(runtime.sessionId);
|
|
743
|
+
sessionSpine.dispose();
|
|
744
|
+
sessionInboundInputs.dispose(); // D3: stop the inbound steer poll interval on exit
|
|
745
|
+
sessionUnionCache.dispose(); // stop the wire-refresh interval on exit
|
|
639
746
|
await deferredStartup.drain(100);
|
|
640
747
|
if (externalServicesPromise) {
|
|
641
748
|
try {
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// code-index-services.ts — Wave 5 (wo804, W5.3 Stage A TUI wiring)
|
|
3
|
+
//
|
|
4
|
+
// Constructs the TUI's repo source-tree code index: CodeIndexStore
|
|
5
|
+
// (@pellux/goodvibes-sdk/platform/state, landed on SDK main as wo802/W5.3
|
|
6
|
+
// Stage A). Extracted into its own module rather than built inline in
|
|
7
|
+
// services.ts: services.ts sits at the architecture check's 800-line cap
|
|
8
|
+
// (scripts/check-architecture.ts), so any new service gets its own
|
|
9
|
+
// construction module and a single wiring call there (mirrors
|
|
10
|
+
// createWorkstreamServices's one-function-bundle shape).
|
|
11
|
+
//
|
|
12
|
+
// Schema-initialized eagerly (mirrors memoryStore: construction + init()
|
|
13
|
+
// happen unconditionally so the store is queryable — /codebase status, the
|
|
14
|
+
// fleet node — even before any build has ever run). The actual walk/chunk/
|
|
15
|
+
// embed build is NEVER auto-triggered by default: the hundreds of existing
|
|
16
|
+
// test fixtures (and every headless invocation) construct RuntimeServices
|
|
17
|
+
// without asking for a full source-tree walk, and init() alone never runs
|
|
18
|
+
// one (mirrors the SDK's own RuntimeServices wiring in
|
|
19
|
+
// platform/runtime/services.ts, which gates its equivalent scheduleBuild()
|
|
20
|
+
// call behind an explicit autoStartCodeIndex option).
|
|
21
|
+
//
|
|
22
|
+
// REALITY-WINS DIVERGENCE from the SDK's own shape: the SDK's
|
|
23
|
+
// RuntimeServicesOptions exposes autoStartCodeIndex as a constructor-time
|
|
24
|
+
// boolean because that call site is a library entrypoint threaded by each
|
|
25
|
+
// embedder. This TUI's createRuntimeServices has no such per-call knob —
|
|
26
|
+
// auto-start is decided from a TUI-local config key instead
|
|
27
|
+
// (CODE_INDEX_ENABLED_CONFIG_KEY, default OFF), so every construction path
|
|
28
|
+
// (interactive main.ts, the daemon, and every test fixture) shares one
|
|
29
|
+
// honest, user-visible on/off switch rather than needing to thread a new
|
|
30
|
+
// boolean through every call site. With it off (the default), `/codebase
|
|
31
|
+
// build` is the explicit, visible trigger — exactly the shape wo804's brief
|
|
32
|
+
// asks for.
|
|
33
|
+
//
|
|
34
|
+
// Shares memoryEmbeddingRegistry with MemoryStore (constructed in
|
|
35
|
+
// services.ts) so code + memory retrieval use one embedding provider and one
|
|
36
|
+
// dimensionality, per the SDK design doc (code-index-store.ts's constructor
|
|
37
|
+
// takes the SAME MemoryEmbeddingProviderRegistry instance memory uses).
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
import { join } from 'node:path';
|
|
41
|
+
import { CodeIndexStore, CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
|
|
42
|
+
import type { MemoryEmbeddingProviderRegistry } from '@pellux/goodvibes-sdk/platform/state';
|
|
43
|
+
import type { ConfigKey, ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
44
|
+
import { readBooleanConfig } from '../core/alert-gating.ts';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* TUI-local synthetic config key (not yet in the SDK's ConfigKey union) that
|
|
48
|
+
* gates the code index's auto-build on construction. Default OFF — see this
|
|
49
|
+
* module's header doc. Surfaced in /config via a synthetic entry
|
|
50
|
+
* (settings-modal-data.ts) exactly like behavior.notifyAfterSeconds.
|
|
51
|
+
*/
|
|
52
|
+
export const CODE_INDEX_ENABLED_CONFIG_KEY = 'storage.codeIndexEnabled';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Honest, generous bounds passed EXPLICITLY to CodeIndexStore (values equal
|
|
56
|
+
* to its own internal defaults) so /codebase status and the settings
|
|
57
|
+
* description can state them in one place without reaching into store
|
|
58
|
+
* internals or duplicating magic numbers.
|
|
59
|
+
*/
|
|
60
|
+
export const CODE_INDEX_MAX_FILES = 5000;
|
|
61
|
+
export const CODE_INDEX_MAX_FILE_BYTES = 512 * 1024;
|
|
62
|
+
export const CODE_INDEX_MAX_TOTAL_BYTES = 256 * 1024 * 1024;
|
|
63
|
+
|
|
64
|
+
export interface CodeIndexServicesDeps {
|
|
65
|
+
readonly workingDirectory: string;
|
|
66
|
+
readonly configManager: Pick<ConfigManager, 'get'>;
|
|
67
|
+
readonly memoryEmbeddingRegistry: MemoryEmbeddingProviderRegistry;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Whether Stage B code auto-injection / tool-site reindex may run: the SAME
|
|
72
|
+
* storage.codeIndexEnabled switch that gates auto-build. Read live so a runtime
|
|
73
|
+
* /config toggle takes effect without restart. (The separate, default-off
|
|
74
|
+
* `agent-passive-code-injection` feature flag gates injection additionally; this
|
|
75
|
+
* is only the storage-side setting the SDK asks embedders to respect.)
|
|
76
|
+
*/
|
|
77
|
+
export function isCodeInjectionSettingEnabled(configManager: Pick<ConfigManager, 'get'>): boolean {
|
|
78
|
+
return isCodeIndexAutoStartEnabled(configManager);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface CodeIndexServices {
|
|
82
|
+
readonly codeIndexStore: CodeIndexStore;
|
|
83
|
+
/**
|
|
84
|
+
* Wave-5 Stage B tool-site incremental reindex scheduler, bound to codeIndexStore and
|
|
85
|
+
* gated live on storage.codeIndexEnabled. Threaded into both SDK orchestrators (main +
|
|
86
|
+
* agent) so a successful write/edit debounces an incremental reindex, and into the command
|
|
87
|
+
* context so /codebase status can report the last reindex activity honestly.
|
|
88
|
+
*/
|
|
89
|
+
readonly codeIndexReindexScheduler: CodeIndexReindexScheduler;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Absolute path to the TUI's code-index sqlite file, sibling to memory.sqlite under .goodvibes/tui/. */
|
|
93
|
+
export function codeIndexDbPath(workingDirectory: string): string {
|
|
94
|
+
return join(workingDirectory, '.goodvibes', 'tui', 'code-index.sqlite');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether the code index's initial build should auto-start on construction.
|
|
99
|
+
* Exported so /codebase status and the settings modal read the exact same
|
|
100
|
+
* config key + default this module decides auto-start from.
|
|
101
|
+
*/
|
|
102
|
+
export function isCodeIndexAutoStartEnabled(configManager: Pick<ConfigManager, 'get'>): boolean {
|
|
103
|
+
return readBooleanConfig(
|
|
104
|
+
(key) => configManager.get(key as ConfigKey),
|
|
105
|
+
CODE_INDEX_ENABLED_CONFIG_KEY,
|
|
106
|
+
false,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Constructs the TUI's CodeIndexStore. Schema-init runs unconditionally
|
|
112
|
+
* (init() never throws — it degrades to an honest `available: false` +
|
|
113
|
+
* recorded error on failure, mirrored by CodeIndexStats/describeDegradation);
|
|
114
|
+
* the initial full build only fires when isCodeIndexAutoStartEnabled() is
|
|
115
|
+
* true, via the SAME fire-and-forget scheduleBuild() an explicit
|
|
116
|
+
* `/codebase build` invocation uses.
|
|
117
|
+
*/
|
|
118
|
+
export function createCodeIndexServices(deps: CodeIndexServicesDeps): CodeIndexServices {
|
|
119
|
+
const codeIndexStore = new CodeIndexStore(
|
|
120
|
+
deps.workingDirectory,
|
|
121
|
+
codeIndexDbPath(deps.workingDirectory),
|
|
122
|
+
deps.memoryEmbeddingRegistry,
|
|
123
|
+
{ maxFiles: CODE_INDEX_MAX_FILES, maxFileBytes: CODE_INDEX_MAX_FILE_BYTES, maxTotalBytes: CODE_INDEX_MAX_TOTAL_BYTES },
|
|
124
|
+
);
|
|
125
|
+
codeIndexStore.init();
|
|
126
|
+
if (isCodeIndexAutoStartEnabled(deps.configManager)) {
|
|
127
|
+
codeIndexStore.scheduleBuild();
|
|
128
|
+
}
|
|
129
|
+
const codeIndexReindexScheduler = new CodeIndexReindexScheduler({
|
|
130
|
+
target: codeIndexStore,
|
|
131
|
+
workingDirectory: deps.workingDirectory,
|
|
132
|
+
isEnabled: () => isCodeInjectionSettingEnabled(deps.configManager),
|
|
133
|
+
});
|
|
134
|
+
return { codeIndexStore, codeIndexReindexScheduler };
|
|
135
|
+
}
|