@pellux/goodvibes-tui 1.9.1 → 1.10.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 +36 -5
- package/README.md +1 -1
- package/docs/foundation-artifacts/knowledge-graphql.graphql +5 -0
- package/docs/foundation-artifacts/knowledge-store.sql +33 -0
- package/docs/foundation-artifacts/operator-contract.json +4026 -1376
- package/package.json +2 -2
- package/src/audio/player.ts +91 -5
- package/src/audio/spoken-turn-wiring.ts +10 -2
- package/src/cli/management-utils.ts +4 -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/export/cost-utils.ts +9 -1
- package/src/input/command-registry.ts +16 -5
- package/src/input/commands/config.ts +1 -1
- package/src/input/commands/control-room-runtime.ts +12 -5
- package/src/input/commands/incident-runtime.ts +2 -2
- package/src/input/commands/memory.ts +12 -12
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/recall-bundle.ts +8 -8
- package/src/input/commands/recall-capture.ts +3 -3
- package/src/input/commands/recall-query.ts +41 -15
- package/src/input/commands/recall-review.ts +12 -12
- 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 +56 -2
- package/src/main.ts +8 -6
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/operations.ts +4 -4
- package/src/panels/builtin/shared.ts +4 -4
- package/src/panels/cost-tracker-panel.ts +2 -2
- 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/modals/memory-modal.ts +77 -16
- 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-command-context.ts +4 -0
- package/src/runtime/bootstrap-command-parts.ts +4 -1
- package/src/runtime/bootstrap-core.ts +3 -3
- package/src/runtime/bootstrap-hook-bridge.ts +2 -2
- package/src/runtime/bootstrap-shell.ts +18 -7
- package/src/runtime/bootstrap.ts +22 -22
- 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/memory-spine-transport.ts +289 -0
- 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 +29 -4
- package/src/runtime/process-lifecycle.ts +19 -5
- package/src/runtime/services.ts +27 -28
- 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/audio/spoken-turn-controller.ts +0 -243
- package/src/audio/text-chunker.ts +0 -110
|
@@ -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
|
|
@@ -2,6 +2,7 @@ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
2
2
|
import type { AdaptivePlanner } from '@pellux/goodvibes-sdk/platform/core';
|
|
3
3
|
import type { ConversationManager } from '../core/conversation';
|
|
4
4
|
import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
5
|
+
import type { MemorySpineClient } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
|
|
5
6
|
import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
6
7
|
import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
7
8
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
@@ -107,6 +108,7 @@ export type CreateBootstrapCommandContextOptions = {
|
|
|
107
108
|
operatorClient?: OperatorClient;
|
|
108
109
|
peerClient?: PeerClient;
|
|
109
110
|
knowledgeApi?: KnowledgeApi;
|
|
111
|
+
memorySpine?: MemorySpineClient;
|
|
110
112
|
hookApi?: HookApi;
|
|
111
113
|
mcpApi?: McpApi;
|
|
112
114
|
opsApi?: OpsApi;
|
|
@@ -195,6 +197,7 @@ export function createBootstrapCommandContext(
|
|
|
195
197
|
operatorClient,
|
|
196
198
|
peerClient,
|
|
197
199
|
knowledgeApi,
|
|
200
|
+
memorySpine,
|
|
198
201
|
hookApi,
|
|
199
202
|
mcpApi,
|
|
200
203
|
opsApi,
|
|
@@ -279,6 +282,7 @@ export function createBootstrapCommandContext(
|
|
|
279
282
|
peerClient,
|
|
280
283
|
providerApi,
|
|
281
284
|
knowledgeApi,
|
|
285
|
+
memorySpine,
|
|
282
286
|
hookApi,
|
|
283
287
|
mcpApi,
|
|
284
288
|
opsApi,
|
|
@@ -3,6 +3,7 @@ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
3
3
|
import type { AdaptivePlanner } from '@pellux/goodvibes-sdk/platform/core';
|
|
4
4
|
import type { ConversationManager } from '../core/conversation';
|
|
5
5
|
import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
6
|
+
import type { MemorySpineClient } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
|
|
6
7
|
import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
7
8
|
import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
8
9
|
import type { PanelManager, PanelDeepLinkTarget } from '../panels/panel-manager.ts';
|
|
@@ -133,6 +134,7 @@ export interface BootstrapCommandSectionOptions {
|
|
|
133
134
|
readonly peerClient?: PeerClient;
|
|
134
135
|
readonly providerApi?: ProviderApi;
|
|
135
136
|
readonly knowledgeApi?: KnowledgeApi;
|
|
137
|
+
readonly memorySpine?: MemorySpineClient;
|
|
136
138
|
readonly hookApi?: HookApi;
|
|
137
139
|
readonly mcpApi?: McpApi;
|
|
138
140
|
readonly opsApi?: OpsApi;
|
|
@@ -421,7 +423,7 @@ export function createBootstrapCommandExtensionsSection(
|
|
|
421
423
|
export function createBootstrapCommandClientsSection(
|
|
422
424
|
options: Pick<
|
|
423
425
|
BootstrapCommandSectionOptions,
|
|
424
|
-
'operatorClient' | 'peerClient' | 'providerApi' | 'knowledgeApi' | 'hookApi' | 'mcpApi' | 'opsApi' | 'directTransport'
|
|
426
|
+
'operatorClient' | 'peerClient' | 'providerApi' | 'knowledgeApi' | 'memorySpine' | 'hookApi' | 'mcpApi' | 'opsApi' | 'directTransport'
|
|
425
427
|
>,
|
|
426
428
|
): BootstrapCommandClientSection {
|
|
427
429
|
return {
|
|
@@ -429,6 +431,7 @@ export function createBootstrapCommandClientsSection(
|
|
|
429
431
|
peer: options.peerClient,
|
|
430
432
|
providerApi: options.providerApi,
|
|
431
433
|
knowledgeApi: options.knowledgeApi,
|
|
434
|
+
memorySpine: options.memorySpine,
|
|
432
435
|
hookApi: options.hookApi,
|
|
433
436
|
mcpApi: options.mcpApi,
|
|
434
437
|
opsApi: options.opsApi,
|
|
@@ -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[] = [];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { readBudgetAlertUsd, BUDGET_ALERT_USD_DEFAULT } from '../export/cost-utils.ts';
|
|
3
|
+
import { refreshMemoryRecallSnapshot } from './orchestrator-core-services.ts';
|
|
3
4
|
import { sumConversationUsage, type ConversationManager } from '../core/conversation';
|
|
4
5
|
import type { Orchestrator } from '../core/orchestrator';
|
|
5
6
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
@@ -48,7 +49,7 @@ export interface BootstrapShellOptions {
|
|
|
48
49
|
readonly runtimeBus: RuntimeEventBus;
|
|
49
50
|
readonly runtimeStore: RuntimeStore;
|
|
50
51
|
readonly services: RuntimeServices;
|
|
51
|
-
/**
|
|
52
|
+
/** Dormant until bootstrap.ts activates it for an adopted 'external' daemon. */
|
|
52
53
|
readonly sessionSpine: SessionSpineClient;
|
|
53
54
|
readonly conversation: ConversationManager;
|
|
54
55
|
readonly runtime: MutableRuntimeState;
|
|
@@ -181,7 +182,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
181
182
|
forensicsRegistry,
|
|
182
183
|
policyRuntimeState,
|
|
183
184
|
approvalBroker: services.approvalBroker,
|
|
184
|
-
//
|
|
185
|
+
// Panels read the cross-surface union facade, not the raw local broker.
|
|
185
186
|
sessionBroker: uiServices.sessions.sessionBroker,
|
|
186
187
|
automationManager: services.automationManager,
|
|
187
188
|
getControlPlaneRecentEvents,
|
|
@@ -189,7 +190,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
189
190
|
componentHealthMonitor: services.componentHealthMonitor,
|
|
190
191
|
worktreeRegistry: services.worktreeRegistry,
|
|
191
192
|
sandboxSessionRegistry: services.sandboxSessionRegistry,
|
|
192
|
-
|
|
193
|
+
// Memory modal reads via the spine client, not the raw registry (see builtin/shared.ts).
|
|
194
|
+
memoryRegistry: services.memorySpine,
|
|
193
195
|
uiServices,
|
|
194
196
|
pluginManager: services.pluginManager,
|
|
195
197
|
hookDispatcher: services.hookDispatcher,
|
|
@@ -289,7 +291,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
289
291
|
codeIndexStore: services.codeIndexStore,
|
|
290
292
|
codeIndexReindexScheduler: services.codeIndexReindexScheduler,
|
|
291
293
|
isPassiveCodeInjectionFlagEnabled: () => services.featureFlags.isEnabled('agent-passive-code-injection'),
|
|
292
|
-
//
|
|
294
|
+
// Expose the MAIN session's per-turn passive-injection ring
|
|
293
295
|
// so `/recall injections` (no agent id) renders it — see recall-review.ts.
|
|
294
296
|
getMainSessionTurnInjections: () => orchestrator.getTurnInjections(),
|
|
295
297
|
changeTracker: services.sessionChangeTracker,
|
|
@@ -299,6 +301,10 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
299
301
|
operatorClient: directTransport.operator,
|
|
300
302
|
peerClient: directTransport.peer,
|
|
301
303
|
knowledgeApi,
|
|
304
|
+
// /recall's browse/link/queue/export/import subcommands and the per-turn
|
|
305
|
+
// knowledge-injection read route through the spine client, not the raw
|
|
306
|
+
// local registry, so they fully detach when a daemon is adopted.
|
|
307
|
+
memorySpine: services.memorySpine,
|
|
302
308
|
hookApi,
|
|
303
309
|
mcpApi,
|
|
304
310
|
opsApi,
|
|
@@ -309,9 +315,14 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
309
315
|
loadSystemPrompt: () => loadBootstrapSystemPrompt(configManager),
|
|
310
316
|
activatePlan: (_planId, task) => {
|
|
311
317
|
setTimeout(() => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
318
|
+
void (async () => {
|
|
319
|
+
// Refresh the recall snapshot before this plan-driven turn — see
|
|
320
|
+
// the matching comment in main.ts's submitInput.
|
|
321
|
+
await refreshMemoryRecallSnapshot(services);
|
|
322
|
+
orchestrator.handleUserInput(task).catch((err) => {
|
|
323
|
+
logger.debug('activatePlan handler failed', { error: summarizeError(err) });
|
|
324
|
+
});
|
|
325
|
+
})();
|
|
315
326
|
}, 50);
|
|
316
327
|
},
|
|
317
328
|
completeModelSelectionSideEffect,
|
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -39,6 +39,7 @@ import { startExternalServices, type ExternalServicesHandle, type HostServiceSta
|
|
|
39
39
|
import { createHttpTransport } from '@/runtime/index.ts';
|
|
40
40
|
import { foldLegacySpineStore, deriveSpineFooterStatus } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
41
41
|
import { createTuiSpineTransport, type SpineSessionsClient } from './session-spine-transport.ts';
|
|
42
|
+
import { syncMemorySpineToHostStatus, type MemorySpineActiveRef } from './memory-spine-transport.ts';
|
|
42
43
|
import { pruneStaleOperatorTokens } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
43
44
|
import { resolveDaemonCompanionToken, workspaceOperatorTokenCandidates } from './operator-token-cleanup.ts';
|
|
44
45
|
import type { UiRuntimeServices } from './ui-services.ts';
|
|
@@ -46,7 +47,7 @@ import { createDeferredStartupCoordinator } from '@/runtime/index.ts';
|
|
|
46
47
|
import { initializeBootstrapCore } from './bootstrap-core.ts';
|
|
47
48
|
import { createBootstrapShell } from './bootstrap-shell.ts';
|
|
48
49
|
import { announceResumeState } from './resume-notice.ts';
|
|
49
|
-
import { buildSharedOrchestratorCoreServices } from './orchestrator-core-services.ts';
|
|
50
|
+
import { buildSharedOrchestratorCoreServices, refreshMemoryRecallSnapshot } from './orchestrator-core-services.ts';
|
|
50
51
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
51
52
|
import { DaemonServer } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
52
53
|
import { HttpListener } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
@@ -199,7 +200,7 @@ export async function bootstrapRuntime(
|
|
|
199
200
|
pluginManager,
|
|
200
201
|
} = services;
|
|
201
202
|
|
|
202
|
-
//
|
|
203
|
+
// A liveness flip is only PAINTED once something calls requestRender();
|
|
203
204
|
// without this, the footer's spine segment sat correct-but-undrawn until
|
|
204
205
|
// incidental activity redrew it (minutes, during an idle stretch).
|
|
205
206
|
sessionUnionCache.setOnTransition(() => requestRender());
|
|
@@ -238,9 +239,9 @@ export async function bootstrapRuntime(
|
|
|
238
239
|
},
|
|
239
240
|
});
|
|
240
241
|
conversationFollowUpRef.value = (item) => orchestrator.enqueueConversationFollowUp(item);
|
|
241
|
-
// Wire orchestratorHandleUserInputRef so COMPANION_MESSAGE_RECEIVED fires a real LLM turn.
|
|
242
|
+
// Wire orchestratorHandleUserInputRef so COMPANION_MESSAGE_RECEIVED fires a real LLM turn (after a pre-turn recall-snapshot refresh; see main.ts's submitInput).
|
|
242
243
|
orchestratorHandleUserInputRef.value = (text: string, options?: OrchestratorUserInputOptions) => {
|
|
243
|
-
orchestrator.handleUserInput(text, undefined, options).catch((err: unknown) => {
|
|
244
|
+
void refreshMemoryRecallSnapshot(services).then(() => orchestrator.handleUserInput(text, undefined, options)).catch((err: unknown) => {
|
|
244
245
|
logger.debug('companion handleUserInput safety catch', { error: String(err) });
|
|
245
246
|
});
|
|
246
247
|
};
|
|
@@ -300,13 +301,10 @@ export async function bootstrapRuntime(
|
|
|
300
301
|
wrfcPersistence.rehydrate();
|
|
301
302
|
const commandRegistry = shell.commandRegistry;
|
|
302
303
|
const commandContext = shell.commandContext;
|
|
303
|
-
// Boot resume notice (UX-D item 1): after rehydrate() so chain history is
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
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.
|
|
304
|
+
// Boot resume notice (UX-D item 1): after rehydrate() so chain history is ready, before
|
|
305
|
+
// the operator can type anything. Fire-and-forget, same as main.ts's non-blocking
|
|
306
|
+
// `void workspaceCheckpointManager.init().catch(() => {})` — local file I/O only,
|
|
307
|
+
// resolves well before a human can react to the first rendered frame.
|
|
310
308
|
void announceResumeState({
|
|
311
309
|
workingDirectory: services.workingDirectory,
|
|
312
310
|
homeDirectory: services.homeDirectory,
|
|
@@ -390,7 +388,13 @@ export async function bootstrapRuntime(
|
|
|
390
388
|
// 'unavailable') also stays local-only and honest. Only 'external' (a separately-
|
|
391
389
|
// running daemon this TUI adopted) activates the wire mirror.
|
|
392
390
|
let spineActiveForBaseUrl: string | null = null;
|
|
391
|
+
// Same adoption signal drives the memory spine (WO memory-adopt) — see
|
|
392
|
+
// memory-spine-transport.ts. 'embedded' (this process hosts its own daemon)
|
|
393
|
+
// stays local: the daemon's canonical store IS this process's own
|
|
394
|
+
// memoryRegistry, so there is no wire hop to make.
|
|
395
|
+
const memorySpineActiveRef: MemorySpineActiveRef = { value: null };
|
|
393
396
|
const syncSessionSpineToHostStatus = (daemonStatus: HostServiceStatus, sharedDaemonToken: string): void => {
|
|
397
|
+
syncMemorySpineToHostStatus(services.memorySpine, daemonStatus.mode, daemonStatus.baseUrl, sharedDaemonToken, memorySpineActiveRef, logger);
|
|
394
398
|
if (daemonStatus.mode !== 'external') {
|
|
395
399
|
if (spineActiveForBaseUrl !== null) {
|
|
396
400
|
sessionSpine.deactivate(`daemon mode changed to '${daemonStatus.mode}'`);
|
|
@@ -413,7 +417,7 @@ export async function bootstrapRuntime(
|
|
|
413
417
|
close: (sessionId) => httpTransport.operator.sessions.close(sessionId),
|
|
414
418
|
};
|
|
415
419
|
sessionSpine.activate(createTuiSpineTransport(sessionsClient));
|
|
416
|
-
//
|
|
420
|
+
// Adopt the same daemon's wire for the INBOUND steer path — collect
|
|
417
421
|
// steer/follow-up inputs another live surface queued for this session and
|
|
418
422
|
// inject them into the turn machinery (acking delivery on the wire).
|
|
419
423
|
sessionInboundInputs.activate({
|
|
@@ -450,14 +454,10 @@ export async function bootstrapRuntime(
|
|
|
450
454
|
httpListenerPortInUse: hostServiceIsBlocked(httpListenerStatus),
|
|
451
455
|
daemonStatus,
|
|
452
456
|
httpListenerStatus,
|
|
453
|
-
// Honest session-spine posture, independent of daemonRunning —
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
//
|
|
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.
|
|
457
|
+
// Honest session-spine posture, independent of daemonRunning — degrades to 'offline'
|
|
458
|
+
// when adopted-but-unreachable even though daemonRunning might still read a stale
|
|
459
|
+
// handle as true; sessionSpine.status() alone is activity-gated, so it's derived
|
|
460
|
+
// together with the union cache's 5s liveness probe (one signal, no new timer).
|
|
461
461
|
sessionSpineActive: sessionSpine.active,
|
|
462
462
|
sessionSpineStatus: deriveSpineFooterStatus(sessionSpine.status(), sessionUnionCache.crossSurfaceView),
|
|
463
463
|
};
|
|
@@ -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.
|