@pellux/goodvibes-tui 1.9.1 → 1.9.2
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 +16 -5
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/audio/player.ts +91 -5
- package/src/audio/spoken-turn-controller.ts +30 -2
- package/src/audio/spoken-turn-wiring.ts +3 -0
- package/src/config/credential-availability.ts +1 -1
- package/src/config/index.ts +1 -1
- package/src/core/turn-cancellation.ts +1 -1
- package/src/daemon/cli.ts +4 -4
- package/src/daemon/service-commands.ts +8 -8
- package/src/input/command-registry.ts +5 -5
- package/src/input/commands/config.ts +1 -1
- package/src/input/commands/memory.ts +1 -1
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/feed-context-factory.ts +1 -1
- package/src/input/handler-content-actions.ts +2 -2
- package/src/input/handler-feed-routes.ts +2 -2
- package/src/input/handler-feed.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +3 -3
- package/src/input/handler-picker-routes.ts +2 -2
- package/src/input/handler-shortcuts.ts +1 -1
- package/src/input/model-picker.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-apply.ts +3 -3
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +3 -3
- package/src/input/onboarding/onboarding-wizard-steps.ts +1 -1
- package/src/input/session-picker-modal.ts +1 -1
- package/src/input/settings-modal-data.ts +2 -2
- package/src/main.ts +5 -4
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/operations.ts +4 -4
- package/src/panels/builtin/shared.ts +1 -1
- package/src/panels/fleet-panel-format.ts +1 -1
- package/src/panels/fleet-panel.ts +15 -15
- package/src/panels/fleet-read-model.ts +11 -11
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +2 -2
- package/src/panels/fleet-tabs.ts +4 -4
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/types.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/fleet-tab-strip.ts +2 -2
- package/src/renderer/footer-tips.ts +1 -1
- package/src/renderer/model-workspace.ts +1 -1
- package/src/renderer/session-picker-modal.ts +4 -4
- package/src/renderer/shell-surface.ts +1 -1
- package/src/renderer/status-glyphs.ts +3 -3
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +2 -2
- package/src/renderer/turn-injection.ts +3 -3
- package/src/renderer/ui-factory.ts +7 -7
- package/src/renderer/ui-primitives.ts +3 -3
- package/src/runtime/bootstrap-core.ts +3 -3
- package/src/runtime/bootstrap-hook-bridge.ts +2 -2
- package/src/runtime/bootstrap-shell.ts +3 -3
- package/src/runtime/bootstrap.ts +5 -5
- package/src/runtime/code-index-services.ts +2 -2
- package/src/runtime/index.ts +11 -3
- package/src/runtime/legacy-daemon-migration.ts +9 -9
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/onboarding/snapshot.ts +3 -3
- package/src/runtime/onboarding/types.ts +2 -2
- package/src/runtime/operator-token-cleanup.ts +2 -2
- package/src/runtime/orchestrator-core-services.ts +2 -2
- package/src/runtime/process-lifecycle.ts +19 -5
- package/src/runtime/services.ts +14 -14
- package/src/runtime/session-inbound-inputs.ts +1 -1
- package/src/runtime/session-spine-transport.ts +2 -2
- package/src/runtime/ui-services.ts +1 -1
- package/src/runtime/workstream-services.ts +1 -1
- package/src/runtime/wrfc-persistence.ts +2 -2
- package/src/shell/ui-openers.ts +2 -2
- package/src/version.ts +1 -1
package/src/renderer/theme.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Dark mode values are the historically used colours.
|
|
9
9
|
* Light mode values are defined now for correctness parity; they are consumed
|
|
10
|
-
* when background-detection (
|
|
10
|
+
* when background-detection (terminal-bg-probe) lands and passes the
|
|
11
11
|
* resolved mode down. Callers that do not yet have mode detection MUST call
|
|
12
12
|
* resolveTheme('dark') as the safe default.
|
|
13
13
|
*
|
|
@@ -242,7 +242,7 @@ export function resolveUiTones(mode: ThemeMode): Readonly<UiToneTokens> {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
// ===========================================================================
|
|
245
|
-
// Active-mode runtime (DEBT-2
|
|
245
|
+
// Active-mode runtime (DEBT-2 terminal-bg-probe landing).
|
|
246
246
|
//
|
|
247
247
|
// The mode is decided ONCE at startup — from appearance config (display.themeMode
|
|
248
248
|
// forced dark/light) or the terminal-background probe (auto) — and is then stable
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-turn knowledge injection record rendering
|
|
2
|
+
* Per-turn knowledge injection record rendering.
|
|
3
3
|
*
|
|
4
4
|
* The SDK's passive per-turn retrieval engine (wo801, W5.1 —
|
|
5
5
|
* packages/sdk/src/platform/agents/turn-knowledge-injection.ts) stores one
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* the entry type here is DERIVED from `AgentRecord` rather than imported by
|
|
14
14
|
* name — this needs no SDK export change.
|
|
15
15
|
*
|
|
16
|
-
* Reality check (updated
|
|
16
|
+
* Reality check (since updated): the TUI's main interactive
|
|
17
17
|
* session DOES route through this engine. The SDK `Orchestrator` runs per-turn
|
|
18
18
|
* passive injection on the evolving primary conversation and records each turn
|
|
19
19
|
* on its own bounded ring, exposed via `Orchestrator.getTurnInjections()` — the
|
|
@@ -68,7 +68,7 @@ export function formatTurnInjectionEntry(entry: TurnInjectionEntry): string {
|
|
|
68
68
|
: '';
|
|
69
69
|
// The retrieval query is part of the record's honesty contract — without it
|
|
70
70
|
// an injected line can't be traced back to WHY those ids were retrieved
|
|
71
|
-
// (
|
|
71
|
+
// (a replay finding flagged the omission). Truncated to keep the line scannable.
|
|
72
72
|
const queryStr = entry.query ? ` for ${JSON.stringify(truncateQuery(entry.query))}` : '';
|
|
73
73
|
return (
|
|
74
74
|
` turn ${entry.turn}: injected ${labelInjectedIds(entry)}${queryStr} ` +
|
|
@@ -195,7 +195,7 @@ export class UIFactory {
|
|
|
195
195
|
composerFlags?: readonly string[],
|
|
196
196
|
composerPendingRisk?: 'none' | 'approval-wait' | 'shell' | 'command' | 'remote',
|
|
197
197
|
compact: boolean = false,
|
|
198
|
-
//
|
|
198
|
+
// Honest cross-surface spine posture. Defined ONLY in adopted-daemon
|
|
199
199
|
// mode (undefined in embedded/local), so the segment is absent otherwise.
|
|
200
200
|
sessionSpineStatus?: 'online' | 'offline',
|
|
201
201
|
): Line[] {
|
|
@@ -442,7 +442,7 @@ export class UIFactory {
|
|
|
442
442
|
if (model) {
|
|
443
443
|
ctxParts.push({ text: model + (provider ? ` (${provider})` : ''), priority: 0 });
|
|
444
444
|
}
|
|
445
|
-
//
|
|
445
|
+
// Cross-surface spine posture — plain words, no blame. Adopted mode only.
|
|
446
446
|
if (sessionSpineStatus) ctxParts.push({ text: `spine:${sessionSpineStatus}`, priority: 1 });
|
|
447
447
|
if (toolCount) ctxParts.push({ text: `${toolCount} tools`, priority: 2 });
|
|
448
448
|
// Labeled "notify" (not "hitl") — /mode (aliased /hitl) governs UX
|
|
@@ -484,11 +484,11 @@ export class UIFactory {
|
|
|
484
484
|
return lines;
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
//
|
|
487
|
+
// The rotating "thinking" phrase pool and the honest waiting-state
|
|
488
488
|
// wording (approval/reconnecting/pre-first-token/stalled/thinking) are no
|
|
489
489
|
// longer minted here. They come from the SDK presentation contract's
|
|
490
|
-
// waitingPhrase() (@pellux/goodvibes-sdk/platform/presentation,
|
|
491
|
-
//
|
|
490
|
+
// waitingPhrase() (@pellux/goodvibes-sdk/platform/presentation, already
|
|
491
|
+
// adopted by the agent) — see createThinkingFragment
|
|
492
492
|
// below. This renderer still decides WHICH state applies from its own
|
|
493
493
|
// stall/reconnect/approval signals (computeStallInfo/computeRenderStallInfo
|
|
494
494
|
// stay renderer-local per the extraction decision record); only the exact
|
|
@@ -544,8 +544,8 @@ export class UIFactory {
|
|
|
544
544
|
// long enough to be misleading (THINKING_STALL_FREEZE_MS). Decide WHICH
|
|
545
545
|
// honest waiting state applies (renderer-local — this signal computation
|
|
546
546
|
// stays here per the extraction decision record), then defer the exact
|
|
547
|
-
// wording to the SDK presentation contract's waitingPhrase() (
|
|
548
|
-
// mirrors the agent's
|
|
547
|
+
// wording to the SDK presentation contract's waitingPhrase() (which
|
|
548
|
+
// mirrors the agent's adoption). Precedence matches the contract:
|
|
549
549
|
// approval > reconnecting > pre-first-token > stalled > thinking.
|
|
550
550
|
const isStalled = stallInfo !== undefined && stallInfo.msSinceLastDelta >= THINKING_STALL_FREEZE_MS;
|
|
551
551
|
let state: WaitingState;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// ui-primitives.ts — glyph registry + tone-token table.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// These four tables (GLYPHS, UI_TONES, DIFF_TONES, SPINNER_FRAMES) are
|
|
5
5
|
// no longer minted locally. They are the SDK presentation contract
|
|
6
|
-
// (@pellux/goodvibes-sdk/platform/presentation,
|
|
7
|
-
// adopted by the agent
|
|
6
|
+
// (@pellux/goodvibes-sdk/platform/presentation, already
|
|
7
|
+
// adopted by the agent), consumed here so the TUI and the agent
|
|
8
8
|
// share ONE source (Mike's move-to-SDK ruling — machinery needed by 2+
|
|
9
9
|
// surfaces => SDK). The TUI was the reference these values were lifted from
|
|
10
10
|
// verbatim, so this swap is byte-identical. See
|
|
@@ -119,7 +119,7 @@ export interface BootstrapCoreState {
|
|
|
119
119
|
readonly runtimeSessionIdRef: { value: string };
|
|
120
120
|
/** Cross-surface identity mirror; permanently dormant for embedded/local-only (docs/decisions/2026-07-06-session-spine-mode-branch-is-permanent.md), activated by bootstrap.ts only for an adopted 'external' daemon. */
|
|
121
121
|
readonly sessionSpine: SessionSpineClient;
|
|
122
|
-
/**
|
|
122
|
+
/** Inbound steer/follow-up delivery; dormant until bootstrap.ts activates it. */
|
|
123
123
|
readonly sessionInboundInputs: SessionInboundInputPoller;
|
|
124
124
|
/** Cache-backed read facade; bootstrap.ts drives its mode (embedded/external/local-only) from the same HostServiceMode as the spine above. */
|
|
125
125
|
readonly sessionUnionCache: SessionUnionCache;
|
|
@@ -408,7 +408,7 @@ export async function initializeBootstrapCore(
|
|
|
408
408
|
void memoryStore.save();
|
|
409
409
|
memoryStore.close();
|
|
410
410
|
});
|
|
411
|
-
//
|
|
411
|
+
// Fold this project's legacy per-project TUI memory into the home-scoped canonical store, ONCE, AFTER init(). Idempotent and non-fatal.
|
|
412
412
|
await runBootMemoryFold(memoryStore, services.memoryEmbeddingRegistry, workingDir, logger);
|
|
413
413
|
|
|
414
414
|
const renderRequestRef = { value: (): void => {} };
|
|
@@ -644,7 +644,7 @@ export async function initializeBootstrapCore(
|
|
|
644
644
|
},
|
|
645
645
|
));
|
|
646
646
|
|
|
647
|
-
//
|
|
647
|
+
// Inbound steer delivery — see createBootstrapInboundInputPoller's doc comment.
|
|
648
648
|
const sessionInboundInputs = createBootstrapInboundInputPoller({
|
|
649
649
|
runtimeSessionIdRef, routeOrBuffer, orchestratorHandleUserInputRef, conversation, requestRender,
|
|
650
650
|
});
|
|
@@ -23,7 +23,7 @@ export interface ResumeSessionOptions {
|
|
|
23
23
|
readonly requestRender: () => void;
|
|
24
24
|
readonly onSessionIdChanged?: (sessionId: string) => void;
|
|
25
25
|
readonly sharedSessionBroker: Pick<SharedSessionBroker, 'reopenSession'>;
|
|
26
|
-
/**
|
|
26
|
+
/** Fire-and-forget daemon-spine mirror. Reopen (not register) is the
|
|
27
27
|
* ONLY resume-time verb — see the SDK session-spine client.ts header doc. */
|
|
28
28
|
readonly sessionSpine: Pick<SessionSpineClient, 'reopen'>;
|
|
29
29
|
readonly project: string;
|
|
@@ -80,7 +80,7 @@ export function createResumeSessionHandler(options: ResumeSessionOptions): (sess
|
|
|
80
80
|
if (meta?.provider) options.runtime.provider = meta.provider;
|
|
81
81
|
options.writeLastSessionPointer(sessionId);
|
|
82
82
|
void options.sharedSessionBroker.reopenSession(sessionId).catch((err) => { logger.debug('session broker reopen session failed', { err }); });
|
|
83
|
-
//
|
|
83
|
+
// Fire-and-forget spine mirror (reopen:true — the user resume verb).
|
|
84
84
|
options.sessionSpine.reopen({ sessionId, project: options.project, title: options.conversation.title || meta.title });
|
|
85
85
|
options.conversation.log(`Resumed session: ${sessionId}`, { fg: '135' });
|
|
86
86
|
const reopenedPanels: string[] = [];
|
|
@@ -48,7 +48,7 @@ export interface BootstrapShellOptions {
|
|
|
48
48
|
readonly runtimeBus: RuntimeEventBus;
|
|
49
49
|
readonly runtimeStore: RuntimeStore;
|
|
50
50
|
readonly services: RuntimeServices;
|
|
51
|
-
/**
|
|
51
|
+
/** Dormant until bootstrap.ts activates it for an adopted 'external' daemon. */
|
|
52
52
|
readonly sessionSpine: SessionSpineClient;
|
|
53
53
|
readonly conversation: ConversationManager;
|
|
54
54
|
readonly runtime: MutableRuntimeState;
|
|
@@ -181,7 +181,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
181
181
|
forensicsRegistry,
|
|
182
182
|
policyRuntimeState,
|
|
183
183
|
approvalBroker: services.approvalBroker,
|
|
184
|
-
//
|
|
184
|
+
// Panels read the cross-surface union facade, not the raw local broker.
|
|
185
185
|
sessionBroker: uiServices.sessions.sessionBroker,
|
|
186
186
|
automationManager: services.automationManager,
|
|
187
187
|
getControlPlaneRecentEvents,
|
|
@@ -289,7 +289,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
289
289
|
codeIndexStore: services.codeIndexStore,
|
|
290
290
|
codeIndexReindexScheduler: services.codeIndexReindexScheduler,
|
|
291
291
|
isPassiveCodeInjectionFlagEnabled: () => services.featureFlags.isEnabled('agent-passive-code-injection'),
|
|
292
|
-
//
|
|
292
|
+
// Expose the MAIN session's per-turn passive-injection ring
|
|
293
293
|
// so `/recall injections` (no agent id) renders it — see recall-review.ts.
|
|
294
294
|
getMainSessionTurnInjections: () => orchestrator.getTurnInjections(),
|
|
295
295
|
changeTracker: services.sessionChangeTracker,
|
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -199,7 +199,7 @@ export async function bootstrapRuntime(
|
|
|
199
199
|
pluginManager,
|
|
200
200
|
} = services;
|
|
201
201
|
|
|
202
|
-
//
|
|
202
|
+
// A liveness flip is only PAINTED once something calls requestRender();
|
|
203
203
|
// without this, the footer's spine segment sat correct-but-undrawn until
|
|
204
204
|
// incidental activity redrew it (minutes, during an idle stretch).
|
|
205
205
|
sessionUnionCache.setOnTransition(() => requestRender());
|
|
@@ -413,7 +413,7 @@ export async function bootstrapRuntime(
|
|
|
413
413
|
close: (sessionId) => httpTransport.operator.sessions.close(sessionId),
|
|
414
414
|
};
|
|
415
415
|
sessionSpine.activate(createTuiSpineTransport(sessionsClient));
|
|
416
|
-
//
|
|
416
|
+
// Adopt the same daemon's wire for the INBOUND steer path — collect
|
|
417
417
|
// steer/follow-up inputs another live surface queued for this session and
|
|
418
418
|
// inject them into the turn machinery (acking delivery on the wire).
|
|
419
419
|
sessionInboundInputs.activate({
|
|
@@ -453,7 +453,7 @@ export async function bootstrapRuntime(
|
|
|
453
453
|
// Honest session-spine posture, independent of daemonRunning —
|
|
454
454
|
// 'external'-adopted-but-currently-unreachable degrades to 'offline' here
|
|
455
455
|
// even though daemonRunning might still read true from a stale handle.
|
|
456
|
-
//
|
|
456
|
+
// sessionSpine.status() alone is ACTIVITY-gated (only updates on a
|
|
457
457
|
// register/heartbeat/close), so after the daemon dies mid-idle it would
|
|
458
458
|
// keep reading 'online'. Derive the footer status from the union cache's
|
|
459
459
|
// 5s liveness probe too — one signal, no new timer — so offline surfaces
|
|
@@ -568,7 +568,7 @@ export async function bootstrapRuntime(
|
|
|
568
568
|
// authenticate against the embedded daemon this surface starts.
|
|
569
569
|
const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
|
|
570
570
|
const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir);
|
|
571
|
-
//
|
|
571
|
+
// Fix (TUI 0.19.20): remove stale pre-0.21.28 workspace-scoped operator
|
|
572
572
|
// token files so only the canonical <daemonHomeDir>/operator-tokens.json survives.
|
|
573
573
|
// The prune is best-effort — it silently skips missing files, no-ops when tokens
|
|
574
574
|
// already match, and records un-deletable candidates in `failedPaths` for logging.
|
|
@@ -741,7 +741,7 @@ export async function bootstrapRuntime(
|
|
|
741
741
|
// a no-op when the spine was never activated (embedded/local-only topology).
|
|
742
742
|
sessionSpine.close(runtime.sessionId);
|
|
743
743
|
sessionSpine.dispose();
|
|
744
|
-
sessionInboundInputs.dispose(); //
|
|
744
|
+
sessionInboundInputs.dispose(); // stop the inbound steer poll interval on exit
|
|
745
745
|
sessionUnionCache.dispose(); // stop the wire-refresh interval on exit
|
|
746
746
|
await deferredStartup.drain(100);
|
|
747
747
|
if (externalServicesPromise) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
|
-
// code-index-services.ts —
|
|
2
|
+
// code-index-services.ts — repo source-tree code index (TUI wiring)
|
|
3
3
|
//
|
|
4
4
|
// Constructs the TUI's repo source-tree code index: CodeIndexStore
|
|
5
5
|
// (@pellux/goodvibes-sdk/platform/state, landed on SDK main as wo802/W5.3
|
|
@@ -81,7 +81,7 @@ export function isCodeInjectionSettingEnabled(configManager: Pick<ConfigManager,
|
|
|
81
81
|
export interface CodeIndexServices {
|
|
82
82
|
readonly codeIndexStore: CodeIndexStore;
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
84
|
+
* Tool-site incremental reindex scheduler, bound to codeIndexStore and
|
|
85
85
|
* gated live on storage.codeIndexEnabled. Threaded into both SDK orchestrators (main +
|
|
86
86
|
* agent) so a successful write/edit debounces an incremental reindex, and into the command
|
|
87
87
|
* context so /codebase status can report the last reindex activity honestly.
|
package/src/runtime/index.ts
CHANGED
|
@@ -274,9 +274,17 @@ export const reachableFrom = operations.reachableFrom;
|
|
|
274
274
|
export const evaluateSessionMaintenance = operations.evaluateSessionMaintenance;
|
|
275
275
|
export const formatSessionMaintenanceLines = operations.formatSessionMaintenanceLines;
|
|
276
276
|
export const getGuidanceMode = operations.getGuidanceMode;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
// Snapshot-retention symbols (SnapshotPruner, RetentionPolicy,
|
|
278
|
+
// DEFAULT_RETENTION_CONFIG) are intentionally NOT re-exported here. No app code
|
|
279
|
+
// consumes them — only the retention unit test does, and it now imports them
|
|
280
|
+
// straight from the SDK `operations` namespace. Re-exporting them created a
|
|
281
|
+
// second top-level binding named `SnapshotPruner` that collided with the SDK's
|
|
282
|
+
// own `class SnapshotPruner`, forcing the bundler to rename ours to
|
|
283
|
+
// `SnapshotPruner2` and emit a fragile `SnapshotPruner2 = operations.SnapshotPruner`
|
|
284
|
+
// module-init assignment. `bun build --compile` occasionally drops that renamed
|
|
285
|
+
// `var` declaration on the darwin-arm64 target, so the compiled binary died at
|
|
286
|
+
// startup with `ReferenceError: SnapshotPruner2 is not defined`. Dropping the
|
|
287
|
+
// re-export removes the collision (and the same latent hazard for the other two).
|
|
280
288
|
export const buildPersistedSessionContext = operations.buildPersistedSessionContext;
|
|
281
289
|
export const buildLocalReturnContextSummary = operations.buildLocalReturnContextSummary;
|
|
282
290
|
export const formatReturnContextForDisplay = operations.formatReturnContextForDisplay;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The legacy `goodvibes-daemon.service` detect/migrate engine, shared
|
|
3
3
|
* by the daemon CLI (`src/daemon/service-commands.ts`, the `migrate-service`
|
|
4
4
|
* subcommand) and the interactive TUI's onboarding guided UX
|
|
5
5
|
* (`src/input/handler-onboarding-daemon-adopt.ts`).
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* depending on CLI/daemon entrypoint concerns), and `src/runtime/**` is the
|
|
12
12
|
* shared, entrypoint-agnostic layer both sides are already allowed to import.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* the prior
|
|
14
|
+
* An earlier release shipped DETECT + DISCLOSE only: a read-only check for
|
|
15
|
+
* the prior generation's systemd unit name plus a manual-removal hint, never
|
|
16
16
|
* touching it. This module adds the guided, CONSENTED migration itself.
|
|
17
17
|
* Design constraints, all load-bearing (see also the per-function docs):
|
|
18
18
|
* - NEVER auto-migrate. Without explicit consent nothing runs except a
|
|
@@ -63,7 +63,7 @@ export const MANAGED_SERVICE_NAME = 'goodvibes';
|
|
|
63
63
|
export const MANAGED_SERVICE_DESCRIPTION = 'GoodVibes daemon (shared session broker + companion host)';
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Follow-up: resolve the unit name the SDK's `PlatformServiceManager`
|
|
67
67
|
* would actually manage, from config alone — for callers that need the
|
|
68
68
|
* honest display name BEFORE any manager/status exists (the onboarding
|
|
69
69
|
* wizard's detection banner resolves this at snapshot-collection time and
|
|
@@ -218,7 +218,7 @@ export function detectLegacyUnit(input: DetectLegacyUnitInput): LegacyUnitInfo {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
|
-
*
|
|
221
|
+
* The unit name `PlatformServiceManager` is ACTUALLY about to mutate can
|
|
222
222
|
* differ from `MANAGED_SERVICE_NAME` / `definitionOverride.name`. The SDK's
|
|
223
223
|
* internal `resolveServiceName()` — used by `install()`, `uninstall()`, and
|
|
224
224
|
* `status()` alike to compute the unit file PATH — resolves from the
|
|
@@ -303,7 +303,7 @@ export interface RunLegacyDaemonMigrationParams {
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
/**
|
|
306
|
-
*
|
|
306
|
+
* Belt-and-braces guard: throws if the resolved unit `status` is the
|
|
307
307
|
* legacy unit. Called immediately before the two mutation calls
|
|
308
308
|
* (`manager.install()`, and `manager.uninstall()` on the failed-health
|
|
309
309
|
* rollback path) that would otherwise write to or remove that path. This is
|
|
@@ -343,7 +343,7 @@ export async function runLegacyDaemonMigration(
|
|
|
343
343
|
const { trackedServiceName } = params;
|
|
344
344
|
// Computed once, up front, and reused for every branch below (this is the
|
|
345
345
|
// exact same single call each branch made individually before — see the
|
|
346
|
-
//
|
|
346
|
+
// fix note on `resolveManagedUnitName` for why the name/path it reports
|
|
347
347
|
// can differ from `trackedServiceName`).
|
|
348
348
|
const currentStatus = manager.status();
|
|
349
349
|
const resolvedUnitName = resolveManagedUnitName(currentStatus);
|
|
@@ -393,7 +393,7 @@ export async function runLegacyDaemonMigration(
|
|
|
393
393
|
};
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
-
//
|
|
396
|
+
// Before any mutation, confirm the unit PlatformServiceManager is
|
|
397
397
|
// actually about to install/uninstall isn't the legacy unit itself. This
|
|
398
398
|
// happens when the host's `service.serviceName` config key is set to the
|
|
399
399
|
// legacy unit's own name — see `resolveManagedUnitName`'s doc comment for
|
|
@@ -440,7 +440,7 @@ export async function runLegacyDaemonMigration(
|
|
|
440
440
|
|
|
441
441
|
// Consented: new-up-then-old-down. The legacy unit is not touched until the
|
|
442
442
|
// new unit is verified healthy.
|
|
443
|
-
// Belt-and-braces
|
|
443
|
+
// Belt-and-braces: the collision check above already returns before
|
|
444
444
|
// reaching here whenever the resolved unit is the legacy one — this
|
|
445
445
|
// re-asserts the same invariant right at the mutation site so a future
|
|
446
446
|
// change to the check above can never silently reopen the hole.
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '@pellux/goodvibes-sdk/platform/state';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Fold the TUI's legacy per-project memory store into the canonical
|
|
13
13
|
* cross-surface store. Called once at boot AFTER `memoryStore.init()` so records written
|
|
14
14
|
* before unification survive. Id-keyed and idempotent — a re-run imports nothing new and
|
|
15
15
|
* never deletes the legacy file. Returns the report so boot can log what moved.
|
|
@@ -403,12 +403,12 @@ export async function collectOnboardingSnapshot(
|
|
|
403
403
|
snapshot: authSnapshotResult.value,
|
|
404
404
|
},
|
|
405
405
|
bindSettings: {
|
|
406
|
-
// danger.daemon (removed
|
|
407
|
-
// used to gate the
|
|
406
|
+
// danger.daemon (since removed — see docs/decisions/2026-07-05-daemon-by-default.md)
|
|
407
|
+
// used to gate the earlier opt-in daemon posture; this site read it RAW
|
|
408
408
|
// (not through resolveDaemonEnabled) so the wizard's network-mode
|
|
409
409
|
// classification tracked "did the user explicitly request the legacy
|
|
410
410
|
// dangerous posture" rather than "does the daemon run" (which has defaulted
|
|
411
|
-
// true unconditionally since
|
|
411
|
+
// true unconditionally since daemon-by-default landed, and would misclassify every default,
|
|
412
412
|
// local-only install as server-mode if read here). Every realistic config
|
|
413
413
|
// already evaluated this to `false` (unset, or an explicit `danger.daemon:
|
|
414
414
|
// false` both did); the alias's removal migration preserves the one case
|
|
@@ -126,7 +126,7 @@ export interface OnboardingSurfacesSnapshot {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
129
|
+
* Wizard wiring: read-only detection of a legacy `goodvibes-daemon.service`
|
|
130
130
|
* systemd unit (see `../legacy-daemon-migration.ts`'s `LegacyUnitInfo`), carried
|
|
131
131
|
* on the snapshot so the Network step can show the guided migration action only
|
|
132
132
|
* when there is actually something to migrate. Never implies anything was
|
|
@@ -137,7 +137,7 @@ export interface OnboardingLegacyDaemonSnapshot {
|
|
|
137
137
|
readonly active: boolean;
|
|
138
138
|
readonly path: string;
|
|
139
139
|
/**
|
|
140
|
-
*
|
|
140
|
+
* Follow-up: the unit name this tool actually manages on this host,
|
|
141
141
|
* resolved from the `service.serviceName` config key at snapshot-collection
|
|
142
142
|
* time (`resolveConfiguredServiceName`, `../legacy-daemon-migration.ts`) —
|
|
143
143
|
* so the wizard's detection banner names the real unit instead of the
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Shared helper that enumerates the legacy workspace-scoped `operator-tokens.json`
|
|
5
5
|
* locations the TUI has written at various pre-0.21.28 versions. Used by both the
|
|
6
6
|
* in-process bootstrap path (`src/runtime/bootstrap.ts`) and the standalone daemon
|
|
7
|
-
* CLI (`src/daemon/cli.ts`) so
|
|
7
|
+
* CLI (`src/daemon/cli.ts`) so stale-token pruning has a single source of
|
|
8
8
|
* truth for where to look.
|
|
9
9
|
*
|
|
10
10
|
* Adding a new legacy location: append to `workspaceOperatorTokenCandidates` and
|
|
@@ -32,7 +32,7 @@ export function workspaceOperatorTokenCandidates(workingDirectory: string): read
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* External-daemon adoption: resolve the operator/companion
|
|
36
36
|
* token this TUI process uses to authenticate with its daemon, honoring
|
|
37
37
|
* `GOODVIBES_DAEMON_TOKEN` as a non-interactive override.
|
|
38
38
|
*
|
|
@@ -24,7 +24,7 @@ export type OrchestratorCoreServicesSource = Pick<
|
|
|
24
24
|
* cacheHitTracker; main: favoritesStore) — setCoreServices() merges, so the
|
|
25
25
|
* later main.ts call only overlays, never erases.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
27
|
+
* BLOCKER regression guard: `memoryRegistry` here is what turns
|
|
28
28
|
* on per-turn passive knowledge injection for the MAIN interactive session —
|
|
29
29
|
* the SDK turn loop hard-gates on `coreServices.memoryRegistry` (undefined is
|
|
30
30
|
* a silent no-op: Orchestrator.getTurnInjections() stays empty forever and
|
|
@@ -48,7 +48,7 @@ export function buildSharedOrchestratorCoreServices(input: {
|
|
|
48
48
|
sessionLineageTracker: services.sessionLineageTracker,
|
|
49
49
|
idempotencyStore: services.idempotencyStore,
|
|
50
50
|
memoryRegistry: services.memoryRegistry,
|
|
51
|
-
//
|
|
51
|
+
// Main-session code auto-injection + tool-site reindex. Injection is
|
|
52
52
|
// additionally gated by the default-off `agent-passive-code-injection` flag inside the
|
|
53
53
|
// SDK; here we supply the source, the live storage.codeIndexEnabled predicate, and the
|
|
54
54
|
// reindex scheduler.
|
|
@@ -57,7 +57,7 @@ export interface ProcessLifecycleDeps {
|
|
|
57
57
|
readonly unsubs: ReadonlyArray<() => void>;
|
|
58
58
|
readonly getRecoveryInterval: () => ReturnType<typeof setInterval> | null;
|
|
59
59
|
readonly setRecoveryInterval: (value: ReturnType<typeof setInterval> | null) => void;
|
|
60
|
-
readonly getStopSpokenOutputForExit: () => (() => void) | null;
|
|
60
|
+
readonly getStopSpokenOutputForExit: () => (() => void | Promise<void>) | null;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export interface ProcessLifecycleHandlers {
|
|
@@ -153,7 +153,16 @@ export function installProcessLifecycle(deps: ProcessLifecycleDeps): ProcessLife
|
|
|
153
153
|
// must not re-run teardown or double-fire ctx.shutdown.
|
|
154
154
|
if (exiting) return;
|
|
155
155
|
exiting = true;
|
|
156
|
-
|
|
156
|
+
// Exit lets the spoken audio the user is already hearing finish inside a
|
|
157
|
+
// short bounded window (capped inside stopForExit, under the 3s shutdown
|
|
158
|
+
// budget below) while the rest of teardown proceeds; queued-but-unplayed
|
|
159
|
+
// speech is dropped. Deliberate interrupts (Ctrl+C, /tts stop) still cut
|
|
160
|
+
// instantly through controller.stop() before this path is ever reached.
|
|
161
|
+
let spokenOutputDrain: Promise<void> = Promise.resolve();
|
|
162
|
+
try {
|
|
163
|
+
spokenOutputDrain = Promise.resolve(getStopSpokenOutputForExit()?.()).then(() => undefined);
|
|
164
|
+
} catch { /* non-fatal to exit */ }
|
|
165
|
+
spokenOutputDrain = spokenOutputDrain.catch(() => undefined);
|
|
157
166
|
unsubs.forEach(fn => fn());
|
|
158
167
|
const interval = getRecoveryInterval();
|
|
159
168
|
if (interval !== null) { clearInterval(interval); setRecoveryInterval(null); }
|
|
@@ -169,9 +178,14 @@ export function installProcessLifecycle(deps: ProcessLifecycleDeps): ProcessLife
|
|
|
169
178
|
try {
|
|
170
179
|
// Race the graceful shutdown against a hard timeout — externalServices.stop() can hang
|
|
171
180
|
// and we must still exit; deferredStartup.drain only budgets 100ms internally.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
// The spoken-audio drain runs concurrently and is internally capped below
|
|
182
|
+
// this budget, so it never extends the exit beyond the hard timeout.
|
|
183
|
+
await Promise.all([
|
|
184
|
+
spokenOutputDrain,
|
|
185
|
+
Promise.race([
|
|
186
|
+
ctx.shutdown({ ...snapshot, ...buildPersistedSessionContext(snapshot.messages, ctx.conversation.getTitleSource(), buildSessionContinuityHints()) }).then(() => { shutdownOk = true; }),
|
|
187
|
+
new Promise<void>((resolve) => setTimeout(resolve, 3000)),
|
|
188
|
+
]),
|
|
175
189
|
]);
|
|
176
190
|
} catch (err) {
|
|
177
191
|
logger.debug('ctx.shutdown error during exitApp (non-fatal)', { error: summarizeError(err) });
|
package/src/runtime/services.ts
CHANGED
|
@@ -222,12 +222,12 @@ export interface RuntimeServices {
|
|
|
222
222
|
readonly agentOrchestrator: AgentOrchestrator;
|
|
223
223
|
readonly wrfcController: WrfcController;
|
|
224
224
|
readonly processManager: ProcessManager;
|
|
225
|
-
/**
|
|
225
|
+
/** The phase/work-item orchestration engine — see runtime/workstream-services.ts. */
|
|
226
226
|
readonly orchestrationEngine: OrchestrationEngine;
|
|
227
227
|
readonly workstreamCommands: WorkstreamCommandService;
|
|
228
|
-
/**
|
|
228
|
+
/** The repo source-tree code index — see runtime/code-index-services.ts. */
|
|
229
229
|
readonly codeIndexStore: CodeIndexStore;
|
|
230
|
-
readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; //
|
|
230
|
+
readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; // tool-site reindex
|
|
231
231
|
/** W2.1/W2.2: unified live process registry (agents, WRFC chains, workflows, watchers, background processes) backing the Fleet panel. */
|
|
232
232
|
readonly processRegistry: ProcessRegistry;
|
|
233
233
|
readonly modeManager: ModeManager;
|
|
@@ -338,7 +338,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
338
338
|
executor: agentOrchestrator,
|
|
339
339
|
configManager,
|
|
340
340
|
});
|
|
341
|
-
agentOrchestrator.setConversationSink({ //
|
|
341
|
+
agentOrchestrator.setConversationSink({ // Conversation-snapshot bridge (mirrors the SDK's own createRuntimeServices)
|
|
342
342
|
register: (agentId, source) => agentManager.registerConversationSource(agentId, source),
|
|
343
343
|
release: (agentId) => agentManager.releaseConversationSource(agentId),
|
|
344
344
|
});
|
|
@@ -387,7 +387,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
387
387
|
});
|
|
388
388
|
const artifactStore = new ArtifactStore({ configManager });
|
|
389
389
|
const memoryEmbeddingRegistry = new MemoryEmbeddingProviderRegistry({ configManager });
|
|
390
|
-
//
|
|
390
|
+
// Open the ONE home-scoped canonical store; legacy per-project TUI memory folds in at boot (foldTuiLegacyMemory).
|
|
391
391
|
const memoryDbPath = resolveCanonicalMemoryDbPath(homeDirectory);
|
|
392
392
|
const memoryStore = new MemoryStore(memoryDbPath, {
|
|
393
393
|
embeddingRegistry: memoryEmbeddingRegistry,
|
|
@@ -595,18 +595,18 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
595
595
|
artifactStore,
|
|
596
596
|
});
|
|
597
597
|
const processManager = new ProcessManager();
|
|
598
|
-
//
|
|
598
|
+
// The phase/work-item orchestration engine, constructed
|
|
599
599
|
// before the process registry so its fleet nodes (workstream/phase/
|
|
600
600
|
// work-item) can be folded in below via the registry's optional
|
|
601
601
|
// orchestrationEngine dep.
|
|
602
602
|
const { orchestrationEngine, workstreamCommands } = createWorkstreamServices({
|
|
603
603
|
agentManager, configManager, adaptivePlanner, runtimeBus: options.runtimeBus, projectRoot: workingDirectory,
|
|
604
604
|
});
|
|
605
|
-
//
|
|
605
|
+
// Repo source-tree code index, sharing memoryEmbeddingRegistry
|
|
606
606
|
// with MemoryStore above. Auto-build is config-gated (default off) — see
|
|
607
607
|
// code-index-services.ts's header doc.
|
|
608
608
|
const { codeIndexStore, codeIndexReindexScheduler } = createCodeIndexServices({ workingDirectory, configManager, memoryEmbeddingRegistry });
|
|
609
|
-
const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler }; //
|
|
609
|
+
const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler }; // Code-injection seam (agent here; main via orchestrator-core-services.ts)
|
|
610
610
|
// W2.1/W2.2: one shared process registry aggregating the managers above —
|
|
611
611
|
// the Fleet panel (panels/fleet-read-model.ts) is its first consumer.
|
|
612
612
|
// Constructed once here (not per-consumer) so the coalesced tick and the
|
|
@@ -614,15 +614,15 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
614
614
|
const processRegistry = createProcessRegistry({
|
|
615
615
|
agentManager,
|
|
616
616
|
wrfcController,
|
|
617
|
-
orchestrationEngine, //
|
|
618
|
-
codeIndexService: codeIndexStore, //
|
|
617
|
+
orchestrationEngine, // Folds workstream/phase/work-item nodes into the fleet
|
|
618
|
+
codeIndexService: codeIndexStore, // Folds a single 'code-index' node into the fleet
|
|
619
619
|
processManager,
|
|
620
620
|
watcherRegistry,
|
|
621
621
|
workflow,
|
|
622
622
|
approvalBroker,
|
|
623
623
|
sessionBroker,
|
|
624
|
-
messageBus: agentMessageBus, //
|
|
625
|
-
automationManager, //
|
|
624
|
+
messageBus: agentMessageBus, // Backs steer()/`steerable` (the Fleet steer composer builds on top)
|
|
625
|
+
automationManager, // Folds /schedule AutomationJobs into the fleet as 'schedule' nodes
|
|
626
626
|
runtimeBus: options.runtimeBus,
|
|
627
627
|
// Honest pricing: never fabricate a cost for an unrecognized model.
|
|
628
628
|
priceUsage: (model, usage) => {
|
|
@@ -681,7 +681,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
681
681
|
remoteRunnerRegistry,
|
|
682
682
|
knowledgeService,
|
|
683
683
|
memoryRegistry,
|
|
684
|
-
...codeInjectionOrchestratorDeps, //
|
|
684
|
+
...codeInjectionOrchestratorDeps, // Agent-run code injection + tool-site reindex
|
|
685
685
|
archetypeLoader,
|
|
686
686
|
configManager,
|
|
687
687
|
providerRegistry,
|
|
@@ -790,7 +790,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
790
790
|
workspaceCheckpointManager,
|
|
791
791
|
integrationHelpers,
|
|
792
792
|
async rerootStores(newWorkingDir: string): Promise<void> {
|
|
793
|
-
//
|
|
793
|
+
// The memory store is the home-scoped canonical cross-surface store and
|
|
794
794
|
// deliberately does NOT reroot per-project (that would re-silo memory, the E6
|
|
795
795
|
// regression). Only working-tree-bound stores (code index, project index) reroot.
|
|
796
796
|
await codeIndexStore.reroot(newWorkingDir, codeIndexDbPath(newWorkingDir));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* session-inbound-inputs.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Live-surface steer delivery (the inbound half of the
|
|
5
5
|
* session spine): the TUI's INBOUND path for steer/follow-up inputs that another
|
|
6
6
|
* live surface (e.g. the webui) queued against THIS TUI's session.
|
|
7
7
|
*
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* session-spine-transport.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The TUI session-spine cutover: the thin typed-client
|
|
5
5
|
* adapter that lets the TUI's bootstrap drive the SDK's
|
|
6
6
|
* `@pellux/goodvibes-sdk/platform/runtime/session-spine` `SessionSpineClient`
|
|
7
|
-
*
|
|
7
|
+
* via its injected `SpineTransport` seam.
|
|
8
8
|
*
|
|
9
9
|
* The TUI is SDK-clean here: `SpineSessionsClient` is exactly the narrow wire
|
|
10
10
|
* surface the old TUI-local `session-spine-client.ts` declared (structurally
|
|
@@ -114,7 +114,7 @@ export interface UiRuntimeSharedServices {
|
|
|
114
114
|
readonly remoteSupervisor: RuntimeServices['remoteSupervisor'] & RemoteSupervisor;
|
|
115
115
|
/** W2.2: the shared live process registry backing the Fleet panel. */
|
|
116
116
|
readonly processRegistry: RuntimeServices['processRegistry'];
|
|
117
|
-
/**
|
|
117
|
+
/** The shared runtime event bus — the Fleet panel subscribes to its 'communication' domain for the honest steer-consumed signal. */
|
|
118
118
|
readonly runtimeBus: RuntimeServices['runtimeBus'];
|
|
119
119
|
};
|
|
120
120
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
|
-
// workstream-services.ts —
|
|
2
|
+
// workstream-services.ts — phase/work-item orchestration engine
|
|
3
3
|
//
|
|
4
4
|
// Constructs the TUI's OrchestrationEngine instance (@pellux/goodvibes-sdk/
|
|
5
5
|
// platform/orchestration, landed on SDK main as wo701/W4.1) and a thin
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* additionally exposes the recovered set for inspection.
|
|
20
20
|
*
|
|
21
21
|
* A chain that looked interrupted (non-terminal) in the old snapshot can be
|
|
22
|
-
* reaped to terminal IN PLACE by `WrfcController.importChain` (
|
|
22
|
+
* reaped to terminal IN PLACE by `WrfcController.importChain` (the zombie-reap pass:
|
|
23
23
|
* no member agent survived the restart — see wrfc-controller.ts). This
|
|
24
24
|
* module always re-checks `chain.state` AFTER the import call, never the
|
|
25
25
|
* pre-import classification, so a reaped chain is treated as history, not
|
|
@@ -209,7 +209,7 @@ class WrfcPersistenceImpl implements WrfcPersistence {
|
|
|
209
209
|
// chain. The accessor is optional for read-only test doubles.
|
|
210
210
|
//
|
|
211
211
|
// NOTE: importChain may reap this chain to a terminal state IN PLACE
|
|
212
|
-
// (
|
|
212
|
+
// (zombie reap: no member agent survived the restart) —
|
|
213
213
|
// always read chain.state AFTER this call below, never the pre-import
|
|
214
214
|
// classification captured by candidateInterrupted.
|
|
215
215
|
this.controller.importChain?.(chain);
|