@pellux/goodvibes-tui 1.1.0 → 1.7.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 +20 -11
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +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/conversation-rendering.ts +19 -0
- 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/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +36 -0
- package/src/input/command-registry.ts +38 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- 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 +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- 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 +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- 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-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +15 -7
- 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 +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-feed-routes.ts +61 -23
- package/src/input/handler-feed.ts +47 -23
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +6 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +24 -27
- 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 +9 -43
- 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-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -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 +151 -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 +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -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 +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +13 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- 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/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/turn-injection.ts +114 -0
- package/src/runtime/bootstrap-command-context.ts +21 -1
- package/src/runtime/bootstrap-command-parts.ts +34 -7
- package/src/runtime/bootstrap-core.ts +14 -4
- package/src/runtime/bootstrap-shell.ts +29 -16
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- 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
|
@@ -18,15 +18,21 @@
|
|
|
18
18
|
* When neither target is available the function is an honest no-op (debug log
|
|
19
19
|
* only; no user-facing error spam).
|
|
20
20
|
*
|
|
21
|
-
* Focus tracking:
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* Focus tracking (W2.3): when `focusTracker` is supplied, notifications are
|
|
22
|
+
* gated the same way as the other unfocused-user alert classes (see
|
|
23
|
+
* alert-gating.ts) — they fire when the terminal is unfocused or focus state
|
|
24
|
+
* was never observed, and are suppressed when it's known to be focused,
|
|
25
|
+
* unless `behavior.notifyOnlyWhenUnfocused` is turned off. `focusTracker` is
|
|
26
|
+
* optional and defaults to "always fire" (the pre-W2.3 behavior) when
|
|
27
|
+
* omitted, so existing callers that don't have a FocusTracker in scope are
|
|
28
|
+
* unaffected.
|
|
25
29
|
*/
|
|
26
30
|
|
|
27
31
|
import { notifyCompletion } from '@pellux/goodvibes-sdk/platform/utils';
|
|
28
32
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
29
33
|
import type { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
|
|
34
|
+
import type { FocusTracker } from './focus-tracker.ts';
|
|
35
|
+
import { readNotifyOnlyWhenUnfocused, type ConfigGet } from './alert-gating.ts';
|
|
30
36
|
|
|
31
37
|
/** Default threshold in seconds. Turns shorter than this do not notify. */
|
|
32
38
|
export const NOTIFY_AFTER_SECONDS_DEFAULT = 60;
|
|
@@ -74,6 +80,18 @@ export interface MaybeNotifyLongTaskOptions {
|
|
|
74
80
|
* skipped silently.
|
|
75
81
|
*/
|
|
76
82
|
readonly webhookNotifier?: WebhookNotifier | null;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Terminal focus tracker (W2.3). When supplied together with `configGet`,
|
|
86
|
+
* the notification additionally respects behavior.notifyOnlyWhenUnfocused
|
|
87
|
+
* (default on): suppressed when the terminal is known to be focused, fires
|
|
88
|
+
* when unfocused or when focus was never observed. Omit both to preserve
|
|
89
|
+
* pre-W2.3 behavior (always fire once the threshold is met).
|
|
90
|
+
*/
|
|
91
|
+
readonly focusTracker?: Pick<FocusTracker, 'shouldAlertWhenUnfocused'> | null;
|
|
92
|
+
|
|
93
|
+
/** Config reader used only for the notifyOnlyWhenUnfocused gate above. */
|
|
94
|
+
readonly configGet?: ConfigGet;
|
|
77
95
|
}
|
|
78
96
|
|
|
79
97
|
/**
|
|
@@ -81,13 +99,14 @@ export interface MaybeNotifyLongTaskOptions {
|
|
|
81
99
|
* exceeds the configured threshold.
|
|
82
100
|
*
|
|
83
101
|
* Returns true when at least one delivery was attempted, false when the
|
|
84
|
-
* call was a no-op (threshold not reached, or off
|
|
102
|
+
* call was a no-op (threshold not reached, off-state, or focus-gated off —
|
|
103
|
+
* see `focusTracker`/`configGet` above).
|
|
85
104
|
*
|
|
86
105
|
* PRIVACY: builds message text from structural metadata only (kind, elapsed,
|
|
87
106
|
* status, sessionId). Never includes conversation content.
|
|
88
107
|
*/
|
|
89
108
|
export function maybeNotifyLongTask(opts: MaybeNotifyLongTaskOptions): boolean {
|
|
90
|
-
const { elapsedMs, status, kind, sessionId, thresholdSeconds, webhookNotifier } = opts;
|
|
109
|
+
const { elapsedMs, status, kind, sessionId, thresholdSeconds, webhookNotifier, focusTracker, configGet } = opts;
|
|
91
110
|
|
|
92
111
|
// Off-state: 0 disables notifications entirely.
|
|
93
112
|
if (thresholdSeconds === NOTIFY_AFTER_SECONDS_OFF) {
|
|
@@ -102,6 +121,14 @@ export function maybeNotifyLongTask(opts: MaybeNotifyLongTaskOptions): boolean {
|
|
|
102
121
|
return false;
|
|
103
122
|
}
|
|
104
123
|
|
|
124
|
+
// Focus gate (W2.3): only applied when both a tracker and a config reader
|
|
125
|
+
// are supplied. Absent either one, behavior is unchanged from pre-W2.3
|
|
126
|
+
// (always fire once the threshold is met).
|
|
127
|
+
if (focusTracker && configGet && readNotifyOnlyWhenUnfocused(configGet) && !focusTracker.shouldAlertWhenUnfocused()) {
|
|
128
|
+
logger.debug('long-task-notifier: suppressed — terminal focused');
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
105
132
|
// Build concise, metadata-only message. No conversation text.
|
|
106
133
|
const statusLabel = status === 'ok' ? 'completed' : 'failed';
|
|
107
134
|
const title = `GoodVibes — ${kind} ${statusLabel}`;
|
|
@@ -5,19 +5,26 @@
|
|
|
5
5
|
* Delivery is resolved from the message KIND ('system' | 'wrfc' |
|
|
6
6
|
* 'operational'), which maps to a configurable routing target
|
|
7
7
|
* (ui.systemMessages / ui.wrfcMessages / ui.operationalMessages, each
|
|
8
|
-
* 'panel' | 'conversation' | 'both'). resolveSystemMessageDelivery() turns
|
|
9
|
-
* target — plus whether a panel is attached — into a { toPanel,
|
|
10
|
-
* decision.
|
|
11
|
-
* not by itself decide conversation delivery.
|
|
8
|
+
* 'panel' | 'conversation' | 'both'). resolveSystemMessageDelivery() turns
|
|
9
|
+
* that target — plus whether a panel is attached — into a { toPanel,
|
|
10
|
+
* toConversation } decision.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
* (
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* W6.1 (the purge): the SystemMessagesPanel this router used to optionally
|
|
13
|
+
* push into was DELETE-disposition (no surviving human surface — a picker
|
|
14
|
+
* over the old panel registry, not something worth a dedicated console) and
|
|
15
|
+
* has been removed entirely, so this router now always resolves with
|
|
16
|
+
* `hasPanel = false`. Per resolveSystemMessageDelivery's own contract that
|
|
17
|
+
* means EVERY kind/target combination (including 'panel'-only) falls back
|
|
18
|
+
* to `toConversation: true` — nothing this router routes can vanish; it all
|
|
19
|
+
* reaches conversation.addTypedSystemMessage(), which the transcript
|
|
20
|
+
* renders as a navigable system line. This is deliberate, not a regression:
|
|
21
|
+
* operational chatter that used to be tucked away in a rarely-opened panel
|
|
22
|
+
* now surfaces inline, same as the messages that were already forced there
|
|
23
|
+
* (see FORCE_CONVERSATION_PREFIXES below).
|
|
17
24
|
*
|
|
18
25
|
* Usage:
|
|
19
26
|
* ```ts
|
|
20
|
-
* const router = createSystemMessageRouter(conversation
|
|
27
|
+
* const router = createSystemMessageRouter(conversation);
|
|
21
28
|
* router.routeSystemMessage('[Provider] anthropic registered', 'low');
|
|
22
29
|
* router.routeSystemMessage('[Session] Saved session abc123', 'high');
|
|
23
30
|
* ```
|
|
@@ -33,7 +40,6 @@
|
|
|
33
40
|
*/
|
|
34
41
|
|
|
35
42
|
import type { ConversationManager } from './conversation';
|
|
36
|
-
import type { SystemMessagesPanel, SystemMessagePriority } from '../panels/system-messages-panel.ts';
|
|
37
43
|
import {
|
|
38
44
|
classifySystemMessageKind,
|
|
39
45
|
classifySystemMessagePriority,
|
|
@@ -48,13 +54,15 @@ export type {
|
|
|
48
54
|
SystemMessageTarget,
|
|
49
55
|
} from '@/runtime/index.ts';
|
|
50
56
|
|
|
57
|
+
/** Panel emphasis level. Panel delivery was removed in W6.1 (see file doc); kept as the priority vocabulary for callers and for the SDK's delivery-resolution signature. */
|
|
58
|
+
export type SystemMessagePriority = 'high' | 'low';
|
|
59
|
+
|
|
51
60
|
/**
|
|
52
61
|
* Message categories that are operationally critical enough that the user must
|
|
53
62
|
* see them inline in the main conversation, regardless of the configured
|
|
54
|
-
* routing target
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* failed over, or the context was compacted.
|
|
63
|
+
* routing target. Errors, provider failovers, and compaction/context notices
|
|
64
|
+
* fall here: a user should never have to go hunting to discover that a turn
|
|
65
|
+
* errored, a provider failed over, or the context was compacted.
|
|
58
66
|
*
|
|
59
67
|
* Detection is by the stable message prefix tag, mirroring how the SDK
|
|
60
68
|
* classifiers key off message content. This deliberately does NOT force every
|
|
@@ -72,13 +80,12 @@ function mustReachConversation(message: string): boolean {
|
|
|
72
80
|
// ---------------------------------------------------------------------------
|
|
73
81
|
|
|
74
82
|
/**
|
|
75
|
-
* Routes system messages to the conversation
|
|
76
|
-
*
|
|
83
|
+
* Routes system messages to the conversation based on priority level and
|
|
84
|
+
* configured target. See file doc for the W6.1 panel removal.
|
|
77
85
|
*/
|
|
78
86
|
export class SystemMessageRouter {
|
|
79
87
|
constructor(
|
|
80
88
|
private readonly conversation: ConversationManager,
|
|
81
|
-
private panel: SystemMessagesPanel | null,
|
|
82
89
|
private readonly getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget = defaultSystemMessageTarget,
|
|
83
90
|
) {}
|
|
84
91
|
|
|
@@ -87,28 +94,26 @@ export class SystemMessageRouter {
|
|
|
87
94
|
/**
|
|
88
95
|
* Route a system message.
|
|
89
96
|
*
|
|
90
|
-
* Delivery is resolved from the kind and its configured target
|
|
91
|
-
*
|
|
92
|
-
*
|
|
97
|
+
* Delivery is resolved from the kind and its configured target. Critical
|
|
98
|
+
* notices (errors/failover/compaction, see FORCE_CONVERSATION_PREFIXES)
|
|
99
|
+
* are additionally forced into the conversation.
|
|
93
100
|
*
|
|
94
101
|
* @param message - Message text.
|
|
95
|
-
* @param priority - 'high' | 'low'
|
|
102
|
+
* @param priority - 'high' | 'low' (kept for callers; no longer changes
|
|
103
|
+
* delivery now that there is no panel to emphasize on).
|
|
96
104
|
* @param kind - Classification kind ('system' | 'wrfc' | 'operational');
|
|
97
105
|
* used to resolve routing target and conversation navigability.
|
|
98
106
|
*/
|
|
99
107
|
routeTypedSystemMessage(
|
|
100
108
|
message: string,
|
|
101
|
-
|
|
109
|
+
_priority: SystemMessagePriority,
|
|
102
110
|
kind: SystemMessageKind,
|
|
103
111
|
): void {
|
|
104
112
|
const target = this.getTargetForKind(kind);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// Critical notices (errors, failover, compaction) must surface inline even
|
|
110
|
-
// when the configured target is panel-only — otherwise a user without the
|
|
111
|
-
// SystemMessagesPanel open would never see them.
|
|
113
|
+
// hasPanel is always false post-W6.1 — resolveSystemMessageDelivery's own
|
|
114
|
+
// contract means every target ('panel' | 'conversation' | 'both')
|
|
115
|
+
// resolves toConversation: true in that case (see file doc).
|
|
116
|
+
const delivery = resolveSystemMessageDelivery(target, false);
|
|
112
117
|
const toConversation = delivery.toConversation || mustReachConversation(message);
|
|
113
118
|
if (toConversation) {
|
|
114
119
|
// addTypedSystemMessage threads the kind into the conversation so the
|
|
@@ -153,21 +158,6 @@ export class SystemMessageRouter {
|
|
|
153
158
|
wrfc(message: string, priority: SystemMessagePriority = 'high'): void {
|
|
154
159
|
this.routeTypedSystemMessage(message, priority, 'wrfc');
|
|
155
160
|
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Returns the current panel reference.
|
|
159
|
-
*/
|
|
160
|
-
getPanel(): SystemMessagesPanel | null {
|
|
161
|
-
return this.panel;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Replace the panel reference after construction (late binding).
|
|
166
|
-
* Pass null to detach the panel.
|
|
167
|
-
*/
|
|
168
|
-
setPanel(panel: SystemMessagesPanel | null): void {
|
|
169
|
-
this.panel = panel;
|
|
170
|
-
}
|
|
171
161
|
}
|
|
172
162
|
|
|
173
163
|
// ---------------------------------------------------------------------------
|
|
@@ -175,17 +165,13 @@ export class SystemMessageRouter {
|
|
|
175
165
|
// ---------------------------------------------------------------------------
|
|
176
166
|
|
|
177
167
|
/**
|
|
178
|
-
* Create a SystemMessageRouter wired to the given conversation
|
|
168
|
+
* Create a SystemMessageRouter wired to the given conversation.
|
|
179
169
|
*
|
|
180
|
-
* @param conversation - The ConversationManager
|
|
181
|
-
* @param panel - The SystemMessagesPanel for all messages. Can be null
|
|
182
|
-
* (router still works; messages to panel are silently
|
|
183
|
-
* dropped until a panel is available).
|
|
170
|
+
* @param conversation - The ConversationManager all routed messages reach.
|
|
184
171
|
*/
|
|
185
172
|
export function createSystemMessageRouter(
|
|
186
173
|
conversation: ConversationManager,
|
|
187
|
-
panel: SystemMessagesPanel | null = null,
|
|
188
174
|
getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget = defaultSystemMessageTarget,
|
|
189
175
|
): SystemMessageRouter {
|
|
190
|
-
return new SystemMessageRouter(conversation,
|
|
176
|
+
return new SystemMessageRouter(conversation, getTargetForKind);
|
|
191
177
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Orchestrator } from './orchestrator';
|
|
2
|
+
import type { SpokenTurnRuntime } from '../audio/spoken-turn-wiring.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds the shared "cancel the active turn" action: stops any in-flight
|
|
6
|
+
* spoken output, then aborts the orchestrator's turn if one is actively
|
|
7
|
+
* streaming. Extracted from main.ts so the file can stay under its 800-line
|
|
8
|
+
* cap while still wiring `commandContext.isGenerating` (see W1.6 FIX 1).
|
|
9
|
+
*/
|
|
10
|
+
export function createCancelGeneration(
|
|
11
|
+
orchestrator: Orchestrator,
|
|
12
|
+
spokenTurns: Pick<SpokenTurnRuntime, 'stop'>,
|
|
13
|
+
): () => void {
|
|
14
|
+
return () => {
|
|
15
|
+
spokenTurns.stop('Spoken output stopped.');
|
|
16
|
+
if (orchestrator.isThinking) {
|
|
17
|
+
orchestrator.abort();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -6,7 +6,12 @@ import type { HookDispatcher, HookPhase, HookCategory, HookEventPath } from '@pe
|
|
|
6
6
|
import type { ConversationManager } from './conversation.ts';
|
|
7
7
|
import { journalPathFor, openTranscriptJournal, type TranscriptJournal } from './transcript-journal.ts';
|
|
8
8
|
import type { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
|
|
9
|
+
import { notifyCompletion } from '@pellux/goodvibes-sdk/platform/utils';
|
|
9
10
|
import { maybeNotifyLongTask, readNotifyAfterSeconds, type LongTaskStatus } from './long-task-notifier.ts';
|
|
11
|
+
import type { FocusTracker } from './focus-tracker.ts';
|
|
12
|
+
import { shouldFireAlert, FORCE_NOTIFY_DURATION_MS } from './alert-gating.ts';
|
|
13
|
+
import { createBudgetBreachNotifier, type BudgetBreachNotifier } from './budget-breach-notifier.ts';
|
|
14
|
+
import { readBudgetAlertUsd } from '../export/cost-utils.ts';
|
|
10
15
|
|
|
11
16
|
/** Infer the options param of persistConversation to pick up SessionManager correctly. */
|
|
12
17
|
type PersistOptions = NonNullable<Parameters<typeof persistConversation>[5]>;
|
|
@@ -14,11 +19,13 @@ type PersistOptions = NonNullable<Parameters<typeof persistConversation>[5]>;
|
|
|
14
19
|
/** Minimal orchestrator surface required by turn-event wiring. */
|
|
15
20
|
interface TurnOrchestrator {
|
|
16
21
|
readonly lastInputTokens: number;
|
|
22
|
+
/** Cumulative session usage — same object CostTrackerPanel reads, used here for budget-breach checks. */
|
|
23
|
+
readonly usage: { readonly input: number; readonly output: number; readonly cacheRead: number; readonly cacheWrite: number };
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
/** Minimal provider registry surface required by turn-event wiring. */
|
|
20
27
|
interface TurnProviderRegistry {
|
|
21
|
-
getCurrentModel(): { readonly contextWindow: number };
|
|
28
|
+
getCurrentModel(): { readonly contextWindow: number; readonly id?: string };
|
|
22
29
|
getContextWindowForModel(model: { readonly contextWindow: number }): number;
|
|
23
30
|
}
|
|
24
31
|
|
|
@@ -57,6 +64,16 @@ export interface WireTurnEventHandlersOptions {
|
|
|
57
64
|
* when absent.
|
|
58
65
|
*/
|
|
59
66
|
readonly webhookNotifier?: WebhookNotifier | null;
|
|
67
|
+
/**
|
|
68
|
+
* Terminal focus tracker (W2.3). Gates the long-task, budget-breach, and
|
|
69
|
+
* agent/chain-failure desktop alerts wired in this module — see
|
|
70
|
+
* alert-gating.ts. Optional; when absent, none of the new W2.3 alert
|
|
71
|
+
* behavior is gated by focus (long-task keeps its pre-W2.3 always-fire
|
|
72
|
+
* behavior, and budget-breach/failure alerts are skipped entirely, since
|
|
73
|
+
* they are new-in-W2.3 and have no pre-existing unconditional-fire path
|
|
74
|
+
* to fall back to).
|
|
75
|
+
*/
|
|
76
|
+
readonly focusTracker?: Pick<FocusTracker, 'shouldAlertWhenUnfocused'> | null;
|
|
60
77
|
/**
|
|
61
78
|
* Minimal test seam: injectable clock for controlling Date.now() in tests.
|
|
62
79
|
* Defaults to the real Date.now when absent.
|
|
@@ -94,13 +111,14 @@ export function wireTurnEventHandlers(
|
|
|
94
111
|
options: WireTurnEventHandlersOptions,
|
|
95
112
|
): WireTurnEventHandlersResult {
|
|
96
113
|
const {
|
|
97
|
-
events, conversation, runtime, configManager, hookDispatcher,
|
|
114
|
+
events, conversation, runtime, configManager, hookDispatcher, orchestrator, providerRegistry,
|
|
98
115
|
workingDir, homeDirectory, sessionManager, gitStatusProvider,
|
|
99
|
-
lastGitInfoRef, buildSessionContinuityHints, render, webhookNotifier,
|
|
116
|
+
lastGitInfoRef, buildSessionContinuityHints, render, webhookNotifier, focusTracker,
|
|
100
117
|
_clock = Date.now,
|
|
101
118
|
} = options;
|
|
102
119
|
|
|
103
120
|
const unsubs: Array<() => void> = [];
|
|
121
|
+
const configGet = (k: string): unknown => configManager.get(k as Parameters<typeof configManager.get>[0]);
|
|
104
122
|
|
|
105
123
|
// Create the per-session transcript journal. Path mirrors recovery-file
|
|
106
124
|
// convention (homeDirectory-scoped). Created lazily on first append.
|
|
@@ -112,6 +130,13 @@ export function wireTurnEventHandlers(
|
|
|
112
130
|
// Track turn start time for long-task notification threshold.
|
|
113
131
|
let turnStartTime: number | null = null;
|
|
114
132
|
|
|
133
|
+
// Budget-breach edge-trigger checker (W2.3) — one instance per session,
|
|
134
|
+
// piggybacking on the same TURN_COMPLETED handler as the long-task
|
|
135
|
+
// notification below rather than adding a second TURN_COMPLETED subscription.
|
|
136
|
+
const budgetBreachNotifier: BudgetBreachNotifier | null = focusTracker
|
|
137
|
+
? createBudgetBreachNotifier({ focusTracker, configGet, webhookNotifier: webhookNotifier ?? null, sessionId: runtime.sessionId })
|
|
138
|
+
: null;
|
|
139
|
+
|
|
115
140
|
const refreshGit = (): void => {
|
|
116
141
|
gitStatusProvider.refresh().then((info) => { lastGitInfoRef.value = info; render(); }).catch(() => { /* non-fatal */ });
|
|
117
142
|
};
|
|
@@ -140,7 +165,15 @@ export function wireTurnEventHandlers(
|
|
|
140
165
|
sessionId: runtime.sessionId,
|
|
141
166
|
thresholdSeconds: notifyThreshold,
|
|
142
167
|
webhookNotifier: webhookNotifier ?? null,
|
|
168
|
+
focusTracker,
|
|
169
|
+
configGet,
|
|
143
170
|
});
|
|
171
|
+
// Budget-breach alert (W2.3): edge-triggered, piggybacks on this same
|
|
172
|
+
// TURN_COMPLETED handler rather than a second subscription.
|
|
173
|
+
if (budgetBreachNotifier) {
|
|
174
|
+
const sessionModel = providerRegistry.getCurrentModel().id ?? 'unknown';
|
|
175
|
+
budgetBreachNotifier.check(orchestrator.usage, sessionModel, readBudgetAlertUsd(configGet));
|
|
176
|
+
}
|
|
144
177
|
// Auto-save after every LLM turn so kills don't lose the session
|
|
145
178
|
try {
|
|
146
179
|
const snapshot = conversation.toJSON() as { messages: Array<import('./conversation.ts').ConversationMessageSnapshot>; timestamp?: number };
|
|
@@ -180,5 +213,33 @@ export function wireTurnEventHandlers(
|
|
|
180
213
|
refreshGit();
|
|
181
214
|
}));
|
|
182
215
|
|
|
216
|
+
// Agent/chain-failure desktop alerts (W2.3). The SDK's WebhookNotifier and
|
|
217
|
+
// Notifier already fire webhook/Slack/Discord notifications for these two
|
|
218
|
+
// events unconditionally (attachToRuntimeBus in the SDK's
|
|
219
|
+
// platform/integrations — pre-existing, not focus-gated: an out-of-band
|
|
220
|
+
// push to another device is useful regardless of terminal focus). What was
|
|
221
|
+
// missing was a desktop notification, gated by focus like the other three
|
|
222
|
+
// alert classes — that's the only thing added here, to avoid double-firing
|
|
223
|
+
// a webhook that's already covered.
|
|
224
|
+
if (focusTracker) {
|
|
225
|
+
unsubs.push(events.agents.on('AGENT_FAILED', (payload) => {
|
|
226
|
+
if (!shouldFireAlert(focusTracker, configGet, 'behavior.notifyOnAgentFailure')) return;
|
|
227
|
+
try {
|
|
228
|
+
notifyCompletion('GoodVibes — agent failed', `agent ${payload.agentId.slice(0, 8)} failed: ${payload.error}`, FORCE_NOTIFY_DURATION_MS);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
logger.debug('turn-event-wiring: agent-failure notify error', { error: String(err) });
|
|
231
|
+
}
|
|
232
|
+
}));
|
|
233
|
+
unsubs.push(events.workflows.on('WORKFLOW_CHAIN_FAILED', (payload) => {
|
|
234
|
+
if (!shouldFireAlert(focusTracker, configGet, 'behavior.notifyOnChainFailure')) return;
|
|
235
|
+
const kindLabel = payload.failureKind === 'transport' ? 'transient transport error' : payload.reason;
|
|
236
|
+
try {
|
|
237
|
+
notifyCompletion('GoodVibes — WRFC chain failed', `chain ${payload.chainId.slice(0, 12)} failed: ${kindLabel}`, FORCE_NOTIFY_DURATION_MS);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
logger.debug('turn-event-wiring: chain-failure notify error', { error: String(err) });
|
|
240
|
+
}
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
|
|
183
244
|
return { refreshGit, unsubs, transcriptJournal };
|
|
184
245
|
}
|
package/src/export/cost-utils.ts
CHANGED
|
@@ -151,3 +151,39 @@ export function calcSessionCost(
|
|
|
151
151
|
const billableInput = inputTokens + cacheRead + cacheWrite;
|
|
152
152
|
return (billableInput * pricing.input + outputTokens * pricing.output) / 1_000_000;
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* computeBudgetBreach — pure predicate shared by CostTrackerPanel's render-time
|
|
157
|
+
* "OVER BUDGET" flag and the background budget-breach notifier
|
|
158
|
+
* (core/budget-breach-notifier.ts), so both agree on exactly one definition
|
|
159
|
+
* of "over budget". `budgetThreshold <= 0` means no budget is configured
|
|
160
|
+
* (disabled), which can never be breached.
|
|
161
|
+
*/
|
|
162
|
+
export function computeBudgetBreach(sessionCost: number, budgetThreshold: number): boolean {
|
|
163
|
+
return budgetThreshold > 0 && sessionCost > budgetThreshold;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Read/write access to the `behavior.budgetAlertUsd` TUI-local config key. Plain
|
|
167
|
+
* callbacks (rather than a ConfigManager reference) so callers can pass a
|
|
168
|
+
* generic-key-cast wrapper without this module depending on the SDK config types. */
|
|
169
|
+
export interface BudgetAlertConfigAccess {
|
|
170
|
+
readonly get: (key: string) => unknown;
|
|
171
|
+
readonly set: (key: string, value: unknown) => void;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Default for the `behavior.budgetAlertUsd` synthetic setting: 0 = no budget configured. */
|
|
175
|
+
export const BUDGET_ALERT_USD_DEFAULT = 0;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* readBudgetAlertUsd — read the session cost-budget alert threshold (USD) from
|
|
179
|
+
* config. This is the single source of truth CostTrackerPanel and the
|
|
180
|
+
* background budget-breach notifier (core/budget-breach-notifier.ts) both
|
|
181
|
+
* read, so a threshold set via the panel's 'b' key is immediately visible to
|
|
182
|
+
* the notifier and vice versa. Falls back to BUDGET_ALERT_USD_DEFAULT (off)
|
|
183
|
+
* when the key is absent or invalid.
|
|
184
|
+
*/
|
|
185
|
+
export function readBudgetAlertUsd(configGet: (key: string) => unknown): number {
|
|
186
|
+
const raw = configGet('behavior.budgetAlertUsd');
|
|
187
|
+
const parsed = typeof raw === 'number' ? raw : Number(raw);
|
|
188
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : BUDGET_ALERT_USD_DEFAULT;
|
|
189
|
+
}
|
|
@@ -7,6 +7,7 @@ import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
|
|
|
7
7
|
import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
8
8
|
import type { SelectionItem, SelectionResult, SelectionAction } from './selection-modal.ts';
|
|
9
9
|
import type { FileUndoManager } from '@pellux/goodvibes-sdk/platform/state';
|
|
10
|
+
import type { WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
|
|
10
11
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
11
12
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
12
13
|
import type { OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
|
|
@@ -68,6 +69,9 @@ export interface CommandUiActions {
|
|
|
68
69
|
};
|
|
69
70
|
executeCommand?: (name: string, args: string[]) => Promise<boolean>;
|
|
70
71
|
cancelGeneration?: () => void;
|
|
72
|
+
/** True while an LLM turn is actively streaming. Used to give Escape
|
|
73
|
+
* cancel-turn precedence over a focused panel's own escape handling. */
|
|
74
|
+
isGenerating?: () => boolean;
|
|
71
75
|
completeModelSelection?: (selection: {
|
|
72
76
|
model: { id: string; provider: string; displayName: string; registryKey: string };
|
|
73
77
|
effort: string;
|
|
@@ -75,6 +79,14 @@ export interface CommandUiActions {
|
|
|
75
79
|
/** Which config target to write the selected model to. Defaults to 'main'. */
|
|
76
80
|
target?: import('./model-picker.ts').ModelPickerTarget;
|
|
77
81
|
}) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Commit an embedding-provider selection from the model picker's
|
|
84
|
+
* 'embeddingProvider' mode (the 'embeddings' target). Deliberately separate
|
|
85
|
+
* from completeModelSelection — embedding providers are not
|
|
86
|
+
* ModelDefinition-shaped, so routing them through the same commit path
|
|
87
|
+
* would require fabricating a fake model object.
|
|
88
|
+
*/
|
|
89
|
+
completeEmbeddingProviderSelection?: (providerId: string) => void;
|
|
78
90
|
clearScreen?: () => void;
|
|
79
91
|
activatePlan?: (planId: string, task: string) => void;
|
|
80
92
|
requestPermission?: PermissionRequestHandler;
|
|
@@ -107,6 +119,13 @@ export interface CommandShellUiOpeners {
|
|
|
107
119
|
callback: (result: SelectionResult | null) => void,
|
|
108
120
|
) => void;
|
|
109
121
|
openSettingsModal?: (target?: string) => void;
|
|
122
|
+
/**
|
|
123
|
+
* Open a MIGRATE-TO-MODAL surface by name (W6.1 purge skeleton — WO-A/B
|
|
124
|
+
* command runtimes call this instead of openCommandPanel once their panel
|
|
125
|
+
* is converted to a ModalFactory config). Threaded from ui-openers.ts the
|
|
126
|
+
* same way openSettingsModal is.
|
|
127
|
+
*/
|
|
128
|
+
openModal?: (name: string) => void;
|
|
110
129
|
openSessionPicker?: () => void;
|
|
111
130
|
openProfilePicker?: () => void;
|
|
112
131
|
openShortcutsOverlay?: () => void;
|
|
@@ -157,6 +176,18 @@ export interface CommandSessionServices {
|
|
|
157
176
|
* fresh Orchestrator's zeroed default (W0.9).
|
|
158
177
|
*/
|
|
159
178
|
readonly hydrateSessionUsage?: () => void;
|
|
179
|
+
/** Wave 4 (wo703): the orchestration engine's command-facing facade — see runtime/workstream-services.ts. */
|
|
180
|
+
readonly workstreamEngine?: import('../runtime/workstream-services.ts').WorkstreamCommandService;
|
|
181
|
+
/** Wave 5 (wo804): the repo source-tree code index — see runtime/code-index-services.ts. */
|
|
182
|
+
readonly codeIndexStore?: import('@pellux/goodvibes-sdk/platform/state').CodeIndexStore;
|
|
183
|
+
/**
|
|
184
|
+
* Wave 5 (wo805): the MAIN interactive session's per-turn passive-injection
|
|
185
|
+
* honesty ring — `Orchestrator.getTurnInjections()`, the main-session
|
|
186
|
+
* counterpart to `AgentRecord.turnInjections` (wo801). `/recall injections`
|
|
187
|
+
* with no agent id reads this. Optional so command contexts built without an
|
|
188
|
+
* orchestrator (headless, tests) fall back to the honest empty state.
|
|
189
|
+
*/
|
|
190
|
+
readonly getMainSessionTurnInjections?: () => readonly import('../renderer/turn-injection.ts').TurnInjectionEntry[];
|
|
160
191
|
}
|
|
161
192
|
|
|
162
193
|
export interface CommandProviderServices {
|
|
@@ -173,6 +204,7 @@ export interface CommandProviderServices {
|
|
|
173
204
|
export interface CommandWorkspaceUiServices {
|
|
174
205
|
keybindingsManager?: KeybindingsManager;
|
|
175
206
|
fileUndoManager?: FileUndoManager;
|
|
207
|
+
workspaceCheckpointManager?: WorkspaceCheckpointManager;
|
|
176
208
|
panelManager?: PanelManager;
|
|
177
209
|
profileManager?: import('@pellux/goodvibes-sdk/platform/profiles').ProfileManager;
|
|
178
210
|
bookmarkManager?: import('@pellux/goodvibes-sdk/platform/bookmarks').BookmarkManager;
|
|
@@ -190,6 +222,11 @@ export interface CommandPlatformConfigServices {
|
|
|
190
222
|
readonly configManager: ConfigManager;
|
|
191
223
|
readonly voiceProviderRegistry?: VoiceProviderRegistry;
|
|
192
224
|
readonly voiceService?: VoiceService;
|
|
225
|
+
/** Direct-command consumer (`/search`) alongside the existing agent-tool consumer. */
|
|
226
|
+
readonly webSearchService?: import('@pellux/goodvibes-sdk/platform/web-search').WebSearchService;
|
|
227
|
+
/** Direct-command consumer (`/image`) — first production caller of `.generate()`. */
|
|
228
|
+
readonly mediaProviders?: import('@pellux/goodvibes-sdk/platform/media').MediaProviderRegistry;
|
|
229
|
+
readonly artifactStore?: import('@pellux/goodvibes-sdk/platform/artifacts').ArtifactStore;
|
|
193
230
|
}
|
|
194
231
|
|
|
195
232
|
export interface CommandPlatformServices
|
|
@@ -202,7 +239,7 @@ export interface CommandOpsServices
|
|
|
202
239
|
export interface CommandExtensionRegistryServices {
|
|
203
240
|
readonly toolRegistry: ToolRegistry;
|
|
204
241
|
readonly mcpRegistry: McpRegistry;
|
|
205
|
-
readonly evalRegistry?: import('../panels/eval-
|
|
242
|
+
readonly evalRegistry?: import('../panels/eval-registry.ts').EvalRegistry;
|
|
206
243
|
}
|
|
207
244
|
|
|
208
245
|
export interface CommandExtensionServices
|