@pellux/goodvibes-tui 0.21.0 → 0.22.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 +23 -0
- package/README.md +1 -1
- package/package.json +2 -1
- package/src/cli/completions/generate.ts +4 -8
- package/src/cli/entrypoint.ts +6 -0
- package/src/cli/parser.ts +17 -0
- package/src/cli/types.ts +2 -0
- package/src/config/goodvibes-home-audit.ts +2 -0
- package/src/core/context-auto-compact.ts +77 -0
- package/src/core/turn-event-wiring.ts +124 -0
- package/src/daemon/cli.ts +5 -0
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/control-room-runtime.ts +5 -5
- package/src/input/commands/provider.ts +57 -3
- package/src/input/commands/session-workflow.ts +8 -16
- package/src/input/commands/session.ts +70 -20
- package/src/input/commands.ts +0 -2
- package/src/input/handler-modal-routes.ts +37 -0
- package/src/input/handler-modal-token-routes.ts +19 -5
- package/src/input/handler-onboarding.ts +18 -0
- package/src/input/handler.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +10 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +14 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +6 -0
- package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
- package/src/input/settings-modal-behavior.ts +5 -0
- package/src/input/settings-modal-data.ts +77 -3
- package/src/input/settings-modal-mutations.ts +3 -0
- package/src/input/settings-modal-reset.ts +154 -0
- package/src/input/settings-modal.ts +55 -13
- package/src/main.ts +36 -28
- package/src/panels/agent-inspector-panel.ts +120 -18
- package/src/panels/agent-inspector-shared.ts +29 -0
- package/src/panels/builtin/development.ts +1 -0
- package/src/panels/builtin/knowledge.ts +14 -13
- package/src/panels/builtin/operations.ts +22 -1
- package/src/panels/builtin/shared.ts +7 -0
- package/src/panels/cockpit-panel.ts +123 -3
- package/src/panels/cockpit-read-model.ts +232 -0
- package/src/panels/index.ts +1 -1
- package/src/panels/knowledge-graph-panel.ts +84 -0
- package/src/panels/memory-panel.ts +370 -40
- package/src/panels/session-maintenance.ts +66 -15
- package/src/renderer/agent-detail-modal.ts +107 -3
- package/src/renderer/context-status-hint.ts +54 -0
- package/src/renderer/settings-modal.ts +14 -3
- package/src/renderer/shell-surface.ts +10 -0
- package/src/runtime/bootstrap-command-parts.ts +4 -0
- package/src/runtime/bootstrap-core.ts +24 -0
- package/src/runtime/bootstrap-shell.ts +11 -0
- package/src/runtime/bootstrap.ts +7 -0
- package/src/runtime/services.ts +6 -1
- package/src/version.ts +1 -1
- package/src/panels/knowledge-panel.ts +0 -343
|
@@ -8,18 +8,25 @@ import { formatDuration } from './modal-utils.ts';
|
|
|
8
8
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
9
9
|
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
10
10
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
11
|
+
import { handleConfirmInput, type ConfirmState } from '../panels/confirm-state.ts';
|
|
12
|
+
import { AGENT_TERMINAL_STATUSES as MODAL_TERMINAL_STATUSES, AGENT_STALL_THRESHOLD_MS as MODAL_STALL_THRESHOLD_MS } from '../panels/agent-inspector-shared.ts';
|
|
11
13
|
|
|
12
14
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
13
15
|
|
|
14
16
|
const MAX_LOG_ENTRIES = 10;
|
|
15
17
|
const AGENT_ID_DISPLAY_LENGTH = 16;
|
|
16
18
|
|
|
19
|
+
// MODAL_TERMINAL_STATUSES and MODAL_STALL_THRESHOLD_MS are re-exported aliases
|
|
20
|
+
// from agent-inspector-shared.ts (imported above alongside ConfirmState).
|
|
21
|
+
|
|
17
22
|
export interface AgentDetailModalDeps {
|
|
18
|
-
readonly agentManager: Pick<AgentManager, 'getStatus'>;
|
|
23
|
+
readonly agentManager: Pick<AgentManager, 'getStatus' | 'list'>;
|
|
19
24
|
readonly agentMessageBus: Pick<AgentMessageBus, 'getMessages'>;
|
|
20
25
|
readonly sessionLogPathResolver: (agentId: string) => string;
|
|
21
26
|
/** Optional — when supplied, constraint data from the agent's WRFC chain is shown (SDK 0.23.0). */
|
|
22
27
|
readonly wrfcController?: Pick<WrfcController, 'getChain'>;
|
|
28
|
+
/** Cancel the agent by id using the same orphan-free path as WRFC. Returns true if cancelled. */
|
|
29
|
+
readonly cancelAgent: (agentId: string) => boolean;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
// ─── AgentDetailModal ─────────────────────────────────────────────────────────
|
|
@@ -39,6 +46,9 @@ export class AgentDetailModal {
|
|
|
39
46
|
public logEntries: Record<string, unknown>[] = [];
|
|
40
47
|
public logTotal = 0;
|
|
41
48
|
|
|
49
|
+
/** Pending cancel confirmation. Subject is the agent id to cancel. */
|
|
50
|
+
public confirmCancel: ConfirmState<string> | null = null;
|
|
51
|
+
|
|
42
52
|
private refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
43
53
|
private onRefresh: (() => void) | null = null;
|
|
44
54
|
|
|
@@ -67,12 +77,88 @@ export class AgentDetailModal {
|
|
|
67
77
|
this.agentId = null;
|
|
68
78
|
this.logEntries = [];
|
|
69
79
|
this.logTotal = 0;
|
|
80
|
+
this.confirmCancel = null;
|
|
70
81
|
if (this.refreshTimer) {
|
|
71
82
|
clearInterval(this.refreshTimer);
|
|
72
83
|
this.refreshTimer = null;
|
|
73
84
|
}
|
|
74
85
|
}
|
|
75
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Handle a key press while the modal is active.
|
|
89
|
+
* Must be called BEFORE the Esc handler closes the modal.
|
|
90
|
+
*
|
|
91
|
+
* Routes:
|
|
92
|
+
* - 'c' initiates cancel confirm (if agent is non-terminal)
|
|
93
|
+
* - confirm keys (Enter/y/n/Esc) are forwarded to handleConfirmInput
|
|
94
|
+
*
|
|
95
|
+
* Returns true when the key was consumed (caller should NOT propagate).
|
|
96
|
+
*/
|
|
97
|
+
handleKey(key: string): boolean {
|
|
98
|
+
if (!this.active) return false;
|
|
99
|
+
|
|
100
|
+
if (this.confirmCancel) {
|
|
101
|
+
const result = handleConfirmInput(this.confirmCancel, key);
|
|
102
|
+
if (result === 'confirmed') {
|
|
103
|
+
if (this.agentId) {
|
|
104
|
+
const rec = this.deps.agentManager.getStatus(this.agentId);
|
|
105
|
+
if (rec && !MODAL_TERMINAL_STATUSES.has(rec.status)) {
|
|
106
|
+
this.deps.cancelAgent(rec.id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.confirmCancel = null;
|
|
110
|
+
this.onRefresh?.();
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (result === 'cancelled') {
|
|
114
|
+
this.confirmCancel = null;
|
|
115
|
+
this.onRefresh?.();
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
// absorbed — key swallowed while confirm is pending
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (key === 'c') {
|
|
123
|
+
if (this.agentId) {
|
|
124
|
+
const rec = this.deps.agentManager.getStatus(this.agentId);
|
|
125
|
+
if (rec && !MODAL_TERMINAL_STATUSES.has(rec.status)) {
|
|
126
|
+
const label = rec.task.split('\n')[0]?.slice(0, 40) ?? rec.id.slice(-8);
|
|
127
|
+
this.confirmCancel = { subject: rec.id, label };
|
|
128
|
+
this.onRefresh?.();
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Non-cancellable — absorb key silently
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Returns whether the current agent is considered stalled.
|
|
141
|
+
* Non-terminal agent with elapsed time exceeding MODAL_STALL_THRESHOLD_MS.
|
|
142
|
+
*/
|
|
143
|
+
isCurrentAgentStalled(): boolean {
|
|
144
|
+
if (!this.agentId) return false;
|
|
145
|
+
const rec = this.deps.agentManager.getStatus(this.agentId);
|
|
146
|
+
if (!rec || MODAL_TERMINAL_STATUSES.has(rec.status)) return false;
|
|
147
|
+
return (Date.now() - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Count of all stalled agents across the agentManager list.
|
|
152
|
+
* Non-terminal agents with elapsed time >= MODAL_STALL_THRESHOLD_MS.
|
|
153
|
+
*/
|
|
154
|
+
getStalledAgentCount(): number {
|
|
155
|
+
const now = Date.now();
|
|
156
|
+
return this.deps.agentManager.list().filter(rec => {
|
|
157
|
+
if (MODAL_TERMINAL_STATUSES.has(rec.status)) return false;
|
|
158
|
+
return (now - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
|
|
159
|
+
}).length;
|
|
160
|
+
}
|
|
161
|
+
|
|
76
162
|
async loadLog(): Promise<void> {
|
|
77
163
|
if (!this.agentId) { this.logEntries = []; this.logTotal = 0; return; }
|
|
78
164
|
try {
|
|
@@ -161,7 +247,8 @@ export function renderAgentDetailModal(
|
|
|
161
247
|
const modelStr = rec.model ? `${rec.provider ?? ''}/${rec.model}` : (rec.provider ?? '(default)');
|
|
162
248
|
sections.push({ type: 'text', content: `Template : ${rec.template}` });
|
|
163
249
|
sections.push({ type: 'text', content: `Model : ${modelStr}` });
|
|
164
|
-
|
|
250
|
+
const isStalled = !MODAL_TERMINAL_STATUSES.has(rec.status) && (now - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
|
|
251
|
+
sections.push({ type: 'text', content: `Status : ${rec.status}${isStalled ? ' [STALLED — 5+ min no activity]' : ''}` });
|
|
165
252
|
sections.push({ type: 'text', content: `Duration : ${formatDuration(elapsedMs)}` });
|
|
166
253
|
sections.push({ type: 'separator' });
|
|
167
254
|
|
|
@@ -321,12 +408,29 @@ export function renderAgentDetailModal(
|
|
|
321
408
|
}
|
|
322
409
|
}
|
|
323
410
|
|
|
411
|
+
// Cancel confirm overlay (when pending)
|
|
412
|
+
const cancellable = !MODAL_TERMINAL_STATUSES.has(rec.status);
|
|
413
|
+
if (modal.confirmCancel) {
|
|
414
|
+
sections.push({ type: 'separator' });
|
|
415
|
+
sections.push({
|
|
416
|
+
type: 'text',
|
|
417
|
+
content: `Cancel agent "${modal.confirmCancel.label}"?`,
|
|
418
|
+
style: { fg: '#f59e0b' },
|
|
419
|
+
});
|
|
420
|
+
sections.push({
|
|
421
|
+
type: 'text',
|
|
422
|
+
content: 'y / Enter confirm n / Esc cancel',
|
|
423
|
+
style: { dim: true },
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const cancelHint = cancellable ? '[c] Cancel ' : '';
|
|
324
428
|
return ModalFactory.createModal({
|
|
325
429
|
title: `Agent: ${rec.id.slice(0, AGENT_ID_DISPLAY_LENGTH)}`,
|
|
326
430
|
width: metrics.boxWidth,
|
|
327
431
|
margin: metrics.margin,
|
|
328
432
|
targetContentRows,
|
|
329
433
|
sections,
|
|
330
|
-
hints: ['[Esc] Close'],
|
|
434
|
+
hints: [cancelHint + '[Esc] Close'],
|
|
331
435
|
}, width);
|
|
332
436
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context status hint — TASK-056.
|
|
3
|
+
*
|
|
4
|
+
* Produces a short, dismissible status-line hint when the session maintenance
|
|
5
|
+
* level indicates compaction is recommended or repair is needed. The hint is
|
|
6
|
+
* passive and non-blocking: it appears in the footer status row and disappears
|
|
7
|
+
* once the pressure signal clears.
|
|
8
|
+
*
|
|
9
|
+
* Honest wording policy:
|
|
10
|
+
* - suggest-compact → describes the situation and offers /compact
|
|
11
|
+
* - needs-repair → names the failure state honestly without alarming
|
|
12
|
+
* - compacting → shows in-progress text so the user knows something is running
|
|
13
|
+
* - watch → no hint (not yet actionable)
|
|
14
|
+
* - stable/unknown → no hint
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { PanelSessionMaintenanceLevel } from '../panels/session-maintenance.ts';
|
|
18
|
+
|
|
19
|
+
export interface ContextStatusHintOptions {
|
|
20
|
+
/** Maintenance level from evaluateSessionMaintenance. */
|
|
21
|
+
readonly level: PanelSessionMaintenanceLevel;
|
|
22
|
+
/** Whether auto-compaction is active (threshold > 0 in config). */
|
|
23
|
+
readonly autoCompactEnabled: boolean;
|
|
24
|
+
/** Current usage percent 0–100. */
|
|
25
|
+
readonly usagePct: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build the passive status-line hint text for context pressure.
|
|
30
|
+
*
|
|
31
|
+
* Returns null when no hint is warranted (stable / watch / unknown).
|
|
32
|
+
* The caller renders this as a dim informational line — no prompts, no
|
|
33
|
+
* blocking, no confirmation required.
|
|
34
|
+
*/
|
|
35
|
+
export function buildContextStatusHint(options: ContextStatusHintOptions): string | null {
|
|
36
|
+
const { level, autoCompactEnabled, usagePct } = options;
|
|
37
|
+
|
|
38
|
+
switch (level) {
|
|
39
|
+
case 'needs-repair':
|
|
40
|
+
return ` Context pressure critical (${usagePct}% used) — compaction needs attention. Run /compact or /health review.`;
|
|
41
|
+
|
|
42
|
+
case 'suggest-compact':
|
|
43
|
+
if (autoCompactEnabled) {
|
|
44
|
+
return ` Context high (${usagePct}% used) — auto-compact will run before the next turn.`;
|
|
45
|
+
}
|
|
46
|
+
return ` Context high (${usagePct}% used) — run /compact to recover headroom.`;
|
|
47
|
+
|
|
48
|
+
case 'compacting':
|
|
49
|
+
return ` Compacting context — freeing headroom...`;
|
|
50
|
+
|
|
51
|
+
default:
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -539,14 +539,25 @@ function rowColorForSetting(modal: SettingsModal, rowText: string): string {
|
|
|
539
539
|
return valueColor(selected);
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
-
function footerText(modal: SettingsModal): string {
|
|
542
|
+
function footerText(modal: SettingsModal, width: number): string {
|
|
543
|
+
// Armed reset gate takes priority over all other footer states.
|
|
544
|
+
if (modal.resetCategoryConfirm !== null || modal.resetAllConfirm !== null)
|
|
545
|
+
return 'Reset armed · Enter/y confirm · Esc/n cancel';
|
|
543
546
|
if (modal.searchFocused) return 'Search · type to filter · Up/Down navigate results · Enter select · Esc exit search';
|
|
544
547
|
if (modal.editingMode) return 'Enter Confirm edit · Esc Cancel edit · text keys edit the selected field';
|
|
545
548
|
if (modal.focusPane === 'categories') return 'Focus categories · Up/Down choose · Right/Enter settings · Tab pane · / search · Esc close';
|
|
546
549
|
if (modal.currentCategory === 'subscriptions') return 'Focus settings · Up/Down provider · Left categories · Tab pane · / search · Enter review/sign out · Esc close';
|
|
547
550
|
if (modal.currentCategory === 'mcp') return 'Focus settings · Up/Down server · Left categories · Tab pane · / search · Enter edit trust · Esc close';
|
|
548
551
|
if (modal.currentCategory === 'flags') return 'Focus feature flags · Up/Down flag · Left categories · Tab pane · / search · Enter/Space toggle · Esc close';
|
|
549
|
-
|
|
552
|
+
// Default settings pane: tier the reset affordances by available width.
|
|
553
|
+
// W<80: minimal — only the most critical action survives.
|
|
554
|
+
// W<160: compact but still shows both reset affordances.
|
|
555
|
+
// W≥160: standard with all navigation tokens.
|
|
556
|
+
if (width < 80)
|
|
557
|
+
return 'R reset · Esc';
|
|
558
|
+
if (width < 160)
|
|
559
|
+
return 'Up/Down · Enter/Space edit · ⇧R reset cat · ^⇧R reset all · Esc';
|
|
560
|
+
return 'Focus settings · Up/Down setting · Left · Enter/Space edit/toggle · ⇧R reset cat · ^⇧R reset all · Esc close';
|
|
550
561
|
}
|
|
551
562
|
|
|
552
563
|
export function renderSettingsModal(
|
|
@@ -601,6 +612,6 @@ export function renderSettingsModal(
|
|
|
601
612
|
})),
|
|
602
613
|
contextRows,
|
|
603
614
|
controlRows,
|
|
604
|
-
footer: footerText(modal),
|
|
615
|
+
footer: footerText(modal, width),
|
|
605
616
|
});
|
|
606
617
|
}
|
|
@@ -29,6 +29,11 @@ export interface ShellFooterBuildOptions {
|
|
|
29
29
|
readonly composerStatus?: string;
|
|
30
30
|
readonly composerFlags?: readonly string[];
|
|
31
31
|
readonly composerPendingRisk?: 'none' | 'approval-wait' | 'shell' | 'command' | 'remote';
|
|
32
|
+
/**
|
|
33
|
+
* Passive context pressure hint from buildContextStatusHint.
|
|
34
|
+
* Rendered as a dim informational line above the prompt when non-null.
|
|
35
|
+
*/
|
|
36
|
+
readonly contextStatusHint?: string | null;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
export interface ShellFooterBuildResult {
|
|
@@ -84,5 +89,10 @@ export function buildShellFooter(
|
|
|
84
89
|
);
|
|
85
90
|
const inputBoxRows = Math.max(1, options.promptLineCount) + 2;
|
|
86
91
|
lines.splice(inputBoxRows, 0, ...processIndicator);
|
|
92
|
+
// Passive context status hint — rendered as a dim informational line before the prompt.
|
|
93
|
+
if (options.contextStatusHint) {
|
|
94
|
+
const hintLine = UIFactory.stringToLine(options.contextStatusHint, options.width, { fg: '#64748b' });
|
|
95
|
+
lines.unshift(hintLine);
|
|
96
|
+
}
|
|
87
97
|
return { lines, height: lines.length };
|
|
88
98
|
}
|
|
@@ -160,6 +160,7 @@ export function createBootstrapCommandActions(
|
|
|
160
160
|
| 'openMcpWorkspace'
|
|
161
161
|
| 'openSecurityPanel'
|
|
162
162
|
| 'openKnowledgePanel'
|
|
163
|
+
| 'openMemoryPanel'
|
|
163
164
|
| 'openRemotePanel'
|
|
164
165
|
| 'openSubscriptionPanel'
|
|
165
166
|
| 'openLocalAuthMaskedEntry'
|
|
@@ -273,6 +274,9 @@ export function createBootstrapCommandActions(
|
|
|
273
274
|
openKnowledgePanel: () => {
|
|
274
275
|
showPanel('knowledge');
|
|
275
276
|
},
|
|
277
|
+
openMemoryPanel: () => {
|
|
278
|
+
showPanel('memory');
|
|
279
|
+
},
|
|
276
280
|
openRemotePanel: () => {
|
|
277
281
|
showPanel('remote');
|
|
278
282
|
},
|
|
@@ -426,6 +426,30 @@ export async function initializeBootstrapCore(
|
|
|
426
426
|
}
|
|
427
427
|
};
|
|
428
428
|
|
|
429
|
+
// Startup TLS banner — emitted via wrfcBuffer.push() because the
|
|
430
|
+
// SystemMessageRouter is not attached yet at this point in bootstrap. The
|
|
431
|
+
// smart-ref setter on systemMessageRouterRef auto-flushes the buffer when
|
|
432
|
+
// the router attaches, so the message will appear in the WRFC panel on startup.
|
|
433
|
+
{
|
|
434
|
+
const cpEnabled = Boolean(configManager.get('controlPlane.enabled'));
|
|
435
|
+
const cpHostMode = String(configManager.get('controlPlane.hostMode') ?? 'local');
|
|
436
|
+
const cpTlsMode = String(configManager.get('controlPlane.tls.mode') ?? 'off');
|
|
437
|
+
const hlEnabled = Boolean(configManager.get('danger.httpListener'));
|
|
438
|
+
const hlHostMode = String(configManager.get('httpListener.hostMode') ?? 'local');
|
|
439
|
+
const hlTlsMode = String(configManager.get('httpListener.tls.mode') ?? 'off');
|
|
440
|
+
const cpNetworkPlaintext = cpEnabled && cpHostMode !== 'local' && cpTlsMode === 'off';
|
|
441
|
+
const hlNetworkPlaintext = hlEnabled && hlHostMode !== 'local' && hlTlsMode === 'off';
|
|
442
|
+
if (cpNetworkPlaintext || hlNetworkPlaintext) {
|
|
443
|
+
const affected: string[] = [];
|
|
444
|
+
if (cpNetworkPlaintext) affected.push('control plane');
|
|
445
|
+
if (hlNetworkPlaintext) affected.push('HTTP listener');
|
|
446
|
+
wrfcBuffer.push(
|
|
447
|
+
`[SECURITY] TLS is off for the ${affected.join(' and ')} but it is network-reachable. All traffic (credentials, tokens, conversation content) travels in plaintext. Enable TLS (controlPlane.tls.mode / httpListener.tls.mode) or restrict to loopback before exposing to untrusted networks.`,
|
|
448
|
+
'high',
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
429
453
|
runtimeUnsubs.push(
|
|
430
454
|
runtimeBus.on<Extract<import('@/runtime/index.ts').WorkflowEvent, { type: 'WORKFLOW_CONSTRAINTS_ENUMERATED' }>>(
|
|
431
455
|
'WORKFLOW_CONSTRAINTS_ENUMERATED',
|
|
@@ -40,6 +40,11 @@ export interface BootstrapShellState {
|
|
|
40
40
|
readonly lastGitInfoRef: { value: GitHeaderInfo | undefined };
|
|
41
41
|
readonly inputHistory: InputHistory;
|
|
42
42
|
readonly systemMessageRouter: SystemMessageRouter;
|
|
43
|
+
/**
|
|
44
|
+
* Wire the agent detail modal opener after InputHandler is constructed.
|
|
45
|
+
* Call with `(id) => input.agentDetailModal.open(id)` from main.ts.
|
|
46
|
+
*/
|
|
47
|
+
readonly setOpenAgentDetail: (fn: (agentId: string) => void) => void;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
export interface BootstrapShellOptions {
|
|
@@ -103,6 +108,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
103
108
|
providerRegistry: services.providerRegistry,
|
|
104
109
|
});
|
|
105
110
|
|
|
111
|
+
const openAgentDetailRef: { fn: (agentId: string) => void } = { fn: (_agentId: string) => {} };
|
|
112
|
+
|
|
106
113
|
let commandContextRef: CommandContext | null = null;
|
|
107
114
|
registerBuiltinPanels(services.panelManager, {
|
|
108
115
|
configManager,
|
|
@@ -143,6 +150,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
143
150
|
hookActivityTracker: services.hookActivityTracker,
|
|
144
151
|
hookWorkbench: services.hookWorkbench,
|
|
145
152
|
mcpRegistry: services.mcpRegistry,
|
|
153
|
+
openAgentDetail: (agentId: string) => openAgentDetailRef.fn(agentId),
|
|
146
154
|
daemonHomeDir: join(services.homeDirectory, '.goodvibes', 'daemon'),
|
|
147
155
|
});
|
|
148
156
|
services.panelManager.prewarmRegistered();
|
|
@@ -278,5 +286,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
278
286
|
lastGitInfoRef,
|
|
279
287
|
inputHistory,
|
|
280
288
|
systemMessageRouter,
|
|
289
|
+
setOpenAgentDetail: (fn: (agentId: string) => void) => {
|
|
290
|
+
openAgentDetailRef.fn = fn;
|
|
291
|
+
},
|
|
281
292
|
};
|
|
282
293
|
}
|
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -118,6 +118,11 @@ export type BootstrapContext = RuntimeContext & {
|
|
|
118
118
|
* stay out of the main conversation and go to the SystemMessagesPanel instead.
|
|
119
119
|
*/
|
|
120
120
|
systemMessageRouter: SystemMessageRouter;
|
|
121
|
+
/**
|
|
122
|
+
* Wire the agent detail modal opener after InputHandler is constructed in main.ts.
|
|
123
|
+
* Call with `(id) => input.agentDetailModal.open(id)` once the InputHandler is ready.
|
|
124
|
+
*/
|
|
125
|
+
setOpenAgentDetail: (fn: (agentId: string) => void) => void;
|
|
121
126
|
};
|
|
122
127
|
|
|
123
128
|
// ── Bootstrap function ────────────────────────────────────────────────────
|
|
@@ -292,6 +297,7 @@ export async function bootstrapRuntime(
|
|
|
292
297
|
const gitStatusProvider = shell.gitStatusProvider;
|
|
293
298
|
const inputHistory = shell.inputHistory;
|
|
294
299
|
const lastGitInfoRef = shell.lastGitInfoRef;
|
|
300
|
+
const setOpenAgentDetail = shell.setOpenAgentDetail;
|
|
295
301
|
const pluginCommandRegistry = {
|
|
296
302
|
register(command: {
|
|
297
303
|
readonly name: string;
|
|
@@ -622,6 +628,7 @@ export async function bootstrapRuntime(
|
|
|
622
628
|
_getConfiguredProviderIds: () => services.providerRegistry.getConfiguredProviderIds(),
|
|
623
629
|
commandRegistry,
|
|
624
630
|
systemMessageRouter,
|
|
631
|
+
setOpenAgentDetail,
|
|
625
632
|
shutdown: async (sessionData) => {
|
|
626
633
|
// Clear bootstrap-owned subscriptions
|
|
627
634
|
bootstrapUnsubs.forEach(fn => fn());
|
package/src/runtime/services.ts
CHANGED
|
@@ -78,6 +78,7 @@ import { ComponentHealthMonitor } from '@/runtime/index.ts';
|
|
|
78
78
|
import { WorktreeRegistry } from '@/runtime/index.ts';
|
|
79
79
|
import { SandboxSessionRegistry } from '@/runtime/index.ts';
|
|
80
80
|
import { createShellPathService, type ShellPathService } from '@/runtime/index.ts';
|
|
81
|
+
import { isFeatureFlagEnabled } from './surface-feature-flags.ts';
|
|
81
82
|
import type { FeatureFlagManager } from '@/runtime/index.ts';
|
|
82
83
|
import { createFeatureFlagManager } from '@/runtime/index.ts';
|
|
83
84
|
import { PolicyRuntimeState } from '@/runtime/index.ts';
|
|
@@ -530,7 +531,11 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
530
531
|
const worktreeRegistry = new WorktreeRegistry(workingDirectory);
|
|
531
532
|
const webhookNotifier = new WebhookNotifier();
|
|
532
533
|
const replayEngine = new DeterministicReplayEngine(workingDirectory);
|
|
533
|
-
const providerOptimizer = new ProviderOptimizer(
|
|
534
|
+
const providerOptimizer = new ProviderOptimizer(
|
|
535
|
+
providerRegistry,
|
|
536
|
+
providerCapabilityRegistry,
|
|
537
|
+
isFeatureFlagEnabled(configManager, 'provider-optimizer'),
|
|
538
|
+
);
|
|
534
539
|
const sessionMemoryStore = new SessionMemoryStore();
|
|
535
540
|
const sessionLineageTracker = new SessionLineageTracker();
|
|
536
541
|
const sessionChangeTracker = new SessionChangeTracker();
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.
|
|
9
|
+
let _version = '0.22.0';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|