@pellux/goodvibes-tui 0.20.3 → 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 +50 -0
- package/README.md +23 -2
- package/docs/foundation-artifacts/operator-contract.json +78 -1
- package/package.json +4 -2
- package/src/audio/spoken-turn-controller.ts +31 -1
- package/src/audio/spoken-turn-wiring.ts +26 -4
- package/src/cli/bundle-command.ts +1 -1
- package/src/cli/completions/generate.ts +658 -0
- package/src/cli/config-overrides.ts +68 -0
- package/src/cli/entrypoint.ts +6 -0
- package/src/cli/help.ts +4 -2
- package/src/cli/management-commands.ts +1 -1
- package/src/cli/management.ts +1 -8
- package/src/cli/parser.ts +31 -18
- package/src/cli/service-command.ts +1 -1
- package/src/cli/surface-command.ts +1 -1
- package/src/cli/tui-startup.ts +72 -10
- package/src/cli/types.ts +14 -3
- package/src/cli-flags.ts +1 -0
- package/src/config/atomic-write.ts +70 -0
- package/src/config/goodvibes-home-audit.ts +2 -0
- package/src/config/read-versioned.ts +115 -0
- package/src/core/context-auto-compact.ts +77 -0
- package/src/core/conversation-rendering.ts +49 -15
- package/src/core/conversation.ts +101 -16
- package/src/core/format-user-error.ts +192 -0
- package/src/core/stream-event-wiring.ts +144 -0
- package/src/core/stream-stall-watchdog.ts +103 -0
- package/src/core/system-message-router.ts +5 -1
- package/src/core/turn-event-wiring.ts +124 -0
- package/src/daemon/cli.ts +5 -0
- package/src/export/cost-utils.ts +71 -0
- package/src/export/gist-uploader.ts +136 -0
- package/src/input/command-registry.ts +32 -1
- package/src/input/commands/control-room-runtime.ts +10 -10
- package/src/input/commands/experience-runtime.ts +5 -4
- package/src/input/commands/knowledge.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +27 -5
- package/src/input/commands/local-setup.ts +4 -6
- package/src/input/commands/memory-product-runtime.ts +8 -6
- package/src/input/commands/operator-panel-runtime.ts +1 -1
- package/src/input/commands/operator-runtime.ts +3 -10
- package/src/input/commands/{integration-runtime.ts → plugin-runtime.ts} +1 -1
- package/src/input/commands/provider.ts +57 -3
- package/src/input/commands/recall-review.ts +26 -2
- package/src/input/commands/services-runtime.ts +2 -2
- package/src/input/commands/session-workflow.ts +8 -16
- package/src/input/commands/session.ts +70 -20
- package/src/input/commands/share-runtime.ts +99 -12
- package/src/input/commands/tts-runtime.ts +30 -4
- package/src/input/commands.ts +2 -4
- package/src/input/delete-key-policy.ts +46 -0
- package/src/input/feed-context-factory.ts +2 -0
- package/src/input/handler-feed.ts +3 -0
- package/src/input/handler-interactions.ts +2 -15
- package/src/input/handler-modal-routes.ts +128 -12
- package/src/input/handler-modal-token-routes.ts +22 -5
- package/src/input/handler-onboarding-cloudflare.ts +1 -1
- package/src/input/handler-onboarding.ts +73 -69
- package/src/input/handler-types.ts +163 -0
- package/src/input/handler.ts +6 -2
- package/src/input/input-history.ts +76 -6
- package/src/input/model-picker-filter.ts +265 -0
- package/src/input/model-picker-items.ts +208 -0
- package/src/input/model-picker.ts +92 -325
- package/src/input/onboarding/handler-onboarding-routes.ts +7 -2
- package/src/input/onboarding/onboarding-verification-helpers.ts +76 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +14 -4
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +16 -2
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +8 -8
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +2 -29
- package/src/input/onboarding/onboarding-wizard-rules.ts +28 -28
- package/src/input/onboarding/onboarding-wizard-state.ts +20 -20
- package/src/input/onboarding/onboarding-wizard-steps.ts +24 -25
- package/src/input/onboarding/onboarding-wizard-types.ts +145 -3
- package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
- package/src/input/onboarding/onboarding-wizard.ts +3 -3
- package/src/input/settings-modal-behavior.ts +5 -0
- package/src/input/settings-modal-data.ts +378 -0
- package/src/input/settings-modal-mutations.ts +157 -0
- package/src/input/settings-modal-reset.ts +154 -0
- package/src/input/settings-modal.ts +236 -232
- package/src/main.ts +93 -85
- package/src/panels/agent-inspector-panel.ts +120 -18
- package/src/panels/agent-inspector-shared.ts +29 -0
- package/src/panels/builtin/agent.ts +4 -1
- package/src/panels/builtin/development.ts +5 -1
- 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/confirm-state.ts +27 -12
- package/src/panels/cost-tracker-panel.ts +23 -67
- package/src/panels/eval-panel.ts +10 -9
- package/src/panels/index.ts +1 -1
- package/src/panels/knowledge-graph-panel.ts +84 -0
- package/src/panels/local-auth-panel.ts +124 -4
- package/src/panels/memory-panel.ts +370 -40
- package/src/panels/project-planning-panel.ts +42 -4
- package/src/panels/search-focus.ts +11 -5
- package/src/panels/session-maintenance.ts +66 -15
- package/src/panels/subscription-panel.ts +33 -25
- package/src/panels/types.ts +28 -1
- package/src/panels/wrfc-panel.ts +224 -41
- package/src/renderer/agent-detail-modal.ts +118 -13
- package/src/renderer/code-block.ts +10 -2
- package/src/renderer/compositor.ts +18 -4
- package/src/renderer/context-inspector.ts +1 -5
- package/src/renderer/context-status-hint.ts +54 -0
- package/src/renderer/diff.ts +94 -21
- package/src/renderer/markdown.ts +29 -13
- package/src/renderer/settings-modal-helpers.ts +1 -1
- package/src/renderer/settings-modal.ts +90 -10
- package/src/renderer/shell-surface.ts +10 -0
- package/src/renderer/syntax-highlighter.ts +10 -3
- package/src/renderer/term-caps.ts +318 -0
- package/src/renderer/theme.ts +158 -0
- package/src/renderer/tool-call.ts +12 -2
- package/src/renderer/ui-factory.ts +50 -6
- package/src/runtime/bootstrap-command-context.ts +1 -0
- package/src/runtime/bootstrap-command-parts.ts +18 -0
- package/src/runtime/bootstrap-core.ts +145 -13
- package/src/runtime/bootstrap-shell.ts +11 -0
- package/src/runtime/bootstrap.ts +9 -0
- package/src/runtime/onboarding/apply.ts +4 -6
- package/src/runtime/onboarding/index.ts +1 -0
- package/src/runtime/onboarding/markers.ts +42 -49
- package/src/runtime/onboarding/progress.ts +148 -0
- package/src/runtime/onboarding/state.ts +133 -55
- package/src/runtime/onboarding/types.ts +20 -0
- package/src/runtime/services.ts +27 -1
- package/src/runtime/wrfc-persistence.ts +237 -0
- package/src/shell/blocking-input.ts +20 -5
- package/src/tools/wrfc-agent-guard.ts +64 -3
- package/src/utils/format-elapsed.ts +30 -0
- package/src/utils/terminal-width.ts +45 -0
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +4 -6
- package/src/panels/knowledge-panel.ts +0 -345
- package/src/planning/project-planning-coordinator.ts +0 -543
package/src/main.ts
CHANGED
|
@@ -33,12 +33,10 @@ import { renderPanelTabBar } from './renderer/panel-tab-bar.ts';
|
|
|
33
33
|
import { bootstrapRuntime } from './runtime/bootstrap.ts';
|
|
34
34
|
import type { BootstrapContext } from './runtime/bootstrap.ts';
|
|
35
35
|
import type { HITLMode } from '@pellux/goodvibes-sdk/platform/state';
|
|
36
|
-
import type { HookPhase, HookCategory, HookEventPath } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
37
36
|
import {
|
|
38
37
|
checkRecoveryFile,
|
|
39
38
|
deleteRecoveryFile,
|
|
40
39
|
loadRecoveryConversation,
|
|
41
|
-
persistConversation,
|
|
42
40
|
writeRecoveryFile,
|
|
43
41
|
} from '@/runtime/index.ts';
|
|
44
42
|
import { handleBlockingShellInput, type PendingPermissionState } from './shell/blocking-input.ts';
|
|
@@ -48,12 +46,18 @@ import { buildPersistedSessionContext, formatReturnContextForDisplay, getReturnC
|
|
|
48
46
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
49
47
|
import { prepareShellCliRuntime } from './cli/entrypoint.ts';
|
|
50
48
|
import { applyInitialTuiCliState } from './cli/tui-startup.ts';
|
|
49
|
+
import { applyRuntimeConfigDefault, applyRuntimeConfigValue } from './cli/config-overrides.ts';
|
|
50
|
+
import { renderToolCallBlock } from './renderer/tool-call.ts';
|
|
51
51
|
import { wireSpokenTurnRuntime } from './audio/spoken-turn-wiring.ts';
|
|
52
52
|
import { attachSpokenTurnModelRouting, createSpokenTurnInputOptions } from './audio/spoken-turn-model-routing.ts';
|
|
53
53
|
import { allowTerminalWrite, installTuiTerminalOutputGuard } from './runtime/terminal-output-guard.ts';
|
|
54
|
-
import { ProjectPlanningCoordinator } from './planning/project-planning-coordinator.ts';
|
|
55
54
|
import { buildCommandArgsHint } from './input/command-args-hint.ts';
|
|
56
55
|
import { summarizeRunningAgents } from './renderer/process-summary.ts';
|
|
56
|
+
import { formatUserFacingErrorLine } from './core/format-user-error.ts';
|
|
57
|
+
import { wireStreamEventMetrics, type StreamMetrics } from './core/stream-event-wiring.ts';
|
|
58
|
+
import { wireTurnEventHandlers } from './core/turn-event-wiring.ts';
|
|
59
|
+
import { buildContextStatusHint } from './renderer/context-status-hint.ts';
|
|
60
|
+
import { evaluateSessionMaintenance } from './panels/session-maintenance.ts';
|
|
57
61
|
|
|
58
62
|
const ALT_SCREEN_ENTER = '\x1b[?1049h';
|
|
59
63
|
const ALT_SCREEN_EXIT = '\x1b[?1049l';
|
|
@@ -101,6 +105,7 @@ async function main() {
|
|
|
101
105
|
permissionPromptRef,
|
|
102
106
|
_writeLastSessionPointer: writeLastSessionPointer,
|
|
103
107
|
systemMessageRouter,
|
|
108
|
+
setOpenAgentDetail,
|
|
104
109
|
} = ctx;
|
|
105
110
|
const workingDir = ctx.services.workingDirectory;
|
|
106
111
|
const homeDirectory = ctx.services.homeDirectory;
|
|
@@ -127,6 +132,15 @@ async function main() {
|
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
// TUI default: show token speed ON. The SDK schema default is false;
|
|
136
|
+
// applyRuntimeConfigDefault reads both the global settings file and the project
|
|
137
|
+
// settings file from disk before deciding whether to apply the default. If the
|
|
138
|
+
// user has explicitly set this key to false in EITHER their global or project
|
|
139
|
+
// persisted config, their value is respected and the default is NOT applied.
|
|
140
|
+
// Only when the key is absent from both files (e.g. a new install) does the
|
|
141
|
+
// TUI default of true take effect in-memory — no disk write occurs either way.
|
|
142
|
+
applyRuntimeConfigDefault(configManager, 'display.showTokenSpeed', true);
|
|
143
|
+
|
|
130
144
|
const panelManager = ctx.services.panelManager;
|
|
131
145
|
const buildSessionContinuityHints = () => {
|
|
132
146
|
const sessionSnapshot = uiServices.readModels.session.getSnapshot();
|
|
@@ -154,12 +168,18 @@ async function main() {
|
|
|
154
168
|
render();
|
|
155
169
|
});
|
|
156
170
|
|
|
157
|
-
let streamStartTime = 0;
|
|
158
|
-
let streamDeltaCount = 0;
|
|
159
|
-
let streamTokenSpeed = 0;
|
|
160
|
-
|
|
161
171
|
let scrollTop = 0;
|
|
162
172
|
let scrollLocked = true;
|
|
173
|
+
// Stream and tool-timer state; mutated by wireStreamEventMetrics handlers, read during render.
|
|
174
|
+
const streamMetrics: StreamMetrics = {
|
|
175
|
+
startTime: 0,
|
|
176
|
+
deltaCount: 0,
|
|
177
|
+
tokenSpeed: 0,
|
|
178
|
+
ttftMs: undefined,
|
|
179
|
+
ttftRecorded: false,
|
|
180
|
+
activeToolStartedAtMs: undefined,
|
|
181
|
+
activeToolName: undefined,
|
|
182
|
+
};
|
|
163
183
|
|
|
164
184
|
const getPromptContentWidth = () => {
|
|
165
185
|
const w = stdout.columns || 80;
|
|
@@ -213,7 +233,8 @@ async function main() {
|
|
|
213
233
|
`[Critical] Multiple errors detected (${_unhandledRejectionCount} in 10s). If the issue persists, please restart. Latest: ${msg}`
|
|
214
234
|
);
|
|
215
235
|
} else {
|
|
216
|
-
|
|
236
|
+
const formatted = formatUserFacingErrorLine(reason);
|
|
237
|
+
systemMessageRouter.high(`[Error] ${formatted}`);
|
|
217
238
|
logger.error('unhandledRejection', { error: String(reason) });
|
|
218
239
|
}
|
|
219
240
|
render();
|
|
@@ -260,18 +281,6 @@ async function main() {
|
|
|
260
281
|
configManager,
|
|
261
282
|
notify: (message) => { systemMessageRouter.high(message); render(); },
|
|
262
283
|
}));
|
|
263
|
-
const projectPlanningCoordinator = new ProjectPlanningCoordinator({
|
|
264
|
-
service: ctx.services.projectPlanningService,
|
|
265
|
-
projectId: ctx.services.projectPlanningProjectId,
|
|
266
|
-
workingDirectory: workingDir,
|
|
267
|
-
notify: (message) => { systemMessageRouter.high(message); render(); },
|
|
268
|
-
openPanel: () => {
|
|
269
|
-
panelManager.open('project-planning');
|
|
270
|
-
panelManager.show();
|
|
271
|
-
render();
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
|
|
275
284
|
const submitInput = (text: string, content?: ContentPart[], options: { readonly spokenOutput?: boolean } = {}) => {
|
|
276
285
|
input.clearModalStack();
|
|
277
286
|
scrollLocked = true; // Re-lock on any user input
|
|
@@ -307,32 +316,6 @@ async function main() {
|
|
|
307
316
|
if (processedText || content) {
|
|
308
317
|
void (async () => {
|
|
309
318
|
let inputOptions = options.spokenOutput ? createSpokenTurnInputOptions() : undefined;
|
|
310
|
-
if (!options.spokenOutput && processedText) {
|
|
311
|
-
try {
|
|
312
|
-
const planning = await projectPlanningCoordinator.prepareTurn(processedText);
|
|
313
|
-
if (planning) {
|
|
314
|
-
if (planning.handledLocally) {
|
|
315
|
-
systemMessageRouter.high(planning.statusMessage);
|
|
316
|
-
render();
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
conversation.addSystemMessage(planning.systemMessage);
|
|
320
|
-
inputOptions = {
|
|
321
|
-
origin: {
|
|
322
|
-
source: 'project-planning',
|
|
323
|
-
surface: 'tui',
|
|
324
|
-
metadata: {
|
|
325
|
-
projectId: ctx.services.projectPlanningProjectId,
|
|
326
|
-
knowledgeSpaceId: planning.state.knowledgeSpaceId,
|
|
327
|
-
readiness: planning.evaluation.readiness,
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
} catch (err) {
|
|
333
|
-
systemMessageRouter.high(`[Planning] ${summarizeError(err)}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
319
|
if (options.spokenOutput && processedText) {
|
|
337
320
|
spokenTurns.submitNextTurn(processedText);
|
|
338
321
|
}
|
|
@@ -458,6 +441,7 @@ async function main() {
|
|
|
458
441
|
input.filePicker.setOnUpdate(() => render());
|
|
459
442
|
input.agentDetailModal.setOnRefresh(() => render());
|
|
460
443
|
input.processModal.setOnRefresh(() => render());
|
|
444
|
+
setOpenAgentDetail((id) => input.agentDetailModal.open(id));
|
|
461
445
|
|
|
462
446
|
// Model picker callback is handled in bootstrap.ts — do not duplicate here.
|
|
463
447
|
input.setHistory(inputHistory);
|
|
@@ -498,6 +482,17 @@ async function main() {
|
|
|
498
482
|
hasAttachments: input.getImageAttachments().size > 0,
|
|
499
483
|
turnState: sessionSnapshot.turnState,
|
|
500
484
|
});
|
|
485
|
+
const maintenanceStatus = evaluateSessionMaintenance({
|
|
486
|
+
configManager,
|
|
487
|
+
currentTokens: orchestrator.lastInputTokens,
|
|
488
|
+
contextWindow: currentModel.contextWindow,
|
|
489
|
+
sessionMemoryCount: ctx.services.sessionMemoryStore.list().length,
|
|
490
|
+
});
|
|
491
|
+
const contextStatusHint = buildContextStatusHint({
|
|
492
|
+
level: maintenanceStatus.level,
|
|
493
|
+
autoCompactEnabled: maintenanceStatus.autoCompactEnabled,
|
|
494
|
+
usagePct: maintenanceStatus.usagePct,
|
|
495
|
+
});
|
|
501
496
|
const footerLines = buildShellFooter({
|
|
502
497
|
width,
|
|
503
498
|
promptText: promptInfo.visibleLines.join('\n'),
|
|
@@ -515,7 +510,10 @@ async function main() {
|
|
|
515
510
|
workingDir,
|
|
516
511
|
provider: runtime.provider,
|
|
517
512
|
contextWindow: currentModel.contextWindow,
|
|
518
|
-
|
|
513
|
+
contextStatusHint,
|
|
514
|
+
// behavior.autoCompactThreshold is stored as a percent integer (e.g. 80);
|
|
515
|
+
// the meter expects a fraction [0..1]. Clamp to [0,1] to guard nonsense values.
|
|
516
|
+
compactThreshold: Math.min(1, Math.max(0, (configManager.get('behavior.autoCompactThreshold') as number) / 100)),
|
|
519
517
|
dangerMode: (() => {
|
|
520
518
|
if (configManager.get('behavior.autoApprove')) return true;
|
|
521
519
|
const permMode = configManager.get('permissions.mode');
|
|
@@ -598,16 +596,25 @@ async function main() {
|
|
|
598
596
|
const showSpeed = configManager.get('display.showTokenSpeed') as boolean;
|
|
599
597
|
const showPreview = configManager.get('display.showToolPreview') as boolean;
|
|
600
598
|
const partialToolPreview = showPreview ? sessionSnapshot.streamToolPreview : undefined;
|
|
599
|
+
// Elapsed from turn start (stream or tool execution), used for the thinking indicator timer.
|
|
600
|
+
const turnElapsedMs = streamMetrics.startTime > 0 ? Date.now() - streamMetrics.startTime : undefined;
|
|
601
601
|
const thinking = UIFactory.createThinkingFragment(
|
|
602
602
|
conversationWidth,
|
|
603
603
|
orchestrator.getSpinner(),
|
|
604
604
|
orchestrator.thinkingFrame,
|
|
605
|
-
showSpeed ?
|
|
605
|
+
showSpeed ? streamMetrics.tokenSpeed : undefined,
|
|
606
606
|
showPreview ? partialToolPreview : undefined,
|
|
607
607
|
orchestrator.streamingInputTokens > 0 ? orchestrator.streamingInputTokens : undefined,
|
|
608
608
|
orchestrator.streamingOutputTokens > 0 ? orchestrator.streamingOutputTokens : undefined,
|
|
609
|
+
turnElapsedMs,
|
|
610
|
+
streamMetrics.ttftMs,
|
|
609
611
|
);
|
|
610
612
|
viewport.push(...thinking);
|
|
613
|
+
// Live tool timer: render the currently executing tool row with ticking elapsed.
|
|
614
|
+
if (streamMetrics.activeToolName !== undefined && streamMetrics.activeToolStartedAtMs !== undefined) {
|
|
615
|
+
const liveToolCall = { id: 'live', name: streamMetrics.activeToolName, arguments: {} };
|
|
616
|
+
viewport.push(...renderToolCallBlock(liveToolCall, 'executing', undefined, conversationWidth, undefined, undefined, undefined, streamMetrics.activeToolStartedAtMs));
|
|
617
|
+
}
|
|
611
618
|
}
|
|
612
619
|
|
|
613
620
|
if (pendingPermission) {
|
|
@@ -682,44 +689,36 @@ async function main() {
|
|
|
682
689
|
render,
|
|
683
690
|
});
|
|
684
691
|
|
|
685
|
-
// ---
|
|
686
|
-
const refreshGit
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}));
|
|
705
|
-
unsubs.push(uiServices.events.tools.on('TOOL_SUCCEEDED', () => {
|
|
706
|
-
refreshGit();
|
|
707
|
-
}));
|
|
708
|
-
unsubs.push(uiServices.events.tools.on('TOOL_FAILED', () => {
|
|
709
|
-
refreshGit();
|
|
710
|
-
}));
|
|
692
|
+
// --- Turn-completed / git-refresh event wiring ---
|
|
693
|
+
const { refreshGit, unsubs: turnUnsubs } = wireTurnEventHandlers({
|
|
694
|
+
events: uiServices.events,
|
|
695
|
+
conversation,
|
|
696
|
+
runtime,
|
|
697
|
+
orchestrator,
|
|
698
|
+
configManager,
|
|
699
|
+
providerRegistry,
|
|
700
|
+
systemMessageRouter,
|
|
701
|
+
hookDispatcher,
|
|
702
|
+
workingDir,
|
|
703
|
+
homeDirectory,
|
|
704
|
+
sessionManager: ctx.services.sessionManager,
|
|
705
|
+
gitStatusProvider,
|
|
706
|
+
lastGitInfoRef,
|
|
707
|
+
buildSessionContinuityHints,
|
|
708
|
+
render,
|
|
709
|
+
});
|
|
710
|
+
unsubs.push(...turnUnsubs);
|
|
711
711
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}));
|
|
712
|
+
// --- Stream metrics + tool-timer event wiring ---
|
|
713
|
+
const streamUnsubs = wireStreamEventMetrics({
|
|
714
|
+
events: uiServices.events,
|
|
715
|
+
orchestrator,
|
|
716
|
+
providerRegistry,
|
|
717
|
+
systemMessageRouter,
|
|
718
|
+
render,
|
|
719
|
+
metrics: streamMetrics,
|
|
720
|
+
});
|
|
721
|
+
unsubs.push(...streamUnsubs);
|
|
723
722
|
|
|
724
723
|
// --- Terminal setup ---
|
|
725
724
|
stdin.setRawMode(true);
|
|
@@ -747,6 +746,15 @@ async function main() {
|
|
|
747
746
|
render,
|
|
748
747
|
loadRecoveryConversation: () => loadRecoveryConversation({ homeDirectory }),
|
|
749
748
|
deleteRecoveryFile: () => deleteRecoveryFile({ homeDirectory }),
|
|
749
|
+
reopenPanels: (snapshot) => {
|
|
750
|
+
const panels = snapshot.returnContext?.openPanels;
|
|
751
|
+
if (!panels || panels.length === 0) return;
|
|
752
|
+
for (const panelId of panels.slice(0, 4)) {
|
|
753
|
+
try { panelManager.open(panelId); } catch { /* unknown panel id — skip */ }
|
|
754
|
+
}
|
|
755
|
+
panelManager.show();
|
|
756
|
+
render();
|
|
757
|
+
},
|
|
750
758
|
});
|
|
751
759
|
pendingPermission = blocking.pendingPermission;
|
|
752
760
|
recoveryPending = blocking.recoveryPending;
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
resolveScrollablePanelSection,
|
|
22
22
|
DEFAULT_PANEL_PALETTE,
|
|
23
23
|
} from './polish.ts';
|
|
24
|
+
import {
|
|
25
|
+
type ConfirmState,
|
|
26
|
+
handleConfirmInput,
|
|
27
|
+
} from './confirm-state.ts';
|
|
24
28
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
25
29
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
26
30
|
import {
|
|
@@ -32,6 +36,8 @@ import {
|
|
|
32
36
|
formatAgentDuration as formatMs,
|
|
33
37
|
formatAgentTime as shortTime,
|
|
34
38
|
jsonlToTimeline,
|
|
39
|
+
AGENT_TERMINAL_STATUSES,
|
|
40
|
+
AGENT_STALL_THRESHOLD_MS,
|
|
35
41
|
} from './agent-inspector-shared.ts';
|
|
36
42
|
|
|
37
43
|
// ---------------------------------------------------------------------------
|
|
@@ -77,10 +83,14 @@ const COLOR = {
|
|
|
77
83
|
// AgentInspectorPanel
|
|
78
84
|
// ---------------------------------------------------------------------------
|
|
79
85
|
|
|
86
|
+
// AGENT_TERMINAL_STATUSES and AGENT_STALL_THRESHOLD_MS imported from agent-inspector-shared.ts
|
|
87
|
+
|
|
80
88
|
export interface AgentInspectorPanelDeps {
|
|
81
|
-
readonly agentManager: Pick<AgentManager, 'list' | 'getStatus'>;
|
|
89
|
+
readonly agentManager: Pick<AgentManager, 'list' | 'getStatus' | 'cancel'>;
|
|
82
90
|
readonly agentMessageBus: Pick<AgentMessageBus, 'getMessages'>;
|
|
83
91
|
readonly workingDirectory: string;
|
|
92
|
+
/** Cancel the agent by id. Uses the same orphan-free path as WRFC. Returns true if cancelled. */
|
|
93
|
+
readonly cancelAgent: (agentId: string) => boolean;
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
export class AgentInspectorPanel extends BasePanel {
|
|
@@ -102,6 +112,9 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
102
112
|
// Row cache — cleared on markDirty(), computed once per render cycle
|
|
103
113
|
private _cachedRows: DisplayRow[] | null = null;
|
|
104
114
|
|
|
115
|
+
/** Pending cancel confirmation — subject is the agent id to cancel. */
|
|
116
|
+
private confirmCancel: ConfirmState<string> | null = null;
|
|
117
|
+
|
|
105
118
|
constructor(private readonly deps: AgentInspectorPanelDeps) {
|
|
106
119
|
super('inspector', 'Inspector', 'I', 'agent');
|
|
107
120
|
}
|
|
@@ -157,13 +170,36 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
157
170
|
// -------------------------------------------------------------------------
|
|
158
171
|
|
|
159
172
|
handleInput(key: string): boolean {
|
|
173
|
+
// Confirm-cancel flow takes priority — same contract as WRFC panel.
|
|
174
|
+
if (this.confirmCancel) {
|
|
175
|
+
const result = handleConfirmInput(this.confirmCancel, key);
|
|
176
|
+
if (result === 'confirmed') {
|
|
177
|
+
const rec = this.selectedAgentId
|
|
178
|
+
? this.deps.agentManager.getStatus(this.selectedAgentId)
|
|
179
|
+
: null;
|
|
180
|
+
if (rec && !AGENT_TERMINAL_STATUSES.has(rec.status)) {
|
|
181
|
+
this.deps.cancelAgent(rec.id);
|
|
182
|
+
}
|
|
183
|
+
this.confirmCancel = null;
|
|
184
|
+
this.markDirty();
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
if (result === 'cancelled') {
|
|
188
|
+
this.confirmCancel = null;
|
|
189
|
+
this.markDirty();
|
|
190
|
+
}
|
|
191
|
+
// absorbed: confirm stays pending
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
160
195
|
switch (key) {
|
|
161
|
-
case 'up': this._moveCursor(-1);
|
|
162
|
-
case 'down': this._moveCursor(1);
|
|
163
|
-
case 'pageup': this._scroll(-10);
|
|
164
|
-
case 'pagedown': this._scroll(10);
|
|
165
|
-
case 'return': this._toggleExpand();
|
|
166
|
-
case 'tab': this._nextAgent();
|
|
196
|
+
case 'up': this._moveCursor(-1); return true;
|
|
197
|
+
case 'down': this._moveCursor(1); return true;
|
|
198
|
+
case 'pageup': this._scroll(-10); return true;
|
|
199
|
+
case 'pagedown': this._scroll(10); return true;
|
|
200
|
+
case 'return': this._toggleExpand(); return true;
|
|
201
|
+
case 'tab': this._nextAgent(); return true;
|
|
202
|
+
case 'c': this._beginCancelConfirm(); return true;
|
|
167
203
|
default: return false;
|
|
168
204
|
}
|
|
169
205
|
}
|
|
@@ -217,6 +253,11 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
217
253
|
}
|
|
218
254
|
|
|
219
255
|
summaryLines.push(this._renderAgentInfoSummary(width, rec));
|
|
256
|
+
const now = Date.now();
|
|
257
|
+
const isStalled = this._isAgentStalled(rec, now);
|
|
258
|
+
if (isStalled) {
|
|
259
|
+
summaryLines.push(buildPanelLine(width, [[' STALLED', '#f59e0b'], [' — no activity for 5+ minutes', DEFAULT_PANEL_PALETTE.dim]]));
|
|
260
|
+
}
|
|
220
261
|
const allRows = this._getCachedRows();
|
|
221
262
|
if (allRows.length === 0) {
|
|
222
263
|
return buildPanelWorkspace(width, height, {
|
|
@@ -243,13 +284,42 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
243
284
|
}
|
|
244
285
|
|
|
245
286
|
this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, allRows.length - 1));
|
|
287
|
+
const selectedRec = this.selectedAgentId
|
|
288
|
+
? this.deps.agentManager.getStatus(this.selectedAgentId)
|
|
289
|
+
: null;
|
|
290
|
+
const cancellable = selectedRec && !AGENT_TERMINAL_STATUSES.has(selectedRec.status);
|
|
246
291
|
const summarySection = { title: 'Summary', lines: summaryLines } as const;
|
|
247
292
|
const agentsSection = { title: 'Agents', lines: [selectorLine] } as const;
|
|
293
|
+
|
|
294
|
+
// Confirm-cancel overlay section.
|
|
295
|
+
const confirmSection = this.confirmCancel ? {
|
|
296
|
+
title: 'Confirm Cancel',
|
|
297
|
+
lines: [
|
|
298
|
+
buildPanelLine(width, [
|
|
299
|
+
[' Cancel agent "', DEFAULT_PANEL_PALETTE.warn],
|
|
300
|
+
[this.confirmCancel.label, DEFAULT_PANEL_PALETTE.value],
|
|
301
|
+
['"?', DEFAULT_PANEL_PALETTE.warn],
|
|
302
|
+
]),
|
|
303
|
+
buildPanelLine(width, [
|
|
304
|
+
[' y', DEFAULT_PANEL_PALETTE.info], [' confirm', DEFAULT_PANEL_PALETTE.dim],
|
|
305
|
+
[' Enter', DEFAULT_PANEL_PALETTE.info], [' confirm', DEFAULT_PANEL_PALETTE.dim],
|
|
306
|
+
[' n / Esc', DEFAULT_PANEL_PALETTE.info], [' cancel', DEFAULT_PANEL_PALETTE.dim],
|
|
307
|
+
]),
|
|
308
|
+
],
|
|
309
|
+
} : null;
|
|
310
|
+
|
|
311
|
+
const cancelHintFg = cancellable ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim;
|
|
312
|
+
const footerLine = buildPanelLine(width, [
|
|
313
|
+
[` L${this.cursorIndex + 1}/${allRows.length}`, DEFAULT_PANEL_PALETTE.dim],
|
|
314
|
+
[' Tab', DEFAULT_PANEL_PALETTE.info], [' cycle agents', DEFAULT_PANEL_PALETTE.dim],
|
|
315
|
+
[' Up/Down', DEFAULT_PANEL_PALETTE.info], [' navigate', DEFAULT_PANEL_PALETTE.dim],
|
|
316
|
+
[' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim],
|
|
317
|
+
[' c', cancelHintFg], [cancellable ? ' cancel' : ' cancel (n/a)', DEFAULT_PANEL_PALETTE.dim],
|
|
318
|
+
]);
|
|
319
|
+
|
|
248
320
|
const timelineSection = resolveScrollablePanelSection(width, height, {
|
|
249
321
|
intro: 'Inspect a selected agent timeline, tool activity, expanded details, and live/historical message flow.',
|
|
250
|
-
footerLines: [
|
|
251
|
-
buildPanelLine(width, [[` L${this.cursorIndex + 1}/${allRows.length}`, DEFAULT_PANEL_PALETTE.dim], [' Tab', DEFAULT_PANEL_PALETTE.info], [' cycle agents', DEFAULT_PANEL_PALETTE.dim], [' Up/Down', DEFAULT_PANEL_PALETTE.info], [' navigate', DEFAULT_PANEL_PALETTE.dim], [' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim]]),
|
|
252
|
-
],
|
|
322
|
+
footerLines: [footerLine],
|
|
253
323
|
palette: DEFAULT_PANEL_PALETTE,
|
|
254
324
|
beforeSections: [summarySection, agentsSection],
|
|
255
325
|
section: {
|
|
@@ -259,20 +329,22 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
259
329
|
scrollOffset: this.scrollOffset,
|
|
260
330
|
minRows: 8,
|
|
261
331
|
},
|
|
332
|
+
afterSections: confirmSection ? [confirmSection] : undefined,
|
|
262
333
|
});
|
|
263
334
|
this.scrollOffset = timelineSection.scrollOffset;
|
|
264
335
|
|
|
336
|
+
const sections = [
|
|
337
|
+
summarySection,
|
|
338
|
+
agentsSection,
|
|
339
|
+
timelineSection.section,
|
|
340
|
+
...(confirmSection ? [confirmSection] : []),
|
|
341
|
+
];
|
|
342
|
+
|
|
265
343
|
return buildPanelWorkspace(width, height, {
|
|
266
344
|
title: ` Inspector [${agents.length} agent${agents.length !== 1 ? 's' : ''}]`,
|
|
267
345
|
intro: 'Inspect a selected agent timeline, tool activity, expanded details, and live/historical message flow.',
|
|
268
|
-
sections
|
|
269
|
-
|
|
270
|
-
agentsSection,
|
|
271
|
-
timelineSection.section,
|
|
272
|
-
],
|
|
273
|
-
footerLines: [
|
|
274
|
-
buildPanelLine(width, [[` L${this.cursorIndex + 1}/${allRows.length}`, DEFAULT_PANEL_PALETTE.dim], [' Tab', DEFAULT_PANEL_PALETTE.info], [' cycle agents', DEFAULT_PANEL_PALETTE.dim], [' Up/Down', DEFAULT_PANEL_PALETTE.info], [' navigate', DEFAULT_PANEL_PALETTE.dim], [' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim]]),
|
|
275
|
-
],
|
|
346
|
+
sections,
|
|
347
|
+
footerLines: [footerLine],
|
|
276
348
|
palette: DEFAULT_PANEL_PALETTE,
|
|
277
349
|
});
|
|
278
350
|
}
|
|
@@ -517,4 +589,34 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
517
589
|
this.inspectAgent(next.id);
|
|
518
590
|
}
|
|
519
591
|
}
|
|
592
|
+
|
|
593
|
+
// -------------------------------------------------------------------------
|
|
594
|
+
// Private — cancel + stall
|
|
595
|
+
// -------------------------------------------------------------------------
|
|
596
|
+
|
|
597
|
+
/** Initiate cancel-confirm flow for the selected agent (noop if terminal or none selected). */
|
|
598
|
+
private _beginCancelConfirm(): void {
|
|
599
|
+
if (!this.selectedAgentId) return;
|
|
600
|
+
const rec = this.deps.agentManager.getStatus(this.selectedAgentId);
|
|
601
|
+
if (!rec || AGENT_TERMINAL_STATUSES.has(rec.status)) return;
|
|
602
|
+
const label = rec.task.split('\n')[0]?.slice(0, 40) ?? rec.id.slice(-8);
|
|
603
|
+
this.confirmCancel = { subject: rec.id, label };
|
|
604
|
+
this.markDirty();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** Returns whether an agent is considered stalled (non-terminal, running past threshold). */
|
|
608
|
+
private _isAgentStalled(rec: AgentRecord, now: number): boolean {
|
|
609
|
+
if (AGENT_TERMINAL_STATUSES.has(rec.status)) return false;
|
|
610
|
+
return (now - rec.startedAt) >= AGENT_STALL_THRESHOLD_MS;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Count of all tracked agents that are stalled (non-terminal, no activity
|
|
615
|
+
* for AGENT_STALL_THRESHOLD_MS). Exposed so callers can aggregate a
|
|
616
|
+
* stalledAgentCount for cockpit / roster read-models.
|
|
617
|
+
*/
|
|
618
|
+
getStalledAgentCount(): number {
|
|
619
|
+
const now = Date.now();
|
|
620
|
+
return this.deps.agentManager.list().filter(rec => this._isAgentStalled(rec, now)).length;
|
|
621
|
+
}
|
|
520
622
|
}
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
export type AgentInspectorEntryKind = 'user' | 'assistant' | 'tool_call' | 'tool_result' | 'session' | 'error';
|
|
2
2
|
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Shared agent status / stall constants
|
|
5
|
+
// Used by AgentInspectorPanel, AgentDetailModal, and cockpit read-model consumers.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
/** Terminal statuses — cancel not offered; stall check skipped. */
|
|
9
|
+
export const AGENT_TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
10
|
+
|
|
11
|
+
/** Agents in a non-terminal state for longer than this are considered STALLED. */
|
|
12
|
+
export const AGENT_STALL_THRESHOLD_MS = 5 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Count stalled agents from a raw record list.
|
|
16
|
+
* An agent is stalled when it is non-terminal and has been running for at
|
|
17
|
+
* least AGENT_STALL_THRESHOLD_MS without completing.
|
|
18
|
+
*
|
|
19
|
+
* Extracted as a standalone export so read-models and panels can share the
|
|
20
|
+
* canonical stall-count logic (TASK-046).
|
|
21
|
+
*/
|
|
22
|
+
export function countStalledAgents(
|
|
23
|
+
records: ReadonlyArray<{ status: string; startedAt: number }>,
|
|
24
|
+
now: number = Date.now(),
|
|
25
|
+
): number {
|
|
26
|
+
return records.filter(
|
|
27
|
+
(r) => !AGENT_TERMINAL_STATUSES.has(r.status) && (now - r.startedAt) >= AGENT_STALL_THRESHOLD_MS,
|
|
28
|
+
).length;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
3
32
|
export interface AgentTimelineEntry {
|
|
4
33
|
kind: AgentInspectorEntryKind;
|
|
5
34
|
timestamp: number;
|
|
@@ -76,7 +76,10 @@ export function registerAgentPanels(manager: PanelManager, deps: ResolvedBuiltin
|
|
|
76
76
|
preload: true,
|
|
77
77
|
factory: () => {
|
|
78
78
|
const ui = requireUiServices(deps);
|
|
79
|
-
return new WrfcPanel(ui.events.workflows, {
|
|
79
|
+
return new WrfcPanel(ui.events.workflows, {
|
|
80
|
+
controller: ui.agents.wrfcController,
|
|
81
|
+
cancelChain: (agentId: string) => ui.agents.agentManager.cancel(agentId),
|
|
82
|
+
});
|
|
80
83
|
},
|
|
81
84
|
});
|
|
82
85
|
|
|
@@ -51,6 +51,7 @@ export function registerDevelopmentPanels(manager: PanelManager, deps: ResolvedB
|
|
|
51
51
|
agentManager: ui.agents.agentManager,
|
|
52
52
|
agentMessageBus: ui.agents.agentMessageBus,
|
|
53
53
|
workingDirectory: ui.environment.workingDirectory,
|
|
54
|
+
cancelAgent: (agentId: string) => ui.agents.agentManager.cancel(agentId),
|
|
54
55
|
});
|
|
55
56
|
},
|
|
56
57
|
});
|
|
@@ -65,7 +66,10 @@ export function registerDevelopmentPanels(manager: PanelManager, deps: ResolvedB
|
|
|
65
66
|
description: 'Estimated costs per session, agent, and plan with budget alerts',
|
|
66
67
|
factory: () => {
|
|
67
68
|
const ui = requireUiServices(deps);
|
|
68
|
-
return new CostTrackerPanel(ui.events.turns, ui.events.agents, getOrchestratorUsage, {
|
|
69
|
+
return new CostTrackerPanel(ui.events.turns, ui.events.agents, getOrchestratorUsage, {
|
|
70
|
+
budgetThreshold,
|
|
71
|
+
getAgentStatus: (id) => ui.agents.agentManager.getStatus(id),
|
|
72
|
+
});
|
|
69
73
|
},
|
|
70
74
|
});
|
|
71
75
|
}
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import type { PanelManager } from '../panel-manager.ts';
|
|
2
2
|
import { MemoryPanel } from '../memory-panel.ts';
|
|
3
|
-
import {
|
|
3
|
+
import { KnowledgeGraphPanel } from '../knowledge-graph-panel.ts';
|
|
4
4
|
import type { ResolvedBuiltinPanelDeps } from './shared.ts';
|
|
5
5
|
|
|
6
6
|
export function registerKnowledgePanels(manager: PanelManager, deps: ResolvedBuiltinPanelDeps): void {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const { memoryRegistry } = deps;
|
|
7
|
+
// KnowledgeGraphPanel is a no-arg panel — always register it regardless of memoryRegistry.
|
|
10
8
|
manager.registerType({
|
|
11
9
|
id: 'knowledge',
|
|
12
10
|
name: 'Knowledge',
|
|
13
11
|
icon: 'K',
|
|
14
12
|
category: 'agent',
|
|
15
13
|
description: 'Structured project knowledge: risks, runbooks, architecture notes, incidents, and durable facts',
|
|
16
|
-
factory: () => new
|
|
17
|
-
});
|
|
18
|
-
manager.registerType({
|
|
19
|
-
id: 'memory',
|
|
20
|
-
name: 'Memory',
|
|
21
|
-
icon: 'M',
|
|
22
|
-
category: 'agent',
|
|
23
|
-
description: 'Project memory: decisions, constraints, incidents, and patterns with provenance links',
|
|
24
|
-
factory: () => new MemoryPanel(memoryRegistry),
|
|
14
|
+
factory: () => new KnowledgeGraphPanel(),
|
|
25
15
|
});
|
|
16
|
+
if (deps.memoryRegistry) {
|
|
17
|
+
const { memoryRegistry } = deps;
|
|
18
|
+
manager.registerType({
|
|
19
|
+
id: 'memory',
|
|
20
|
+
name: 'Memory',
|
|
21
|
+
icon: 'M',
|
|
22
|
+
category: 'agent',
|
|
23
|
+
description: 'Project memory: decisions, constraints, incidents, and patterns with provenance links',
|
|
24
|
+
factory: () => new MemoryPanel(memoryRegistry),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
26
27
|
}
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
import { createRuntimeProviderApi } from '@/runtime/index.ts';
|
|
38
38
|
import type { ResolvedBuiltinPanelDeps } from './shared.ts';
|
|
39
39
|
import { requireAutomationManager, requireControlPlanePanelDeps, requireHookPanelDeps, requirePluginManager, requireUiServices } from './shared.ts';
|
|
40
|
+
import { createCockpitRosterReadModel } from '../cockpit-read-model.ts';
|
|
40
41
|
|
|
41
42
|
export function registerOperationsPanels(manager: PanelManager, deps: ResolvedBuiltinPanelDeps): void {
|
|
42
43
|
const ui = requireUiServices(deps);
|
|
@@ -52,13 +53,33 @@ export function registerOperationsPanels(manager: PanelManager, deps: ResolvedBu
|
|
|
52
53
|
environment: createEnvironmentVariableQuery(process.env),
|
|
53
54
|
});
|
|
54
55
|
|
|
56
|
+
const rosterReadModel = createCockpitRosterReadModel(ui.agents.agentManager);
|
|
57
|
+
// Subscribe to agent lifecycle events so the roster re-renders on state changes.
|
|
58
|
+
// AGENT_RUNNING covers status transitions; AGENT_CANCELLED covers the cancellation
|
|
59
|
+
// terminal state not emitted by AGENT_FAILED. Noisy mid-run events (STREAM_DELTA,
|
|
60
|
+
// AWAITING_TOOL, etc.) are intentionally excluded — they don't affect roster fields.
|
|
61
|
+
// Note: stall detection is time-based, so stalled/stalledAgentCount will only refresh
|
|
62
|
+
// on the next lifecycle event; a periodic tick would be needed for real-time stall display.
|
|
63
|
+
ui.events.agents.on('AGENT_SPAWNING', () => rosterReadModel.markDirty());
|
|
64
|
+
ui.events.agents.on('AGENT_RUNNING', () => rosterReadModel.markDirty());
|
|
65
|
+
ui.events.agents.on('AGENT_COMPLETED', () => rosterReadModel.markDirty());
|
|
66
|
+
ui.events.agents.on('AGENT_FAILED', () => rosterReadModel.markDirty());
|
|
67
|
+
ui.events.agents.on('AGENT_CANCELLED', () => rosterReadModel.markDirty());
|
|
68
|
+
|
|
55
69
|
manager.registerType({
|
|
56
70
|
id: 'cockpit',
|
|
57
71
|
name: 'Cockpit',
|
|
58
72
|
icon: 'O',
|
|
59
73
|
category: 'monitoring',
|
|
60
74
|
description: 'Unified operator summary for orchestration, permissions, communication, MCP, plugins, and integrations',
|
|
61
|
-
factory: () => new CockpitPanel(
|
|
75
|
+
factory: () => new CockpitPanel(
|
|
76
|
+
ui.readModels.cockpit,
|
|
77
|
+
rosterReadModel,
|
|
78
|
+
{
|
|
79
|
+
openAgentDetail: (agentId: string) => deps.openAgentDetail?.(agentId),
|
|
80
|
+
cancelAgent: (agentId: string) => ui.agents.agentManager.cancel(agentId),
|
|
81
|
+
},
|
|
82
|
+
),
|
|
62
83
|
});
|
|
63
84
|
|
|
64
85
|
manager.registerType({
|
|
@@ -114,6 +114,13 @@ export interface BuiltinPanelDeps {
|
|
|
114
114
|
hookActivityTracker?: Pick<HookActivityTracker, 'listRecent'>;
|
|
115
115
|
/** Shared MCP registry for security panels and MCP workspace commands. */
|
|
116
116
|
mcpRegistry?: McpRegistry;
|
|
117
|
+
/**
|
|
118
|
+
* Open the agent detail modal for the given agent id. Wired from
|
|
119
|
+
* InputHandler.agentDetailModal.open() at bootstrap — passed to the
|
|
120
|
+
* CockpitPanel factory so the agents workspace inspect key (i) works
|
|
121
|
+
* without the panel depending on the modal directly.
|
|
122
|
+
*/
|
|
123
|
+
openAgentDetail?: (agentId: string) => void;
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
export type ResolvedBuiltinPanelDeps = Omit<
|