@pellux/goodvibes-tui 0.21.0 → 0.23.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +1 -1
  3. package/package.json +2 -1
  4. package/src/cli/completions/generate.ts +4 -8
  5. package/src/cli/entrypoint.ts +6 -0
  6. package/src/cli/management-commands.ts +1 -1
  7. package/src/cli/management-utils.ts +352 -0
  8. package/src/cli/management.ts +36 -334
  9. package/src/cli/parser.ts +17 -0
  10. package/src/cli/surface-command.ts +1 -1
  11. package/src/cli/types.ts +2 -0
  12. package/src/config/goodvibes-home-audit.ts +2 -0
  13. package/src/core/context-auto-compact.ts +110 -0
  14. package/src/core/conversation-rendering.ts +5 -2
  15. package/src/core/conversation-types.ts +24 -0
  16. package/src/core/conversation.ts +7 -12
  17. package/src/core/stream-event-wiring.ts +125 -7
  18. package/src/core/turn-event-wiring.ts +124 -0
  19. package/src/daemon/cli.ts +5 -0
  20. package/src/input/command-registry.ts +1 -0
  21. package/src/input/commands/channel-runtime.ts +139 -0
  22. package/src/input/commands/control-room-runtime.ts +5 -5
  23. package/src/input/commands/provider.ts +57 -3
  24. package/src/input/commands/runtime-services.ts +30 -1
  25. package/src/input/commands/session-workflow.ts +8 -16
  26. package/src/input/commands/session.ts +70 -20
  27. package/src/input/commands/share-runtime.ts +1 -1
  28. package/src/input/commands/shell-core.ts +54 -4
  29. package/src/input/commands.ts +2 -2
  30. package/src/input/handler-modal-routes.ts +37 -0
  31. package/src/input/handler-modal-token-routes.ts +19 -5
  32. package/src/input/handler-onboarding.ts +18 -0
  33. package/src/input/handler.ts +1 -0
  34. package/src/input/onboarding/onboarding-wizard-apply.ts +10 -0
  35. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +14 -0
  36. package/src/input/onboarding/onboarding-wizard-steps.ts +6 -0
  37. package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
  38. package/src/input/settings-modal-behavior.ts +5 -0
  39. package/src/input/settings-modal-data.ts +77 -3
  40. package/src/input/settings-modal-mutations.ts +3 -0
  41. package/src/input/settings-modal-reset.ts +154 -0
  42. package/src/input/settings-modal.ts +55 -13
  43. package/src/main.ts +58 -50
  44. package/src/panels/agent-inspector-panel.ts +120 -18
  45. package/src/panels/agent-inspector-shared.ts +29 -0
  46. package/src/panels/builtin/development.ts +1 -0
  47. package/src/panels/builtin/knowledge.ts +14 -13
  48. package/src/panels/builtin/operations.ts +22 -1
  49. package/src/panels/builtin/shared.ts +7 -0
  50. package/src/panels/cockpit-panel.ts +123 -3
  51. package/src/panels/cockpit-read-model.ts +232 -0
  52. package/src/panels/index.ts +1 -1
  53. package/src/panels/knowledge-graph-panel.ts +84 -0
  54. package/src/panels/memory-panel.ts +370 -40
  55. package/src/panels/session-maintenance.ts +66 -15
  56. package/src/renderer/agent-detail-modal.ts +107 -3
  57. package/src/renderer/compaction-history-modal.ts +55 -0
  58. package/src/renderer/compaction-preview.ts +146 -0
  59. package/src/renderer/context-status-hint.ts +54 -0
  60. package/src/renderer/settings-modal-helpers.ts +2 -2
  61. package/src/renderer/settings-modal.ts +14 -3
  62. package/src/renderer/shell-surface.ts +10 -0
  63. package/src/runtime/bootstrap-command-parts.ts +4 -0
  64. package/src/runtime/bootstrap-core.ts +116 -0
  65. package/src/runtime/bootstrap-shell.ts +11 -0
  66. package/src/runtime/bootstrap.ts +7 -0
  67. package/src/runtime/services.ts +6 -1
  68. package/src/utils/browser.ts +29 -0
  69. package/src/version.ts +1 -1
  70. package/src/panels/knowledge-panel.ts +0 -343
