@pellux/goodvibes-tui 1.1.0 → 1.9.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 +85 -21
- package/README.md +28 -18
- 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/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +90 -14
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- 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 +104 -51
- package/src/core/turn-cancellation.ts +25 -0
- package/src/core/turn-event-wiring.ts +72 -3
- 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/export/cost-utils.ts +36 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +84 -5
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +232 -0
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +10 -2
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +73 -35
- package/src/input/commands/operator-panel-runtime.ts +71 -31
- package/src/input/commands/planning-runtime.ts +116 -4
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +26 -6
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +16 -4
- package/src/input/commands/shell-core.ts +25 -8
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +488 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +175 -0
- package/src/input/config-modal.ts +592 -0
- package/src/input/feed-context-factory.ts +10 -8
- package/src/input/handler-command-route.ts +37 -20
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +74 -114
- package/src/input/handler-feed.ts +79 -27
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +136 -6
- package/src/input/handler-modal-stack.ts +14 -7
- package/src/input/handler-modal-token-routes.ts +16 -49
- 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 +26 -102
- package/src/input/handler-shortcuts.ts +74 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +12 -29
- package/src/input/keybindings.ts +23 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +77 -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-integration-actions.ts +9 -170
- 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 +146 -2
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +49 -51
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +18 -46
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- 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 +758 -0
- package/src/panels/fleet-read-model.ts +520 -0
- package/src/panels/fleet-steer.ts +125 -0
- package/src/panels/fleet-stop.ts +153 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +166 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +42 -0
- package/src/panels/modals/pairing-modal.ts +163 -0
- package/src/panels/modals/planning-modal.ts +304 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +177 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +123 -9
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -11
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +37 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +216 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +95 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +15 -3
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +43 -1
- package/src/renderer/help-overlay.ts +2 -2
- 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 +62 -4
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/panel-workspace-bar.ts +8 -2
- 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 +133 -0
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +27 -1
- package/src/runtime/bootstrap-command-parts.ts +41 -10
- package/src/runtime/bootstrap-core.ts +47 -15
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +48 -17
- package/src/runtime/bootstrap.ts +129 -22
- package/src/runtime/code-index-services.ts +135 -0
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +26 -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 +59 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +98 -46
- 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 +27 -3
- package/src/runtime/workstream-services.ts +327 -0
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +68 -4
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +171 -26
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// cockpit-read-model.ts
|
|
3
|
-
//
|
|
4
|
-
// TASK-046: Cockpit agent roster slice + cost/token aggregates + stalledAgentCount
|
|
5
|
-
//
|
|
6
|
-
// Provides a thin read-model over AgentManager.list() / getStatus() so the
|
|
7
|
-
// CockpitPanel can display an agent roster without depending on the full
|
|
8
|
-
// AgentInspectorPanel object.
|
|
9
|
-
//
|
|
10
|
-
// Design notes:
|
|
11
|
-
// - Per-agent cost delegates to calcSessionCost() from cost-utils.ts (canonical
|
|
12
|
-
// billing formula) and requires real usage data from AgentRecord.usage. When
|
|
13
|
-
// usage is absent (agent spawned but not yet completed), cost/tokens show as n/a
|
|
14
|
-
// rather than fabricated values (39327f86 honest-UX standard).
|
|
15
|
-
// - stalledAgentCount delegates to countStalledAgents() from agent-inspector-shared.ts
|
|
16
|
-
// (canonical stall-count function extracted from TASK-046 review) — no reimplementation.
|
|
17
|
-
// - The read-model is a plain object (snapshot + subscribe) so it can be
|
|
18
|
-
// wired in tests without a full runtime.
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
import type { AgentRecord } from '@pellux/goodvibes-sdk/platform/tools';
|
|
22
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
23
|
-
import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
|
|
24
|
-
import {
|
|
25
|
-
AGENT_TERMINAL_STATUSES,
|
|
26
|
-
AGENT_STALL_THRESHOLD_MS,
|
|
27
|
-
countStalledAgents,
|
|
28
|
-
hasReportedUsage,
|
|
29
|
-
} from './agent-inspector-shared.ts';
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Public types
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
/** Status of a single agent in the cockpit roster. */
|
|
36
|
-
export type CockpitAgentStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
37
|
-
|
|
38
|
-
/** A single row in the cockpit agent roster. */
|
|
39
|
-
export interface CockpitAgentRosterEntry {
|
|
40
|
-
/** Full agent id. */
|
|
41
|
-
readonly id: string;
|
|
42
|
-
/** Short task description (truncated at 50 chars). */
|
|
43
|
-
readonly task: string;
|
|
44
|
-
/** Model identifier, or 'unknown' when not yet resolved. */
|
|
45
|
-
readonly model: string;
|
|
46
|
-
/** Agent lifecycle status. */
|
|
47
|
-
readonly status: CockpitAgentStatus;
|
|
48
|
-
/** True when the agent is non-terminal and has exceeded AGENT_STALL_THRESHOLD_MS. */
|
|
49
|
-
readonly stalled: boolean;
|
|
50
|
-
/** Input tokens consumed (including cache read+write), or null when unavailable. */
|
|
51
|
-
readonly inputTokens: number | null;
|
|
52
|
-
/** Output tokens produced, or null when unavailable. */
|
|
53
|
-
readonly outputTokens: number | null;
|
|
54
|
-
/** Estimated cost in USD, or null when token data is unavailable. */
|
|
55
|
-
readonly cost: number | null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Aggregate snapshot produced by the cockpit roster read-model. */
|
|
59
|
-
export interface CockpitRosterSnapshot {
|
|
60
|
-
/** All agents in the manager, newest-first by startedAt. */
|
|
61
|
-
readonly roster: readonly CockpitAgentRosterEntry[];
|
|
62
|
-
/** Number of non-terminal agents running past AGENT_STALL_THRESHOLD_MS. */
|
|
63
|
-
readonly stalledAgentCount: number;
|
|
64
|
-
/**
|
|
65
|
-
* Sum of all input tokens across agents with real usage data.
|
|
66
|
-
* null when NO agent has usage data yet (avoids showing 0 when data is simply absent).
|
|
67
|
-
*/
|
|
68
|
-
readonly totalInputTokens: number | null;
|
|
69
|
-
/**
|
|
70
|
-
* Sum of all output tokens across agents with real usage data.
|
|
71
|
-
* null when NO agent has usage data yet.
|
|
72
|
-
*/
|
|
73
|
-
readonly totalOutputTokens: number | null;
|
|
74
|
-
/**
|
|
75
|
-
* Total estimated cost in USD across agents with real pricing data.
|
|
76
|
-
* null when NO agent has priceable data yet.
|
|
77
|
-
*/
|
|
78
|
-
readonly totalCost: number | null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// AgentManager minimal interface (subset used here)
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
export interface CockpitRosterAgentManager {
|
|
86
|
-
list(): AgentRecord[];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// Snapshot builder — pure, testable
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Build a CockpitRosterSnapshot from a raw AgentRecord list.
|
|
95
|
-
* Exported so unit tests can drive it directly without a manager stub.
|
|
96
|
-
*/
|
|
97
|
-
export function buildCockpitRosterSnapshot(
|
|
98
|
-
records: AgentRecord[],
|
|
99
|
-
now: number = Date.now(),
|
|
100
|
-
): CockpitRosterSnapshot {
|
|
101
|
-
// Sort newest-first by startedAt
|
|
102
|
-
const sorted = [...records].sort((a, b) => b.startedAt - a.startedAt);
|
|
103
|
-
|
|
104
|
-
let hasUsage = false;
|
|
105
|
-
let totalInputTokens = 0;
|
|
106
|
-
let totalOutputTokens = 0;
|
|
107
|
-
let totalCost = 0;
|
|
108
|
-
|
|
109
|
-
const roster: CockpitAgentRosterEntry[] = sorted.map((rec) => {
|
|
110
|
-
const isTerminal = AGENT_TERMINAL_STATUSES.has(rec.status);
|
|
111
|
-
const elapsed = now - rec.startedAt;
|
|
112
|
-
const stalled = !isTerminal && elapsed >= AGENT_STALL_THRESHOLD_MS;
|
|
113
|
-
|
|
114
|
-
let inputTokens: number | null = null;
|
|
115
|
-
let outputTokens: number | null = null;
|
|
116
|
-
let cost: number | null = null;
|
|
117
|
-
|
|
118
|
-
if (hasReportedUsage(rec.usage)) {
|
|
119
|
-
hasUsage = true;
|
|
120
|
-
const usage = rec.usage;
|
|
121
|
-
const inp =
|
|
122
|
-
usage.inputTokens +
|
|
123
|
-
(usage.cacheReadTokens ?? 0) +
|
|
124
|
-
(usage.cacheWriteTokens ?? 0);
|
|
125
|
-
const out = usage.outputTokens;
|
|
126
|
-
const agentCost = calcSessionCost(
|
|
127
|
-
usage.inputTokens,
|
|
128
|
-
usage.outputTokens,
|
|
129
|
-
usage.cacheReadTokens ?? 0,
|
|
130
|
-
usage.cacheWriteTokens ?? 0,
|
|
131
|
-
rec.model ?? 'unknown',
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
inputTokens = inp;
|
|
135
|
-
outputTokens = out;
|
|
136
|
-
// Usage exists but the model never resolved to a real price — reuse the
|
|
137
|
-
// existing "usage absent" null/n-a convention rather than showing a
|
|
138
|
-
// fabricated $0.00 (WO-315).
|
|
139
|
-
cost = isModelPriced(rec.model ?? 'unknown') ? agentCost : null;
|
|
140
|
-
|
|
141
|
-
totalInputTokens += inp;
|
|
142
|
-
totalOutputTokens += out;
|
|
143
|
-
totalCost += agentCost;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const task = truncateDisplay(rec.task, 50);
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
id: rec.id,
|
|
150
|
-
task,
|
|
151
|
-
model: rec.model ?? 'unknown',
|
|
152
|
-
status: rec.status,
|
|
153
|
-
stalled,
|
|
154
|
-
inputTokens,
|
|
155
|
-
outputTokens,
|
|
156
|
-
cost,
|
|
157
|
-
};
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
roster,
|
|
162
|
-
stalledAgentCount: countStalledAgents(sorted, now),
|
|
163
|
-
totalInputTokens: hasUsage ? totalInputTokens : null,
|
|
164
|
-
totalOutputTokens: hasUsage ? totalOutputTokens : null,
|
|
165
|
-
totalCost: hasUsage ? totalCost : null,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// Read-model factory
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
|
|
173
|
-
/** Minimal read-model interface matching the existing UiReadModel shape. */
|
|
174
|
-
export interface CockpitRosterReadModel {
|
|
175
|
-
getSnapshot(): CockpitRosterSnapshot;
|
|
176
|
-
/**
|
|
177
|
-
* Notify all subscribers that the roster has changed and the cockpit panel
|
|
178
|
-
* should re-render. Wire this to an AgentManager event feed so live agent
|
|
179
|
-
* state changes propagate:
|
|
180
|
-
*
|
|
181
|
-
* const roster = createCockpitRosterReadModel(agentManager);
|
|
182
|
-
* agentEvents.subscribe(() => roster.markDirty());
|
|
183
|
-
*
|
|
184
|
-
* For static/test fixtures the implementation is a no-op.
|
|
185
|
-
*/
|
|
186
|
-
markDirty(): void;
|
|
187
|
-
/**
|
|
188
|
-
* Subscribe to changes. The listener is called whenever markDirty() is
|
|
189
|
-
* invoked. Returns an unsubscribe function.
|
|
190
|
-
*
|
|
191
|
-
* For static/test fixtures, returns a no-op unsubscribe.
|
|
192
|
-
*/
|
|
193
|
-
subscribe(listener: () => void): () => void;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Create a live CockpitRosterReadModel backed by an AgentManager.
|
|
198
|
-
*
|
|
199
|
-
* The returned read-model re-derives its snapshot on every getSnapshot() call
|
|
200
|
-
* so it always reflects the current agent state without needing an event bus.
|
|
201
|
-
* Callers that want reactive updates should poll or wire in their own
|
|
202
|
-
* event-driven markDirty() path.
|
|
203
|
-
*/
|
|
204
|
-
export function createCockpitRosterReadModel(
|
|
205
|
-
agentManager: CockpitRosterAgentManager,
|
|
206
|
-
): CockpitRosterReadModel {
|
|
207
|
-
const listeners = new Set<() => void>();
|
|
208
|
-
|
|
209
|
-
function markDirty(): void {
|
|
210
|
-
for (const listener of listeners) {
|
|
211
|
-
listener();
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
getSnapshot(): CockpitRosterSnapshot {
|
|
217
|
-
return buildCockpitRosterSnapshot(agentManager.list());
|
|
218
|
-
},
|
|
219
|
-
markDirty,
|
|
220
|
-
subscribe(listener: () => void): () => void {
|
|
221
|
-
listeners.add(listener);
|
|
222
|
-
return () => listeners.delete(listener);
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Create a static CockpitRosterReadModel for testing.
|
|
229
|
-
*/
|
|
230
|
-
export function createStaticCockpitRosterReadModel(
|
|
231
|
-
snapshot: CockpitRosterSnapshot,
|
|
232
|
-
): CockpitRosterReadModel {
|
|
233
|
-
return {
|
|
234
|
-
getSnapshot: () => snapshot,
|
|
235
|
-
markDirty: () => {},
|
|
236
|
-
subscribe: () => () => {},
|
|
237
|
-
};
|
|
238
|
-
}
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
3
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
4
|
-
import type { UiCommunicationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
5
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
6
|
-
import {
|
|
7
|
-
buildBodyText,
|
|
8
|
-
buildDetailBlock,
|
|
9
|
-
buildEmptyState,
|
|
10
|
-
buildGuidanceLine,
|
|
11
|
-
buildKeyValueLine,
|
|
12
|
-
buildKeyboardHints,
|
|
13
|
-
buildPanelLine,
|
|
14
|
-
buildPanelWorkspace,
|
|
15
|
-
buildStatusBadge,
|
|
16
|
-
DEFAULT_PANEL_PALETTE,
|
|
17
|
-
type PanelPalette,
|
|
18
|
-
type StatusBadgeKind,
|
|
19
|
-
} from './polish.ts';
|
|
20
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
21
|
-
|
|
22
|
-
// Base chrome only — title band, state colors, and text tokens all come
|
|
23
|
-
// straight from DEFAULT_PANEL_PALETTE (WO-002).
|
|
24
|
-
const C = DEFAULT_PANEL_PALETTE;
|
|
25
|
-
|
|
26
|
-
type CommunicationRecord = UiCommunicationSnapshot['records'][number];
|
|
27
|
-
|
|
28
|
-
function statusBadgeKind(status: CommunicationRecord['status']): StatusBadgeKind {
|
|
29
|
-
return status === 'blocked' ? 'blocked' : status === 'delivered' ? 'completed' : 'running';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function fmtAgo(ts: number): string {
|
|
33
|
-
const sec = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
|
34
|
-
if (sec < 60) return `${sec}s`;
|
|
35
|
-
if (sec < 3600) return `${Math.floor(sec / 60)}m`;
|
|
36
|
-
return `${Math.floor(sec / 3600)}h`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Set by handleInput (enter/o) and consumed on the very next
|
|
40
|
-
// handlePanelIntegrationAction dispatch of that same key — handleInput has
|
|
41
|
-
// no access to the panelManager/openAgentDetail callback.
|
|
42
|
-
type PendingCommunicationAction =
|
|
43
|
-
| { readonly kind: 'open-orchestration' }
|
|
44
|
-
| { readonly kind: 'open-agent-detail'; readonly agentId: string };
|
|
45
|
-
|
|
46
|
-
export class CommunicationPanel extends ScrollableListPanel<CommunicationRecord> {
|
|
47
|
-
private readonly readModel?: UiReadModel<UiCommunicationSnapshot>;
|
|
48
|
-
private readonly unsub: (() => void) | null;
|
|
49
|
-
private readonly openAgentDetail?: (agentId: string) => void;
|
|
50
|
-
/** When true, the list is narrowed to blocked messages only (b toggles). */
|
|
51
|
-
private blockedOnly = false;
|
|
52
|
-
private pendingAction: PendingCommunicationAction | null = null;
|
|
53
|
-
|
|
54
|
-
public constructor(
|
|
55
|
-
readModel?: UiReadModel<UiCommunicationSnapshot>,
|
|
56
|
-
openAgentDetail?: (agentId: string) => void,
|
|
57
|
-
) {
|
|
58
|
-
super('communication', 'Communication', 'Y', 'runtime-ops');
|
|
59
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
60
|
-
this.filterEnabled = true;
|
|
61
|
-
this.filterLabel = 'Filter messages';
|
|
62
|
-
this.readModel = readModel;
|
|
63
|
-
this.openAgentDetail = openAgentDetail;
|
|
64
|
-
this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public override onDestroy(): void {
|
|
68
|
-
this.unsub?.();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
protected override getPalette(): PanelPalette {
|
|
72
|
-
return C;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
protected getItems(): readonly CommunicationRecord[] {
|
|
76
|
-
if (!this.readModel) return [];
|
|
77
|
-
const records = this.readModel.getSnapshot().records;
|
|
78
|
-
return this.blockedOnly ? records.filter((r) => r.status === 'blocked') : records;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
protected override filterMatches(record: CommunicationRecord, q: string): boolean {
|
|
82
|
-
return (record.content ?? '').toLowerCase().includes(q)
|
|
83
|
-
|| record.kind.toLowerCase().includes(q)
|
|
84
|
-
|| String(record.fromId ?? '').toLowerCase().includes(q)
|
|
85
|
-
|| String(record.toId ?? '').toLowerCase().includes(q);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
public override handleInput(key: string): boolean {
|
|
89
|
-
// `b` toggles the blocked-lanes-only view — guarded so it only acts when not
|
|
90
|
-
// typing into the filter input.
|
|
91
|
-
if (key === 'b' && !this.filterActive) {
|
|
92
|
-
this.blockedOnly = !this.blockedOnly;
|
|
93
|
-
this.selectedIndex = 0;
|
|
94
|
-
this.markDirty();
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
// Enter jumps to the orchestration workspace that actually produces this
|
|
98
|
-
// traffic (a direct panel jump, not a printed "/orchestration" signpost).
|
|
99
|
-
// Guarded so it doesn't hijack the filter input's commit-on-enter.
|
|
100
|
-
if ((key === 'enter' || key === 'return') && !this.filterActive) {
|
|
101
|
-
this.pendingAction = { kind: 'open-orchestration' };
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
// `o` on a blocked record opens the sender's agent detail (falling back
|
|
105
|
-
// to the receiver when the sender id is missing) via deps.openAgentDetail.
|
|
106
|
-
if (key === 'o' && !this.filterActive) {
|
|
107
|
-
const record = this.getSelectedItem();
|
|
108
|
-
if (!record || record.status !== 'blocked') return false;
|
|
109
|
-
const agentId = record.fromId || record.toId;
|
|
110
|
-
if (!agentId) return false;
|
|
111
|
-
this.pendingAction = { kind: 'open-agent-detail', agentId };
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
return super.handleInput(key);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
public handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
118
|
-
if (!this.pendingAction) return false;
|
|
119
|
-
const action = this.pendingAction;
|
|
120
|
-
this.pendingAction = null;
|
|
121
|
-
if (action.kind === 'open-orchestration') {
|
|
122
|
-
ctx.panelManager.open('orchestration');
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
this.openAgentDetail?.(action.agentId);
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
protected renderItem(record: CommunicationRecord, _index: number, selected: boolean, width: number): Line {
|
|
130
|
-
const bg = selected ? C.selectBg : undefined;
|
|
131
|
-
const badge = buildStatusBadge(statusBadgeKind(record.status), record.status)[0]!;
|
|
132
|
-
const route = truncateDisplay(`${record.fromId} → ${record.toId}`, 26);
|
|
133
|
-
const contentWidth = Math.max(0, width - 50);
|
|
134
|
-
// Blocked messages have no delivered content; surface the policy reason
|
|
135
|
-
// inline so the most important detail (why a lane is blocked) is visible
|
|
136
|
-
// without selecting the row.
|
|
137
|
-
const tail = record.status === 'blocked' && record.reason
|
|
138
|
-
? { text: record.reason, fg: C.warn }
|
|
139
|
-
: { text: record.content, fg: C.dim };
|
|
140
|
-
return buildPanelLine(width, [
|
|
141
|
-
[' ', C.label, bg],
|
|
142
|
-
[`${fmtAgo(record.timestamp)} `.padStart(5), C.dim, bg],
|
|
143
|
-
[` ${badge.text} `.padEnd(12), badge.fg, bg],
|
|
144
|
-
[`${record.kind} `.padEnd(9), C.info, bg],
|
|
145
|
-
[`${route} `.padEnd(27), C.value, bg],
|
|
146
|
-
[truncateDisplay(tail.text, contentWidth), tail.fg, bg],
|
|
147
|
-
]);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
protected override getEmptyStateMessage(): string {
|
|
151
|
-
return ' No structured communication recorded yet.';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
155
|
-
return [
|
|
156
|
-
{ command: '/orchestration', summary: 'review graphs and recursive agent activity that emit messages' },
|
|
157
|
-
];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public render(width: number, height: number): Line[] {
|
|
161
|
-
const intro = 'Structured agent communication, routing policy outcomes, and delivery status across orchestration trees.';
|
|
162
|
-
|
|
163
|
-
if (!this.readModel) {
|
|
164
|
-
const workspace = buildPanelWorkspace(width, height, {
|
|
165
|
-
title: 'Communication Control Room',
|
|
166
|
-
intro,
|
|
167
|
-
sections: [{
|
|
168
|
-
lines: buildEmptyState(
|
|
169
|
-
width,
|
|
170
|
-
' Runtime store not wired into this panel yet.',
|
|
171
|
-
'This workspace needs the live runtime store before it can show communication history and policy outcomes.',
|
|
172
|
-
[{ command: '/orchestration', summary: 'inspect recursive routing and agent activity while this workspace is unwired' }],
|
|
173
|
-
C,
|
|
174
|
-
),
|
|
175
|
-
}],
|
|
176
|
-
palette: C,
|
|
177
|
-
});
|
|
178
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
179
|
-
return workspace;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const snapshot = this.readModel.getSnapshot();
|
|
183
|
-
const records = this.getItems();
|
|
184
|
-
|
|
185
|
-
// Posture: blocked lanes / message flow surfaced first per the panel's purpose.
|
|
186
|
-
const postureLines = (): Line[] => [
|
|
187
|
-
buildPanelLine(width, [
|
|
188
|
-
[' ', C.label],
|
|
189
|
-
...buildStatusBadge('blocked', 'blocked', { count: snapshot.totalBlocked }),
|
|
190
|
-
[' ', C.dim],
|
|
191
|
-
...buildStatusBadge('completed', 'delivered', { count: snapshot.totalDelivered }),
|
|
192
|
-
[' ', C.dim],
|
|
193
|
-
...buildStatusBadge('running', 'sent', { count: snapshot.totalSent }),
|
|
194
|
-
...(this.blockedOnly ? ([[' (blocked-only view)', C.warn]] as Array<[string, string]>) : []),
|
|
195
|
-
]),
|
|
196
|
-
snapshot.totalBlocked > 0
|
|
197
|
-
? buildPanelLine(width, [[` ${snapshot.totalBlocked} message${snapshot.totalBlocked !== 1 ? 's' : ''} blocked by routing policy — press b to isolate blocked lanes.`, C.warn]])
|
|
198
|
-
: buildPanelLine(width, [[' No blocked lanes — press Enter on a record to inspect routing in the orchestration console.', C.dim]]),
|
|
199
|
-
];
|
|
200
|
-
|
|
201
|
-
if (records.length === 0) {
|
|
202
|
-
return this.renderList(width, height, {
|
|
203
|
-
title: 'Communication Control Room',
|
|
204
|
-
header: postureLines(),
|
|
205
|
-
footer: [buildKeyboardHints(width, this.footerHints(false), C)],
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
this.clampSelection();
|
|
210
|
-
const selected = this.getSelectedItem();
|
|
211
|
-
|
|
212
|
-
const detailLines: Line[] = [];
|
|
213
|
-
if (selected) {
|
|
214
|
-
detailLines.push(...buildDetailBlock(width, `Message · ${selected.kind}`, [
|
|
215
|
-
buildKeyValueLine(width, [
|
|
216
|
-
{ label: 'status', value: selected.status, valueColor: selected.status === 'blocked' ? C.bad : C.good },
|
|
217
|
-
{ label: 'scope', value: selected.scope, valueColor: C.value },
|
|
218
|
-
{ label: 'when', value: `${fmtAgo(selected.timestamp)} ago`, valueColor: C.dim },
|
|
219
|
-
], C),
|
|
220
|
-
buildKeyValueLine(width, [
|
|
221
|
-
{ label: 'from', value: `${selected.fromId} (${selected.fromRole ?? 'unknown'})`, valueColor: C.value },
|
|
222
|
-
{ label: 'to', value: `${selected.toId} (${selected.toRole ?? 'unknown'})`, valueColor: C.value },
|
|
223
|
-
], C),
|
|
224
|
-
...(selected.reason
|
|
225
|
-
? buildBodyText(width, `blocked: ${selected.reason}`, C, C.warn)
|
|
226
|
-
: []),
|
|
227
|
-
...buildBodyText(width, selected.content, C, C.value),
|
|
228
|
-
], C));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return this.renderList(width, height, {
|
|
232
|
-
title: 'Communication Control Room',
|
|
233
|
-
header: postureLines(),
|
|
234
|
-
footer: [...detailLines, buildKeyboardHints(width, this.footerHints(true, selected?.status === 'blocked'), C)],
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Context-aware hints: filter keys reflect filter state, `b` reflects the
|
|
239
|
-
// blocked-only toggle, `o` only appears on a blocked selection, and the
|
|
240
|
-
// inspect hint only appears when rows exist.
|
|
241
|
-
private footerHints(hasRows: boolean, onBlockedSelection = false): Array<{ keys: string; label: string }> {
|
|
242
|
-
const hints: Array<{ keys: string; label: string }> = [];
|
|
243
|
-
if (hasRows) hints.push({ keys: '↑/↓', label: 'select' });
|
|
244
|
-
hints.push({ keys: 'Enter', label: 'orchestration' });
|
|
245
|
-
if (onBlockedSelection) hints.push({ keys: 'o', label: 'sender detail' });
|
|
246
|
-
hints.push({ keys: 'b', label: this.blockedOnly ? 'show all lanes' : 'blocked only' });
|
|
247
|
-
if (this.filterActive) {
|
|
248
|
-
hints.push({ keys: 'Esc', label: 'clear filter' });
|
|
249
|
-
} else if (this.filterQuery) {
|
|
250
|
-
hints.push({ keys: '/', label: 'edit filter' }, { keys: 'Esc', label: 'clear filter' });
|
|
251
|
-
} else {
|
|
252
|
-
hints.push({ keys: '/', label: 'filter' });
|
|
253
|
-
}
|
|
254
|
-
return hints;
|
|
255
|
-
}
|
|
256
|
-
}
|