@pellux/goodvibes-tui 1.7.0 → 1.9.1
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 +11 -0
- package/README.md +10 -9
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/config/credential-availability.ts +122 -0
- package/src/config/index.ts +15 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +71 -14
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +68 -1
- package/src/core/turn-cancellation.ts +7 -2
- package/src/core/turn-event-wiring.ts +10 -2
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +46 -4
- package/src/input/commands/codebase-runtime.ts +46 -6
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/health-runtime.ts +9 -1
- package/src/input/commands/memory.ts +68 -35
- package/src/input/commands/operator-panel-runtime.ts +31 -7
- package/src/input/commands/planning-runtime.ts +95 -6
- package/src/input/commands/qrcode-runtime.ts +25 -5
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/settings-sync-runtime.ts +15 -3
- package/src/input/commands/shell-core.ts +10 -1
- package/src/input/commands/workstream-runtime.ts +168 -18
- package/src/input/config-modal-types.ts +15 -1
- package/src/input/config-modal.ts +227 -12
- package/src/input/feed-context-factory.ts +3 -0
- package/src/input/handler-command-route.ts +10 -3
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +43 -121
- package/src/input/handler-feed.ts +32 -4
- package/src/input/handler-modal-routes.ts +78 -13
- package/src/input/handler-modal-stack.ts +11 -2
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +15 -8
- package/src/input/handler-shortcuts.ts +59 -9
- package/src/input/handler.ts +6 -1
- package/src/input/keybindings.ts +6 -5
- package/src/input/model-picker.ts +19 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +51 -2
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +28 -27
- package/src/panels/builtin/shared.ts +9 -3
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +89 -131
- package/src/panels/fleet-read-model.ts +43 -0
- package/src/panels/fleet-steer.ts +35 -2
- package/src/panels/fleet-stop.ts +29 -1
- package/src/panels/modals/keybindings-modal.ts +16 -1
- package/src/panels/modals/modal-theme.ts +35 -29
- package/src/panels/modals/pairing-modal.ts +25 -6
- package/src/panels/modals/planning-modal.ts +43 -21
- package/src/panels/modals/work-plan-modal.ts +28 -0
- package/src/panels/panel-manager.ts +15 -4
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -13
- package/src/panels/types.ts +24 -0
- package/src/permissions/prompt.ts +160 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +19 -5
- package/src/renderer/footer-tips.ts +5 -1
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +3 -1
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +18 -3
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +22 -3
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +6 -0
- package/src/runtime/bootstrap-command-parts.ts +8 -4
- package/src/runtime/bootstrap-core.ts +46 -24
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +19 -1
- package/src/runtime/bootstrap.ts +118 -5
- package/src/runtime/code-index-services.ts +25 -2
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +55 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +10 -0
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +12 -8
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +19 -3
- package/src/runtime/workstream-services.ts +160 -28
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +46 -3
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +42 -9
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
|
@@ -1,134 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
moreBelow: '▼',
|
|
29
|
-
next: '→',
|
|
30
|
-
back: '←',
|
|
31
|
-
pipeSeparator: '│',
|
|
32
|
-
},
|
|
33
|
-
status: {
|
|
34
|
-
success: '✓',
|
|
35
|
-
failure: '✕',
|
|
36
|
-
pending: '•',
|
|
37
|
-
active: '●',
|
|
38
|
-
idle: '◌',
|
|
39
|
-
info: '○',
|
|
40
|
-
warn: '⚠',
|
|
41
|
-
blocked: '⊘',
|
|
42
|
-
skipped: '◇',
|
|
43
|
-
review: '◈',
|
|
44
|
-
retry: '↻',
|
|
45
|
-
handoff: '⇢',
|
|
46
|
-
reference: '↗',
|
|
47
|
-
partial: '◐',
|
|
48
|
-
dualPane: '◆',
|
|
49
|
-
star: '★',
|
|
50
|
-
},
|
|
51
|
-
meter: {
|
|
52
|
-
filled: '█',
|
|
53
|
-
medium: '▓',
|
|
54
|
-
light: '▒',
|
|
55
|
-
empty: '░',
|
|
56
|
-
spark: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'],
|
|
57
|
-
},
|
|
58
|
-
} as const;
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// ui-primitives.ts — glyph registry + tone-token table.
|
|
3
|
+
//
|
|
4
|
+
// W6-P1: these four tables (GLYPHS, UI_TONES, DIFF_TONES, SPINNER_FRAMES) are
|
|
5
|
+
// no longer minted locally. They are the SDK presentation contract
|
|
6
|
+
// (@pellux/goodvibes-sdk/platform/presentation, landed by W4-S1 and already
|
|
7
|
+
// adopted by the agent in W4-R4), consumed here so the TUI and the agent
|
|
8
|
+
// share ONE source (Mike's move-to-SDK ruling — machinery needed by 2+
|
|
9
|
+
// surfaces => SDK). The TUI was the reference these values were lifted from
|
|
10
|
+
// verbatim, so this swap is byte-identical. See
|
|
11
|
+
// docs/decisions/2026-07-05-presentation-contract-sdk-extraction.md in the SDK.
|
|
12
|
+
//
|
|
13
|
+
// Re-exported under the historical names (GLYPHS, UI_TONES) so every existing
|
|
14
|
+
// importer keeps working with no call-site churn. UI_TONES is the dark-mode
|
|
15
|
+
// tone table (== resolveTones('dark')); light is resolved via theme.ts's
|
|
16
|
+
// activeUiTones() / resolveUiTones(), which composes the mode dimension over
|
|
17
|
+
// this dark constant unchanged.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
GLYPHS,
|
|
22
|
+
TONE_TOKENS,
|
|
23
|
+
DIFF_TONES,
|
|
24
|
+
SPINNER_FRAMES,
|
|
25
|
+
} from '@pellux/goodvibes-sdk/platform/presentation';
|
|
26
|
+
|
|
27
|
+
export { GLYPHS, DIFF_TONES, SPINNER_FRAMES };
|
|
59
28
|
|
|
60
29
|
/**
|
|
61
30
|
* UI_TONES — the single chrome/color-token source for src/renderer and
|
|
62
31
|
* src/panels. Concrete (dark-mode) values; resolveUiTones(mode) in theme.ts
|
|
63
|
-
* is the single mode-resolved read path — this object is the 'dark' entry
|
|
64
|
-
|
|
65
|
-
export const UI_TONES = {
|
|
66
|
-
fg: {
|
|
67
|
-
primary: '#e2e8f0',
|
|
68
|
-
secondary: '#cbd5e1',
|
|
69
|
-
muted: '#94a3b8',
|
|
70
|
-
dim: '#475569',
|
|
71
|
-
inverse: '#0f172a',
|
|
72
|
-
/** Empty-state / placeholder foreground (formerly DEFAULT_PANEL_PALETTE.empty literal). */
|
|
73
|
-
empty: '#334155',
|
|
74
|
-
},
|
|
75
|
-
bg: {
|
|
76
|
-
base: '#11131a',
|
|
77
|
-
surface: '#161a22',
|
|
78
|
-
title: '#0f172a',
|
|
79
|
-
section: '#18202b',
|
|
80
|
-
summary: '#1b2430',
|
|
81
|
-
selected: '#223049',
|
|
82
|
-
input: '#1e293b',
|
|
83
|
-
warning: '#2b2116',
|
|
84
|
-
error: '#2a161b',
|
|
85
|
-
success: '#14241b',
|
|
86
|
-
/** Fullscreen/shell footer background. */
|
|
87
|
-
footer: '#111827',
|
|
88
|
-
},
|
|
89
|
-
state: {
|
|
90
|
-
info: '#38bdf8',
|
|
91
|
-
good: '#22c55e',
|
|
92
|
-
warn: '#f59e0b',
|
|
93
|
-
bad: '#ef4444',
|
|
94
|
-
blocked: '#f97316',
|
|
95
|
-
active: '#60a5fa',
|
|
96
|
-
/** Single canonical reasoning/thinking purple (replaces #9945FF and ad-hoc purples). */
|
|
97
|
-
reasoning: '#a855f7',
|
|
98
|
-
},
|
|
99
|
-
accent: {
|
|
100
|
-
browser: '#7dd3fc',
|
|
101
|
-
control: '#22d3ee',
|
|
102
|
-
inspector: '#c4b5fd',
|
|
103
|
-
workflow: '#fbbf24',
|
|
104
|
-
conversation: '#93c5fd',
|
|
105
|
-
/** Neon brand accent — header/splash/thinking gradient only. */
|
|
106
|
-
brand: '#00ffff',
|
|
107
|
-
gradientStart: '#00ffff',
|
|
108
|
-
gradientEnd: '#d000ff',
|
|
109
|
-
},
|
|
110
|
-
/** Canonical border stroke color for fullscreen/panel chrome. */
|
|
111
|
-
border: '#64748b',
|
|
112
|
-
} as const;
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Diff surface accent color shared across the three diff-rendering surfaces —
|
|
116
|
-
* the conversation diff view (diff-view.ts), the file diff panel
|
|
117
|
-
* (diff-panel.ts), and the git panel's inline diff (git-panel.ts). Hunk-header
|
|
118
|
-
* blue has no matching UI_TONES.state/accent role, so WO-204 gives it one
|
|
119
|
-
* named token here (value converged on diff-panel.ts's pre-existing hunk
|
|
120
|
-
* color, the only one of the three surfaces that predates this token; add/del
|
|
121
|
-
* on all three surfaces already converge on UI_TONES.state.good/bad).
|
|
32
|
+
* is the single mode-resolved read path — this object is the 'dark' entry
|
|
33
|
+
* (== resolveTones('dark') from the SDK presentation contract).
|
|
122
34
|
*/
|
|
123
|
-
export const
|
|
124
|
-
// add/del carry diff-panel.ts's shipped colors: the diff panel is the only
|
|
125
|
-
// diff surface users have ever seen (diff-view.ts was unwired from v0.9.6
|
|
126
|
-
// until WO-204), so its look is the reference — the conversation surface
|
|
127
|
-
// adopts it, never the other way around.
|
|
128
|
-
add: '#00ff88',
|
|
129
|
-
del: '#ff4444',
|
|
130
|
-
hunk: '#88aaff',
|
|
131
|
-
} as const;
|
|
132
|
-
|
|
133
|
-
/** Single spinner-frame source — layout.ts and progress.ts both re-export this. */
|
|
134
|
-
export const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] as const;
|
|
35
|
+
export const UI_TONES = TONE_TOKENS;
|
|
@@ -123,6 +123,8 @@ export type CreateBootstrapCommandContextOptions = {
|
|
|
123
123
|
hydrateSessionUsage?: () => void;
|
|
124
124
|
workstreamEngine?: import('./workstream-services.ts').WorkstreamCommandService;
|
|
125
125
|
codeIndexStore?: import('@pellux/goodvibes-sdk/platform/state').CodeIndexStore;
|
|
126
|
+
codeIndexReindexScheduler?: import('@pellux/goodvibes-sdk/platform/state').CodeIndexReindexScheduler;
|
|
127
|
+
isPassiveCodeInjectionFlagEnabled?: () => boolean;
|
|
126
128
|
getMainSessionTurnInjections?: () => readonly import('../renderer/turn-injection.ts').TurnInjectionEntry[];
|
|
127
129
|
};
|
|
128
130
|
|
|
@@ -184,6 +186,8 @@ export function createBootstrapCommandContext(
|
|
|
184
186
|
hydrateSessionUsage,
|
|
185
187
|
workstreamEngine,
|
|
186
188
|
codeIndexStore,
|
|
189
|
+
codeIndexReindexScheduler,
|
|
190
|
+
isPassiveCodeInjectionFlagEnabled,
|
|
187
191
|
getMainSessionTurnInjections,
|
|
188
192
|
planManager,
|
|
189
193
|
adaptivePlanner,
|
|
@@ -244,6 +248,8 @@ export function createBootstrapCommandContext(
|
|
|
244
248
|
hydrateSessionUsage,
|
|
245
249
|
workstreamEngine,
|
|
246
250
|
codeIndexStore,
|
|
251
|
+
codeIndexReindexScheduler,
|
|
252
|
+
isPassiveCodeInjectionFlagEnabled,
|
|
247
253
|
getMainSessionTurnInjections,
|
|
248
254
|
});
|
|
249
255
|
const provider = createBootstrapCommandProviderSection({
|
|
@@ -5,7 +5,7 @@ import type { ConversationManager } from '../core/conversation';
|
|
|
5
5
|
import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
6
6
|
import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
7
7
|
import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
8
|
-
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
8
|
+
import type { PanelManager, PanelDeepLinkTarget } from '../panels/panel-manager.ts';
|
|
9
9
|
import type { ProviderApi } from '@pellux/goodvibes-sdk/platform/providers';
|
|
10
10
|
import type { OpsApi } from '@/runtime/index.ts';
|
|
11
11
|
import type { MutableRuntimeState } from '@/runtime/index.ts';
|
|
@@ -118,6 +118,8 @@ export interface BootstrapCommandSectionOptions {
|
|
|
118
118
|
readonly hydrateSessionUsage?: () => void;
|
|
119
119
|
readonly workstreamEngine?: import('./workstream-services.ts').WorkstreamCommandService;
|
|
120
120
|
readonly codeIndexStore?: CodeIndexStore;
|
|
121
|
+
readonly codeIndexReindexScheduler?: import('@pellux/goodvibes-sdk/platform/state').CodeIndexReindexScheduler;
|
|
122
|
+
readonly isPassiveCodeInjectionFlagEnabled?: () => boolean;
|
|
121
123
|
readonly getMainSessionTurnInjections?: () => readonly import('../renderer/turn-injection.ts').TurnInjectionEntry[];
|
|
122
124
|
readonly agentManager?: ShellAgentManagerService;
|
|
123
125
|
readonly modeManager?: ShellModeManagerService;
|
|
@@ -190,7 +192,7 @@ export function createBootstrapCommandActions(
|
|
|
190
192
|
localUserAuthManager,
|
|
191
193
|
} = options;
|
|
192
194
|
|
|
193
|
-
const showPanel = (panelId: string, pane?: 'top' | 'bottom') => {
|
|
195
|
+
const showPanel = (panelId: string, pane?: 'top' | 'bottom', target?: PanelDeepLinkTarget) => {
|
|
194
196
|
// W6.1 (the purge): a MIGRATE-TO-MODAL id resolves to a modal, not a panel.
|
|
195
197
|
// panelManager.open() fires the injected openModal callback and returns a
|
|
196
198
|
// no-op sentinel — so skip panelManager.show() (which would reveal an empty
|
|
@@ -198,7 +200,7 @@ export function createBootstrapCommandActions(
|
|
|
198
200
|
// showPanel-based front-door (openHooksPanel/openSecurityPanel/… and the
|
|
199
201
|
// migrated command runtimes) opening the modal cleanly.
|
|
200
202
|
const redirected = panelManager.getModalRedirect(panelId) !== undefined;
|
|
201
|
-
panelManager.open(panelId, pane);
|
|
203
|
+
panelManager.open(panelId, pane, target);
|
|
202
204
|
if (!redirected) panelManager.show();
|
|
203
205
|
requestRender();
|
|
204
206
|
};
|
|
@@ -321,7 +323,7 @@ export function createBootstrapCommandActions(
|
|
|
321
323
|
export function createBootstrapCommandSessionSection(
|
|
322
324
|
options: Pick<
|
|
323
325
|
BootstrapCommandSectionOptions,
|
|
324
|
-
'conversation' | 'runtime' | 'sessionManager' | 'sessionMemoryStore' | 'sessionLineageTracker' | 'wrfcController' | 'changeTracker' | 'hydrateSessionUsage' | 'workstreamEngine' | 'codeIndexStore' | 'getMainSessionTurnInjections'
|
|
326
|
+
'conversation' | 'runtime' | 'sessionManager' | 'sessionMemoryStore' | 'sessionLineageTracker' | 'wrfcController' | 'changeTracker' | 'hydrateSessionUsage' | 'workstreamEngine' | 'codeIndexStore' | 'codeIndexReindexScheduler' | 'isPassiveCodeInjectionFlagEnabled' | 'getMainSessionTurnInjections'
|
|
325
327
|
>,
|
|
326
328
|
): BootstrapCommandSessionSection {
|
|
327
329
|
return {
|
|
@@ -335,6 +337,8 @@ export function createBootstrapCommandSessionSection(
|
|
|
335
337
|
hydrateSessionUsage: options.hydrateSessionUsage,
|
|
336
338
|
workstreamEngine: options.workstreamEngine,
|
|
337
339
|
codeIndexStore: options.codeIndexStore,
|
|
340
|
+
codeIndexReindexScheduler: options.codeIndexReindexScheduler,
|
|
341
|
+
isPassiveCodeInjectionFlagEnabled: options.isPassiveCodeInjectionFlagEnabled,
|
|
338
342
|
getMainSessionTurnInjections: options.getMainSessionTurnInjections,
|
|
339
343
|
};
|
|
340
344
|
}
|
|
@@ -27,12 +27,15 @@ 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';
|
|
33
34
|
import { installWrfcAgentToolGuard } from '../tools/wrfc-agent-guard.ts';
|
|
34
35
|
import { createWrfcPersistence, type WrfcPersistence } from './wrfc-persistence.ts';
|
|
35
36
|
import type { SystemMessagePriority } from '../core/system-message-router.ts';
|
|
37
|
+
import { SessionSpineClient, SessionUnionCache, TUI_SPINE_PARTICIPANT } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
38
|
+
import { SessionInboundInputPoller, createBootstrapInboundInputPoller } from './session-inbound-inputs.ts';
|
|
36
39
|
|
|
37
40
|
// ---------------------------------------------------------------------------
|
|
38
41
|
// Pre-router buffer
|
|
@@ -114,6 +117,12 @@ export interface BootstrapCoreState {
|
|
|
114
117
|
readonly requestRender: () => void;
|
|
115
118
|
readonly setRenderRequest: (fn: () => void) => void;
|
|
116
119
|
readonly runtimeSessionIdRef: { value: string };
|
|
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
|
+
readonly sessionSpine: SessionSpineClient;
|
|
122
|
+
/** D3: inbound steer/follow-up delivery; dormant until bootstrap.ts activates it. */
|
|
123
|
+
readonly sessionInboundInputs: SessionInboundInputPoller;
|
|
124
|
+
/** Cache-backed read facade; bootstrap.ts drives its mode (embedded/external/local-only) from the same HostServiceMode as the spine above. */
|
|
125
|
+
readonly sessionUnionCache: SessionUnionCache;
|
|
117
126
|
/**
|
|
118
127
|
* WRFC chain persistence — call `rehydrate()` once after the SystemMessageRouter
|
|
119
128
|
* is wired so interrupted chains from a previous process are surfaced to the operator.
|
|
@@ -127,17 +136,8 @@ export type CompanionMessagePayload = Extract<SessionEvent, { type: 'COMPANION_M
|
|
|
127
136
|
// Operator narration of inbound channel events
|
|
128
137
|
// ---------------------------------------------------------------------------
|
|
129
138
|
|
|
130
|
-
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* When an external surface (GitHub, Slack, ntfy, etc.) triggers an agent turn,
|
|
134
|
-
* this function produces a human-readable system message so the operator can
|
|
135
|
-
* observe which event caused the turn. Returns null for internal/companion
|
|
136
|
-
* sources that do not need operator narration.
|
|
137
|
-
*
|
|
138
|
-
* @param event - The normalized inbound event descriptor.
|
|
139
|
-
* @returns A narration string, or null if no narration is appropriate.
|
|
140
|
-
*/
|
|
139
|
+
/** Narrate an inbound channel event (GitHub, Slack, ntfy, etc.) that triggered an
|
|
140
|
+
* agent turn, via the SystemMessageRouter — null for internal/companion sources. */
|
|
141
141
|
export function narrateInboundEvent(event: {
|
|
142
142
|
source: string;
|
|
143
143
|
metadata: Readonly<Record<string, unknown>> | undefined;
|
|
@@ -293,6 +293,12 @@ export async function initializeBootstrapCore(
|
|
|
293
293
|
surfaceRegistry,
|
|
294
294
|
watcherRegistry,
|
|
295
295
|
} = services;
|
|
296
|
+
// Permanently dormant for embedded/local-only (nothing to mirror to); bootstrap.ts activates it only for an adopted 'external' daemon.
|
|
297
|
+
const sessionSpine = new SessionSpineClient({ participant: TUI_SPINE_PARTICIPANT, recordKind: 'tui' });
|
|
298
|
+
// Cache-backed read facade over the local broker (passthrough until bootstrap.ts marks it
|
|
299
|
+
// embedded, or activates the adopted-daemon wire union for 'external').
|
|
300
|
+
// selfSessionIds keeps local authoritative for our own wire-mirrored session even when local/wire ids diverge (D-TUI-1).
|
|
301
|
+
const sessionUnionCache = new SessionUnionCache({ local: sharedSessionBroker, selfSessionIds: () => sessionSpine.mirroredSessionIds });
|
|
296
302
|
|
|
297
303
|
routeBindings.attachRuntime({ runtimeBus, runtimeStore: store });
|
|
298
304
|
surfaceRegistry.attachRuntime(store);
|
|
@@ -324,6 +330,7 @@ export async function initializeBootstrapCore(
|
|
|
324
330
|
const uiServices = createUiRuntimeServices(services, {
|
|
325
331
|
forensicsRegistry,
|
|
326
332
|
getControlPlaneRecentEvents,
|
|
333
|
+
sessionUnionCache,
|
|
327
334
|
});
|
|
328
335
|
|
|
329
336
|
const conversation = new ConversationManager(() => {
|
|
@@ -401,24 +408,23 @@ export async function initializeBootstrapCore(
|
|
|
401
408
|
void memoryStore.save();
|
|
402
409
|
memoryStore.close();
|
|
403
410
|
});
|
|
411
|
+
// W6-C2 (E6): 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);
|
|
404
413
|
|
|
405
414
|
const renderRequestRef = { value: (): void => {} };
|
|
406
|
-
// R1: Coalescing render scheduler — collapses N requestRender() calls into 1
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
// of each queuing their own setTimeout. (The streaming hot path drives its own
|
|
413
|
-
// 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.)
|
|
414
421
|
let renderScheduled = false;
|
|
415
422
|
let lastRenderTime = 0;
|
|
416
423
|
const RENDER_INTERVAL_MS = 16;
|
|
417
|
-
// run() performs the actual render. It clears renderScheduled FIRST — even if
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
// 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.
|
|
422
428
|
const run = (): void => {
|
|
423
429
|
renderScheduled = false;
|
|
424
430
|
lastRenderTime = Date.now();
|
|
@@ -427,6 +433,10 @@ export async function initializeBootstrapCore(
|
|
|
427
433
|
} catch (err) {
|
|
428
434
|
logger.error('Render threw; next requestRender will reschedule', { error: String(err) });
|
|
429
435
|
}
|
|
436
|
+
// Debounced spine heartbeat on turn/render activity (no-op while dormant, i.e.
|
|
437
|
+
// embedded/local-only): a cheap synchronous no-op unless its own internal window has
|
|
438
|
+
// elapsed (at most one wire call per heartbeatMinIntervalMs) — safe on the hot path.
|
|
439
|
+
sessionSpine.heartbeat(runtimeSessionIdRef.value);
|
|
430
440
|
};
|
|
431
441
|
const requestRender = (): void => {
|
|
432
442
|
if (renderScheduled) return;
|
|
@@ -634,6 +644,11 @@ export async function initializeBootstrapCore(
|
|
|
634
644
|
},
|
|
635
645
|
));
|
|
636
646
|
|
|
647
|
+
// D3: inbound steer delivery — see createBootstrapInboundInputPoller's doc comment.
|
|
648
|
+
const sessionInboundInputs = createBootstrapInboundInputPoller({
|
|
649
|
+
runtimeSessionIdRef, routeOrBuffer, orchestratorHandleUserInputRef, conversation, requestRender,
|
|
650
|
+
});
|
|
651
|
+
|
|
637
652
|
providerRegistry.startWatching(runtimeBus);
|
|
638
653
|
|
|
639
654
|
// W2.3: attach the SAME WebhookNotifier instance that `/notify add|remove|clear`
|
|
@@ -724,6 +739,10 @@ export async function initializeBootstrapCore(
|
|
|
724
739
|
lastSeenAt: Date.now(),
|
|
725
740
|
},
|
|
726
741
|
}).catch((err) => { logger.debug('session broker create session failed at bootstrap', { err }); });
|
|
742
|
+
// Fire-and-forget spine mirror — a permanent parallel-write posture alongside (never
|
|
743
|
+
// instead of) the still-authoritative local SharedSessionBroker above. Dormant/queued
|
|
744
|
+
// until bootstrap.ts's deferred task activates it for an adopted 'external' daemon.
|
|
745
|
+
sessionSpine.register({ sessionId: runtime.sessionId, project: services.workingDirectory, title: 'Terminal UI session' });
|
|
727
746
|
|
|
728
747
|
domainDispatch.syncSessionState({
|
|
729
748
|
id: userSessionId,
|
|
@@ -774,5 +793,8 @@ export async function initializeBootstrapCore(
|
|
|
774
793
|
},
|
|
775
794
|
runtimeSessionIdRef,
|
|
776
795
|
wrfcPersistence,
|
|
796
|
+
sessionSpine,
|
|
797
|
+
sessionInboundInputs,
|
|
798
|
+
sessionUnionCache,
|
|
777
799
|
};
|
|
778
800
|
}
|
|
@@ -9,6 +9,7 @@ import { HelperModel } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
9
9
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
10
10
|
import { formatReturnContextForDisplay, getReturnContextMode, maybeAssistReturnContextSummary } from '@/runtime/index.ts';
|
|
11
11
|
import type { SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
12
|
+
import type { SessionSpineClient } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
12
13
|
import type { SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
13
14
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
14
15
|
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
@@ -22,6 +23,10 @@ export interface ResumeSessionOptions {
|
|
|
22
23
|
readonly requestRender: () => void;
|
|
23
24
|
readonly onSessionIdChanged?: (sessionId: string) => void;
|
|
24
25
|
readonly sharedSessionBroker: Pick<SharedSessionBroker, 'reopenSession'>;
|
|
26
|
+
/** S3c: fire-and-forget daemon-spine mirror. Reopen (not register) is the
|
|
27
|
+
* ONLY resume-time verb — see the SDK session-spine client.ts header doc. */
|
|
28
|
+
readonly sessionSpine: Pick<SessionSpineClient, 'reopen'>;
|
|
29
|
+
readonly project: string;
|
|
25
30
|
readonly writeLastSessionPointer: (sessionId: string) => void;
|
|
26
31
|
readonly hookDispatcher: HookDispatcher;
|
|
27
32
|
readonly sessionManager: SessionManager;
|
|
@@ -75,6 +80,8 @@ export function createResumeSessionHandler(options: ResumeSessionOptions): (sess
|
|
|
75
80
|
if (meta?.provider) options.runtime.provider = meta.provider;
|
|
76
81
|
options.writeLastSessionPointer(sessionId);
|
|
77
82
|
void options.sharedSessionBroker.reopenSession(sessionId).catch((err) => { logger.debug('session broker reopen session failed', { err }); });
|
|
83
|
+
// S3c: fire-and-forget spine mirror (reopen:true — the user resume verb).
|
|
84
|
+
options.sessionSpine.reopen({ sessionId, project: options.project, title: options.conversation.title || meta.title });
|
|
78
85
|
options.conversation.log(`Resumed session: ${sessionId}`, { fg: '135' });
|
|
79
86
|
const reopenedPanels: string[] = [];
|
|
80
87
|
if (meta.returnContext?.openPanels?.length) {
|
|
@@ -32,6 +32,7 @@ import type { PolicyRuntimeState } from '@/runtime/index.ts';
|
|
|
32
32
|
import type { TaskManager } from '@/runtime/index.ts';
|
|
33
33
|
import type { UiRuntimeServices } from './ui-services.ts';
|
|
34
34
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
35
|
+
import type { SessionSpineClient } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
35
36
|
|
|
36
37
|
export interface BootstrapShellState {
|
|
37
38
|
readonly commandRegistry: CommandRegistry;
|
|
@@ -47,6 +48,8 @@ export interface BootstrapShellOptions {
|
|
|
47
48
|
readonly runtimeBus: RuntimeEventBus;
|
|
48
49
|
readonly runtimeStore: RuntimeStore;
|
|
49
50
|
readonly services: RuntimeServices;
|
|
51
|
+
/** S3c: dormant until bootstrap.ts activates it for an adopted 'external' daemon. */
|
|
52
|
+
readonly sessionSpine: SessionSpineClient;
|
|
50
53
|
readonly conversation: ConversationManager;
|
|
51
54
|
readonly runtime: MutableRuntimeState;
|
|
52
55
|
readonly orchestrator: Orchestrator;
|
|
@@ -70,6 +73,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
70
73
|
runtimeBus,
|
|
71
74
|
runtimeStore,
|
|
72
75
|
services,
|
|
76
|
+
sessionSpine,
|
|
73
77
|
conversation,
|
|
74
78
|
runtime,
|
|
75
79
|
orchestrator,
|
|
@@ -103,6 +107,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
103
107
|
requestRender,
|
|
104
108
|
onSessionIdChanged,
|
|
105
109
|
sharedSessionBroker: services.sessionBroker,
|
|
110
|
+
sessionSpine,
|
|
111
|
+
project: services.workingDirectory,
|
|
106
112
|
writeLastSessionPointer,
|
|
107
113
|
hookDispatcher: services.hookDispatcher,
|
|
108
114
|
sessionManager: services.sessionManager,
|
|
@@ -175,7 +181,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
175
181
|
forensicsRegistry,
|
|
176
182
|
policyRuntimeState,
|
|
177
183
|
approvalBroker: services.approvalBroker,
|
|
178
|
-
|
|
184
|
+
// S3d: panels read the cross-surface union facade, not the raw local broker.
|
|
185
|
+
sessionBroker: uiServices.sessions.sessionBroker,
|
|
179
186
|
automationManager: services.automationManager,
|
|
180
187
|
getControlPlaneRecentEvents,
|
|
181
188
|
tokenAuditor: services.tokenAuditor,
|
|
@@ -207,6 +214,15 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
207
214
|
if (kind === 'operational') return ui.operationalMessages;
|
|
208
215
|
return ui.systemMessages;
|
|
209
216
|
},
|
|
217
|
+
{
|
|
218
|
+
// Suppress stale WRFC replay re-notifications for chains that can no
|
|
219
|
+
// longer act — gone (killed/removed → getChain null) or terminal
|
|
220
|
+
// (passed/failed). (UX-B item 1c.)
|
|
221
|
+
isChainTerminal: (chainId) => {
|
|
222
|
+
const chain = services.wrfcController.getChain(chainId);
|
|
223
|
+
return chain === null || chain.state === 'passed' || chain.state === 'failed';
|
|
224
|
+
},
|
|
225
|
+
},
|
|
210
226
|
);
|
|
211
227
|
orchestrator.setSystemMessageRouter(systemMessageRouter);
|
|
212
228
|
|
|
@@ -271,6 +287,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
271
287
|
wrfcController: services.wrfcController,
|
|
272
288
|
workstreamEngine: services.workstreamCommands,
|
|
273
289
|
codeIndexStore: services.codeIndexStore,
|
|
290
|
+
codeIndexReindexScheduler: services.codeIndexReindexScheduler,
|
|
291
|
+
isPassiveCodeInjectionFlagEnabled: () => services.featureFlags.isEnabled('agent-passive-code-injection'),
|
|
274
292
|
// Wave 5 (wo805): expose the MAIN session's per-turn passive-injection ring
|
|
275
293
|
// so `/recall injections` (no agent id) renders it — see recall-review.ts.
|
|
276
294
|
getMainSessionTurnInjections: () => orchestrator.getTurnInjections(),
|