@pellux/goodvibes-tui 1.16.1 → 1.18.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 +138 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +5490 -1806
- package/package.json +7 -7
- package/src/cli/completions/generate.ts +5 -4
- package/src/cli/config-overrides.ts +51 -11
- package/src/cli/doctor.ts +4 -5
- package/src/cli/entrypoint.ts +13 -8
- package/src/cli/help.ts +7 -6
- package/src/cli/launch-auto-update.ts +214 -0
- package/src/cli/management-commands.ts +82 -15
- package/src/cli/management.ts +0 -2
- package/src/cli/parser.ts +5 -0
- package/src/cli/status.ts +13 -0
- package/src/cli/surface-command.ts +5 -5
- package/src/cli/tui-startup.ts +60 -97
- package/src/cli/types.ts +2 -0
- package/src/config/index.ts +1 -1
- package/src/config/tui-extension-settings.ts +33 -0
- package/src/core/composer-state.ts +1 -1
- package/src/core/consolidation-receipt.ts +41 -0
- package/src/core/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +18 -9
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/memory-provenance.ts +67 -0
- package/src/core/pairing-devices.ts +49 -0
- package/src/core/pairing-handoff.ts +101 -0
- package/src/core/pairing-offers.ts +83 -0
- package/src/core/pairing-origin.ts +78 -0
- package/src/core/pairing-tailscale-gateway.ts +56 -0
- package/src/core/power-chip-source.ts +91 -0
- package/src/core/power-status.ts +95 -0
- package/src/core/stable-host.ts +171 -0
- package/src/core/stream-event-wiring.ts +29 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-budget-outcome.ts +60 -0
- package/src/core/turn-cancellation.ts +40 -1
- package/src/core/turn-event-wiring.ts +14 -8
- package/src/daemon/cli.ts +64 -58
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/service-commands.ts +5 -5
- package/src/export/cost-utils.ts +128 -6
- package/src/input/autocomplete.ts +2 -2
- package/src/input/command-registry.ts +55 -9
- package/src/input/commands/acp-runtime.ts +72 -0
- package/src/input/commands/ci-runtime.ts +5 -1
- package/src/input/commands/codebase-runtime.ts +6 -6
- package/src/input/commands/cost-runtime.ts +1 -1
- package/src/input/commands/devices-runtime.ts +109 -0
- package/src/input/commands/git-runtime.ts +28 -4
- package/src/input/commands/graph-runtime.ts +45 -0
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/intelligence-runtime.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +1 -1
- package/src/input/commands/local-provider-runtime.ts +49 -10
- package/src/input/commands/local-runtime.ts +53 -5
- package/src/input/commands/memory.ts +2 -2
- package/src/input/commands/operator-panel-runtime.ts +5 -5
- package/src/input/commands/operator-rpc.ts +32 -11
- package/src/input/commands/permissions-provenance.ts +1 -1
- package/src/input/commands/permissions-runtime.ts +99 -8
- package/src/input/commands/planning-runtime.ts +3 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -1
- package/src/input/commands/power-runtime.ts +56 -0
- package/src/input/commands/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- package/src/input/commands/qrcode-runtime.ts +14 -19
- package/src/input/commands/queue-runtime.ts +70 -0
- package/src/input/commands/recall-files-git.ts +13 -51
- package/src/input/commands/recall-files.ts +8 -11
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/relay-runtime.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +4 -4
- package/src/input/commands/schedule-runtime.ts +14 -7
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +2 -2
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +46 -68
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/update-runtime.ts +153 -96
- package/src/input/commands/work-plan-runtime.ts +1 -1
- package/src/input/commands/workstream-attempts.ts +6 -1
- package/src/input/commands/workstream-runtime.ts +46 -27
- package/src/input/commands/worktree-runtime.ts +35 -8
- package/src/input/commands.ts +10 -2
- package/src/input/config-modal-types.ts +14 -3
- package/src/input/config-modal.ts +75 -13
- package/src/input/feature-unit-layout.ts +93 -82
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/handler-command-route.ts +2 -2
- package/src/input/handler-content-actions.ts +1 -1
- package/src/input/handler-feed-routes.ts +7 -7
- package/src/input/handler-feed.ts +4 -4
- package/src/input/handler-interactions.ts +3 -2
- package/src/input/handler-modal-routes.ts +12 -4
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
- package/src/input/handler-onboarding.ts +3 -2
- package/src/input/handler-picker-routes.ts +26 -13
- package/src/input/handler-shortcuts.ts +29 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +21 -3
- package/src/input/onboarding/onboarding-feature-units.ts +34 -27
- package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
- package/src/input/panel-integration-actions.ts +2 -2
- package/src/input/panel-mouse-geometry.ts +1 -1
- package/src/input/panel-paste-flood-guard.ts +1 -1
- package/src/input/provider-key-intake.ts +167 -0
- package/src/input/sandbox-exec-config.ts +2 -2
- package/src/input/selection-modal.ts +1 -1
- package/src/input/settings-modal-activation.ts +5 -0
- package/src/input/settings-modal-adjustment.ts +4 -31
- package/src/input/settings-modal-data.ts +52 -57
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +27 -17
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +90 -90
- package/src/panels/agent-inspector-shared.ts +4 -4
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/agent.ts +4 -4
- package/src/panels/builtin/development.ts +4 -4
- package/src/panels/builtin/knowledge.ts +1 -1
- package/src/panels/builtin/operations.ts +83 -11
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +16 -4
- package/src/panels/builtin-modals.ts +59 -42
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +39 -6
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-acts.ts +446 -0
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-gateway.ts +117 -0
- package/src/panels/fleet-observed-render.ts +128 -0
- package/src/panels/fleet-panel-format.ts +163 -11
- package/src/panels/fleet-panel-worktree-detail.ts +14 -0
- package/src/panels/fleet-panel.ts +73 -57
- package/src/panels/fleet-read-model.ts +84 -14
- package/src/panels/fleet-spawn.ts +266 -0
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +41 -5
- package/src/panels/fleet-tabs.ts +15 -16
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/git-panel.ts +2 -2
- package/src/panels/index.ts +4 -1
- package/src/panels/local-auth-panel.ts +1 -1
- package/src/panels/memory-consolidation-gateway.ts +94 -0
- package/src/panels/modals/devices-modal.ts +161 -0
- package/src/panels/modals/hooks-modal.ts +1 -1
- package/src/panels/modals/keybindings-modal.ts +2 -2
- package/src/panels/modals/knowledge-modal.ts +1 -1
- package/src/panels/modals/marketplace-modal.ts +4 -4
- package/src/panels/modals/memory-modal.ts +153 -9
- package/src/panels/modals/modal-surface-helpers.ts +1 -1
- package/src/panels/modals/modal-theme.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +198 -68
- package/src/panels/modals/planning-modal.ts +2 -2
- package/src/panels/modals/plugins-modal.ts +1 -1
- package/src/panels/modals/policy-modal.ts +1 -1
- package/src/panels/modals/security-modal.ts +7 -1
- package/src/panels/modals/skills-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +3 -3
- package/src/panels/notifications-feed.ts +127 -0
- package/src/panels/notifications-panel.ts +160 -0
- package/src/panels/panel-manager.ts +9 -9
- package/src/panels/plugins-panel.ts +4 -4
- package/src/panels/project-planning-answer-actions.ts +1 -1
- package/src/panels/provider-health-routes.ts +2 -2
- package/src/panels/skills-panel.ts +4 -5
- package/src/panels/token-budget-panel.ts +11 -3
- package/src/panels/types.ts +5 -5
- package/src/panels/workstream-graph-render.ts +123 -0
- package/src/permissions/broker-approval-card.ts +201 -0
- package/src/permissions/hunk-selection.ts +28 -4
- package/src/permissions/prompt.ts +244 -29
- package/src/renderer/autocomplete-overlay.ts +107 -54
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compaction-history-modal.ts +1 -1
- package/src/renderer/compaction-preview.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/composer-fragments.ts +69 -0
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/footer-tips.ts +3 -3
- package/src/renderer/hint-grammar.ts +1 -1
- package/src/renderer/modal-factory.ts +1 -1
- package/src/renderer/model-picker-overlay.ts +1 -1
- package/src/renderer/model-workspace.ts +7 -2
- package/src/renderer/panel-composite.ts +2 -2
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/selection-modal-overlay.ts +1 -1
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +23 -5
- package/src/renderer/settings-modal.ts +129 -39
- package/src/renderer/shell-surface.ts +6 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +13 -7
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +38 -1
- package/src/runtime/bootstrap-command-context.ts +7 -2
- package/src/runtime/bootstrap-command-parts.ts +9 -5
- package/src/runtime/bootstrap-core.ts +41 -41
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +13 -5
- package/src/runtime/bootstrap.ts +34 -32
- package/src/runtime/channel-composition.ts +46 -0
- package/src/runtime/code-index-services.ts +3 -4
- package/src/runtime/daemon-attach-notices.ts +120 -0
- package/src/runtime/durability-services.ts +90 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/idle-power-services.ts +85 -0
- package/src/runtime/index.ts +6 -21
- package/src/runtime/interaction-seams.ts +109 -0
- package/src/runtime/legacy-daemon-migration.ts +29 -19
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/notification-dispatch.ts +110 -0
- package/src/runtime/onboarding/apply.ts +3 -29
- package/src/runtime/onboarding/fast-path.ts +39 -6
- package/src/runtime/onboarding/provider-key-capture.ts +126 -0
- package/src/runtime/onboarding/snapshot.ts +0 -1
- package/src/runtime/onboarding/types.ts +9 -3
- package/src/runtime/power-keepawake-remote.ts +75 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/provider-fallback.ts +55 -0
- package/src/runtime/provider-optimizer-wiring.ts +55 -0
- package/src/runtime/release-artifacts.ts +19 -103
- package/src/runtime/resume-notice.ts +1 -1
- package/src/runtime/services.ts +113 -113
- package/src/runtime/session-continuity-hints.ts +38 -0
- package/src/runtime/surface-feature-flags.ts +40 -9
- package/src/runtime/terminal-output-guard.ts +2 -2
- package/src/runtime/trust/workspace-registration.ts +10 -2
- package/src/runtime/trust/workspace-trust.ts +58 -31
- package/src/runtime/ui-services.ts +6 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +108 -132
- package/src/shell/recovery-input-helpers.ts +53 -166
- package/src/shell/ui-openers.ts +38 -6
- package/src/utils/format-duration.ts +1 -1
- package/src/verification/verification-ledger.ts +21 -4
- package/src/version.ts +1 -1
- package/src/input/commands/flags-runtime.ts +0 -328
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// fleet-panel.ts
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// — FleetPanel: the live unified observability tree. Renders the
|
|
5
5
|
// registered process fleet (agents incl. WRFC roles, WRFC chains/subtasks,
|
|
6
6
|
// workflow-tool FSMs, watchers, background processes) as a depth-first tree
|
|
7
|
-
// with a selected-row detail region. Session tabs (
|
|
7
|
+
// with a selected-row detail region. Session tabs (Part C): Enter on an
|
|
8
8
|
// attachable node (agent/wrfc-chain) opens a tab with that process's
|
|
9
9
|
// transcript/member summary — see fleet-tabs.ts + fleet-transcript.ts. This
|
|
10
10
|
// file owns input dispatch and layout; fleet-deep-link.ts owns the
|
|
11
|
-
// cross-panel jump target (
|
|
11
|
+
// cross-panel jump target (item 4).
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
|
|
14
14
|
import { readFile } from 'node:fs/promises';
|
|
@@ -21,12 +21,14 @@ import { PanelConfirmOverlay } from './panel-confirm-overlay.ts';
|
|
|
21
21
|
import { isPanelSearchBackspace, isPanelSearchCancel, isPanelSearchCommit, isPanelSearchPrintable } from './search-focus.ts';
|
|
22
22
|
import { buildKeyboardHints, buildPanelWorkspace, buildSearchInputLine, DEFAULT_PANEL_PALETTE, getPanelWorkspaceContentBudget, type PanelPalette } from './polish.ts';
|
|
23
23
|
import {
|
|
24
|
-
|
|
24
|
+
isBlockedOnUserNode,
|
|
25
25
|
isRunningProcessState,
|
|
26
26
|
isTerminalProcessState,
|
|
27
27
|
type FleetReadModel,
|
|
28
28
|
type FleetTreeRow,
|
|
29
29
|
} from './fleet-read-model.ts';
|
|
30
|
+
import type { FleetActs } from './fleet-acts.ts';
|
|
31
|
+
import type { FleetSpawn } from './fleet-spawn.ts';
|
|
30
32
|
import {
|
|
31
33
|
activeFleetTab,
|
|
32
34
|
appendSteerText,
|
|
@@ -40,7 +42,7 @@ import {
|
|
|
40
42
|
type SteerBadge,
|
|
41
43
|
} from './fleet-tabs.ts';
|
|
42
44
|
import { liveSteerableLabels, reconcileSteerBadges as reconcileSteerBadgesPure, renderSteerBadgeLine, steerRefusalMessage } from './fleet-steer.ts';
|
|
43
|
-
import { FleetStopTracker, toggleFleetPause, buildFleetTreeHints,
|
|
45
|
+
import { FleetStopTracker, toggleFleetPause, buildFleetTreeHints, fleetKillConfirmArgs } from './fleet-stop.ts';
|
|
44
46
|
import { resolveFleetDeepLinkIndex } from './fleet-deep-link.ts';
|
|
45
47
|
import { parseAgentLedger, renderFleetAgentTranscript, renderFleetChainSummary, renderFleetLedgerFallback, renderFleetTranscriptLoading } from './fleet-transcript.ts';
|
|
46
48
|
import { renderFleetTabStrip } from '../renderer/fleet-tab-strip.ts';
|
|
@@ -95,26 +97,28 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
95
97
|
private tickTimer: ReturnType<typeof setInterval> | null = null;
|
|
96
98
|
/**
|
|
97
99
|
* The node id the cursor is anchored to, independent of its row index.
|
|
98
|
-
* `selectedIndex` is a plain offset into `getItems()`'s current
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* re-locates this id in every fresh snapshot and repoints selectedIndex
|
|
103
|
-
* there; null means "no anchor yet" (follows the base class's index-0
|
|
104
|
-
* default until the first navigation/tick establishes one).
|
|
100
|
+
* `selectedIndex` is a plain offset into `getItems()`'s current (rebuilt-every-
|
|
101
|
+
* snapshot) order, so a stale index silently drifts; `reanchorSelection()`
|
|
102
|
+
* re-locates this id each snapshot and repoints selectedIndex. null means "no
|
|
103
|
+
* anchor yet" (base class index-0 default until first navigation/tick).
|
|
105
104
|
*/
|
|
106
105
|
private selectedNodeId: string | null = null;
|
|
107
106
|
|
|
108
|
-
/** d1
|
|
107
|
+
/** d1: the "stopping…" write-window overlay tracker (see fleet-stop.ts). */
|
|
109
108
|
private readonly stopTracker = new FleetStopTracker();
|
|
110
109
|
|
|
111
110
|
public constructor(
|
|
112
111
|
private readonly readModel: FleetReadModel,
|
|
113
112
|
actions: Partial<FleetActionCallbacks> = {},
|
|
114
113
|
private readonly configManager: ConfigManager | null = null,
|
|
114
|
+
// The waiting-on-human acts (pick / conflict / discard). Null in a runtime
|
|
115
|
+
// with no daemon gateway wired — those keys then fall through honestly.
|
|
116
|
+
private readonly acts: FleetActs | null = null,
|
|
117
|
+
// The ACP spawn affordance ('n' hosts a discovered third-party agent); null with no gateway wired.
|
|
118
|
+
private readonly spawn: FleetSpawn | null = null,
|
|
115
119
|
) {
|
|
116
120
|
super('fleet', 'Fleet', '⊟', 'runtime-ops');
|
|
117
|
-
this.showSelectionGutter = true; //
|
|
121
|
+
this.showSelectionGutter = true; // visible ▸ focus indicator
|
|
118
122
|
this.actions = { ...NOOP_ACTIONS, ...actions };
|
|
119
123
|
this.confirmOverlay = new PanelConfirmOverlay(() => this.markDirty());
|
|
120
124
|
this.unsub = readModel.subscribe(() => {
|
|
@@ -232,11 +236,10 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
* silently implying "nothing has ever run" or that a restart brings it back.
|
|
239
|
+
* (cross-restart honesty): a restart starts from an empty `AgentManager` Map
|
|
240
|
+
* (no bridge from a prior registry), so a completed process from a previous
|
|
241
|
+
* session can never reappear here — a real, permanent limitation, documented
|
|
242
|
+
* rather than implying "nothing has ever run" or that a restart brings it back.
|
|
240
243
|
*/
|
|
241
244
|
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
242
245
|
return [
|
|
@@ -275,15 +278,11 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
275
278
|
}
|
|
276
279
|
|
|
277
280
|
/**
|
|
278
|
-
* Re-locate `selectedNodeId` in the current
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* pruned, killed and pruned, etc.) — or no anchor has been established
|
|
284
|
-
* yet — falls back to the base class's clampSelection() (nearest valid
|
|
285
|
-
* index, never an out-of-bounds/vanished selection) and re-anchors to
|
|
286
|
-
* whatever that lands on.
|
|
281
|
+
* Re-locate `selectedNodeId` in the current snapshot and repoint
|
|
282
|
+
* `selectedIndex` at it, so the selection follows a specific process across
|
|
283
|
+
* adds/removes/reorders instead of drifting onto whatever row occupies the old
|
|
284
|
+
* index. When the anchored node is gone (pruned) — or no anchor exists yet —
|
|
285
|
+
* falls back to clampSelection() (nearest valid index) and re-anchors there.
|
|
287
286
|
*/
|
|
288
287
|
private reanchorSelection(): void {
|
|
289
288
|
const rows = this.getItems();
|
|
@@ -303,7 +302,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
303
302
|
return this.getItems().find((row) => row.node.id === nodeId)?.node ?? null;
|
|
304
303
|
}
|
|
305
304
|
|
|
306
|
-
/**
|
|
305
|
+
/** item 4 (see fleet-deep-link.ts). */
|
|
307
306
|
public receiveDeepLink(target: { readonly id: string; readonly kind?: string }): void {
|
|
308
307
|
const idx = resolveFleetDeepLinkIndex(this.getItems(), target);
|
|
309
308
|
if (idx < 0) { this.setError('node no longer present.'); return; }
|
|
@@ -317,12 +316,10 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
317
316
|
|
|
318
317
|
/**
|
|
319
318
|
* Focus the next node blocked on the operator (awaiting approval/input),
|
|
320
|
-
* cycling
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
* message when nothing is currently waiting on the operator. Returns true
|
|
325
|
-
* (the 'b' key is always consumed by the panel).
|
|
319
|
+
* cycling in display order and wrapping. Resolves against the LIVE fleet
|
|
320
|
+
* (blocked nodes never live in the archive) and returns to the tree view from
|
|
321
|
+
* any attached tab so the reveal is visible (receiveDeepLink's contract).
|
|
322
|
+
* Honest no-op message when nothing waits; always consumes 'b' (returns true).
|
|
326
323
|
*/
|
|
327
324
|
private jumpToNextBlocked(): boolean {
|
|
328
325
|
const snapshot = this.readModel.getSnapshot();
|
|
@@ -384,6 +381,13 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
384
381
|
public override handleInput(key: string): boolean {
|
|
385
382
|
if (this.lastError !== null) this.clearError();
|
|
386
383
|
|
|
384
|
+
// The candidate picker (a flagged pick decision) owns the view + input while active.
|
|
385
|
+
if (this.acts?.pickModeActive()) return this.acts.handlePickInput(key);
|
|
386
|
+
// The observed-agent steer composer (a drill-in) likewise owns EVERY key until Enter/Esc.
|
|
387
|
+
if (this.acts?.observedSteerActive()) return this.acts.handleObservedSteerInput(key);
|
|
388
|
+
// The ACP spawn picker (host a third-party agent) likewise owns view + input.
|
|
389
|
+
if (this.spawn?.spawnModeActive()) return this.spawn.handleSpawnInput(key);
|
|
390
|
+
|
|
387
391
|
// Confirm overlay owns input first (K-armed kill confirm).
|
|
388
392
|
if (this.confirmOverlay.handleInput(key)) return true;
|
|
389
393
|
|
|
@@ -391,7 +395,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
391
395
|
// owns EVERY key (same full-priority-while-composing contract as
|
|
392
396
|
// git-panel.ts's commitMessage entry) until Enter (submit) or Esc
|
|
393
397
|
// (cancel) — a burst/paste and single hotkeys like 'j'/'s'/'[' all land
|
|
394
|
-
// in the draft, never as tree/tab navigation (
|
|
398
|
+
// in the draft, never as tree/tab navigation (focus rule).
|
|
395
399
|
const composingTab = activeFleetTab(this.tabsState);
|
|
396
400
|
if (composingTab && composingTab.steerDraft !== null) {
|
|
397
401
|
return this.handleSteerInput(composingTab, key);
|
|
@@ -425,6 +429,18 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
425
429
|
|
|
426
430
|
const selected = this.getSelectedItem();
|
|
427
431
|
|
|
432
|
+
// Waiting-on-human acts on the selected row: Enter on a flagged pick/conflict
|
|
433
|
+
// row, D on a worktree-owning row. Returns false (falls through to attach /
|
|
434
|
+
// ordinary handling) for any other row, so Enter still opens an agent tab.
|
|
435
|
+
if (this.acts && selected && this.acts.handleTreeKey(key, selected.node)) {
|
|
436
|
+
this.captureSelectionAnchor();
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Host a third-party ACP agent: lists discovered agents, then a known dir,
|
|
441
|
+
// then spawns it as an acp-agent row. Quiet when none are discovered.
|
|
442
|
+
if (key === 'n' && this.spawn) { void this.spawn.begin(); return true; }
|
|
443
|
+
|
|
428
444
|
// Archive controls: v toggles the live fleet <-> session archive view;
|
|
429
445
|
// a archives the selected finished subtree (or restores it from the
|
|
430
446
|
// archive view); A archives every fully-finished root subtree.
|
|
@@ -479,29 +495,20 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
479
495
|
|
|
480
496
|
if (key === 'K') {
|
|
481
497
|
if (!selected || isTerminalProcessState(selected.node.state)) return false;
|
|
498
|
+
// Observed foreign agents have killable:false — the refusal below reads
|
|
499
|
+
// honestly; goodvibes only observes them, so stop is never offered.
|
|
482
500
|
if (!selected.node.capabilities.killable) {
|
|
483
501
|
this.setError(`${selected.node.kind} does not support kill.`);
|
|
484
502
|
return true;
|
|
485
503
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
this.confirmOverlay.arm({
|
|
491
|
-
id: node.id,
|
|
492
|
-
label: `${node.kind} ${shortId}${suffix}`,
|
|
493
|
-
verb: 'Kill',
|
|
494
|
-
// d1 (W6.2): mark 'stopping…' only once the kill is confirmed, not while
|
|
495
|
-
// the confirm overlay is merely armed.
|
|
496
|
-
onConfirm: () => {
|
|
497
|
-
this.actions.kill(node.id, { cascade: true });
|
|
498
|
-
this.stopTracker.mark(node.id);
|
|
499
|
-
},
|
|
500
|
-
});
|
|
504
|
+
this.confirmOverlay.arm(fleetKillConfirmArgs(selected.node, this.getItems(), {
|
|
505
|
+
kill: (id) => this.actions.kill(id, { cascade: true }),
|
|
506
|
+
markStopping: (id) => this.stopTracker.mark(id),
|
|
507
|
+
}));
|
|
501
508
|
return true;
|
|
502
509
|
}
|
|
503
510
|
|
|
504
|
-
// d2
|
|
511
|
+
// d2: 'p' toggles pause<->resume by the selected node's state (see
|
|
505
512
|
// toggleFleetPause in fleet-stop.ts).
|
|
506
513
|
if (key === 'p') {
|
|
507
514
|
if (!selected) return false;
|
|
@@ -539,6 +546,9 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
539
546
|
// one press for a steerable node; honest refusal otherwise.
|
|
540
547
|
if (!selected) return false;
|
|
541
548
|
const node = selected.node;
|
|
549
|
+
// Observed foreign agents steer as a DRILL-IN only; that 's' is handled by
|
|
550
|
+
// acts.handleTreeKey above (opens the composer in the row's detail), so it
|
|
551
|
+
// never reaches this attach path.
|
|
542
552
|
// Closure replay note: a node that FINISHED in the hint→keypress race
|
|
543
553
|
// window must say so — "does not support steer" is misleading for an
|
|
544
554
|
// agent whose only problem is being done.
|
|
@@ -621,7 +631,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
621
631
|
|
|
622
632
|
/**
|
|
623
633
|
* Submit a composed steer message. Returns whether it was queued so the caller
|
|
624
|
-
* clears the draft only on a confirmed send (WO
|
|
634
|
+
* clears the draft only on a confirmed send (WO item 4): a refusal (target
|
|
625
635
|
* went idle while composing) PRESERVES the typed text and suggests which agents
|
|
626
636
|
* ARE steerable, instead of silently discarding the draft. On a successful queue
|
|
627
637
|
* the ⧗ badge line is the immediate honest acknowledgment.
|
|
@@ -644,7 +654,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
644
654
|
// Display-only overrides (stopping wins, then blocked-on-user) are derived
|
|
645
655
|
// here from panel state and passed into the pure row renderer.
|
|
646
656
|
const stopping = this.stopTracker.isStopping(row.node.id);
|
|
647
|
-
const blocked = !stopping &&
|
|
657
|
+
const blocked = !stopping && isBlockedOnUserNode(row.node);
|
|
648
658
|
return renderFleetRowLine(row, width, stopping, blocked, this.steerBadgeForNode(row.node.id), C);
|
|
649
659
|
}
|
|
650
660
|
|
|
@@ -658,8 +668,12 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
658
668
|
// Mirror the tree row's display-only overrides (stopping wins, then blocked)
|
|
659
669
|
// so the detail's literal state text never contradicts the row glyph.
|
|
660
670
|
const stopping = this.stopTracker.isStopping(node.id);
|
|
661
|
-
const blocked = !stopping &&
|
|
662
|
-
|
|
671
|
+
const blocked = !stopping && isBlockedOnUserNode(node);
|
|
672
|
+
// The drill-in observed-steer draft (when the composer is open on this row)
|
|
673
|
+
// renders an input line in the observed detail. For a workstream row, lazily
|
|
674
|
+
// fetch its task graph so the edges/pool posture renders in-panel.
|
|
675
|
+
this.acts?.ensureGraphFor(node);
|
|
676
|
+
return renderFleetDetailLines(node, width, stopping, blocked, C, this.acts?.observedSteerDraftFor(node.id) ?? null, this.acts?.graphFor(node.id) ?? null);
|
|
663
677
|
}
|
|
664
678
|
|
|
665
679
|
/** Renders the active session tab's content (transcript / chain summary / ledger fallback) in place of the tree. */
|
|
@@ -718,7 +732,7 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
718
732
|
|
|
719
733
|
if (tab.kind === 'wrfc-chain') {
|
|
720
734
|
const memberRows = snapshotRows.filter((row) => row.node.parentId === tab.nodeId);
|
|
721
|
-
// d3
|
|
735
|
+
// d3: a completed chain prunes its wrapper node, so an empty member
|
|
722
736
|
// list can mean "finished", not "not started yet". Absent (pruned) OR
|
|
723
737
|
// terminal => the honest "completed" wording; present + non-terminal with
|
|
724
738
|
// no members yet => the "not started" wording.
|
|
@@ -777,6 +791,8 @@ export class FleetPanel extends ScrollableListPanel<FleetTreeRow> {
|
|
|
777
791
|
}
|
|
778
792
|
|
|
779
793
|
public override render(width: number, height: number): Line[] {
|
|
794
|
+
if (this.acts?.pickModeActive()) return this.acts.renderPickMode(width, height, C);
|
|
795
|
+
if (this.spawn?.spawnModeActive()) return this.spawn.renderSpawnMode(width, height, C);
|
|
780
796
|
if (this.tabsState.activeTabIndex > 0) return this.renderTabView(width, height);
|
|
781
797
|
return this.renderTreeView(width, height);
|
|
782
798
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// fleet-read-model.ts
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// (Fleet tree panel) — pure, testable read-model over the SDK's live
|
|
5
5
|
// process registry (@pellux/goodvibes-sdk/platform/runtime/fleet, landed on
|
|
6
|
-
// SDK main as
|
|
6
|
+
// SDK main as). No BasePanel/rendering dependency: this module owns the
|
|
7
7
|
// flat ProcessNode[] -> tree transformation, the honest cost/token
|
|
8
8
|
// aggregates, and the state->glyph/tone mapping that FleetPanel renders.
|
|
9
9
|
//
|
|
10
10
|
// Design notes:
|
|
11
|
-
// - The
|
|
11
|
+
// - The brief predates the real SDK API and sketched a bespoke
|
|
12
12
|
// FleetRegistryNode/FleetRegistry adapter pair. The real
|
|
13
13
|
// createProcessRegistry() ProcessNode already carries elapsedMs,
|
|
14
14
|
// usage, costUsd, costState, and currentActivity directly, so no
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
import type {
|
|
32
32
|
ArchivableProcessRegistry,
|
|
33
|
+
ProcessAttention,
|
|
33
34
|
ProcessCostState,
|
|
34
35
|
ProcessKind,
|
|
35
36
|
ProcessNode,
|
|
@@ -136,13 +137,11 @@ const STATE_TONES: Record<ProcessState, FleetStateTone> = {
|
|
|
136
137
|
const TERMINAL_STATES = new Set<ProcessState>(['done', 'failed', 'killed', 'interrupted']);
|
|
137
138
|
|
|
138
139
|
/**
|
|
139
|
-
* States that mean "this node is waiting on ME right now"
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* surface (badge, sort, jump) picks it up for free. Kept as a set for that
|
|
145
|
-
* forward-compatibility, not because more than one state qualifies today.
|
|
140
|
+
* States that mean "this node is waiting on ME right now" via the state field
|
|
141
|
+
* alone. The registry now also publishes the canonical `needsAttention`
|
|
142
|
+
* projection (reason 'approval' | 'input' — see fleetNodeAttention below),
|
|
143
|
+
* which every surface prefers; this set remains only as the fallback mapping
|
|
144
|
+
* for nodes without the projection, so nothing regresses on older registries.
|
|
146
145
|
*/
|
|
147
146
|
const BLOCKED_ON_USER_STATES = new Set<ProcessState>(['awaiting-approval']);
|
|
148
147
|
|
|
@@ -168,6 +167,17 @@ const KIND_TAGS: Record<ProcessKind, string> = {
|
|
|
168
167
|
// A single leaf node — never a rollup of other flat-list nodes (see
|
|
169
168
|
// ROLLUP_KINDS below) — so no other list in this module needs updating.
|
|
170
169
|
'code-index': 'index',
|
|
170
|
+
// A hosted third-party coding agent (Claude Code / Codex / opencode) spawned
|
|
171
|
+
// over ACP (adapters/acp-host.ts). A plain leaf row — steer/stop/attention
|
|
172
|
+
// ride on its node capabilities and the SDK's needsAttention projection, so
|
|
173
|
+
// this glyph/label is the only read-model special-casing it needs.
|
|
174
|
+
'acp-agent': 'acp',
|
|
175
|
+
// An externally-launched foreign coding agent (Claude Code / Codex / opencode)
|
|
176
|
+
// that goodvibes did NOT spawn — observed read-only from OS signals. The row's
|
|
177
|
+
// first job is visibility ("it's in claude or codex"); it is never counted in
|
|
178
|
+
// any our-fleet count, never carries a stop affordance, and steer is drill-in
|
|
179
|
+
// only. See ObservedProcess handling in fleet-panel-format.ts.
|
|
180
|
+
'observed-external': 'observed',
|
|
171
181
|
};
|
|
172
182
|
|
|
173
183
|
export function fleetStateGlyph(state: ProcessState): string {
|
|
@@ -195,8 +205,66 @@ export function isBlockedOnUserState(state: ProcessState): boolean {
|
|
|
195
205
|
return BLOCKED_ON_USER_STATES.has(state);
|
|
196
206
|
}
|
|
197
207
|
|
|
208
|
+
/**
|
|
209
|
+
* The node's waiting-on-human classification, preferring the SDK's canonical
|
|
210
|
+
* `needsAttention` projection (reason 'approval' | 'input' — broader than the
|
|
211
|
+
* awaiting-approval state alone: it also covers a session waiting on user
|
|
212
|
+
* input). Falls back to the state-derived mapping for registries that predate
|
|
213
|
+
* the projection, so nothing regresses. This is THE membership every
|
|
214
|
+
* "blocked on me" surface (badge, sort, jump) derives from.
|
|
215
|
+
*/
|
|
216
|
+
export function fleetNodeAttention(node: ProcessNode): ProcessAttention | null {
|
|
217
|
+
if (node.needsAttention) return node.needsAttention;
|
|
218
|
+
if (isBlockedOnUserState(node.state)) return { reason: 'approval' };
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** True when the node is waiting on the operator right now (approval or input). */
|
|
223
|
+
export function isBlockedOnUserNode(node: ProcessNode): boolean {
|
|
224
|
+
return fleetNodeAttention(node) !== null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The literal badge text for a waiting-on-human node, by reason. Every reason
|
|
229
|
+
* is a first-class member of the ONE waiting-on-human state class (same ⚑
|
|
230
|
+
* glyph, same jump key, same count) but names WHY in its own words:
|
|
231
|
+
* - 'approval' — a tool call is held for an approve/deny decision.
|
|
232
|
+
* - 'input' — the node is otherwise blocked on operator input.
|
|
233
|
+
* - 'pick' — a best-of-N attempt group is ready and only a human's
|
|
234
|
+
* winner pick advances it.
|
|
235
|
+
* - 'conflict' — a merge conflict needs a human resolution before the work
|
|
236
|
+
* can land.
|
|
237
|
+
* The pick/conflict wording is deliberately DISTINCT from the bare
|
|
238
|
+
* 'blocked on you' so an operator reads the required act, not just the state.
|
|
239
|
+
*/
|
|
240
|
+
export function fleetAttentionText(attention: ProcessAttention): string {
|
|
241
|
+
switch (attention.reason) {
|
|
242
|
+
case 'input': return 'needs your input';
|
|
243
|
+
case 'pick': return 'needs your pick';
|
|
244
|
+
case 'conflict': return 'merge conflict waiting on you';
|
|
245
|
+
case 'approval': return 'blocked on you';
|
|
246
|
+
default: return 'blocked on you';
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The stall marker for a row — 'quiet Nm' (or 'quiet NhMm' past an hour),
|
|
252
|
+
* derived from the read-model's stall tell (pure timestamp comparison on the
|
|
253
|
+
* registry side; a live node whose last observable output is older than the
|
|
254
|
+
* threshold). Null when the node is not stalled-quiet.
|
|
255
|
+
*/
|
|
256
|
+
export function fleetStallMarker(node: ProcessNode): string | null {
|
|
257
|
+
const stall = node.stall;
|
|
258
|
+
if (!stall) return null;
|
|
259
|
+
const minutes = Math.max(1, Math.floor(stall.quietForMs / 60_000));
|
|
260
|
+
if (minutes < 60) return `quiet ${minutes}m`;
|
|
261
|
+
const hours = Math.floor(minutes / 60);
|
|
262
|
+
const rest = minutes % 60;
|
|
263
|
+
return rest > 0 ? `quiet ${hours}h${rest}m` : `quiet ${hours}h`;
|
|
264
|
+
}
|
|
265
|
+
|
|
198
266
|
// ---------------------------------------------------------------------------
|
|
199
|
-
// Honest usage/cost helpers (
|
|
267
|
+
// Honest usage/cost helpers (convention, ported from agent-inspector-shared.ts)
|
|
200
268
|
// ---------------------------------------------------------------------------
|
|
201
269
|
|
|
202
270
|
/**
|
|
@@ -261,7 +329,7 @@ function collectBlockedSubtrees(
|
|
|
261
329
|
if (cached !== undefined) return cached;
|
|
262
330
|
if (visiting.has(node.id)) return false; // cycle guard
|
|
263
331
|
visiting.add(node.id);
|
|
264
|
-
let has =
|
|
332
|
+
let has = isBlockedOnUserNode(node);
|
|
265
333
|
for (const child of childrenByParent.get(node.id) ?? []) {
|
|
266
334
|
if (dfs(child)) has = true;
|
|
267
335
|
}
|
|
@@ -431,7 +499,9 @@ function isFleetAggregateRollupNode(node: ProcessNode): boolean {
|
|
|
431
499
|
* count in the way summing its rolled-up usage/cost would be.
|
|
432
500
|
*/
|
|
433
501
|
function isFleetRunningLeaf(node: ProcessNode): boolean {
|
|
434
|
-
|
|
502
|
+
// Observed foreign agents are NEVER counted in our own fleet counts — they
|
|
503
|
+
// are externally-launched sessions goodvibes only watches, not work it runs.
|
|
504
|
+
return node.kind !== 'observed-external' && !ROLLUP_KINDS.has(node.kind) && isRunningProcessState(node.state);
|
|
435
505
|
}
|
|
436
506
|
|
|
437
507
|
/** Build the full FleetSnapshot (rows + honest aggregates) from a flat node list. */
|
|
@@ -458,7 +528,7 @@ export function buildFleetSnapshot(nodes: readonly ProcessNode[], capturedAt: nu
|
|
|
458
528
|
// Blocked ids in display (row) order so the panel's jump key steps through
|
|
459
529
|
// them top-to-bottom exactly as they appear on screen.
|
|
460
530
|
const blockedNodeIds = rows
|
|
461
|
-
.filter((row) =>
|
|
531
|
+
.filter((row) => isBlockedOnUserNode(row.node))
|
|
462
532
|
.map((row) => row.node.id);
|
|
463
533
|
|
|
464
534
|
return { rows, totalCost, totalTokens, runningCount, blockedNodeIds, capturedAt };
|