@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
|
@@ -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,7 @@
|
|
|
33
40
|
*/
|
|
34
41
|
|
|
35
42
|
import type { ConversationManager } from './conversation';
|
|
36
|
-
import
|
|
43
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
37
44
|
import {
|
|
38
45
|
classifySystemMessageKind,
|
|
39
46
|
classifySystemMessagePriority,
|
|
@@ -42,19 +49,27 @@ import {
|
|
|
42
49
|
type SystemMessageKind,
|
|
43
50
|
type SystemMessageTarget,
|
|
44
51
|
} from '@/runtime/index.ts';
|
|
52
|
+
import {
|
|
53
|
+
classifyNoise,
|
|
54
|
+
foldProviderReplayLines,
|
|
55
|
+
providerNameFromReplay,
|
|
56
|
+
type NoiseGateDeps,
|
|
57
|
+
} from './system-message-noise.ts';
|
|
45
58
|
|
|
46
59
|
export type {
|
|
47
60
|
SystemMessageKind,
|
|
48
61
|
SystemMessageTarget,
|
|
49
62
|
} from '@/runtime/index.ts';
|
|
50
63
|
|
|
64
|
+
/** 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. */
|
|
65
|
+
export type SystemMessagePriority = 'high' | 'low';
|
|
66
|
+
|
|
51
67
|
/**
|
|
52
68
|
* Message categories that are operationally critical enough that the user must
|
|
53
69
|
* see them inline in the main conversation, regardless of the configured
|
|
54
|
-
* routing target
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* failed over, or the context was compacted.
|
|
70
|
+
* routing target. Errors, provider failovers, and compaction/context notices
|
|
71
|
+
* fall here: a user should never have to go hunting to discover that a turn
|
|
72
|
+
* errored, a provider failed over, or the context was compacted.
|
|
58
73
|
*
|
|
59
74
|
* Detection is by the stable message prefix tag, mirroring how the SDK
|
|
60
75
|
* classifiers key off message content. This deliberately does NOT force every
|
|
@@ -72,14 +87,19 @@ function mustReachConversation(message: string): boolean {
|
|
|
72
87
|
// ---------------------------------------------------------------------------
|
|
73
88
|
|
|
74
89
|
/**
|
|
75
|
-
* Routes system messages to the conversation
|
|
76
|
-
*
|
|
90
|
+
* Routes system messages to the conversation based on priority level and
|
|
91
|
+
* configured target. See file doc for the W6.1 panel removal.
|
|
77
92
|
*/
|
|
78
93
|
export class SystemMessageRouter {
|
|
94
|
+
/** Buffered provider "from last session" replay lines, folded on a microtask. */
|
|
95
|
+
private providerReplayBuffer: string[] = [];
|
|
96
|
+
private providerReplayScheduled = false;
|
|
97
|
+
|
|
79
98
|
constructor(
|
|
80
99
|
private readonly conversation: ConversationManager,
|
|
81
|
-
private panel: SystemMessagesPanel | null,
|
|
82
100
|
private readonly getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget = defaultSystemMessageTarget,
|
|
101
|
+
/** Noise-gate dependencies (WRFC terminal-chain lookup). See system-message-noise.ts. */
|
|
102
|
+
private readonly noiseDeps: NoiseGateDeps = {},
|
|
83
103
|
) {}
|
|
84
104
|
|
|
85
105
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
@@ -87,28 +107,40 @@ export class SystemMessageRouter {
|
|
|
87
107
|
/**
|
|
88
108
|
* Route a system message.
|
|
89
109
|
*
|
|
90
|
-
* Delivery is resolved from the kind and its configured target
|
|
91
|
-
*
|
|
92
|
-
*
|
|
110
|
+
* Delivery is resolved from the kind and its configured target. Critical
|
|
111
|
+
* notices (errors/failover/compaction, see FORCE_CONVERSATION_PREFIXES)
|
|
112
|
+
* are additionally forced into the conversation.
|
|
93
113
|
*
|
|
94
114
|
* @param message - Message text.
|
|
95
|
-
* @param priority - 'high' | 'low'
|
|
115
|
+
* @param priority - 'high' | 'low' (kept for callers; no longer changes
|
|
116
|
+
* delivery now that there is no panel to emphasize on).
|
|
96
117
|
* @param kind - Classification kind ('system' | 'wrfc' | 'operational');
|
|
97
118
|
* used to resolve routing target and conversation navigability.
|
|
98
119
|
*/
|
|
99
120
|
routeTypedSystemMessage(
|
|
100
121
|
message: string,
|
|
101
|
-
|
|
122
|
+
_priority: SystemMessagePriority,
|
|
102
123
|
kind: SystemMessageKind,
|
|
103
124
|
): void {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
// Noise gate — keep first-run plumbing out of the transcript while the
|
|
126
|
+
// information stays reachable via other live surfaces. (UX-B item 1.)
|
|
127
|
+
const verdict = classifyNoise(message, this.noiseDeps);
|
|
128
|
+
if (verdict.action === 'drop') return;
|
|
129
|
+
if (verdict.action === 'foldProviderReplay') {
|
|
130
|
+
this.bufferProviderReplay(message);
|
|
131
|
+
return;
|
|
108
132
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
133
|
+
|
|
134
|
+
this.deliver(message, kind);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Post-noise-gate delivery: resolve target and append to the conversation. */
|
|
138
|
+
private deliver(message: string, kind: SystemMessageKind): void {
|
|
139
|
+
const target = this.getTargetForKind(kind);
|
|
140
|
+
// hasPanel is always false post-W6.1 — resolveSystemMessageDelivery's own
|
|
141
|
+
// contract means every target ('panel' | 'conversation' | 'both')
|
|
142
|
+
// resolves toConversation: true in that case (see file doc).
|
|
143
|
+
const delivery = resolveSystemMessageDelivery(target, false);
|
|
112
144
|
const toConversation = delivery.toConversation || mustReachConversation(message);
|
|
113
145
|
if (toConversation) {
|
|
114
146
|
// addTypedSystemMessage threads the kind into the conversation so the
|
|
@@ -117,6 +149,45 @@ export class SystemMessageRouter {
|
|
|
117
149
|
}
|
|
118
150
|
}
|
|
119
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Buffer a provider "from last session" replay line and schedule a microtask
|
|
154
|
+
* flush. The SDK emits the whole persisted-provider burst synchronously, so a
|
|
155
|
+
* single microtask captures the full burst and folds it to one quiet line.
|
|
156
|
+
* (UX-B item 1b.)
|
|
157
|
+
*/
|
|
158
|
+
private bufferProviderReplay(message: string): void {
|
|
159
|
+
this.providerReplayBuffer.push(message);
|
|
160
|
+
if (this.providerReplayScheduled) return;
|
|
161
|
+
this.providerReplayScheduled = true;
|
|
162
|
+
queueMicrotask(() => this.flushProviderReplay());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Record the folded provider-replay summary and reset the buffer.
|
|
167
|
+
*
|
|
168
|
+
* Papercut sweep item 2: this used to `deliver()` the folded line into the
|
|
169
|
+
* transcript — one line instead of a burst, but still boot plumbing the
|
|
170
|
+
* user never asked to see there ("the transcript at boot shows product
|
|
171
|
+
* signal only"). The persisted-provider set this summarizes is reachable
|
|
172
|
+
* on demand via `/health provider` (providers-modal lists every registered
|
|
173
|
+
* provider, discovered/local ones included) and `/model` (lists every
|
|
174
|
+
* selectable model, discovered ones included) — so nothing is lost by
|
|
175
|
+
* keeping it out of the transcript. It still goes to the activity log
|
|
176
|
+
* (.goodvibes/logs/activity.md) for diagnosis. Only this boot-only "— from
|
|
177
|
+
* last session" burst moves; unrelated provider-discovery lines emitted
|
|
178
|
+
* mid-session (e.g. "[Scan] Found …", "[Scan] … no longer reachable") are
|
|
179
|
+
* untouched — they never match PROVIDER_REPLAY_RE, so they never enter this
|
|
180
|
+
* buffer/fold path and keep reaching the transcript as live product signal.
|
|
181
|
+
*/
|
|
182
|
+
flushProviderReplay(): void {
|
|
183
|
+
this.providerReplayScheduled = false;
|
|
184
|
+
if (this.providerReplayBuffer.length === 0) return;
|
|
185
|
+
const summary = foldProviderReplayLines(this.providerReplayBuffer);
|
|
186
|
+
const providerNames = this.providerReplayBuffer.map(providerNameFromReplay);
|
|
187
|
+
this.providerReplayBuffer = [];
|
|
188
|
+
logger.info(summary, { count: providerNames.length, providers: providerNames });
|
|
189
|
+
}
|
|
190
|
+
|
|
120
191
|
routeSystemMessage(message: string, priority: SystemMessagePriority): void {
|
|
121
192
|
this.routeTypedSystemMessage(message, priority, classifySystemMessageKind(message));
|
|
122
193
|
}
|
|
@@ -153,21 +224,6 @@ export class SystemMessageRouter {
|
|
|
153
224
|
wrfc(message: string, priority: SystemMessagePriority = 'high'): void {
|
|
154
225
|
this.routeTypedSystemMessage(message, priority, 'wrfc');
|
|
155
226
|
}
|
|
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
227
|
}
|
|
172
228
|
|
|
173
229
|
// ---------------------------------------------------------------------------
|
|
@@ -175,17 +231,14 @@ export class SystemMessageRouter {
|
|
|
175
231
|
// ---------------------------------------------------------------------------
|
|
176
232
|
|
|
177
233
|
/**
|
|
178
|
-
* Create a SystemMessageRouter wired to the given conversation
|
|
234
|
+
* Create a SystemMessageRouter wired to the given conversation.
|
|
179
235
|
*
|
|
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).
|
|
236
|
+
* @param conversation - The ConversationManager all routed messages reach.
|
|
184
237
|
*/
|
|
185
238
|
export function createSystemMessageRouter(
|
|
186
239
|
conversation: ConversationManager,
|
|
187
|
-
panel: SystemMessagesPanel | null = null,
|
|
188
240
|
getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget = defaultSystemMessageTarget,
|
|
241
|
+
noiseDeps: NoiseGateDeps = {},
|
|
189
242
|
): SystemMessageRouter {
|
|
190
|
-
return new SystemMessageRouter(conversation,
|
|
243
|
+
return new SystemMessageRouter(conversation, getTargetForKind, noiseDeps);
|
|
191
244
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
* Returns whether SPEECH was actively stopped — a Ctrl+C that silenced live
|
|
11
|
+
* TTS is consumed by that job (batch replay D5), same as a press that
|
|
12
|
+
* cleared a non-empty prompt; the quit chord starts from a quiet state.
|
|
13
|
+
*/
|
|
14
|
+
export function createCancelGeneration(
|
|
15
|
+
orchestrator: Orchestrator,
|
|
16
|
+
spokenTurns: Pick<SpokenTurnRuntime, 'stop'>,
|
|
17
|
+
): () => boolean {
|
|
18
|
+
return () => {
|
|
19
|
+
const stoppedSpeech = spokenTurns.stop('Spoken output stopped.');
|
|
20
|
+
if (orchestrator.isThinking) {
|
|
21
|
+
orchestrator.abort();
|
|
22
|
+
}
|
|
23
|
+
return stoppedSpeech;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -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,41 @@ 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
|
+
try {
|
|
236
|
+
// An operator cancellation is an intended stop, not a failure — narrate it
|
|
237
|
+
// as cancelled (the reason already carries the landed-work count from the
|
|
238
|
+
// chain's edit ledger), so the notification never contradicts the cancelled
|
|
239
|
+
// chain/owner/cohort surfaces.
|
|
240
|
+
if (payload.failureKind === 'cancelled') {
|
|
241
|
+
notifyCompletion('GoodVibes — WRFC chain cancelled', `chain ${payload.chainId.slice(0, 12)} cancelled: ${payload.reason}`, FORCE_NOTIFY_DURATION_MS);
|
|
242
|
+
} else {
|
|
243
|
+
const kindLabel = payload.failureKind === 'transport' ? 'transient transport error' : payload.reason;
|
|
244
|
+
notifyCompletion('GoodVibes — WRFC chain failed', `chain ${payload.chainId.slice(0, 12)} failed: ${kindLabel}`, FORCE_NOTIFY_DURATION_MS);
|
|
245
|
+
}
|
|
246
|
+
} catch (err) {
|
|
247
|
+
logger.debug('turn-event-wiring: chain-failure notify error', { error: String(err) });
|
|
248
|
+
}
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
|
|
183
252
|
return { refreshGit, unsubs, transcriptJournal };
|
|
184
253
|
}
|
package/src/daemon/cli.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { homedir, networkInterfaces } from 'node:os';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
-
import { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
4
|
+
import { ConfigManager, resolveDaemonEnabled } from '@pellux/goodvibes-sdk/platform/config';
|
|
5
5
|
import { formatProviderModel, getModelIdFromProviderModel, getProviderIdFromModel } from '../config/provider-model.ts';
|
|
6
6
|
import { RuntimeEventBus, GlobalNetworkTransportInstaller } from '@/runtime/index.ts';
|
|
7
7
|
import { createFeatureFlagManager } from '@/runtime/index.ts';
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
persistProviders,
|
|
28
28
|
} from '@pellux/goodvibes-sdk/platform/discovery';
|
|
29
29
|
import { createSafeHostServeFactory } from './safe-serve.ts';
|
|
30
|
+
import { isDaemonServiceSubcommand, resolveInstalledDaemonBinary, runDaemonServiceCli } from './service-commands.ts';
|
|
30
31
|
|
|
31
32
|
import {
|
|
32
33
|
parseGoodVibesCli,
|
|
@@ -161,6 +162,32 @@ async function main(): Promise<void> {
|
|
|
161
162
|
process.env['GOODVIBES_DAEMON_HOST'] = cliFlags.hostname;
|
|
162
163
|
logger.info('daemon: --hostname flag applied', { hostname: cliFlags.hostname });
|
|
163
164
|
}
|
|
165
|
+
|
|
166
|
+
// D7a Layer 1: `install-service` / `uninstall-service` / `service-status` manage
|
|
167
|
+
// the systemd USER unit for the shared daemon. They run BEFORE the daemon boots —
|
|
168
|
+
// no runtime/services are constructed — and exit with the honest result code.
|
|
169
|
+
const serviceSubcommand = cli.positionals[0];
|
|
170
|
+
if (isDaemonServiceSubcommand(serviceSubcommand)) {
|
|
171
|
+
const host = String(process.env.GOODVIBES_DAEMON_HOST ?? config.get('controlPlane.host') ?? '127.0.0.1');
|
|
172
|
+
const port = Number(config.get('controlPlane.port'));
|
|
173
|
+
const binaryPath = resolveInstalledDaemonBinary({ moduleUrl: import.meta.url });
|
|
174
|
+
const result = await runDaemonServiceCli({
|
|
175
|
+
subcommand: serviceSubcommand,
|
|
176
|
+
binaryPath,
|
|
177
|
+
homeDir: homeDirectory,
|
|
178
|
+
host,
|
|
179
|
+
port,
|
|
180
|
+
// migrate-service only: never auto-migrate — requires the same explicit
|
|
181
|
+
// consent as any other non-interactive destructive confirmation.
|
|
182
|
+
confirmMigration: cliFlags.yes,
|
|
183
|
+
});
|
|
184
|
+
for (const line of result.lines) {
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
186
|
+
console.log(line);
|
|
187
|
+
}
|
|
188
|
+
process.exit(result.exitCode);
|
|
189
|
+
}
|
|
190
|
+
|
|
164
191
|
const runtimeBus = new RuntimeEventBus();
|
|
165
192
|
const runtimeStore = createRuntimeStore();
|
|
166
193
|
const featureFlags = createFeatureFlagManager();
|
|
@@ -269,7 +296,7 @@ async function main(): Promise<void> {
|
|
|
269
296
|
process.on('SIGTERM', handleSignal);
|
|
270
297
|
|
|
271
298
|
logger.info('goodvibes daemon host started', {
|
|
272
|
-
daemon: config
|
|
299
|
+
daemon: resolveDaemonEnabled(config),
|
|
273
300
|
httpListener: config.get('danger.httpListener'),
|
|
274
301
|
});
|
|
275
302
|
|
|
@@ -111,7 +111,14 @@ export function registerCatalogHandler<TBody, TResult>(
|
|
|
111
111
|
): Unregister {
|
|
112
112
|
const descriptor = catalog.get(methodId);
|
|
113
113
|
if (!descriptor) {
|
|
114
|
-
|
|
114
|
+
// 'METHOD_NOT_FOUND' (not the old locally-coined 'UNKNOWN_METHOD') so this lines
|
|
115
|
+
// up byte-for-byte with SDKErrorCodes.METHOD_NOT_FOUND — the code the SDK's own
|
|
116
|
+
// uncataloged-method 404 now carries (method-catalog.ts's GatewayMethodCatalog
|
|
117
|
+
// .invoke(), daemon/control-plane.ts's invokeGatewayMethodCall, and daemon-sdk's
|
|
118
|
+
// control-routes.ts getGatewayMethod/invokeGatewayMethod). A literal string, not
|
|
119
|
+
// an import of SDKErrorCodes: the pinned SDK (0.38.0) predates that constant, and
|
|
120
|
+
// matching the value this way needs no SDK version bump to stay aligned.
|
|
121
|
+
throw new HandlerError(`Unknown gateway method: ${methodId}`, 'METHOD_NOT_FOUND', 404);
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
const wrapped = async (inv: GatewayMethodInvocation): Promise<unknown> => {
|