@@ -0,0 +1,232 @@
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 { calcSessionCost } from '../export/cost-utils.ts';
23
+ import {
24
+ AGENT_TERMINAL_STATUSES,
25
+ AGENT_STALL_THRESHOLD_MS,
26
+ countStalledAgents,
27
+ } from './agent-inspector-shared.ts';
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Public types
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /** Status of a single agent in the cockpit roster. */
34
+ export type CockpitAgentStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
35
+
36
+ /** A single row in the cockpit agent roster. */
37
+ export interface CockpitAgentRosterEntry {
38
+ /** Full agent id. */
39
+ readonly id: string;
40
+ /** Short task description (truncated at 50 chars). */
41
+ readonly task: string;
42
+ /** Model identifier, or 'unknown' when not yet resolved. */
43
+ readonly model: string;
44
+ /** Agent lifecycle status. */
45
+ readonly status: CockpitAgentStatus;
46
+ /** True when the agent is non-terminal and has exceeded AGENT_STALL_THRESHOLD_MS. */
47
+ readonly stalled: boolean;
48
+ /** Input tokens consumed (including cache read+write), or null when unavailable. */
49
+ readonly inputTokens: number | null;
50
+ /** Output tokens produced, or null when unavailable. */
51
+ readonly outputTokens: number | null;
52
+ /** Estimated cost in USD, or null when token data is unavailable. */
53
+ readonly cost: number | null;
54
+ }
55
+
56
+ /** Aggregate snapshot produced by the cockpit roster read-model. */
57
+ export interface CockpitRosterSnapshot {
58
+ /** All agents in the manager, newest-first by startedAt. */
59
+ readonly roster: readonly CockpitAgentRosterEntry[];
60
+ /** Number of non-terminal agents running past AGENT_STALL_THRESHOLD_MS. */
61
+ readonly stalledAgentCount: number;
62
+ /**
63
+ * Sum of all input tokens across agents with real usage data.
64
+ * null when NO agent has usage data yet (avoids showing 0 when data is simply absent).
65
+ */
66
+ readonly totalInputTokens: number | null;
67
+ /**
68
+ * Sum of all output tokens across agents with real usage data.
69
+ * null when NO agent has usage data yet.
70
+ */
71
+ readonly totalOutputTokens: number | null;
72
+ /**
73
+ * Total estimated cost in USD across agents with real pricing data.
74
+ * null when NO agent has priceable data yet.
75
+ */
76
+ readonly totalCost: number | null;
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // AgentManager minimal interface (subset used here)
81
+ // ---------------------------------------------------------------------------
82
+
83
+ export interface CockpitRosterAgentManager {
84
+ list(): AgentRecord[];
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Snapshot builder — pure, testable
89
+ // ---------------------------------------------------------------------------
90
+
91
+ /**
92
+ * Build a CockpitRosterSnapshot from a raw AgentRecord list.
93
+ * Exported so unit tests can drive it directly without a manager stub.
94
+ */
95
+ export function buildCockpitRosterSnapshot(
96
+ records: AgentRecord[],
97
+ now: number = Date.now(),
98
+ ): CockpitRosterSnapshot {
99
+ // Sort newest-first by startedAt
100
+ const sorted = [...records].sort((a, b) => b.startedAt - a.startedAt);
101
+
102
+ let hasUsage = false;
103
+ let totalInputTokens = 0;
104
+ let totalOutputTokens = 0;
105
+ let totalCost = 0;
106
+
107
+ const roster: CockpitAgentRosterEntry[] = sorted.map((rec) => {
108
+ const isTerminal = AGENT_TERMINAL_STATUSES.has(rec.status);
109
+ const elapsed = now - rec.startedAt;
110
+ const stalled = !isTerminal && elapsed >= AGENT_STALL_THRESHOLD_MS;
111
+
112
+ let inputTokens: number | null = null;
113
+ let outputTokens: number | null = null;
114
+ let cost: number | null = null;
115
+
116
+ if (rec.usage) {
117
+ hasUsage = true;
118
+ const inp =
119
+ rec.usage.inputTokens +
120
+ (rec.usage.cacheReadTokens ?? 0) +
121
+ (rec.usage.cacheWriteTokens ?? 0);
122
+ const out = rec.usage.outputTokens;
123
+ const agentCost = calcSessionCost(
124
+ rec.usage.inputTokens,
125
+ rec.usage.outputTokens,
126
+ rec.usage.cacheReadTokens ?? 0,
127
+ rec.usage.cacheWriteTokens ?? 0,
128
+ rec.model ?? 'unknown',
129
+ );
130
+
131
+ inputTokens = inp;
132
+ outputTokens = out;
133
+ cost = agentCost;
134
+
135
+ totalInputTokens += inp;
136
+ totalOutputTokens += out;
137
+ totalCost += agentCost;
138
+ }
139
+
140
+ const task = rec.task.length > 50 ? rec.task.slice(0, 47) + '...' : rec.task;
141
+
142
+ return {
143
+ id: rec.id,
144
+ task,
145
+ model: rec.model ?? 'unknown',
146
+ status: rec.status,
147
+ stalled,
148
+ inputTokens,
149
+ outputTokens,
150
+ cost,
151
+ };
152
+ });
153
+
154
+ return {
155
+ roster,
156
+ stalledAgentCount: countStalledAgents(sorted, now),
157
+ totalInputTokens: hasUsage ? totalInputTokens : null,
158
+ totalOutputTokens: hasUsage ? totalOutputTokens : null,
159
+ totalCost: hasUsage ? totalCost : null,
160
+ };
161
+ }
162
+
163
+ // ---------------------------------------------------------------------------
164
+ // Read-model factory
165
+ // ---------------------------------------------------------------------------
166
+
167
+ /** Minimal read-model interface matching the existing UiReadModel shape. */
168
+ export interface CockpitRosterReadModel {
169
+ getSnapshot(): CockpitRosterSnapshot;
170
+ /**
171
+ * Notify all subscribers that the roster has changed and the cockpit panel
172
+ * should re-render. Wire this to an AgentManager event feed so live agent
173
+ * state changes propagate:
174
+ *
175
+ * const roster = createCockpitRosterReadModel(agentManager);
176
+ * agentEvents.subscribe(() => roster.markDirty());
177
+ *
178
+ * For static/test fixtures the implementation is a no-op.
179
+ */
180
+ markDirty(): void;
181
+ /**
182
+ * Subscribe to changes. The listener is called whenever markDirty() is
183
+ * invoked. Returns an unsubscribe function.
184
+ *
185
+ * For static/test fixtures, returns a no-op unsubscribe.
186
+ */
187
+ subscribe(listener: () => void): () => void;
188
+ }
189
+
190
+ /**
191
+ * Create a live CockpitRosterReadModel backed by an AgentManager.
192
+ *
193
+ * The returned read-model re-derives its snapshot on every getSnapshot() call
194
+ * so it always reflects the current agent state without needing an event bus.
195
+ * Callers that want reactive updates should poll or wire in their own
196
+ * event-driven markDirty() path.
197
+ */
198
+ export function createCockpitRosterReadModel(
199
+ agentManager: CockpitRosterAgentManager,
200
+ ): CockpitRosterReadModel {
201
+ const listeners = new Set<() => void>();
202
+
203
+ function markDirty(): void {
204
+ for (const listener of listeners) {
205
+ listener();
206
+ }
207
+ }
208
+
209
+ return {
210
+ getSnapshot(): CockpitRosterSnapshot {
211
+ return buildCockpitRosterSnapshot(agentManager.list());
212
+ },
213
+ markDirty,
214
+ subscribe(listener: () => void): () => void {
215
+ listeners.add(listener);
216
+ return () => listeners.delete(listener);
217
+ },
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Create a static CockpitRosterReadModel for testing.
223
+ */
224
+ export function createStaticCockpitRosterReadModel(
225
+ snapshot: CockpitRosterSnapshot,
226
+ ): CockpitRosterReadModel {
227
+ return {
228
+ getSnapshot: () => snapshot,
229
+ markDirty: () => {},
230
+ subscribe: () => () => {},
231
+ };
232
+ }
@@ -40,7 +40,7 @@ export { SecurityPanel } from './security-panel.ts';
40
40
  export { MarketplacePanel } from './marketplace-panel.ts';
41
41
  export { SandboxPanel } from './sandbox-panel.ts';
42
42
  export { ApprovalPanel } from './approval-panel.ts';
43
- export { KnowledgePanel } from './knowledge-panel.ts';
43
+ export { KnowledgeGraphPanel } from './knowledge-graph-panel.ts';
44
44
  export { SystemMessagesPanel } from './system-messages-panel.ts';
45
45
  export { PanelListPanel } from './panel-list-panel.ts';
46
46
  export type { SystemMessageEntry, SystemMessagePriority } from './system-messages-panel.ts';
@@ -0,0 +1,84 @@
1
+ /**
2
+ * KnowledgeGraphPanel — SDK knowledge graph front-door.
3
+ *
4
+ * TASK-040: The 'knowledge' panel id is repointed here (the SDK graph), fixing
5
+ * the naming inversion where the former panel named 'Knowledge' was actually
6
+ * rendering memory records.
7
+ *
8
+ * This panel is a thin information surface that explains the graph's capabilities
9
+ * and routes the user to the /knowledge command suite for ingest/RAG operations.
10
+ * The full graph UI is command-driven (/knowledge ask, ingest-url, list, search…).
11
+ */
12
+
13
+ import type { Line } from '../types/grid.ts';
14
+ import { BasePanel } from './base-panel.ts';
15
+ import {
16
+ buildBodyText,
17
+ buildGuidanceLine,
18
+ buildPanelLine,
19
+ buildPanelWorkspace,
20
+ DEFAULT_PANEL_PALETTE,
21
+ } from './polish.ts';
22
+
23
+ const C = {
24
+ ...DEFAULT_PANEL_PALETTE,
25
+ header: '#94a3b8',
26
+ headerBg: '#1e293b',
27
+ } as const;
28
+
29
+ export class KnowledgeGraphPanel extends BasePanel {
30
+ constructor() {
31
+ super('knowledge', 'Knowledge', 'K', 'agent');
32
+ }
33
+
34
+ handleInput(_key: string): boolean {
35
+ return false;
36
+ }
37
+
38
+ render(width: number, height: number): Line[] {
39
+ const sections = [
40
+ {
41
+ title: 'SDK Knowledge Graph',
42
+ lines: [
43
+ ...buildBodyText(
44
+ width,
45
+ 'The knowledge graph stores ingested URLs, bookmarks, and structured facts as nodes and edges. ' +
46
+ 'Use /knowledge commands to ingest sources, search the graph, and build task-context packets.',
47
+ C,
48
+ C.value,
49
+ ),
50
+ buildPanelLine(width, [['', C.dim]]),
51
+ buildGuidanceLine(width, '/knowledge status', 'check the graph status and source counts', C),
52
+ buildGuidanceLine(width, '/knowledge ask <query>', 'ask a question against the ingested knowledge', C),
53
+ buildGuidanceLine(width, '/knowledge ingest-url <url>', 'ingest a URL as a knowledge source', C),
54
+ buildGuidanceLine(width, '/knowledge list', 'list ingested sources or graph nodes', C),
55
+ buildGuidanceLine(width, '/knowledge search <query>', 'search the graph for nodes and sources', C),
56
+ buildGuidanceLine(width, '/knowledge packet <task>', 'build a compact prompt packet for a task', C),
57
+ ],
58
+ },
59
+ {
60
+ title: 'Project Memory',
61
+ lines: [
62
+ ...buildBodyText(
63
+ width,
64
+ 'For durable decisions, risks, runbooks, incidents, and architecture records, use the Memory panel ' +
65
+ 'or the /recall command surface. Durable memory is a sub-namespace of the knowledge graph.',
66
+ C,
67
+ C.dim,
68
+ ),
69
+ buildPanelLine(width, [['', C.dim]]),
70
+ buildGuidanceLine(width, '/recall add <class> <summary>', 'capture a new memory record', C),
71
+ buildGuidanceLine(width, '/recall queue', 'show the operator review queue', C),
72
+ buildGuidanceLine(width, '/project-memory (pmem)', 'project-memory alias for /recall front-door', C),
73
+ ],
74
+ },
75
+ ];
76
+
77
+ return buildPanelWorkspace(width, height, {
78
+ title: 'Knowledge Graph',
79
+ intro: 'Ingested sources, graph nodes, and the durable memory bridge.',
80
+ sections,
81
+ palette: C,
82
+ });
83
+ }
84
+ }