@pellux/goodvibes-tui 1.9.0 → 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 +24 -2
- 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 +122 -0
- package/src/config/index.ts +15 -0
- 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 +15 -15
- 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 +30 -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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// fleet-tab-strip.ts
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// Renders FleetPanel's session-tab strip by reusing
|
|
5
5
|
// renderTabStrip exactly as panel-workspace-bar.ts does for the workspace
|
|
6
6
|
// tab bar. Deliberately a SEPARATE, visually distinct strip from the
|
|
7
7
|
// workspace bar: workspace tabs switch PANELS, fleet session tabs switch
|
|
@@ -23,7 +23,7 @@ const LABEL_FG = UI_TONES.fg.secondary;
|
|
|
23
23
|
/**
|
|
24
24
|
* Render the fleet session-tab strip, or `null` when there are no attached
|
|
25
25
|
* tabs — the panel omits the strip entirely in that case (root-tab-only),
|
|
26
|
-
* which is what keeps the pre-
|
|
26
|
+
* which is what keeps the pre-session-tab fleet-panel goldens byte-identical.
|
|
27
27
|
*/
|
|
28
28
|
export function renderFleetTabStrip(
|
|
29
29
|
state: FleetTabsState,
|
|
@@ -29,7 +29,7 @@ export function isAgentActive(composerStatus: string | undefined): boolean {
|
|
|
29
29
|
// shell/ui-openers.ts). The bare noun 'panels' undersold that; naming the verb
|
|
30
30
|
// keeps the tip honest about what the chord actually does.
|
|
31
31
|
const TIP_PANELS = 'Ctrl+P toggle panels';
|
|
32
|
-
//
|
|
32
|
+
// F2 now opens the Fleet panel (the process modal was retired), so the
|
|
33
33
|
// tip names 'fleet', not 'processes'.
|
|
34
34
|
const TIP_PROCESSES = 'F2 fleet';
|
|
35
35
|
const TIP_HELP = '? help';
|
|
@@ -189,7 +189,7 @@ function detailLines(picker: ModelPickerModal, width: number): string[] {
|
|
|
189
189
|
} else {
|
|
190
190
|
lines.push(`Context cap overrides the detected local-model context window for this selection.`);
|
|
191
191
|
}
|
|
192
|
-
//
|
|
192
|
+
// A visible cursor glyph on the query itself when search is focused —
|
|
193
193
|
// the only prior affordance was a footer-hint text swap ('/ search' vs.
|
|
194
194
|
// 'Typing filters search...'), buried at the end of a long hint line and
|
|
195
195
|
// easy to miss (confirmed via live tmux repro). This puts the "you are
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - name, timestamp (formatted), message count
|
|
7
7
|
* Footer hints: [Enter] Load [d] Delete [Esc] Close
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* When the modal was wired with a cross-surface session union
|
|
10
10
|
* (`modal.crossSurfaceView.mode !== 'local'`), an additional read-only
|
|
11
11
|
* "Cross-surface sessions" section is appended, badged kind/status/project
|
|
12
12
|
* (parity with the webui SessionsView) with one of three honest states —
|
|
@@ -66,7 +66,7 @@ function isReapedRecord(record: SharedSessionRecord): boolean {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* UX-lens note: 'reaped' names a mechanism (the idle-session sweep),
|
|
70
70
|
* not a state a first-time reader can guess — the webui pairs its own
|
|
71
71
|
* 'reaped' badge with a tooltip explaining it
|
|
72
72
|
* (SessionsView.tsx: "Closed by the idle-session sweep — reopens
|
|
@@ -96,7 +96,7 @@ const MAX_CROSS_SURFACE_ROWS = 5;
|
|
|
96
96
|
/**
|
|
97
97
|
* The exact honest note for the current state, or null when the union view
|
|
98
98
|
* needs no caveat (fresh/embedded with rows). Precedence: offline > stale >
|
|
99
|
-
* true-empty — the three states
|
|
99
|
+
* true-empty — the three designed states, never collapsed into
|
|
100
100
|
* each other (an offline view never silently renders as "no sessions yet").
|
|
101
101
|
*/
|
|
102
102
|
function crossSurfaceNote(view: SessionPickerModal['crossSurfaceView'], rowCount: number): string | null {
|
|
@@ -238,7 +238,7 @@ export function renderSessionPickerModal(
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
//
|
|
241
|
+
// Cross-surface session union — visible only when a sessionBroker
|
|
242
242
|
// was wired (mode !== 'local'); absent entirely otherwise, so the box size
|
|
243
243
|
// and content of every pre-existing (local-only) caller is unaffected.
|
|
244
244
|
if (modal.crossSurfaceView.mode !== 'local') {
|
|
@@ -48,7 +48,7 @@ export interface ShellFooterBuildOptions {
|
|
|
48
48
|
*/
|
|
49
49
|
readonly compact?: boolean;
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
51
|
+
* Cross-surface session-spine posture for the context-info segment.
|
|
52
52
|
* Set ONLY in adopted-daemon mode ('online'/'offline'); left undefined in
|
|
53
53
|
* embedded/local mode so no segment renders.
|
|
54
54
|
*/
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
// Extracted as a neutral module so both status-token.ts and polish.ts can
|
|
5
5
|
// import from here without creating a circular ESM dependency.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
// contract (@pellux/goodvibes-sdk/platform/presentation,
|
|
9
|
-
// already adopted by the agent
|
|
7
|
+
// STATE_GLYPHS is no longer hardcoded here. It is the SDK presentation
|
|
8
|
+
// contract (@pellux/goodvibes-sdk/platform/presentation, and
|
|
9
|
+
// already adopted by the agent), aliased to GLYPHS.status so the
|
|
10
10
|
// four semantic glyphs are spelled out in exactly one place and can never
|
|
11
11
|
// drift from the registry again. Re-exported under the historical name so
|
|
12
12
|
// status-token.ts and polish.ts import unchanged.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* terminal-bg-probe — OSC 11 terminal-background detection for `auto` theme mode.
|
|
3
3
|
*
|
|
4
|
-
* DEBT-2
|
|
4
|
+
* DEBT-2. On startup, when appearance is `auto` and stdout is a TTY, we ask
|
|
5
5
|
* the terminal for its background colour with an OSC 11 query and classify the
|
|
6
6
|
* reply as light or dark. Everything about this is best-effort and conservative:
|
|
7
7
|
* any timeout, any unparseable reply, any ambiguity resolves to DARK, which is
|
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
|
|
@@ -27,6 +27,7 @@ import { loadBootstrapSystemPrompt, syncConfiguredServices } from '@/runtime/ind
|
|
|
27
27
|
import { registerBootstrapHookBridge } from '@/runtime/index.ts';
|
|
28
28
|
import { registerBootstrapRuntimeEvents } from '@/runtime/index.ts';
|
|
29
29
|
import { createRuntimeServices, type RuntimeServices } from './services.ts';
|
|
30
|
+
import { runBootMemoryFold } from './memory-fold.ts';
|
|
30
31
|
import { setPricingSource } from '../export/cost-utils.ts';
|
|
31
32
|
import { createUiRuntimeServices, type UiRuntimeServices } from './ui-services.ts';
|
|
32
33
|
import { join } from 'node:path';
|
|
@@ -118,7 +119,7 @@ export interface BootstrapCoreState {
|
|
|
118
119
|
readonly runtimeSessionIdRef: { value: string };
|
|
119
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. */
|
|
120
121
|
readonly sessionSpine: SessionSpineClient;
|
|
121
|
-
/**
|
|
122
|
+
/** Inbound steer/follow-up delivery; dormant until bootstrap.ts activates it. */
|
|
122
123
|
readonly sessionInboundInputs: SessionInboundInputPoller;
|
|
123
124
|
/** Cache-backed read facade; bootstrap.ts drives its mode (embedded/external/local-only) from the same HostServiceMode as the spine above. */
|
|
124
125
|
readonly sessionUnionCache: SessionUnionCache;
|
|
@@ -407,24 +408,23 @@ export async function initializeBootstrapCore(
|
|
|
407
408
|
void memoryStore.save();
|
|
408
409
|
memoryStore.close();
|
|
409
410
|
});
|
|
411
|
+
// Fold this project's legacy per-project TUI memory into the home-scoped canonical store, ONCE, AFTER init(). Idempotent and non-fatal.
|
|
412
|
+
await runBootMemoryFold(memoryStore, services.memoryEmbeddingRegistry, workingDir, logger);
|
|
410
413
|
|
|
411
414
|
const renderRequestRef = { value: (): void => {} };
|
|
412
|
-
// R1: Coalescing render scheduler — collapses N requestRender() calls into 1
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
// of each queuing their own setTimeout. (The streaming hot path drives its own
|
|
419
|
-
// direct repaints and does not flow through this scheduler.)
|
|
415
|
+
// R1: Coalescing render scheduler — collapses N requestRender() calls into 1 and
|
|
416
|
+
// enforces a 16ms minimum interval to cap repaints at ~60fps. renderScheduled stays
|
|
417
|
+
// set for the ENTIRE window (until run() executes), so requestRender() calls arriving
|
|
418
|
+
// on later event-loop ticks within the same 16ms window coalesce into the one
|
|
419
|
+
// already-pending tail render instead of each queuing their own setTimeout. (The
|
|
420
|
+
// streaming hot path drives its own direct repaints and skips this scheduler.)
|
|
420
421
|
let renderScheduled = false;
|
|
421
422
|
let lastRenderTime = 0;
|
|
422
423
|
const RENDER_INTERVAL_MS = 16;
|
|
423
|
-
// run() performs the actual render. It clears renderScheduled FIRST — even if
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
// error so the next requestRender() can still reschedule.
|
|
424
|
+
// run() performs the actual render. It clears renderScheduled FIRST — even if the
|
|
425
|
+
// render callback throws — otherwise a single render exception would wedge the entire
|
|
426
|
+
// TUI (no future requestRender() would schedule anything); we log at error so the
|
|
427
|
+
// next requestRender() can still reschedule.
|
|
428
428
|
const run = (): void => {
|
|
429
429
|
renderScheduled = false;
|
|
430
430
|
lastRenderTime = Date.now();
|
|
@@ -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.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import {
|
|
3
3
|
foldMemoryStores,
|
|
4
|
+
formatMemoryFoldReport,
|
|
4
5
|
type LegacyMemorySource,
|
|
5
6
|
type MemoryEmbeddingProviderRegistry,
|
|
6
7
|
type MemoryFoldReport,
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
} from '@pellux/goodvibes-sdk/platform/state';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* Fold the TUI's legacy per-project memory store into the canonical
|
|
12
13
|
* cross-surface store. Called once at boot AFTER `memoryStore.init()` so records written
|
|
13
14
|
* before unification survive. Id-keyed and idempotent — a re-run imports nothing new and
|
|
14
15
|
* never deletes the legacy file. Returns the report so boot can log what moved.
|
|
@@ -24,3 +25,31 @@ export async function foldTuiLegacyMemory(
|
|
|
24
25
|
];
|
|
25
26
|
return foldMemoryStores(memoryStore, sources, { embeddingRegistry: memoryEmbeddingRegistry });
|
|
26
27
|
}
|
|
28
|
+
|
|
29
|
+
/** A minimal boot logger seam (matches the SDK logger's info/warn shape). */
|
|
30
|
+
export interface BootFoldLogger {
|
|
31
|
+
info(message: string, meta?: unknown): void;
|
|
32
|
+
warn(message: string, meta?: unknown): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run the legacy TUI memory fold at boot and surface the report. Never blocks or
|
|
37
|
+
* fails boot: a fold error degrades to a warn and the canonical store serves on.
|
|
38
|
+
* Only logs the report when something actually moved (or a source failed), so a
|
|
39
|
+
* clean boot stays quiet.
|
|
40
|
+
*/
|
|
41
|
+
export async function runBootMemoryFold(
|
|
42
|
+
memoryStore: MemoryStore,
|
|
43
|
+
memoryEmbeddingRegistry: MemoryEmbeddingProviderRegistry,
|
|
44
|
+
workingDirectory: string,
|
|
45
|
+
log: BootFoldLogger,
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
try {
|
|
48
|
+
const report = await foldTuiLegacyMemory(memoryStore, memoryEmbeddingRegistry, workingDirectory);
|
|
49
|
+
if (report.totalImported > 0 || report.failedSources.length > 0) {
|
|
50
|
+
log.info(`[bootstrap] memory fold: ${formatMemoryFoldReport(report)}`);
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
log.warn('memory fold at bootstrap failed (non-fatal; canonical store unaffected)', { err });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -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.
|