@pellux/goodvibes-tui 1.9.1 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -5
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/audio/player.ts +91 -5
- package/src/audio/spoken-turn-controller.ts +30 -2
- package/src/audio/spoken-turn-wiring.ts +3 -0
- package/src/config/credential-availability.ts +1 -1
- package/src/config/index.ts +1 -1
- package/src/core/turn-cancellation.ts +1 -1
- package/src/daemon/cli.ts +4 -4
- package/src/daemon/service-commands.ts +8 -8
- package/src/input/command-registry.ts +5 -5
- package/src/input/commands/config.ts +1 -1
- package/src/input/commands/memory.ts +1 -1
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/feed-context-factory.ts +1 -1
- package/src/input/handler-content-actions.ts +2 -2
- package/src/input/handler-feed-routes.ts +2 -2
- package/src/input/handler-feed.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +3 -3
- package/src/input/handler-picker-routes.ts +2 -2
- package/src/input/handler-shortcuts.ts +1 -1
- package/src/input/model-picker.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-apply.ts +3 -3
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +3 -3
- package/src/input/onboarding/onboarding-wizard-steps.ts +1 -1
- package/src/input/session-picker-modal.ts +1 -1
- package/src/input/settings-modal-data.ts +2 -2
- package/src/main.ts +5 -4
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/operations.ts +4 -4
- package/src/panels/builtin/shared.ts +1 -1
- package/src/panels/fleet-panel-format.ts +1 -1
- package/src/panels/fleet-panel.ts +15 -15
- package/src/panels/fleet-read-model.ts +11 -11
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +2 -2
- package/src/panels/fleet-tabs.ts +4 -4
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/types.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/fleet-tab-strip.ts +2 -2
- package/src/renderer/footer-tips.ts +1 -1
- package/src/renderer/model-workspace.ts +1 -1
- package/src/renderer/session-picker-modal.ts +4 -4
- package/src/renderer/shell-surface.ts +1 -1
- package/src/renderer/status-glyphs.ts +3 -3
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +2 -2
- package/src/renderer/turn-injection.ts +3 -3
- package/src/renderer/ui-factory.ts +7 -7
- package/src/renderer/ui-primitives.ts +3 -3
- package/src/runtime/bootstrap-core.ts +3 -3
- package/src/runtime/bootstrap-hook-bridge.ts +2 -2
- package/src/runtime/bootstrap-shell.ts +3 -3
- package/src/runtime/bootstrap.ts +5 -5
- package/src/runtime/code-index-services.ts +2 -2
- package/src/runtime/index.ts +11 -3
- package/src/runtime/legacy-daemon-migration.ts +9 -9
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/onboarding/snapshot.ts +3 -3
- package/src/runtime/onboarding/types.ts +2 -2
- package/src/runtime/operator-token-cleanup.ts +2 -2
- package/src/runtime/orchestrator-core-services.ts +2 -2
- package/src/runtime/process-lifecycle.ts +19 -5
- package/src/runtime/services.ts +14 -14
- package/src/runtime/session-inbound-inputs.ts +1 -1
- package/src/runtime/session-spine-transport.ts +2 -2
- package/src/runtime/ui-services.ts +1 -1
- package/src/runtime/workstream-services.ts +1 -1
- package/src/runtime/wrfc-persistence.ts +2 -2
- package/src/shell/ui-openers.ts +2 -2
- package/src/version.ts +1 -1
|
@@ -86,13 +86,13 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
|
|
|
86
86
|
setConfig('service.autostart', hasServers);
|
|
87
87
|
setConfig('service.restartOnFailure', true);
|
|
88
88
|
// One-Platform daemon-by-default (SDK docs/decisions/2026-07-05-daemon-by-default.md):
|
|
89
|
-
// the loopback-only cross-surface session daemon is safe-by-design
|
|
90
|
-
// loopback bind, auth-gated, rate-limited) and runs regardless of whether
|
|
89
|
+
// the loopback-only cross-surface session daemon is safe-by-design
|
|
90
|
+
// (loopback bind, auth-gated, rate-limited) and runs regardless of whether
|
|
91
91
|
// browser/LAN/webhook/external-app capabilities are selected here — it is no
|
|
92
92
|
// longer bundled with those network-exposing surfaces, so onboarding leaves
|
|
93
93
|
// daemon.enabled untouched and lets the SDK's own default-true (or an existing
|
|
94
94
|
// explicit user override) govern. (The deprecated danger.daemon alias this
|
|
95
|
-
// comment used to also name was removed
|
|
95
|
+
// comment used to also name was later removed.)
|
|
96
96
|
setConfig('controlPlane.enabled', hasServers);
|
|
97
97
|
setConfig('danger.httpListener', httpListener);
|
|
98
98
|
setConfig('web.enabled', browserAccess);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Friction fix: the Network step's daemon-source choice
|
|
3
3
|
* — start a new daemon owned by this TUI (default) vs connect to one that is
|
|
4
4
|
* already running elsewhere with a known token. Extracted from
|
|
5
5
|
* onboarding-wizard-steps.ts to keep that file under the architecture line cap.
|
|
@@ -78,7 +78,7 @@ export function pushDaemonAdoptionFields(
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
*
|
|
81
|
+
* The guided, visible entry point for migrating a detected legacy
|
|
82
82
|
* `goodvibes-daemon.service` unit — closes the fast-follow-up flagged when
|
|
83
83
|
* `handleMigrateLegacyDaemonServiceForHandler` (handler-onboarding-daemon-adopt.ts)
|
|
84
84
|
* shipped fully built but not wired to any onboarding control. Independent of
|
|
@@ -107,7 +107,7 @@ export function pushLegacyDaemonMigrationFields(
|
|
|
107
107
|
kind: 'status',
|
|
108
108
|
id: 'network.migrate-legacy-daemon-detected',
|
|
109
109
|
label: 'Legacy daemon service detected',
|
|
110
|
-
//
|
|
110
|
+
// Follow-up: name the unit this host actually resolves (carried on the
|
|
111
111
|
// snapshot from `resolveConfiguredServiceName` at collection time) — the
|
|
112
112
|
// MANAGED_SERVICE_NAME fallback only covers snapshots built without it.
|
|
113
113
|
hint: legacyUnitNote(legacy, legacy.trackedServiceName ?? MANAGED_SERVICE_NAME),
|
|
@@ -533,7 +533,7 @@ export function buildNetworkStep(controller: OnboardingWizardControllerLike): On
|
|
|
533
533
|
},
|
|
534
534
|
];
|
|
535
535
|
|
|
536
|
-
//
|
|
536
|
+
// Recognize an adoptable already-running daemon (see onboarding-wizard-network-adopt.ts).
|
|
537
537
|
const daemonSource = getDaemonSource(controller);
|
|
538
538
|
pushDaemonAdoptionFields(fields, controller, daemonSource, bindSettings);
|
|
539
539
|
pushLegacyDaemonMigrationFields(fields, controller);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* control-plane session, see below), tracks selected index, and handles
|
|
7
7
|
* load/delete actions.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* Union-sessions surface: additionally surfaces the cross-surface
|
|
10
10
|
* session union from `sessionBroker` (a SessionReadFacade — normally
|
|
11
11
|
* `uiServices.sessions.sessionBroker`, the SessionUnionCache) so a user can
|
|
12
12
|
* SEE what sessions are live/closed across every surface sharing this
|
|
@@ -156,7 +156,7 @@ export function buildSettingGroups(
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
//
|
|
159
|
+
// Inject the storage.codeIndexEnabled toggle into the
|
|
160
160
|
// storage category. TUI-local synthetic setting (not in the SDK ConfigKey
|
|
161
161
|
// union — see code-index-services.ts), same rationale as
|
|
162
162
|
// notifyAfterSeconds above: opt-in, default off, states its own bounds.
|
|
@@ -342,7 +342,7 @@ export function buildNotifyAlertSyntheticEntries(configManager: Pick<ConfigManag
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
// ---------------------------------------------------------------------------
|
|
345
|
-
//
|
|
345
|
+
// storage.codeIndexEnabled synthetic setting
|
|
346
346
|
// ---------------------------------------------------------------------------
|
|
347
347
|
|
|
348
348
|
/**
|
package/src/main.ts
CHANGED
|
@@ -202,7 +202,7 @@ async function main() {
|
|
|
202
202
|
|
|
203
203
|
const unsubs: Array<() => void> = [];
|
|
204
204
|
let recoveryInterval: ReturnType<typeof setInterval> | null = null;
|
|
205
|
-
let stopSpokenOutputForExit: (() => void) | null = null;
|
|
205
|
+
let stopSpokenOutputForExit: (() => Promise<void>) | null = null;
|
|
206
206
|
let recoveryPending = false;
|
|
207
207
|
// Which file the current recovery prompt should load/delete from (see recovery-input-helpers.ts).
|
|
208
208
|
let recoverySource: 'live' | 'preserved' = 'live';
|
|
@@ -240,7 +240,8 @@ async function main() {
|
|
|
240
240
|
events: uiServices.events,
|
|
241
241
|
notify: (message) => { systemMessageRouter.high(message); render(); },
|
|
242
242
|
});
|
|
243
|
-
|
|
243
|
+
// Exit-path stop: bounded drain of the audio already playing (see stopForExit).
|
|
244
|
+
stopSpokenOutputForExit = () => spokenTurns.stopForExit();
|
|
244
245
|
unsubs.push(...spokenTurns.unsubs);
|
|
245
246
|
unsubs.push(attachSpokenTurnModelRouting({
|
|
246
247
|
orchestrator,
|
|
@@ -497,7 +498,7 @@ async function main() {
|
|
|
497
498
|
lastInputTokens: orchestrator.lastInputTokens,
|
|
498
499
|
commandArgsHint,
|
|
499
500
|
hitlMode: modeManager.getHITLMode(),
|
|
500
|
-
//
|
|
501
|
+
// Cross-surface spine posture segment (adopted-daemon mode only).
|
|
501
502
|
sessionSpineStatus: (() => { const s = uiServices.platform.externalServices?.inspect(); return s?.sessionSpineActive && s.sessionSpineStatus && s.sessionSpineStatus !== 'unknown' ? s.sessionSpineStatus : undefined; })(), runningAgentCount, runningProcessCount,
|
|
502
503
|
// Composer must not read as focused while the panel/process indicator owns keyboard focus.
|
|
503
504
|
promptFocused: !input.panelFocused && !input.indicatorFocused,
|
|
@@ -646,7 +647,7 @@ async function main() {
|
|
|
646
647
|
const terminalOutputGuard = installTuiTerminalOutputGuard({ stdout, stderr: process.stderr, onCapture: (total) => { commandContext.session.runtime.terminalWritesIntercepted = total; render(); } });
|
|
647
648
|
|
|
648
649
|
setRenderRequest(renderNow); // bootstrap's 16ms coalescer calls the composite directly
|
|
649
|
-
setPanelFrameRequester(render); // live panels repaint when idle (
|
|
650
|
+
setPanelFrameRequester(render); // live panels repaint when idle (a replay finding: fleet sat stale until keypress)
|
|
650
651
|
orchestratorRefs.requestRender = render;
|
|
651
652
|
commandContext.renderRequest = render;
|
|
652
653
|
wireShellUiOpeners({
|
package/src/panels/base-panel.ts
CHANGED
|
@@ -14,7 +14,7 @@ const ERROR_FG = '#ef4444';
|
|
|
14
14
|
* markDirty() only sets a flag the compositor reads when a frame is ALREADY
|
|
15
15
|
* being composed for another reason (input, streaming) — so a live panel
|
|
16
16
|
* (fleet ticks, registry subscriptions) sat visibly stale while the app was
|
|
17
|
-
* idle until the next keypress (
|
|
17
|
+
* idle until the next keypress (a replay finding). The scheduler
|
|
18
18
|
* coalesces same-tick requests, so this stays cheap.
|
|
19
19
|
*/
|
|
20
20
|
let panelFrameRequester: (() => void) | null = null;
|
|
@@ -38,7 +38,7 @@ export function registerOperationsPanels(manager: PanelManager, deps: ResolvedBu
|
|
|
38
38
|
// process registry constructed once in runtime/services.ts (shared with
|
|
39
39
|
// every other consumer rather than duplicated here; the registry owns its
|
|
40
40
|
// own coalesced tick, so no manual lifecycle-event wiring is needed).
|
|
41
|
-
//
|
|
41
|
+
// runtimeBus is also passed so the read model can subscribe
|
|
42
42
|
// to the honest COMMUNICATION_CONSUMED steer-ack signal (fleet-read-model.ts).
|
|
43
43
|
const fleetReadModel = createFleetReadModel(ui.runtime.processRegistry, ui.runtime.runtimeBus);
|
|
44
44
|
|
|
@@ -51,16 +51,16 @@ export function registerOperationsPanels(manager: PanelManager, deps: ResolvedBu
|
|
|
51
51
|
description: 'Live unified process tree: agents, WRFC chains, workflows, watchers, and background processes, with interrupt/kill/steer controls',
|
|
52
52
|
factory: () => new FleetPanel(fleetReadModel, {
|
|
53
53
|
interrupt: (id: string) => fleetReadModel.interrupt(id),
|
|
54
|
-
//
|
|
54
|
+
// Re-arm a paused trigger/schedule/automation job.
|
|
55
55
|
resume: (id: string) => fleetReadModel.resume(id),
|
|
56
56
|
kill: (id: string, opts: { cascade: boolean }) => fleetReadModel.kill(id, opts),
|
|
57
|
-
//
|
|
57
|
+
// Full-fidelity transcript source for an attached agent
|
|
58
58
|
// tab — live while the agent runs, frozen briefly after it completes
|
|
59
59
|
// (AgentManager's bounded retention ring), empty once evicted (the
|
|
60
60
|
// panel degrades to the on-disk ledger fallback in that case).
|
|
61
61
|
getConversationSnapshot: (agentId: string) => ui.agents.agentManager.getConversationSnapshot(agentId),
|
|
62
62
|
resolveSessionLogPath: (agentId: string) => ui.environment.shellPaths.resolveProjectPath('tui', 'sessions', `${agentId}.jsonl`),
|
|
63
|
-
//
|
|
63
|
+
// Queue a message for a live in-process agent/wrfc-subtask member.
|
|
64
64
|
steer: (id: string, text: string) => fleetReadModel.steer(id, text),
|
|
65
65
|
}, deps.configManager),
|
|
66
66
|
});
|
|
@@ -86,7 +86,7 @@ export interface BuiltinPanelDeps {
|
|
|
86
86
|
/** Approval broker for control-plane/operator panels. */
|
|
87
87
|
approvalBroker?: ApprovalBroker;
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
89
|
+
* Cross-surface session READ facade for control-plane/operator panels.
|
|
90
90
|
* Sync listSessions()/getSession() shape preserved; in adopted-daemon mode it
|
|
91
91
|
* serves the daemon-hosted union (with an honest offline note when the wire is
|
|
92
92
|
* down) instead of only this process's local broker. See session-union-cache.ts.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// fleet-panel-format.ts
|
|
3
3
|
//
|
|
4
4
|
// Pure column-layout + cell-formatting helpers for FleetPanel's tree rows.
|
|
5
|
-
// Extracted from fleet-panel.ts (
|
|
5
|
+
// Extracted from fleet-panel.ts (file-size hygiene) to hold that file under the
|
|
6
6
|
// 800-line architecture cap; these are stateless functions the panel's
|
|
7
7
|
// renderItem/renderDetail call, with no `this` dependency.
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
@@ -58,21 +58,21 @@ const C = DEFAULT_PANEL_PALETTE;
|
|
|
58
58
|
export interface FleetActionCallbacks {
|
|
59
59
|
/** Graceful interruption (AgentManager.cancel / trigger-schedule disable, via the registry). */
|
|
60
60
|
readonly interrupt: (id: string) => boolean;
|
|
61
|
-
/**
|
|
61
|
+
/** Re-arm a `paused` node (disabled trigger/schedule or automation job) — interrupt()'s inverse. Honest false when not paused / non-resumable. */
|
|
62
62
|
readonly resume: (id: string) => boolean;
|
|
63
63
|
/** Hard stop, optionally cascading to killable descendants. Returns the node ids acted on. */
|
|
64
64
|
readonly kill: (id: string, opts: { readonly cascade: boolean }) => readonly string[];
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Fetch an agent's conversation history — live (running) or
|
|
67
67
|
* frozen (just-completed, still in the SDK's retention ring). Empty array
|
|
68
68
|
* means "unavailable" (evicted, or never registered) — FleetPanel degrades
|
|
69
69
|
* to the on-disk ledger fallback in that case, never fabricating content.
|
|
70
70
|
*/
|
|
71
71
|
readonly getConversationSnapshot: (agentId: string) => readonly ConversationMessageSnapshot[];
|
|
72
|
-
/**
|
|
72
|
+
/** Resolve the on-disk `<agentId>.jsonl` event-ledger path for the degraded fallback view. */
|
|
73
73
|
readonly resolveSessionLogPath: (agentId: string) => string;
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Queue a human message for a live in-process agent (or a
|
|
76
76
|
* wrfc-subtask's current live member agent), delivered at the target's
|
|
77
77
|
* next turn boundary — never mid-token. Honest refusal
|
|
78
78
|
* (`{queued:false,reason}`) for anything that cannot take mid-run input
|
|
@@ -128,7 +128,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
128
128
|
this.reconcileSteerBadges();
|
|
129
129
|
this.markDirty();
|
|
130
130
|
});
|
|
131
|
-
//
|
|
131
|
+
// The honest "the agent actually consumed this steer at
|
|
132
132
|
// its turn boundary" signal — see fleet-tabs.ts's SteerBadgeStatus doc.
|
|
133
133
|
// A read-model without a runtimeBus dep never invokes this (graceful
|
|
134
134
|
// no-op), same degrade as steer()/steerable without a messageBus dep.
|
|
@@ -179,13 +179,13 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
179
179
|
for (const tab of this.tabsState.tabs) tab.lineCache.clear();
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
/** True while a session tab (not the root tree) is focused. Replaces the old
|
|
182
|
+
/** True while a session tab (not the root tree) is focused. Replaces the old isDetailFocused() seam. */
|
|
183
183
|
public isTabActive(): boolean {
|
|
184
184
|
return this.tabsState.activeTabIndex > 0;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
|
-
*
|
|
188
|
+
* True while the steer composer on the active tab is open —
|
|
189
189
|
* every character of a burst (paste, or several fast-typed chars in one
|
|
190
190
|
* stdin chunk) must land in the draft one at a time rather than being
|
|
191
191
|
* routed away as panel hotkeys or exploded to the main composer. See
|
|
@@ -207,7 +207,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
/**
|
|
210
|
-
* Ctrl+X (panel-close) collision resolution
|
|
210
|
+
* Ctrl+X (panel-close) collision resolution: the global
|
|
211
211
|
* shortcut route (handler-shortcuts.ts) calls this BEFORE closing the
|
|
212
212
|
* panel. Detaching the active tab consumes the key; on the root tree tab
|
|
213
213
|
* there is nothing to detach, so this returns false and Ctrl+X falls
|
|
@@ -317,7 +317,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
/**
|
|
320
|
-
*
|
|
320
|
+
* The "dropped inference" reconciliation: called on every read-model
|
|
321
321
|
* snapshot update and on the panel's 1s tick. See fleet-steer.ts's
|
|
322
322
|
* reconcileSteerBadges doc for why this is needed (the SDK emits no
|
|
323
323
|
* cancelled/expired signal for a queued steer).
|
|
@@ -350,7 +350,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
350
350
|
// Confirm overlay owns input first (K-armed kill confirm).
|
|
351
351
|
if (this.confirmOverlay.handleInput(key)) return true;
|
|
352
352
|
|
|
353
|
-
//
|
|
353
|
+
// Once the steer composer is open on the active tab, it
|
|
354
354
|
// owns EVERY key (same full-priority-while-composing contract as
|
|
355
355
|
// git-panel.ts's commitMessage entry) until Enter (submit) or Esc
|
|
356
356
|
// (cancel) — a burst/paste and single hotkeys like 'j'/'s'/'[' all land
|
|
@@ -458,7 +458,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
458
458
|
}
|
|
459
459
|
|
|
460
460
|
if (key === 's') {
|
|
461
|
-
//
|
|
461
|
+
// An earlier replay fix: 's' from the TREE was silently dead — steering
|
|
462
462
|
// required an undiscoverable Enter-attach first. Attach-and-steer in
|
|
463
463
|
// one press for a steerable node; honest refusal otherwise.
|
|
464
464
|
if (!selected) return false;
|
|
@@ -485,7 +485,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
485
485
|
}
|
|
486
486
|
|
|
487
487
|
/**
|
|
488
|
-
*
|
|
488
|
+
* The `s` gate — mirrors the i/K capability-gating template
|
|
489
489
|
* exactly (guard on the live node existing, non-terminal, and the specific
|
|
490
490
|
* capability; consume the key and say so rather than silently no-op-ing).
|
|
491
491
|
*/
|
|
@@ -649,13 +649,13 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
649
649
|
|
|
650
650
|
const snapshotRows = this.readModel.getSnapshot().rows;
|
|
651
651
|
const liveNode = snapshotRows.find((row) => row.node.id === tab.nodeId)?.node ?? null;
|
|
652
|
-
//
|
|
652
|
+
// The steer composer only ever applies to 'agent' tabs
|
|
653
653
|
// (wrfc-chain has no conversation loop of its own — see
|
|
654
654
|
// fleet-tabs.ts's FleetAttachableKind doc); the live-node lookup is
|
|
655
655
|
// shared with the isTerminal computation below either way.
|
|
656
656
|
const canSteer = liveNode !== null && !isTerminalProcessState(liveNode.state) && liveNode.capabilities.steerable;
|
|
657
657
|
|
|
658
|
-
//
|
|
658
|
+
// While composing, Enter/Esc replace the tab-switch/detach
|
|
659
659
|
// hints (mirrors git-panel.ts's renderCommitCompose footer) so the
|
|
660
660
|
// footer never advertises a key the composer itself has absorbed.
|
|
661
661
|
const hints: Array<{ keys: string; label: string }> = tab.steerDraft !== null
|
|
@@ -739,7 +739,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
739
739
|
const hints = buildFleetTreeHints(selected?.node, this.follow, this.tabsState.tabs.length > 0);
|
|
740
740
|
|
|
741
741
|
// Tab strip renders only when tabs exist — omitting it entirely with no
|
|
742
|
-
// tabs attached keeps the pre-
|
|
742
|
+
// tabs attached keeps the pre-session-tab root-tree rendering byte-identical.
|
|
743
743
|
const stripLine = renderFleetTabStrip(this.tabsState, width);
|
|
744
744
|
const header = stripLine ? [stripLine] : undefined;
|
|
745
745
|
|
|
@@ -71,7 +71,7 @@ export interface FleetSnapshot {
|
|
|
71
71
|
|
|
72
72
|
export type FleetStateTone = 'active' | 'success' | 'failure' | 'warn' | 'muted';
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// Design ruling: this is a DIFFERENT table from the SDK presentation
|
|
75
75
|
// contract's STATE_GLYPHS (@pellux/goodvibes-sdk/platform/presentation) — that
|
|
76
76
|
// one is a 4-state semantic alias (good/warn/bad/info) shared by the TUI and
|
|
77
77
|
// agent renderers; this one is a 12-state ProcessNode taxonomy specific to the
|
|
@@ -90,16 +90,16 @@ const STATE_GLYPHS: Record<ProcessState, string> = {
|
|
|
90
90
|
done: '✓',
|
|
91
91
|
failed: '✗',
|
|
92
92
|
killed: '⊘',
|
|
93
|
-
//
|
|
93
|
+
// A distinct TERMINAL outcome from 'killed' —
|
|
94
94
|
// both come from AgentManager.cancel(), but a graceful interrupt request
|
|
95
95
|
// ('the operator asked nicely') is display-distinguishable from a hard
|
|
96
96
|
// kill. '◌' verified free against every other glyph in this table.
|
|
97
97
|
interrupted: '◌',
|
|
98
98
|
idle: '·',
|
|
99
99
|
queued: '…',
|
|
100
|
-
//
|
|
100
|
+
// SDK behavior: schedules/triggers/automation jobs report 'paused' when
|
|
101
101
|
// disabled (previously mislabeled 'killed'). NOT terminal — resumable via
|
|
102
|
-
// ProcessRegistry.resume() (
|
|
102
|
+
// ProcessRegistry.resume() (the full pause/resume UI lives in
|
|
103
103
|
// fleet-stop.ts). '❚' verified free against every other glyph in this table.
|
|
104
104
|
paused: '❚',
|
|
105
105
|
};
|
|
@@ -140,11 +140,11 @@ const KIND_TAGS: Record<ProcessKind, string> = {
|
|
|
140
140
|
schedule: 'sched',
|
|
141
141
|
watcher: 'watch',
|
|
142
142
|
'background-process': 'exec',
|
|
143
|
-
//
|
|
143
|
+
// Orchestration-engine kinds (adapters/orchestration.ts).
|
|
144
144
|
workstream: 'stream',
|
|
145
145
|
phase: 'phase',
|
|
146
146
|
'work-item': 'item',
|
|
147
|
-
//
|
|
147
|
+
// The repo source-tree code index (adapters/code-index.ts).
|
|
148
148
|
// A single leaf node — never a rollup of other flat-list nodes (see
|
|
149
149
|
// ROLLUP_KINDS below) — so no other list in this module needs updating.
|
|
150
150
|
'code-index': 'index',
|
|
@@ -288,7 +288,7 @@ export function buildFleetRows(nodes: readonly ProcessNode[]): FleetTreeRow[] {
|
|
|
288
288
|
* nonzero cost/token reading (defense in depth against a future adapter
|
|
289
289
|
* change, not load-bearing).
|
|
290
290
|
*
|
|
291
|
-
*
|
|
291
|
+
* Orchestration-engine additions — verified against adapters/orchestration.ts:
|
|
292
292
|
* - 'workstream': adaptWorkstream SUMS every work-item's usage/costUsd
|
|
293
293
|
* exactly once (sumWorkItemUsage/aggregateWorkItemCost), the same
|
|
294
294
|
* "rollup of nodes that also appear individually" shape as adaptChain —
|
|
@@ -308,7 +308,7 @@ export function buildFleetRows(nodes: readonly ProcessNode[]): FleetTreeRow[] {
|
|
|
308
308
|
* 'wrfc-subtask'-for-capabilities analogue but the 'agent'-for-usage
|
|
309
309
|
* analogue. Excluding it would silently zero out real cost/token totals.
|
|
310
310
|
*
|
|
311
|
-
*
|
|
311
|
+
* 'code-index' is deliberately NOT in this set either —
|
|
312
312
|
* adaptCodeIndex yields exactly one standalone ProcessNode per registry
|
|
313
313
|
* (never a parent whose children ALSO appear individually in the flat
|
|
314
314
|
* list), so it is a leaf like 'agent'/'work-item', not a grouping construct.
|
|
@@ -435,7 +435,7 @@ export interface FleetReadModel {
|
|
|
435
435
|
/** Graceful interruption where the source supports one. Returns true when accepted. */
|
|
436
436
|
interrupt(id: string): boolean;
|
|
437
437
|
/**
|
|
438
|
-
*
|
|
438
|
+
* Re-arm a `paused` node (a disabled trigger/schedule,
|
|
439
439
|
* or an automation job). The inverse of interrupt()'s pause. Returns true when
|
|
440
440
|
* accepted; false for a node that is not currently `paused` or whose kind has
|
|
441
441
|
* no resume path (honest refusal).
|
|
@@ -444,14 +444,14 @@ export interface FleetReadModel {
|
|
|
444
444
|
/** Hard stop, optionally cascading to descendants. Returns the node ids acted on. */
|
|
445
445
|
kill(id: string, opts?: { readonly cascade?: boolean }): readonly string[];
|
|
446
446
|
/**
|
|
447
|
-
*
|
|
447
|
+
* Queue a human message for a live in-process agent (or a
|
|
448
448
|
* wrfc-subtask's current live member), delivered at its next turn
|
|
449
449
|
* boundary. Honest refusal (`{queued:false,reason}`) for anything that
|
|
450
450
|
* cannot take mid-run input — see ProcessRegistry.steer's doc comment.
|
|
451
451
|
*/
|
|
452
452
|
steer(id: string, text: string): SteerResult;
|
|
453
453
|
/**
|
|
454
|
-
*
|
|
454
|
+
* Subscribe to the honest "the agent actually consumed this
|
|
455
455
|
* steer at its turn boundary" signal (COMMUNICATION_CONSUMED on the
|
|
456
456
|
* runtime bus's 'communication' domain). Returns an unsubscribe function.
|
|
457
457
|
* A read-model constructed without a runtimeBus (e.g. the static factory,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// fleet-steer.ts
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// Steer-badge rendering helpers and the "dropped inference"
|
|
5
5
|
// reconciliation pass, split out of fleet-panel.ts to keep that file under
|
|
6
6
|
// the architecture line cap (see check-architecture.ts's 800-line gate).
|
|
7
7
|
// Pure functions only; FleetPanel still owns the mutable FleetTab.steerBadge
|
|
@@ -67,7 +67,7 @@ export function renderSteerBadgeLine(badge: SteerBadge, width: number, palette:
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
70
|
+
* The "dropped inference" risk: the SDK emits no
|
|
71
71
|
* cancelled/expired signal for a queued steer, so a badge left `queued`
|
|
72
72
|
* after its target node goes terminal (done/failed/killed/interrupted)
|
|
73
73
|
* would hang forever with no honest resolution. Resolves any such badge to
|
package/src/panels/fleet-stop.ts
CHANGED
|
@@ -115,7 +115,7 @@ export function buildFleetTreeHints(
|
|
|
115
115
|
{ keys: 'j/k', label: 'navigate' },
|
|
116
116
|
{ keys: 'Enter', label: 'attach' },
|
|
117
117
|
];
|
|
118
|
-
if (live && selected.capabilities.steerable) hints.push({ keys: 's', label: 'steer' }); //
|
|
118
|
+
if (live && selected.capabilities.steerable) hints.push({ keys: 's', label: 'steer' }); // discoverable from the tree (attach-and-steer)
|
|
119
119
|
if (live && selected.capabilities.interruptible) hints.push({ keys: 'i', label: 'interrupt' });
|
|
120
120
|
if (live && selected.capabilities.killable) hints.push({ keys: 'K', label: 'kill' });
|
|
121
121
|
if (live && !isPaused && selected.capabilities.pausable) hints.push({ keys: 'p', label: 'pause' });
|
|
@@ -125,7 +125,7 @@ export function buildFleetTreeHints(
|
|
|
125
125
|
return hints;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
/** K-confirm descendant stats (UX-C item 6): total = every non-terminal descendant (what a cascade kill takes down); active = the individually-killable subset. Was "active only"
|
|
128
|
+
/** K-confirm descendant stats (UX-C item 6): total = every non-terminal descendant (what a cascade kill takes down); active = the individually-killable subset. Was "active only" in an earlier version, undercounting a mixed subtree. */
|
|
129
129
|
export function countDescendantStats(rows: readonly FleetTreeRow[], nodeId: string): { total: number; active: number } {
|
|
130
130
|
const byParent = new Map<string, string[]>();
|
|
131
131
|
for (const row of rows) {
|
package/src/panels/fleet-tabs.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// fleet-tabs.ts
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// Pure, testable tab-state model for FleetPanel's
|
|
5
5
|
// session tabs. No BasePanel/rendering dependency, mirroring the
|
|
6
6
|
// fleet-read-model.ts convention: this module owns attach/detach/switch as
|
|
7
7
|
// pure state transitions over a small immutable FleetTabsState, so the tab
|
|
@@ -32,7 +32,7 @@ export function isAttachableFleetKind(kind: ProcessKind): kind is FleetAttachabl
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Lifecycle of a queued steer message, tracked
|
|
36
36
|
* per-tab. `queued` means `ProcessRegistry.steer()` accepted the message
|
|
37
37
|
* onto the target's inbox, NOT that the agent has seen it — that is the
|
|
38
38
|
* later, honest `consumed` transition (a `COMMUNICATION_CONSUMED` runtime-bus
|
|
@@ -119,14 +119,14 @@ export interface FleetTab {
|
|
|
119
119
|
ledgerEntries: Record<string, unknown>[] | null;
|
|
120
120
|
ledgerLoadStarted: boolean;
|
|
121
121
|
/**
|
|
122
|
-
*
|
|
122
|
+
* The one-line steer composer's in-progress text, or `null`
|
|
123
123
|
* when not composing. Mirrors git-panel.ts's `commitMessage` mutable-slot
|
|
124
124
|
* convention (FleetPanel.isCapturingTextBurst() gates on this being
|
|
125
125
|
* non-null so a burst/paste lands here char-by-char, never as tree/tab
|
|
126
126
|
* hotkeys — see FleetPanel.handleSteerInput).
|
|
127
127
|
*/
|
|
128
128
|
steerDraft: string | null;
|
|
129
|
-
/**
|
|
129
|
+
/** This tab's most recent steer message's lifecycle, or null. */
|
|
130
130
|
steerBadge: SteerBadge | null;
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// fleet-transcript.ts
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// Renders the content of an attached FleetPanel
|
|
5
5
|
// session tab. Three sources, chosen by what the SDK can actually provide
|
|
6
|
-
// (see the
|
|
6
|
+
// (see the design brief's "central reality check" — a full-fidelity live
|
|
7
7
|
// transcript needs a ConversationMessageSnapshot[] history source, which
|
|
8
8
|
// only exists for a RUNNING agent or a just-completed one still inside the
|
|
9
9
|
// SDK's bounded retention ring):
|
|
@@ -163,8 +163,8 @@ export function renderFleetAgentTranscript(
|
|
|
163
163
|
/**
|
|
164
164
|
* Render a live one-line-per-member summary for an attached wrfc-chain tab.
|
|
165
165
|
*
|
|
166
|
-
* `chainDoneOrAbsent` disambiguates an empty member list
|
|
167
|
-
* chain prunes its wrapper node (
|
|
166
|
+
* `chainDoneOrAbsent` disambiguates an empty member list: a completed
|
|
167
|
+
* chain prunes its wrapper node (zombie reap), so zero members can mean the
|
|
168
168
|
* chain FINISHED, not that it has not started. When the chain node is absent
|
|
169
169
|
* (pruned) or terminal, say so honestly instead of the "(no member agents yet)"
|
|
170
170
|
* not-started wording.
|
|
@@ -284,7 +284,7 @@ function renderLedgerEntry(width: number, entry: Record<string, unknown>, palett
|
|
|
284
284
|
{ text: `${status}${formatLedgerDuration(entry['durationMs'])}`, fg: tone },
|
|
285
285
|
]);
|
|
286
286
|
}
|
|
287
|
-
//
|
|
287
|
+
// Per-turn passive knowledge injection
|
|
288
288
|
// records, appended to this same JSONL ledger as
|
|
289
289
|
// `{type:'knowledge_injection', turn, ...record}` (orchestrator-runner.ts).
|
|
290
290
|
// Without this case these fell through to the generic 'event' default
|
package/src/panels/types.ts
CHANGED
|
@@ -142,7 +142,7 @@ export interface Panel {
|
|
|
142
142
|
/**
|
|
143
143
|
* Optional: called by the global Ctrl+X (`panel-close`) shortcut BEFORE it
|
|
144
144
|
* closes this panel (handler-shortcuts.ts). Return `true` to consume
|
|
145
|
-
* Ctrl+X for an in-panel action instead — e.g. FleetPanel (
|
|
145
|
+
* Ctrl+X for an in-panel action instead — e.g. FleetPanel (session
|
|
146
146
|
* tabs) detaches its active tab and leaves the panel open. Return `false`
|
|
147
147
|
* (or omit this hook) to fall through to the ordinary close behavior.
|
|
148
148
|
* Ctrl+X never reaches a panel's own `handleInput` (ctrl/meta combos are
|
|
@@ -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
|