@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,537 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// ToolInspectorPanel — chronological list of tool calls with args/results.
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
import type { Line } from '../types/grid.ts';
|
|
6
|
-
import { BasePanel } from './base-panel.ts';
|
|
7
|
-
import type { ToolEvent, TurnEvent } from '@/runtime/index.ts';
|
|
8
|
-
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
9
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
10
|
-
import {
|
|
11
|
-
buildEmptyState,
|
|
12
|
-
buildPanelLine,
|
|
13
|
-
buildStyledPanelLine,
|
|
14
|
-
buildPanelWorkspace,
|
|
15
|
-
resolveScrollablePanelSection,
|
|
16
|
-
DEFAULT_PANEL_PALETTE,
|
|
17
|
-
extendPalette,
|
|
18
|
-
type PanelWorkspaceSection,
|
|
19
|
-
} from './polish.ts';
|
|
20
|
-
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
21
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
22
|
-
import { formatDuration } from '../utils/format-duration.ts';
|
|
23
|
-
|
|
24
|
-
// Panel-specific accents only; shared tones come from DEFAULT_PANEL_PALETTE so
|
|
25
|
-
// theme changes propagate. selectedBg->selectBg, errorFg->bad are shared keys.
|
|
26
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
27
|
-
selected: '#00ffff',
|
|
28
|
-
toolFg: '#00ccff',
|
|
29
|
-
argsFg: '#aaaaaa',
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const MAX_ENTRIES = 500;
|
|
33
|
-
|
|
34
|
-
interface ToolCallRecord {
|
|
35
|
-
callId: string;
|
|
36
|
-
tool: string;
|
|
37
|
-
args: Record<string, unknown>;
|
|
38
|
-
/** Wall-clock time the call was received from the LLM (queued, not yet executing). */
|
|
39
|
-
startMs: number;
|
|
40
|
-
/**
|
|
41
|
-
* Real execution-start time from TOOL_EXECUTING.startedAt — used for duration
|
|
42
|
-
* math when present so displayed durations reflect actual run time rather
|
|
43
|
-
* than time spent in permission/hook queueing. Falls back to `startMs` when
|
|
44
|
-
* a call completes without ever reaching TOOL_EXECUTING (e.g. denied before
|
|
45
|
-
* execution).
|
|
46
|
-
*/
|
|
47
|
-
execStartMs?: number;
|
|
48
|
-
endMs?: number;
|
|
49
|
-
result?: unknown;
|
|
50
|
-
error?: string;
|
|
51
|
-
cancelled?: boolean;
|
|
52
|
-
expanded: boolean;
|
|
53
|
-
approved?: boolean;
|
|
54
|
-
outputClass?: string;
|
|
55
|
-
resultSummary?: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
type FilterMode = 'all' | string;
|
|
59
|
-
|
|
60
|
-
type FlatRow =
|
|
61
|
-
| { kind: 'call'; recordIndex: number; text: string }
|
|
62
|
-
| { kind: 'detail'; text: string; isError: boolean };
|
|
63
|
-
|
|
64
|
-
function shortTime(ms: number): string {
|
|
65
|
-
const d = new Date(ms);
|
|
66
|
-
return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function formatMs(ms: number): string {
|
|
70
|
-
return formatDuration(ms);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function truncateJson(val: unknown, maxLen = 120): string {
|
|
74
|
-
const s = JSON.stringify(val) ?? 'null';
|
|
75
|
-
return truncateDisplay(s, maxLen);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function summarizeResult(result: unknown): string | undefined {
|
|
79
|
-
// SDK OBS-05 (0.21.31+): TOOL_SUCCEEDED/TOOL_FAILED.result is a ToolResultSummary
|
|
80
|
-
// { kind: 'text' | 'json' | 'binary' | 'error' | 'empty'; byteSize: number; preview?: string }.
|
|
81
|
-
if (!result || typeof result !== 'object') return undefined;
|
|
82
|
-
const record = result as Record<string, unknown>;
|
|
83
|
-
if (typeof record.preview === 'string' && record.preview.trim()) {
|
|
84
|
-
const compact = record.preview.replace(/\s+/g, ' ').trim();
|
|
85
|
-
return truncateDisplay(compact, 72);
|
|
86
|
-
}
|
|
87
|
-
if (typeof record.kind === 'string' && typeof record.byteSize === 'number') {
|
|
88
|
-
return `${record.kind} (${record.byteSize}B)`;
|
|
89
|
-
}
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function statusLabel(rec: ToolCallRecord): string {
|
|
94
|
-
if (rec.endMs === undefined) return 'running';
|
|
95
|
-
if (rec.cancelled) return 'cancelled';
|
|
96
|
-
if (rec.error) return 'error';
|
|
97
|
-
return 'completed';
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function statusColor(rec: ToolCallRecord): string {
|
|
101
|
-
if (rec.endMs === undefined) return DEFAULT_PANEL_PALETTE.warn;
|
|
102
|
-
if (rec.cancelled) return DEFAULT_PANEL_PALETTE.dim;
|
|
103
|
-
if (rec.error) return DEFAULT_PANEL_PALETTE.bad;
|
|
104
|
-
return DEFAULT_PANEL_PALETTE.good;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function detectOutputClass(tool: string, args: Record<string, unknown>): string {
|
|
108
|
-
const name = tool.toLowerCase();
|
|
109
|
-
if (name.includes('read') || name.includes('find') || name.includes('inspect') || name.includes('grep')) return 'read';
|
|
110
|
-
if (name.includes('write') || name.includes('edit') || name.includes('patch')) return 'write';
|
|
111
|
-
if (name.includes('exec') || name.includes('shell') || name.includes('bash') || typeof args.command === 'string') return 'execute';
|
|
112
|
-
if (name.includes('fetch') || name.includes('http') || name.includes('remote')) return 'network';
|
|
113
|
-
return 'analyze';
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export class ToolInspectorPanel extends BasePanel {
|
|
117
|
-
private records: ToolCallRecord[] = [];
|
|
118
|
-
private filterMode: FilterMode = 'all';
|
|
119
|
-
private unsubs: Array<() => void> = [];
|
|
120
|
-
private cursorIndex = 0;
|
|
121
|
-
private scrollOffset = 0;
|
|
122
|
-
private autoScroll = true;
|
|
123
|
-
private _flatCache: FlatRow[] | null = null;
|
|
124
|
-
/** Pending confirm for 'c' clear — destructive (drops call history), so it goes through the project-standard confirm contract. */
|
|
125
|
-
private _confirm: ConfirmState<'clear'> | null = null;
|
|
126
|
-
/** Set by handleInput('a') when the selected call is awaiting a permission decision; consumed by handlePanelIntegrationAction. */
|
|
127
|
-
private _pendingApprovalJump = false;
|
|
128
|
-
|
|
129
|
-
constructor(
|
|
130
|
-
private readonly toolEvents: UiEventFeed<ToolEvent>,
|
|
131
|
-
private readonly turnEvents: UiEventFeed<TurnEvent>,
|
|
132
|
-
) {
|
|
133
|
-
super('tools', 'Tools', 'X', 'ai');
|
|
134
|
-
this._attachBus();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
override onActivate(): void {
|
|
138
|
-
this.needsRender = true;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
override onDeactivate(): void {
|
|
142
|
-
super.onDeactivate();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
override onDestroy(): void {
|
|
146
|
-
this._detachBus();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
override markDirty(): void {
|
|
150
|
-
this._flatCache = null;
|
|
151
|
-
super.markDirty();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
handleInput(key: string): boolean {
|
|
155
|
-
const confirmResult = handleConfirmInput(this._confirm, key);
|
|
156
|
-
if (confirmResult === 'confirmed') {
|
|
157
|
-
this.records = [];
|
|
158
|
-
this._confirm = null;
|
|
159
|
-
this.markDirty();
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
if (confirmResult === 'cancelled') {
|
|
163
|
-
this._confirm = null;
|
|
164
|
-
this.markDirty();
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
if (confirmResult === 'absorbed') return true;
|
|
168
|
-
|
|
169
|
-
switch (key) {
|
|
170
|
-
case 'up': this._move(-1); this.autoScroll = false; return true;
|
|
171
|
-
case 'down': this._move(1); return true;
|
|
172
|
-
case 'pageup': this._move(-10); this.autoScroll = false; return true;
|
|
173
|
-
case 'pagedown': this._move(10); return true;
|
|
174
|
-
case 'return': this._toggleExpand(); return true;
|
|
175
|
-
case 'c': {
|
|
176
|
-
if (this.records.length === 0) return false;
|
|
177
|
-
this._confirm = { subject: 'clear', label: `${this.records.length} tool call${this.records.length === 1 ? '' : 's'}`, verb: 'Clear' };
|
|
178
|
-
this.markDirty();
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
case 'g': this.autoScroll = true; this.markDirty(); return true;
|
|
182
|
-
case 'f': this._cycleFilter(); return true;
|
|
183
|
-
case 'a': {
|
|
184
|
-
const rec = this._selectedRecord();
|
|
185
|
-
if (!rec || rec.approved !== undefined || rec.endMs !== undefined) return false;
|
|
186
|
-
this._pendingApprovalJump = true;
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
default: return false;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Cross-panel hook — invoked immediately after handleInput('a') returns true
|
|
195
|
-
* for a call still awaiting a permission decision. Jumps to the Approval
|
|
196
|
-
* panel; the actual PanelManager reference is only available here.
|
|
197
|
-
*/
|
|
198
|
-
handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
199
|
-
if (!this._pendingApprovalJump) return false;
|
|
200
|
-
this._pendingApprovalJump = false;
|
|
201
|
-
ctx.panelManager.open('approval');
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
render(width: number, height: number): Line[] {
|
|
206
|
-
if (height <= 0 || width <= 0) return [];
|
|
207
|
-
|
|
208
|
-
const running = this.records.filter(r => r.endMs === undefined).length;
|
|
209
|
-
const distinctTools = new Set(this.records.map((r) => r.tool)).size;
|
|
210
|
-
const filterLabel = this.filterMode === 'all' ? '' : ` [${this.filterMode}]`;
|
|
211
|
-
const title = ` Tools [${this.records.length} calls${running > 0 ? `, ${running} running` : ''}]${filterLabel}`;
|
|
212
|
-
// Context-aware footer: only advertise keys that do something in the current
|
|
213
|
-
// state — filter needs >1 tool, clear/end need at least one call.
|
|
214
|
-
const footerSegments: Array<[string, string, string?]> = [
|
|
215
|
-
[' Up/Down', DEFAULT_PANEL_PALETTE.info], [' scroll', DEFAULT_PANEL_PALETTE.dim],
|
|
216
|
-
[' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim],
|
|
217
|
-
];
|
|
218
|
-
if (distinctTools > 1) {
|
|
219
|
-
footerSegments.push([' f', DEFAULT_PANEL_PALETTE.info], [' filter', DEFAULT_PANEL_PALETTE.dim]);
|
|
220
|
-
}
|
|
221
|
-
if (this.records.length > 0) {
|
|
222
|
-
footerSegments.push([' c', DEFAULT_PANEL_PALETTE.info], [' clear', DEFAULT_PANEL_PALETTE.dim]);
|
|
223
|
-
footerSegments.push([' g', DEFAULT_PANEL_PALETTE.info], [this.autoScroll ? ' end (live)' : ' jump to end', DEFAULT_PANEL_PALETTE.dim]);
|
|
224
|
-
}
|
|
225
|
-
const selectedRecordForFooter = this._selectedRecord();
|
|
226
|
-
if (selectedRecordForFooter && selectedRecordForFooter.approved === undefined && selectedRecordForFooter.endMs === undefined) {
|
|
227
|
-
footerSegments.push([' a', DEFAULT_PANEL_PALETTE.info], [' review approval', DEFAULT_PANEL_PALETTE.dim]);
|
|
228
|
-
}
|
|
229
|
-
const footerLines = [buildPanelLine(width, footerSegments)];
|
|
230
|
-
|
|
231
|
-
if (this._confirm) {
|
|
232
|
-
return buildPanelWorkspace(width, height, {
|
|
233
|
-
title,
|
|
234
|
-
sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this._confirm) }],
|
|
235
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const flat = this._getFlat();
|
|
240
|
-
|
|
241
|
-
if (this.autoScroll) {
|
|
242
|
-
this.cursorIndex = Math.max(0, flat.length - 1);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (flat.length === 0) {
|
|
246
|
-
return buildPanelWorkspace(width, height, {
|
|
247
|
-
title,
|
|
248
|
-
intro: 'Inspect chronological tool activity, arguments, results, errors, and running calls — including calls made by delegated subagents.',
|
|
249
|
-
sections: [
|
|
250
|
-
{
|
|
251
|
-
title: 'Calls',
|
|
252
|
-
lines: buildEmptyState(
|
|
253
|
-
width,
|
|
254
|
-
this.records.length > 0 && this.filterMode !== 'all'
|
|
255
|
-
? ` No "${this.filterMode}" calls`
|
|
256
|
-
: ' No tool calls yet',
|
|
257
|
-
this.records.length > 0 && this.filterMode !== 'all'
|
|
258
|
-
? 'The active tool filter hides every recorded call.'
|
|
259
|
-
: 'Tool executions appear here as the agent works, including calls made by delegated subagents. Expand a call to inspect its arguments and result payload.',
|
|
260
|
-
this.records.length > 0 && this.filterMode !== 'all'
|
|
261
|
-
? [{ command: 'f', summary: 'cycle the tool filter back to all calls' }]
|
|
262
|
-
: [{ command: '/spawn <task>', summary: 'run an agent to populate the tool-call timeline' }],
|
|
263
|
-
DEFAULT_PANEL_PALETTE,
|
|
264
|
-
),
|
|
265
|
-
},
|
|
266
|
-
],
|
|
267
|
-
footerLines,
|
|
268
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, Math.max(0, flat.length - 1)));
|
|
273
|
-
const summary: PanelWorkspaceSection = {
|
|
274
|
-
title: 'Summary',
|
|
275
|
-
lines: [
|
|
276
|
-
buildPanelLine(width, [
|
|
277
|
-
[' Calls ', DEFAULT_PANEL_PALETTE.label],
|
|
278
|
-
[String(this.records.length), DEFAULT_PANEL_PALETTE.value],
|
|
279
|
-
[' Running ', DEFAULT_PANEL_PALETTE.label],
|
|
280
|
-
[String(running), running > 0 ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
|
|
281
|
-
[' Filter ', DEFAULT_PANEL_PALETTE.label],
|
|
282
|
-
[this.filterMode === 'all' ? 'all' : this.filterMode, this.filterMode === 'all' ? DEFAULT_PANEL_PALETTE.dim : DEFAULT_PANEL_PALETTE.info],
|
|
283
|
-
]),
|
|
284
|
-
],
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const selected = flat[this.cursorIndex];
|
|
288
|
-
const detailLines: Line[] = [];
|
|
289
|
-
if (selected?.kind === 'call') {
|
|
290
|
-
const filtered = this.filterMode === 'all'
|
|
291
|
-
? this.records
|
|
292
|
-
: this.records.filter(r => r.tool === this.filterMode);
|
|
293
|
-
const rec = filtered[selected.recordIndex];
|
|
294
|
-
if (rec) {
|
|
295
|
-
detailLines.push(buildPanelLine(width, [[' Tool ', DEFAULT_PANEL_PALETTE.label], [rec.tool, DEFAULT_PANEL_PALETTE.info], [' Started ', DEFAULT_PANEL_PALETTE.label], [shortTime(rec.execStartMs ?? rec.startMs), DEFAULT_PANEL_PALETTE.value]]));
|
|
296
|
-
detailLines.push(buildPanelLine(width, [[' Status ', DEFAULT_PANEL_PALETTE.label], [statusLabel(rec), statusColor(rec)]]));
|
|
297
|
-
detailLines.push(buildPanelLine(width, [
|
|
298
|
-
[' Class ', DEFAULT_PANEL_PALETTE.label],
|
|
299
|
-
[rec.outputClass ?? detectOutputClass(rec.tool, rec.args), DEFAULT_PANEL_PALETTE.warn],
|
|
300
|
-
[' Approved ', DEFAULT_PANEL_PALETTE.label],
|
|
301
|
-
[rec.approved === undefined ? 'unknown' : rec.approved ? 'yes' : 'no', rec.approved === false ? DEFAULT_PANEL_PALETTE.bad : DEFAULT_PANEL_PALETTE.value],
|
|
302
|
-
]));
|
|
303
|
-
if (rec.resultSummary) {
|
|
304
|
-
detailLines.push(buildPanelLine(width, [[' Summary ', DEFAULT_PANEL_PALETTE.label], [rec.resultSummary, DEFAULT_PANEL_PALETTE.value]]));
|
|
305
|
-
}
|
|
306
|
-
if (rec.error) detailLines.push(buildPanelLine(width, [[' Error ', DEFAULT_PANEL_PALETTE.bad], [rec.error, DEFAULT_PANEL_PALETTE.value]]));
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const selectedSection: PanelWorkspaceSection = { title: 'Selected', lines: detailLines };
|
|
311
|
-
const callsSection = resolveScrollablePanelSection(width, height, {
|
|
312
|
-
intro: 'Inspect chronological tool activity, arguments, results, errors, and running calls — including calls made by delegated subagents.',
|
|
313
|
-
footerLines,
|
|
314
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
315
|
-
beforeSections: [summary],
|
|
316
|
-
section: {
|
|
317
|
-
title: 'Calls',
|
|
318
|
-
scrollableLines: flat.map((row, index) => this._renderRow(width, row, index === this.cursorIndex)),
|
|
319
|
-
selectedIndex: this.cursorIndex,
|
|
320
|
-
scrollOffset: this.scrollOffset,
|
|
321
|
-
minRows: 8,
|
|
322
|
-
},
|
|
323
|
-
afterSections: [selectedSection],
|
|
324
|
-
});
|
|
325
|
-
this.scrollOffset = callsSection.scrollOffset;
|
|
326
|
-
|
|
327
|
-
return buildPanelWorkspace(width, height, {
|
|
328
|
-
title,
|
|
329
|
-
intro: 'Inspect chronological tool activity, arguments, results, errors, and running calls — including calls made by delegated subagents.',
|
|
330
|
-
sections: [
|
|
331
|
-
summary,
|
|
332
|
-
callsSection.section,
|
|
333
|
-
selectedSection,
|
|
334
|
-
],
|
|
335
|
-
footerLines,
|
|
336
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
private _renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
|
|
341
|
-
const bg = isCursor ? C.selectBg : '';
|
|
342
|
-
if (row.kind === 'call') {
|
|
343
|
-
return buildStyledPanelLine(width, [
|
|
344
|
-
{ text: isCursor ? '▸' : ' ', fg: C.selected, bg, bold: isCursor },
|
|
345
|
-
{ text: truncateDisplay(row.text, Math.max(0, width - 1)), fg: isCursor ? C.selected : C.toolFg, bg, bold: isCursor },
|
|
346
|
-
]);
|
|
347
|
-
}
|
|
348
|
-
return buildStyledPanelLine(width, [
|
|
349
|
-
{ text: ' ', fg: C.argsFg, bg },
|
|
350
|
-
{ text: truncateDisplay(row.text, Math.max(0, width - 2)), fg: row.isError ? C.bad : C.argsFg, bg },
|
|
351
|
-
]);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
private _getFlat(): FlatRow[] {
|
|
355
|
-
if (this._flatCache) return this._flatCache;
|
|
356
|
-
const flat: FlatRow[] = [];
|
|
357
|
-
const filtered = this.filterMode === 'all'
|
|
358
|
-
? this.records
|
|
359
|
-
: this.records.filter(r => r.tool === this.filterMode);
|
|
360
|
-
|
|
361
|
-
for (let i = 0; i < filtered.length; i++) {
|
|
362
|
-
const rec = filtered[i]!;
|
|
363
|
-
// Duration is measured from real execution start (TOOL_EXECUTING.startedAt) when
|
|
364
|
-
// known, not from queue/receive time, so it reflects actual run time rather than
|
|
365
|
-
// time spent waiting on permission/hook checks. Falls back to startMs for calls
|
|
366
|
-
// that never reached TOOL_EXECUTING (e.g. denied before execution).
|
|
367
|
-
const durBaseMs = rec.execStartMs ?? rec.startMs;
|
|
368
|
-
const dur = rec.cancelled
|
|
369
|
-
? ` (cancelled${rec.endMs !== undefined ? ` ${formatMs(rec.endMs - durBaseMs)}` : ''})`
|
|
370
|
-
: rec.endMs !== undefined ? ` (${formatMs(rec.endMs - durBaseMs)})` : ' (running)';
|
|
371
|
-
const expand = rec.expanded ? ' ▾' : ' ▸';
|
|
372
|
-
const ts = shortTime(rec.startMs);
|
|
373
|
-
const cls = rec.outputClass ?? detectOutputClass(rec.tool, rec.args);
|
|
374
|
-
const callText = `${ts} ${rec.tool} [${cls}]${dur}${expand}`;
|
|
375
|
-
flat.push({ kind: 'call', recordIndex: i, text: callText });
|
|
376
|
-
|
|
377
|
-
if (rec.expanded) {
|
|
378
|
-
const argsStr = truncateJson(rec.args, 200);
|
|
379
|
-
flat.push({ kind: 'detail', text: `Args: ${argsStr}`, isError: false });
|
|
380
|
-
flat.push({ kind: 'detail', text: `Class: ${cls}${rec.approved === undefined ? '' : ` Approved: ${rec.approved ? 'yes' : 'no'}`}`, isError: false });
|
|
381
|
-
if (rec.resultSummary) {
|
|
382
|
-
flat.push({ kind: 'detail', text: `Summary: ${rec.resultSummary}`, isError: false });
|
|
383
|
-
}
|
|
384
|
-
if (rec.result !== undefined) {
|
|
385
|
-
flat.push({ kind: 'detail', text: `Result: ${truncateJson(rec.result, 200)}`, isError: false });
|
|
386
|
-
}
|
|
387
|
-
if (rec.error) {
|
|
388
|
-
flat.push({ kind: 'detail', text: `Error: ${rec.error}`, isError: true });
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
this._flatCache = flat;
|
|
393
|
-
return flat;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
private _toggleExpand(): void {
|
|
397
|
-
const flat = this._getFlat();
|
|
398
|
-
const row = flat[this.cursorIndex];
|
|
399
|
-
if (!row || row.kind !== 'call') return;
|
|
400
|
-
const rec = this.records.filter(r =>
|
|
401
|
-
this.filterMode === 'all' ? true : r.tool === this.filterMode
|
|
402
|
-
)[row.recordIndex];
|
|
403
|
-
if (rec) {
|
|
404
|
-
rec.expanded = !rec.expanded;
|
|
405
|
-
this.markDirty();
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/** The tool-call record backing the currently-selected flat row, if any. */
|
|
410
|
-
private _selectedRecord(): ToolCallRecord | undefined {
|
|
411
|
-
const flat = this._getFlat();
|
|
412
|
-
const row = flat[this.cursorIndex];
|
|
413
|
-
if (!row || row.kind !== 'call') return undefined;
|
|
414
|
-
const filtered = this.filterMode === 'all'
|
|
415
|
-
? this.records
|
|
416
|
-
: this.records.filter(r => r.tool === this.filterMode);
|
|
417
|
-
return filtered[row.recordIndex];
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
private _cycleFilter(): void {
|
|
421
|
-
const tools = [...new Set(this.records.map(r => r.tool))];
|
|
422
|
-
if (tools.length === 0) return;
|
|
423
|
-
if (this.filterMode === 'all') {
|
|
424
|
-
this.filterMode = tools[0]!;
|
|
425
|
-
} else {
|
|
426
|
-
const idx = tools.indexOf(this.filterMode);
|
|
427
|
-
this.filterMode = idx >= tools.length - 1 ? 'all' : tools[idx + 1]!;
|
|
428
|
-
}
|
|
429
|
-
this.markDirty();
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Public counterpart to the 'f' cycle — sets the filter directly to a named
|
|
434
|
-
* tool. Used by cross-panel jumps (WO-136: DocsPanel Enter on a tool row)
|
|
435
|
-
* so the inspector opens already scoped to the tool the operator picked.
|
|
436
|
-
*/
|
|
437
|
-
public filterByTool(tool: string): void {
|
|
438
|
-
this.filterMode = tool;
|
|
439
|
-
this.cursorIndex = 0;
|
|
440
|
-
this.autoScroll = false;
|
|
441
|
-
this.markDirty();
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
private _move(delta: number): void {
|
|
445
|
-
const flat = this._getFlat();
|
|
446
|
-
if (flat.length === 0) return;
|
|
447
|
-
this.cursorIndex = Math.max(0, Math.min(flat.length - 1, this.cursorIndex + delta));
|
|
448
|
-
this.markDirty();
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
private _attachBus(): void {
|
|
452
|
-
if (this.unsubs.length > 0) return;
|
|
453
|
-
|
|
454
|
-
this.unsubs.push(this.toolEvents.on('TOOL_RECEIVED', (data) => {
|
|
455
|
-
if (this.records.length >= MAX_ENTRIES) {
|
|
456
|
-
this.records.shift();
|
|
457
|
-
}
|
|
458
|
-
this.records.push({
|
|
459
|
-
callId: data.callId,
|
|
460
|
-
tool: data.tool,
|
|
461
|
-
args: data.args,
|
|
462
|
-
startMs: Date.now(),
|
|
463
|
-
expanded: false,
|
|
464
|
-
outputClass: detectOutputClass(data.tool, data.args),
|
|
465
|
-
});
|
|
466
|
-
this.autoScroll = true;
|
|
467
|
-
this.markDirty();
|
|
468
|
-
}));
|
|
469
|
-
|
|
470
|
-
this.unsubs.push(this.toolEvents.on('TOOL_PERMISSIONED', (data) => {
|
|
471
|
-
const rec = this.records.findLast(r => r.callId === data.callId);
|
|
472
|
-
if (rec) rec.approved = data.approved;
|
|
473
|
-
this.markDirty();
|
|
474
|
-
}));
|
|
475
|
-
|
|
476
|
-
// Real execution start — recorded separately from TOOL_RECEIVED's queue time so
|
|
477
|
-
// displayed durations reflect actual run time, not time spent waiting on
|
|
478
|
-
// permission/hook checks ahead of execution.
|
|
479
|
-
this.unsubs.push(this.toolEvents.on('TOOL_EXECUTING', (data) => {
|
|
480
|
-
const rec = this.records.findLast(r => r.callId === data.callId);
|
|
481
|
-
if (rec) rec.execStartMs = data.startedAt;
|
|
482
|
-
this.markDirty();
|
|
483
|
-
}));
|
|
484
|
-
|
|
485
|
-
// NOTE: After SDK OBS-05 (0.21.31), TOOL_SUCCEEDED/TOOL_FAILED.result is a ToolResultSummary
|
|
486
|
-
// ({ kind, byteSize, preview? }) rather than the raw ToolResult object.
|
|
487
|
-
this.unsubs.push(this.toolEvents.on('TOOL_SUCCEEDED', (data) => {
|
|
488
|
-
const rec = this.records.findLast(r => r.callId === data.callId);
|
|
489
|
-
if (rec) {
|
|
490
|
-
rec.endMs = Date.now();
|
|
491
|
-
rec.result = data.result;
|
|
492
|
-
rec.resultSummary = summarizeResult(data.result);
|
|
493
|
-
}
|
|
494
|
-
this.markDirty();
|
|
495
|
-
}));
|
|
496
|
-
|
|
497
|
-
this.unsubs.push(this.toolEvents.on('TOOL_FAILED', (data) => {
|
|
498
|
-
const rec = this.records.findLast(r => r.callId === data.callId);
|
|
499
|
-
if (rec) {
|
|
500
|
-
rec.endMs = Date.now();
|
|
501
|
-
rec.result = data.result;
|
|
502
|
-
rec.error = data.error;
|
|
503
|
-
rec.resultSummary = summarizeResult(data.result) ?? data.error;
|
|
504
|
-
}
|
|
505
|
-
this.markDirty();
|
|
506
|
-
}));
|
|
507
|
-
|
|
508
|
-
// Terminal state for a cancelled call — without this subscription, a call
|
|
509
|
-
// cancelled mid-flight (e.g. via Ctrl+C) never gets an endMs and shows as
|
|
510
|
-
// "running" forever.
|
|
511
|
-
this.unsubs.push(this.toolEvents.on('TOOL_CANCELLED', (data) => {
|
|
512
|
-
const rec = this.records.findLast(r => r.callId === data.callId);
|
|
513
|
-
if (rec) {
|
|
514
|
-
rec.endMs = Date.now();
|
|
515
|
-
rec.cancelled = true;
|
|
516
|
-
rec.resultSummary = data.reason ?? 'cancelled';
|
|
517
|
-
}
|
|
518
|
-
this.markDirty();
|
|
519
|
-
}));
|
|
520
|
-
|
|
521
|
-
this.unsubs.push(this.turnEvents.on('TURN_ERROR', (data) => {
|
|
522
|
-
// Mark any running calls as errored
|
|
523
|
-
for (const rec of this.records) {
|
|
524
|
-
if (rec.endMs === undefined) {
|
|
525
|
-
rec.endMs = Date.now();
|
|
526
|
-
rec.error = data.error ?? 'unknown error';
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
this.markDirty();
|
|
530
|
-
}));
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
private _detachBus(): void {
|
|
534
|
-
for (const unsub of this.unsubs) unsub();
|
|
535
|
-
this.unsubs = [];
|
|
536
|
-
}
|
|
537
|
-
}
|