@pellux/goodvibes-tui 1.16.0 → 1.17.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 +95 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1091 -271
- package/package.json +7 -7
- package/src/cli/completions/generate.ts +5 -4
- package/src/cli/config-overrides.ts +51 -11
- package/src/cli/doctor.ts +4 -5
- package/src/cli/entrypoint.ts +13 -8
- package/src/cli/help.ts +7 -6
- package/src/cli/launch-auto-update.ts +214 -0
- package/src/cli/parser.ts +5 -0
- package/src/cli/status.ts +13 -0
- package/src/cli/surface-command.ts +5 -5
- package/src/cli/tui-startup.ts +60 -97
- package/src/cli/types.ts +2 -0
- package/src/config/index.ts +1 -1
- package/src/config/tui-extension-settings.ts +33 -0
- package/src/core/composer-state.ts +1 -1
- package/src/core/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +4 -4
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/stream-event-wiring.ts +8 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-cancellation.ts +1 -1
- package/src/core/turn-event-wiring.ts +8 -8
- package/src/daemon/cli.ts +20 -5
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/service-commands.ts +5 -5
- package/src/export/cost-utils.ts +128 -6
- package/src/input/autocomplete.ts +2 -2
- package/src/input/command-registry.ts +34 -9
- package/src/input/commands/ci-runtime.ts +5 -1
- package/src/input/commands/codebase-runtime.ts +6 -6
- package/src/input/commands/cost-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/intelligence-runtime.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +1 -1
- package/src/input/commands/local-provider-runtime.ts +49 -10
- package/src/input/commands/local-runtime.ts +53 -5
- package/src/input/commands/memory.ts +2 -2
- package/src/input/commands/operator-panel-runtime.ts +5 -5
- package/src/input/commands/permissions-provenance.ts +1 -1
- package/src/input/commands/permissions-runtime.ts +99 -8
- package/src/input/commands/planning-runtime.ts +3 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -1
- package/src/input/commands/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- package/src/input/commands/recall-files-git.ts +13 -51
- package/src/input/commands/recall-files.ts +8 -11
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/relay-runtime.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +4 -4
- package/src/input/commands/schedule-runtime.ts +14 -7
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +2 -2
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +46 -68
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/update-runtime.ts +153 -96
- package/src/input/commands/work-plan-runtime.ts +1 -1
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands.ts +0 -2
- package/src/input/config-modal-types.ts +3 -3
- package/src/input/config-modal.ts +10 -11
- package/src/input/feature-unit-layout.ts +93 -82
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/handler-command-route.ts +2 -2
- package/src/input/handler-content-actions.ts +1 -1
- package/src/input/handler-feed-routes.ts +7 -7
- package/src/input/handler-feed.ts +4 -4
- package/src/input/handler-interactions.ts +3 -2
- package/src/input/handler-modal-routes.ts +12 -4
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
- package/src/input/handler-onboarding.ts +3 -2
- package/src/input/handler-picker-routes.ts +26 -13
- package/src/input/handler-shortcuts.ts +6 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +2 -2
- package/src/input/onboarding/onboarding-feature-units.ts +34 -27
- package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
- package/src/input/panel-integration-actions.ts +2 -2
- package/src/input/panel-mouse-geometry.ts +1 -1
- package/src/input/panel-paste-flood-guard.ts +1 -1
- package/src/input/provider-key-intake.ts +167 -0
- package/src/input/sandbox-exec-config.ts +2 -2
- package/src/input/selection-modal.ts +1 -1
- package/src/input/settings-modal-activation.ts +5 -0
- package/src/input/settings-modal-adjustment.ts +4 -31
- package/src/input/settings-modal-data.ts +22 -21
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +23 -16
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +48 -48
- package/src/panels/agent-inspector-shared.ts +4 -4
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/agent.ts +4 -4
- package/src/panels/builtin/development.ts +4 -4
- package/src/panels/builtin/knowledge.ts +1 -1
- package/src/panels/builtin/operations.ts +29 -10
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +4 -4
- package/src/panels/builtin-modals.ts +10 -10
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +5 -5
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-panel-format.ts +44 -8
- package/src/panels/fleet-panel.ts +16 -16
- package/src/panels/fleet-read-model.ts +52 -13
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +3 -3
- package/src/panels/fleet-tabs.ts +13 -14
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/git-panel.ts +2 -2
- package/src/panels/index.ts +4 -1
- package/src/panels/local-auth-panel.ts +1 -1
- package/src/panels/modals/hooks-modal.ts +1 -1
- package/src/panels/modals/keybindings-modal.ts +2 -2
- package/src/panels/modals/knowledge-modal.ts +1 -1
- package/src/panels/modals/marketplace-modal.ts +4 -4
- package/src/panels/modals/memory-modal.ts +4 -4
- package/src/panels/modals/modal-surface-helpers.ts +1 -1
- package/src/panels/modals/modal-theme.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +2 -2
- package/src/panels/modals/planning-modal.ts +2 -2
- package/src/panels/modals/plugins-modal.ts +1 -1
- package/src/panels/modals/policy-modal.ts +1 -1
- package/src/panels/modals/security-modal.ts +1 -1
- package/src/panels/modals/skills-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +3 -3
- package/src/panels/notifications-feed.ts +127 -0
- package/src/panels/notifications-panel.ts +160 -0
- package/src/panels/panel-manager.ts +9 -9
- package/src/panels/plugins-panel.ts +4 -4
- package/src/panels/project-planning-answer-actions.ts +1 -1
- package/src/panels/provider-health-routes.ts +2 -2
- package/src/panels/skills-panel.ts +4 -5
- package/src/panels/token-budget-panel.ts +11 -3
- package/src/panels/types.ts +5 -5
- package/src/permissions/broker-approval-card.ts +201 -0
- package/src/permissions/hunk-selection.ts +28 -4
- package/src/permissions/prompt.ts +244 -29
- package/src/renderer/autocomplete-overlay.ts +107 -54
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compaction-history-modal.ts +1 -1
- package/src/renderer/compaction-preview.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/footer-tips.ts +3 -3
- package/src/renderer/hint-grammar.ts +1 -1
- package/src/renderer/modal-factory.ts +1 -1
- package/src/renderer/model-picker-overlay.ts +1 -1
- package/src/renderer/model-workspace.ts +7 -2
- package/src/renderer/onboarding/onboarding-wizard.ts +22 -8
- package/src/renderer/panel-composite.ts +2 -2
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/selection-modal-overlay.ts +102 -24
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +14 -5
- package/src/renderer/settings-modal.ts +127 -39
- package/src/renderer/shell-surface.ts +3 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +1 -1
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +6 -1
- package/src/runtime/bootstrap-command-context.ts +4 -1
- package/src/runtime/bootstrap-command-parts.ts +6 -4
- package/src/runtime/bootstrap-core.ts +35 -35
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +6 -5
- package/src/runtime/bootstrap.ts +34 -32
- package/src/runtime/channel-composition.ts +46 -0
- package/src/runtime/code-index-services.ts +3 -4
- package/src/runtime/daemon-attach-notices.ts +120 -0
- package/src/runtime/durability-services.ts +55 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/index.ts +4 -21
- package/src/runtime/legacy-daemon-migration.ts +29 -19
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/notification-dispatch.ts +110 -0
- package/src/runtime/onboarding/apply.ts +3 -29
- package/src/runtime/onboarding/fast-path.ts +39 -6
- package/src/runtime/onboarding/provider-key-capture.ts +126 -0
- package/src/runtime/onboarding/snapshot.ts +0 -1
- package/src/runtime/onboarding/types.ts +9 -3
- package/src/runtime/provider-fallback.ts +55 -0
- package/src/runtime/provider-optimizer-wiring.ts +55 -0
- package/src/runtime/release-artifacts.ts +19 -103
- package/src/runtime/resume-notice.ts +1 -1
- package/src/runtime/services.ts +81 -82
- package/src/runtime/session-continuity-hints.ts +38 -0
- package/src/runtime/surface-feature-flags.ts +40 -9
- package/src/runtime/terminal-output-guard.ts +2 -2
- package/src/runtime/trust/workspace-registration.ts +10 -2
- package/src/runtime/trust/workspace-trust.ts +58 -31
- package/src/runtime/ui-services.ts +3 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +102 -6
- package/src/shell/ui-openers.ts +38 -6
- package/src/utils/format-duration.ts +1 -1
- package/src/verification/verification-ledger.ts +21 -4
- package/src/version.ts +1 -1
- package/src/input/commands/flags-runtime.ts +0 -328
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
import { encodeConnectionPayload, generateQrMatrix, renderQrToString } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
10
10
|
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
|
-
// QR Code → 'pairing' config-modal surface (
|
|
12
|
+
// QR Code → 'pairing' config-modal surface (group-B port). Connection
|
|
13
13
|
// URL/token/username(/password) plus a scannable QR block — read/navigate only.
|
|
14
14
|
// Token regeneration is a destructive mutation (it invalidates any live
|
|
15
15
|
// companion session) that the panel gated behind an in-panel confirm; per
|
|
@@ -18,7 +18,7 @@ import { encodeConnectionPayload, generateQrMatrix, renderQrToString } from '@pe
|
|
|
18
18
|
// buildView, cached) so a missing daemon home degrades honestly rather than
|
|
19
19
|
// throwing at modal-registration time.
|
|
20
20
|
//
|
|
21
|
-
//
|
|
21
|
+
// the regenerate action now dispatches the real `/qrcode regenerate`
|
|
22
22
|
// verb (which rotates the shared operator-token store) and, once it resolves,
|
|
23
23
|
// RE-PULLS the lazy connection-info thunk so the modal shows the rotated token +
|
|
24
24
|
// QR in place without being re-opened. `confirm: true` keeps the two-press
|
|
@@ -18,12 +18,12 @@ import type {
|
|
|
18
18
|
import { buildAnswerActions, isGenericRecommendation, type PlanningAnswerAction } from '../project-planning-answer-actions.ts';
|
|
19
19
|
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
|
-
// Project Planning → 'planning' config-modal surface (
|
|
21
|
+
// Project Planning → 'planning' config-modal surface (group-B port). Shows
|
|
22
22
|
// readiness/questions/decisions/task-graph/handoff — read-only except choosing
|
|
23
23
|
// an answer to the current open question, approving execution, dismissing the
|
|
24
24
|
// plan, or refreshing.
|
|
25
25
|
//
|
|
26
|
-
//
|
|
26
|
+
// — the seams are now real:
|
|
27
27
|
// - A CANNED answer to a real open question dispatches `/plan answer <id> <text>`
|
|
28
28
|
// (records it; the open-question gap clears on the next refine).
|
|
29
29
|
// - The CUSTOM free-form typed answer (and any answer to a synthetic readiness
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
} from '@pellux/goodvibes-sdk/platform/plugins';
|
|
14
14
|
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
|
-
// Plugins → config-modal surface (
|
|
16
|
+
// Plugins → config-modal surface (group-B port). Mirrors the retired
|
|
17
17
|
// PluginsPanel's read surface (list/capabilities/getTrustRecord/
|
|
18
18
|
// getQuarantineRecord) plus `verify` (a pure read-only signature check).
|
|
19
19
|
// enable/disable/liftQuarantine are DROPPED from the dep shape on purpose:
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
} from '../../input/config-modal-types.ts';
|
|
19
19
|
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
|
-
// Policy → config-modal surface (
|
|
21
|
+
// Policy → config-modal surface (group-B port). Governance CONTENT view,
|
|
22
22
|
// not a selectable list — current/candidate bundle state, diff, divergence
|
|
23
23
|
// gate, history, permission audit, lint findings, preflight review, and
|
|
24
24
|
// simulation samples render as non-selectable rows (the host windows/scrolls
|
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
} from '../../input/config-modal-types.ts';
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
|
-
// Security → config-modal surface (
|
|
15
|
+
// Security → config-modal surface (group-B port). Two tabs: 'Tokens' (the
|
|
16
16
|
// token scope/rotation audit list under the governance summary) and
|
|
17
17
|
// 'Governance' (policy preflight, MCP/plugin quarantine, latest incident, and
|
|
18
18
|
// the MCP attack-path review). Read-model-backed, so refresh() is a no-op —
|
|
@@ -8,7 +8,7 @@ import type { SkillOrigin, SkillRecord } from '../skills-panel.ts';
|
|
|
8
8
|
import { listInstalledEcosystemEntries, type EcosystemCatalogPathOptions, type ShellPathService } from '@/runtime/index.ts';
|
|
9
9
|
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
|
-
// Skills → config-modal surface (
|
|
11
|
+
// Skills → config-modal surface (group-B port). Project-local and global
|
|
12
12
|
// skill-pack discovery. Disk scans happen only in refresh() (never in
|
|
13
13
|
// buildView), mirroring the panel's onActivate()-triggered load. Browse-only:
|
|
14
14
|
// the panel never had a command-routable mutation verb (no /skills delete/remove
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
import type { WorkPlanItemStatus } from '../../work-plans/work-plan-store.ts';
|
|
8
8
|
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
|
-
// Work Plan → config-modal surface (
|
|
10
|
+
// Work Plan → config-modal surface (group-B port). Lists the active
|
|
11
11
|
// persistent plan's checklist items with a progress summary + status counts.
|
|
12
12
|
// All mutations (add/edit item, cycle/set status, remove, clear-completed)
|
|
13
13
|
// route to the existing `/work-plan` command path (charter: no
|
|
@@ -65,8 +65,8 @@ class WorkPlanModalSurface implements ConfigModalSurface {
|
|
|
65
65
|
private readonly hasRow = (row: ConfigModalRow | null): boolean => row !== null;
|
|
66
66
|
|
|
67
67
|
/** 'i'/'w' jump to the selected item's linked agent/WRFC chain in Fleet
|
|
68
|
-
* (
|
|
69
|
-
* lost when
|
|
68
|
+
* (item 4 — restores the retired WorkPlanPanel's per-item deep-link,
|
|
69
|
+
* lost when migrated this surface to a modal without it). Gated on
|
|
70
70
|
* the link actually being present, same as the retired panel gated the key
|
|
71
71
|
* on `item?.linked?.agentId`/`wrfcId` before ever reaching the jump. */
|
|
72
72
|
private readonly hasAgentLink = (row: ConfigModalRow | null): boolean =>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PanelNotificationFeed — the read-model behind the `panel_only` notification
|
|
3
|
+
* target.
|
|
4
|
+
*
|
|
5
|
+
* The SDK's NotificationRouter (`@/runtime/index.ts` -> platform runtime `ui`
|
|
6
|
+
* barrel) can route a notification's `RoutingDecision.target` to
|
|
7
|
+
* `'panel_only'`, meaning: don't surface this in the conversation or the
|
|
8
|
+
* status bar, but it must still reach a real, visible place. Before this
|
|
9
|
+
* file, nothing in this repo consumed that target outside of tests — a
|
|
10
|
+
* panel-routed notification (including every burst- or batch-collapsed one)
|
|
11
|
+
* had nowhere to go. This feed is that place: a caller holding a routed
|
|
12
|
+
* `Notification` + `RoutingDecision` pair calls `record()`, and
|
|
13
|
+
* `notifications-panel.ts` renders whatever has accumulated.
|
|
14
|
+
*
|
|
15
|
+
* Collapsed groups (reasonCode `burst_collapsed` / `batch_window_collapsed`)
|
|
16
|
+
* are tracked by their `batchKey` and accumulate an honest running count
|
|
17
|
+
* instead of rendering one row per suppressed duplicate.
|
|
18
|
+
*
|
|
19
|
+
* Production wiring — something in the running app actually calling
|
|
20
|
+
* `record()` with live notifications — rides the SDK round that turns on
|
|
21
|
+
* adaptive notification suppression by default; this feed and its panel are
|
|
22
|
+
* the render target that work lands on, built ahead of it so the target is
|
|
23
|
+
* never missing when that switch flips.
|
|
24
|
+
*/
|
|
25
|
+
import type { Notification, RoutingDecision } from '@/runtime/index.ts';
|
|
26
|
+
|
|
27
|
+
export interface PanelFeedEntry {
|
|
28
|
+
readonly key: string;
|
|
29
|
+
readonly domain: string;
|
|
30
|
+
readonly level: Notification['level'];
|
|
31
|
+
readonly title: string;
|
|
32
|
+
readonly body: string | undefined;
|
|
33
|
+
/** Unix ms of the most recent notification folded into this entry. */
|
|
34
|
+
readonly timestamp: number;
|
|
35
|
+
readonly reasonCode: RoutingDecision['reasonCode'];
|
|
36
|
+
/** How many notifications this entry represents. 1 for a standalone item; >1 for a collapsed group. Always the true count — never estimated. */
|
|
37
|
+
readonly collapsedCount: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const MAX_ENTRIES = 200;
|
|
41
|
+
|
|
42
|
+
/** Reason codes whose notifications fold into one running-count entry per batch key, rather than one entry per occurrence. */
|
|
43
|
+
const COLLAPSING_REASON_CODES: ReadonlySet<RoutingDecision['reasonCode']> = new Set([
|
|
44
|
+
'burst_collapsed',
|
|
45
|
+
'batch_window_collapsed',
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export class PanelNotificationFeed {
|
|
49
|
+
private readonly entries = new Map<string, PanelFeedEntry>();
|
|
50
|
+
/** Insertion order of `entries` keys (oldest first), for bounded eviction. */
|
|
51
|
+
private order: string[] = [];
|
|
52
|
+
private readonly listeners = new Set<() => void>();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Record a routed notification. Only notifications actually targeted at
|
|
56
|
+
* `panel_only` are kept — a caller passing anything else is a mistake (the
|
|
57
|
+
* router sent it elsewhere), so it's dropped rather than shown in the
|
|
58
|
+
* wrong place.
|
|
59
|
+
*/
|
|
60
|
+
record(notification: Notification, decision: RoutingDecision): void {
|
|
61
|
+
if (decision.target !== 'panel_only') return;
|
|
62
|
+
|
|
63
|
+
const collapsing = COLLAPSING_REASON_CODES.has(decision.reasonCode) && Boolean(decision.batchKey);
|
|
64
|
+
const key = collapsing ? `group:${decision.batchKey}` : `single:${notification.id}`;
|
|
65
|
+
const previousCount = collapsing ? (this.entries.get(key)?.collapsedCount ?? 0) : 0;
|
|
66
|
+
|
|
67
|
+
const entry: PanelFeedEntry = {
|
|
68
|
+
key,
|
|
69
|
+
domain: notification.domain,
|
|
70
|
+
level: notification.level,
|
|
71
|
+
title: notification.title,
|
|
72
|
+
body: notification.body,
|
|
73
|
+
timestamp: notification.timestamp,
|
|
74
|
+
reasonCode: decision.reasonCode,
|
|
75
|
+
collapsedCount: previousCount + 1,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (!this.entries.has(key)) {
|
|
79
|
+
this.order.push(key);
|
|
80
|
+
if (this.order.length > MAX_ENTRIES) {
|
|
81
|
+
const evicted = this.order.shift();
|
|
82
|
+
if (evicted !== undefined) this.entries.delete(evicted);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
this.entries.set(key, entry);
|
|
86
|
+
this.emitChange();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Every entry, most recently updated first. */
|
|
90
|
+
list(): readonly PanelFeedEntry[] {
|
|
91
|
+
const items: PanelFeedEntry[] = [];
|
|
92
|
+
for (const key of this.order) {
|
|
93
|
+
const entry = this.entries.get(key);
|
|
94
|
+
if (entry) items.push(entry);
|
|
95
|
+
}
|
|
96
|
+
return items.reverse();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
clear(): void {
|
|
100
|
+
this.entries.clear();
|
|
101
|
+
this.order = [];
|
|
102
|
+
this.emitChange();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Subscribe to feed changes (e.g. to mark a panel dirty). Returns an unsubscribe function. */
|
|
106
|
+
subscribe(listener: () => void): () => void {
|
|
107
|
+
this.listeners.add(listener);
|
|
108
|
+
return () => this.listeners.delete(listener);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private emitChange(): void {
|
|
112
|
+
for (const listener of this.listeners) listener();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let sharedFeed: PanelNotificationFeed | null = null;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The process-wide `panel_only` feed. Lazily created so the running app has
|
|
120
|
+
* exactly one feed that every future caller and the NotificationsPanel agree
|
|
121
|
+
* on, while tests construct their own isolated `PanelNotificationFeed`
|
|
122
|
+
* instance instead of reaching for this one.
|
|
123
|
+
*/
|
|
124
|
+
export function getSharedNotificationFeed(): PanelNotificationFeed {
|
|
125
|
+
if (!sharedFeed) sharedFeed = new PanelNotificationFeed();
|
|
126
|
+
return sharedFeed;
|
|
127
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationsPanel — the visible home for notifications routed to the
|
|
3
|
+
* `panel_only` target (see notifications-feed.ts for why this exists).
|
|
4
|
+
*
|
|
5
|
+
* Renders every entry the feed has accumulated: standalone panel-routed
|
|
6
|
+
* notifications, and collapsed burst/batch groups shown honestly with their
|
|
7
|
+
* real running count and the plain-language reason they were held back from
|
|
8
|
+
* the conversation. Descriptive text (titles, bodies, reason descriptions)
|
|
9
|
+
* is always wrapped in full — never clipped to a column width.
|
|
10
|
+
*/
|
|
11
|
+
import type { Line } from '../types/grid.ts';
|
|
12
|
+
import type { RoutingDecision } from '@/runtime/index.ts';
|
|
13
|
+
import { BasePanel } from './base-panel.ts';
|
|
14
|
+
import {
|
|
15
|
+
buildEmptyState,
|
|
16
|
+
buildKeyboardHints,
|
|
17
|
+
buildPanelWorkspace,
|
|
18
|
+
buildStyledPanelLine,
|
|
19
|
+
DEFAULT_PANEL_PALETTE,
|
|
20
|
+
resolveScrollablePanelSection,
|
|
21
|
+
} from './polish.ts';
|
|
22
|
+
import { wrapText } from '../utils/terminal-width.ts';
|
|
23
|
+
import { getSharedNotificationFeed, type PanelFeedEntry, type PanelNotificationFeed } from './notifications-feed.ts';
|
|
24
|
+
|
|
25
|
+
// Reuses the shared, mode-aware (dark/light) panel palette's existing
|
|
26
|
+
// bad/warn/info/dim tokens rather than minting new hex literals — this
|
|
27
|
+
// panel has no color needs the shared palette doesn't already name.
|
|
28
|
+
const C = DEFAULT_PANEL_PALETTE;
|
|
29
|
+
|
|
30
|
+
/** Plain-language description of why a notification never reached the conversation or status bar. Every reason code is covered so a new one fails visibly (a TypeScript error on this object) rather than silently. */
|
|
31
|
+
const REASON_DESCRIPTIONS: Record<RoutingDecision['reasonCode'], string> = {
|
|
32
|
+
allowed: 'routed here by domain verbosity',
|
|
33
|
+
quiet_while_typing: 'held while you were typing',
|
|
34
|
+
mode_context_minimal: 'held back by quiet mode',
|
|
35
|
+
mode_context_normal: 'held back by balanced mode',
|
|
36
|
+
burst_collapsed: 'rapid repeats collapsed',
|
|
37
|
+
batch_window_collapsed: 'repeated within a few seconds, collapsed',
|
|
38
|
+
domain_verbosity_low: "below this domain's verbosity setting",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function levelColor(level: PanelFeedEntry['level']): string {
|
|
42
|
+
switch (level) {
|
|
43
|
+
case 'critical': return C.bad;
|
|
44
|
+
case 'warning': return C.warn;
|
|
45
|
+
case 'debug': return C.dim;
|
|
46
|
+
default: return C.info;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function levelTag(level: PanelFeedEntry['level']): string {
|
|
51
|
+
return `[${level}]`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Build every Line an entry needs — title/meta row, then the full body and reason text wrapped with no line cap (descriptive text is never clipped). */
|
|
55
|
+
function renderEntry(entry: PanelFeedEntry, width: number): Line[] {
|
|
56
|
+
const lines: Line[] = [];
|
|
57
|
+
const fg = levelColor(entry.level);
|
|
58
|
+
const countSuffix = entry.collapsedCount > 1 ? ` ×${entry.collapsedCount}` : '';
|
|
59
|
+
const headerText = `${levelTag(entry.level)} ${entry.domain} — ${entry.title}${countSuffix}`;
|
|
60
|
+
for (const wrapped of wrapText(headerText, Math.max(1, width - 1))) {
|
|
61
|
+
lines.push(buildStyledPanelLine(width, [{ text: ` ${wrapped}`, fg, bold: true }]));
|
|
62
|
+
}
|
|
63
|
+
const reasonText = entry.collapsedCount > 1
|
|
64
|
+
? ` ${entry.collapsedCount} notifications collapsed — ${REASON_DESCRIPTIONS[entry.reasonCode]}`
|
|
65
|
+
: ` ${REASON_DESCRIPTIONS[entry.reasonCode]}`;
|
|
66
|
+
for (const wrapped of wrapText(reasonText, Math.max(1, width - 1))) {
|
|
67
|
+
lines.push(buildStyledPanelLine(width, [{ text: ` ${wrapped}`, fg: C.dim }]));
|
|
68
|
+
}
|
|
69
|
+
if (entry.body) {
|
|
70
|
+
for (const wrapped of wrapText(` ${entry.body}`, Math.max(1, width - 1))) {
|
|
71
|
+
lines.push(buildStyledPanelLine(width, [{ text: ` ${wrapped}`, fg: C.value }]));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return lines;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class NotificationsPanel extends BasePanel {
|
|
78
|
+
private readonly unsubscribe: () => void;
|
|
79
|
+
private scrollOffset = 0;
|
|
80
|
+
|
|
81
|
+
constructor(private readonly feed: PanelNotificationFeed = getSharedNotificationFeed()) {
|
|
82
|
+
super('notifications', 'Notifications', 'N', 'runtime-ops');
|
|
83
|
+
this.unsubscribe = this.feed.subscribe(() => this.markDirty());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
override onDestroy(): void {
|
|
87
|
+
this.unsubscribe();
|
|
88
|
+
super.onDestroy();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
handleInput(key: string): boolean {
|
|
92
|
+
switch (key) {
|
|
93
|
+
case 'up': return this.scroll(-1);
|
|
94
|
+
case 'down': return this.scroll(1);
|
|
95
|
+
case 'pageup': return this.scroll(-10);
|
|
96
|
+
case 'pagedown': return this.scroll(10);
|
|
97
|
+
default: return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private scroll(delta: number): boolean {
|
|
102
|
+
const entries = this.feed.list();
|
|
103
|
+
if (entries.length <= 1) return false;
|
|
104
|
+
const maxOffset = Math.max(0, entries.length - 1);
|
|
105
|
+
const prev = this.scrollOffset;
|
|
106
|
+
this.scrollOffset = Math.max(0, Math.min(maxOffset, this.scrollOffset + delta));
|
|
107
|
+
if (this.scrollOffset !== prev) this.markDirty();
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
render(width: number, height: number): Line[] {
|
|
112
|
+
if (width <= 0 || height <= 0) return [];
|
|
113
|
+
const entries = this.feed.list();
|
|
114
|
+
const hintRow = buildKeyboardHints(width, [
|
|
115
|
+
{ keys: 'Up/Down', label: 'scroll' },
|
|
116
|
+
{ keys: 'PgUp/PgDn', label: 'page' },
|
|
117
|
+
], DEFAULT_PANEL_PALETTE);
|
|
118
|
+
|
|
119
|
+
if (entries.length === 0) {
|
|
120
|
+
return buildPanelWorkspace(width, height, {
|
|
121
|
+
title: ' Notifications',
|
|
122
|
+
intro: 'Notifications held back from the conversation or status bar (panel_only) land here, including their real collapsed counts.',
|
|
123
|
+
sections: [{
|
|
124
|
+
lines: buildEmptyState(
|
|
125
|
+
width,
|
|
126
|
+
' No panel-routed notifications yet',
|
|
127
|
+
'Nothing has been routed here this session. Quiet/balanced-mode operational chatter and collapsed bursts will appear here honestly, with their real counts, once something routes to this target.',
|
|
128
|
+
[],
|
|
129
|
+
DEFAULT_PANEL_PALETTE,
|
|
130
|
+
),
|
|
131
|
+
}],
|
|
132
|
+
footerLines: [hintRow],
|
|
133
|
+
palette: DEFAULT_PANEL_PALETTE,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const scrollableLines = entries.flatMap((entry) => renderEntry(entry, width - 2));
|
|
138
|
+
const section = resolveScrollablePanelSection(width, height, {
|
|
139
|
+
intro: 'Notifications held back from the conversation or status bar (panel_only) land here, including their real collapsed counts.',
|
|
140
|
+
footerLines: [hintRow],
|
|
141
|
+
palette: DEFAULT_PANEL_PALETTE,
|
|
142
|
+
section: {
|
|
143
|
+
title: `Notifications (${entries.length})`,
|
|
144
|
+
scrollableLines,
|
|
145
|
+
scrollOffset: this.scrollOffset,
|
|
146
|
+
minRows: 4,
|
|
147
|
+
appendWindowSummary: scrollableLines.length > 0 ? { dimColor: DEFAULT_PANEL_PALETTE.dim } : undefined,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
this.scrollOffset = section.scrollOffset;
|
|
151
|
+
|
|
152
|
+
return buildPanelWorkspace(width, height, {
|
|
153
|
+
title: ' Notifications',
|
|
154
|
+
intro: 'Notifications held back from the conversation or status bar (panel_only) land here, including their real collapsed counts.',
|
|
155
|
+
sections: [section.section],
|
|
156
|
+
footerLines: [hintRow],
|
|
157
|
+
palette: DEFAULT_PANEL_PALETTE,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -34,18 +34,18 @@ export interface WorkspaceTab {
|
|
|
34
34
|
export class PanelManager {
|
|
35
35
|
private registry: PanelRegistration[] = [];
|
|
36
36
|
private retainedPanels = new Map<string, Panel>();
|
|
37
|
-
/** Old/absorbed panel id -> merged target id (
|
|
37
|
+
/** Old/absorbed panel id -> merged target id (from the console panel merges). */
|
|
38
38
|
private aliases = new Map<string, string>();
|
|
39
39
|
/**
|
|
40
|
-
* Retired panel id -> modal name (
|
|
40
|
+
* Retired panel id -> modal name (panels migrated to config-modal surfaces).
|
|
41
41
|
* Unlike `aliases` (panel -> panel), a hit here means no panel is ever
|
|
42
42
|
* constructed for this id — `open()` invokes `openModalCallback` instead
|
|
43
|
-
* and returns a sentinel.
|
|
44
|
-
*
|
|
43
|
+
* and returns a sentinel. This map and the open()-time check are the
|
|
44
|
+
* sole mechanism for that redirect.
|
|
45
45
|
*/
|
|
46
46
|
private modalRedirects = new Map<string, string>();
|
|
47
47
|
/**
|
|
48
|
-
* Modal name -> the surface the config-modal host renders
|
|
48
|
+
* Modal name -> the surface the config-modal host renders. Built once
|
|
49
49
|
* in builtin-modals.ts (closing over read-models) and looked up by
|
|
50
50
|
* ui-openers' openModal callback. Distinct from `modalRedirects` (panel id ->
|
|
51
51
|
* modal name): a redirect resolves a legacy panel id to a name; this map
|
|
@@ -100,7 +100,7 @@ export class PanelManager {
|
|
|
100
100
|
|
|
101
101
|
registerType(registration: PanelRegistration): void {
|
|
102
102
|
const existing = this.registry.findIndex(r => r.id === registration.id);
|
|
103
|
-
//
|
|
103
|
+
// registry-time icon-uniqueness assertion. Tab-bar icons are a
|
|
104
104
|
// single glyph; two panels sharing one silently made the workspace tab
|
|
105
105
|
// strip ambiguous (the historical W/R/U/K/M/Q/Y/J/P collisions). Compare
|
|
106
106
|
// against every OTHER registration (excluding a re-registration of the
|
|
@@ -276,7 +276,7 @@ export class PanelManager {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/**
|
|
279
|
-
* Hand an optional deep-link target (
|
|
279
|
+
* Hand an optional deep-link target (item 4) to a panel that just
|
|
280
280
|
* resolved from open(). Panels without a "node" concept simply don't
|
|
281
281
|
* implement `receiveDeepLink` — the call is a no-op via optional chaining.
|
|
282
282
|
*/
|
|
@@ -496,7 +496,7 @@ export class PanelManager {
|
|
|
496
496
|
this.bottomPane.panels.push(panel);
|
|
497
497
|
this.bottomPane.activeIndex = 0;
|
|
498
498
|
} else {
|
|
499
|
-
// Open a predictable default panel in the bottom pane.
|
|
499
|
+
// Open a predictable default panel in the bottom pane. purge:
|
|
500
500
|
// 'panel-list' was deleted (dead weight over a 5-panel registry —
|
|
501
501
|
// see the DELETE disposition), so the default is explicitly
|
|
502
502
|
// 'fleet' rather than falling back to registry[0] (whatever
|
|
@@ -606,7 +606,7 @@ export class PanelManager {
|
|
|
606
606
|
toggle(): void {
|
|
607
607
|
this._visible = !this._visible;
|
|
608
608
|
// Auto-open a default panel if toggling visible with nothing open.
|
|
609
|
-
//
|
|
609
|
+
// purge: explicitly 'fleet' rather than registry[0] — see the
|
|
610
610
|
// matching comment in toggleBottomPane() above.
|
|
611
611
|
if (this._visible && this.topPane.panels.length === 0 && this.bottomPane.panels.length === 0) {
|
|
612
612
|
const defaultPanel = this._getRegistration('fleet') ?? this.registry[0];
|
|
@@ -21,11 +21,11 @@ import {
|
|
|
21
21
|
} from './polish.ts';
|
|
22
22
|
|
|
23
23
|
// Base chrome only — title band, state colors, and text tokens all come
|
|
24
|
-
// straight from DEFAULT_PANEL_PALETTE
|
|
24
|
+
// straight from DEFAULT_PANEL_PALETTE.
|
|
25
25
|
const C = DEFAULT_PANEL_PALETTE;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* PluginsPanel drives real plugin-manager mutations (enable/disable/
|
|
29
29
|
* verify/lift-quarantine) from the keyboard, not just read-only inspection.
|
|
30
30
|
* `PluginManagerObserver` alone (the read-only surface most panels need) has
|
|
31
31
|
* no mutation methods, so the constructor param is widened to also require
|
|
@@ -87,7 +87,7 @@ export class PluginsPanel extends ScrollableListPanel<PluginStatus> {
|
|
|
87
87
|
|| plugin.version.toLowerCase().includes(q);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
//
|
|
90
|
+
// selection is preserved across onActivate — BasePanel's default
|
|
91
91
|
// (mark dirty, no index reset) is exactly what we want, so no override.
|
|
92
92
|
|
|
93
93
|
public override onDestroy(): void {
|
|
@@ -149,7 +149,7 @@ export class PluginsPanel extends ScrollableListPanel<PluginStatus> {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
|
-
*
|
|
152
|
+
* optional one-key capture-to-memory for a quarantined plugin — 'm'
|
|
153
153
|
* dispatches `/recall capture plugin <name>` (recall-capture.ts:146 pattern)
|
|
154
154
|
* instead of requiring the operator to type the command by hand.
|
|
155
155
|
*/
|
|
@@ -37,7 +37,7 @@ function compactAnswerDetail(text: string): string {
|
|
|
37
37
|
* recommendedAnswer echoing one of the scope answers verbatim, must not show
|
|
38
38
|
* the same suggested answer twice), then the fixed ask-narrower/custom actions.
|
|
39
39
|
*
|
|
40
|
-
*
|
|
40
|
+
* dismissing planning is no longer a pseudo answer-row — it is a
|
|
41
41
|
* first-class confirmed modal action (the `d` key), so no dismiss row is
|
|
42
42
|
* produced here. A canned answer records against the current open question via
|
|
43
43
|
* /plan answer; the custom row's free-form text is submitted to chat.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// provider-health-routes.ts
|
|
3
3
|
//
|
|
4
|
-
// Single-source auth-route posture for the provider console (
|
|
4
|
+
// Single-source auth-route posture for the provider console (merge of
|
|
5
5
|
// the retired accounts panel into provider-health). All route data flows from
|
|
6
6
|
// ProviderRuntimeInspectionQuery.inspectAll() snapshots; this module only
|
|
7
7
|
// normalizes descriptors (synthesizing them for providers that do not declare
|
|
@@ -137,7 +137,7 @@ function pickRoute(
|
|
|
137
137
|
/**
|
|
138
138
|
* Derive the console's account/auth posture for one inspectAll() snapshot.
|
|
139
139
|
* This is the only route-posture derivation in the TUI (the retired accounts
|
|
140
|
-
* panel's parallel snapshot model was deleted with
|
|
140
|
+
* panel's parallel snapshot model was deleted with).
|
|
141
141
|
*/
|
|
142
142
|
export function buildAccountPosture(snapshot: ProviderRuntimeSnapshotLike): ProviderAccountPosture {
|
|
143
143
|
const auth = snapshot.runtime.auth;
|
|
@@ -259,7 +259,7 @@ export class SkillsPanel extends ScrollableListPanel<SkillRecord> {
|
|
|
259
259
|
public constructor(options: SkillsPanelOptions) {
|
|
260
260
|
super('skills', 'Skills', '▩', 'automation-control', options.componentHealthMonitor);
|
|
261
261
|
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
262
|
-
this.filterEnabled = true; //
|
|
262
|
+
this.filterEnabled = true; // converged modal '/' filter
|
|
263
263
|
this.filterLabel = 'Filter';
|
|
264
264
|
this.shellPaths = options.shellPaths;
|
|
265
265
|
this.ecosystemPaths = options.ecosystemPaths;
|
|
@@ -367,12 +367,12 @@ export class SkillsPanel extends ScrollableListPanel<SkillRecord> {
|
|
|
367
367
|
|
|
368
368
|
public override onDestroy(): void {}
|
|
369
369
|
|
|
370
|
-
//
|
|
370
|
+
// (the purge): Enter used to open the skill's markdown source in the
|
|
371
371
|
// preview panel via a staged pendingOpenPreview flag + handlePanelIntegrationAction
|
|
372
372
|
// (same pattern diff-panel.ts used). 'preview' is DELETE-disposition with no
|
|
373
373
|
// successor surface (no file-picker-overlay preview to repoint to either —
|
|
374
374
|
// verified), so that cross-panel jump was removed rather than repointed;
|
|
375
|
-
// Enter is currently a no-op on this list (browse-only until
|
|
375
|
+
// Enter is currently a no-op on this list (browse-only until migrates
|
|
376
376
|
// Skills to a modal).
|
|
377
377
|
|
|
378
378
|
public handleInput(key: string): boolean {
|
|
@@ -393,8 +393,7 @@ export class SkillsPanel extends ScrollableListPanel<SkillRecord> {
|
|
|
393
393
|
if (confirmResult === 'absorbed') return true;
|
|
394
394
|
|
|
395
395
|
// I1: 'd' prompts delete confirmation — only outside filter mode, so 'd'
|
|
396
|
-
// remains typeable into the filter query while it is active (
|
|
397
|
-
// converged modal '/' filter coexists with single-letter action keys).
|
|
396
|
+
// remains typeable into the filter query while it is active (// converged modal '/' filter coexists with single-letter action keys).
|
|
398
397
|
if (!this.filterActive && key === 'd') {
|
|
399
398
|
const skill = this.getSelectedItem();
|
|
400
399
|
if (skill) {
|
|
@@ -7,7 +7,7 @@ import type { TurnEvent } from '@/runtime/index.ts';
|
|
|
7
7
|
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
8
8
|
import type { UiReadModel, UiSessionSnapshot } from '../runtime/ui-read-models.ts';
|
|
9
9
|
import type { SessionMemoryQuery } from '../runtime/ui-service-queries.ts';
|
|
10
|
-
import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
|
|
10
|
+
import { calcSessionCost, describePricingSource, isModelPriced } from '../export/cost-utils.ts';
|
|
11
11
|
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
12
12
|
import type { PanelIntegrationContext } from './types.ts';
|
|
13
13
|
import {
|
|
@@ -460,7 +460,7 @@ export class TokenBudgetPanel extends BasePanel {
|
|
|
460
460
|
|
|
461
461
|
if (status.guidanceMode !== 'off' && status.nextSteps.length > 0) {
|
|
462
462
|
const nextStep = status.nextSteps[0]!;
|
|
463
|
-
//
|
|
463
|
+
// when the recommended next step is /compact and C is armed
|
|
464
464
|
// (see handleInput's elevated-only C branch and the footer hint),
|
|
465
465
|
// advertise the key instead of the command — pressing C already
|
|
466
466
|
// dispatches it for real, so printing '/compact' here was a redundant
|
|
@@ -618,9 +618,17 @@ export class TokenBudgetPanel extends BasePanel {
|
|
|
618
618
|
const modelId = this.getCurrentModelId();
|
|
619
619
|
const priced = isModelPriced(modelId);
|
|
620
620
|
const cost = calcSessionCost(u.input, u.output, u.cacheRead, u.cacheWrite, modelId);
|
|
621
|
+
// Dollars carry their source; an unknown price renders the explicit
|
|
622
|
+
// marker (never $0.00) and names the one-key fix on the cost panel.
|
|
623
|
+
const source = priced ? describePricingSource(modelId) : null;
|
|
621
624
|
lines.push(buildStyledPanelLine(width, [
|
|
622
625
|
{ text: ' Cost: ', fg: C.label },
|
|
623
|
-
{ text: priced ? `$${cost.toFixed(4)}` : '
|
|
626
|
+
{ text: priced ? `$${cost.toFixed(4)}` : 'price unknown', fg: C.value, bold: true },
|
|
627
|
+
...(priced && source
|
|
628
|
+
? [{ text: ` (${source})`, fg: C.dim }]
|
|
629
|
+
: priced
|
|
630
|
+
? []
|
|
631
|
+
: [{ text: ' — set it with p on the cost panel', fg: C.dim }]),
|
|
624
632
|
]));
|
|
625
633
|
}
|
|
626
634
|
|
package/src/panels/types.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface PanelIntegrationContext {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* A cross-panel deep-link target (
|
|
20
|
+
* A cross-panel deep-link target (item 4): a specific node a panel
|
|
21
21
|
* should select + reveal when opened via `PanelManager.open(id, pane, target)`,
|
|
22
22
|
* e.g. a Fleet tree node keyed by its process id and (optionally) its
|
|
23
23
|
* ProcessKind for disambiguation. Panel-agnostic on purpose — only panels that
|
|
@@ -56,7 +56,7 @@ export type NamedKey =
|
|
|
56
56
|
export type KeyName = NamedKey | (string & {});
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
59
|
+
* the former single 'monitoring' bucket held 33 panels pre-merge —
|
|
60
60
|
* too coarse for the picker to be useful and too large for any one operator
|
|
61
61
|
* mental model. Split into named operator domains so each category holds a
|
|
62
62
|
* bounded, coherent set (no category may exceed 10 registrations):
|
|
@@ -97,7 +97,7 @@ export interface Panel {
|
|
|
97
97
|
isPinned: boolean;
|
|
98
98
|
needsRender: boolean;
|
|
99
99
|
|
|
100
|
-
// Dirty-flag contract (
|
|
100
|
+
// Dirty-flag contract (activated panel render skipping)
|
|
101
101
|
/** Mark this panel as needing a re-render on the next frame. */
|
|
102
102
|
invalidate(): void;
|
|
103
103
|
/** Called by the compositor after a successful render to clear the dirty flag. */
|
|
@@ -154,7 +154,7 @@ export interface Panel {
|
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
156
|
* Optional: consume a deep-link target handed in by `PanelManager.open(id,
|
|
157
|
-
* pane, target)` (
|
|
157
|
+
* pane, target)` (item 4) — select + reveal the matching node on the
|
|
158
158
|
* panel's next snapshot. A panel without a "node" concept simply doesn't
|
|
159
159
|
* implement this (the call is a no-op via optional chaining in
|
|
160
160
|
* PanelManager). A panel that DOES implement it owns its own honest
|
|
@@ -169,7 +169,7 @@ export interface PanelRegistration extends Pick<Panel, 'id' | 'name' | 'icon' |
|
|
|
169
169
|
factory: () => Panel;
|
|
170
170
|
description: string;
|
|
171
171
|
/**
|
|
172
|
-
*
|
|
172
|
+
* lifecycle flags. `preload` and `retainOnClose` are independent —
|
|
173
173
|
* a panel can eagerly instantiate without being kept alive on close, or be
|
|
174
174
|
* kept alive on close without eager bootstrap instantiation. They happened
|
|
175
175
|
* to be identical for all builtin panels before this split (both driven by
|