@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
package/src/cli/tui-startup.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CommandContext, CommandRegistry } from '../input/command-registry.ts';
|
|
2
2
|
import type { InputHandler } from '../input/handler.ts';
|
|
3
3
|
import type { SelectionItem } from '../input/selection-modal.ts';
|
|
4
|
+
import type { WorkspaceRegistrationManager } from '../runtime/trust/workspace-registration.ts';
|
|
4
5
|
import { hasResumableWizardProgress, readOnboardingCheckMarker, readWizardProgress } from '../runtime/onboarding/index.ts';
|
|
5
6
|
import { startOnboardingFastPath } from '../runtime/onboarding/fast-path.ts';
|
|
6
7
|
import { readLastSessionPointer } from '@/runtime/index.ts';
|
|
@@ -13,111 +14,62 @@ export type TuiStartupShellPaths = Parameters<typeof readOnboardingCheckMarker>[
|
|
|
13
14
|
|
|
14
15
|
export type FirstOpenTrustLevel = 'trusted' | 'restricted';
|
|
15
16
|
|
|
16
|
-
/** The decisions a first-open prompt choice resolves to. Absent halves were not asked. */
|
|
17
|
-
export interface FirstOpenDecision {
|
|
18
|
-
readonly trust?: FirstOpenTrustLevel;
|
|
19
|
-
readonly register?: 'yes' | 'no';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
/**
|
|
23
|
-
* decodeFirstOpenChoice — pure mapping from a chosen selection-item id (or
|
|
24
|
-
* on Escape/enter-through) to the trust
|
|
25
|
-
*
|
|
26
|
-
* are unit-testable
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* settles to 'restricted' (matching the standalone trust prompt), and an offered
|
|
30
|
-
* register half defaults to NO — recorded as a subtree-scoped decline so the
|
|
31
|
-
* directory is not re-asked every startup.
|
|
18
|
+
* decodeFirstOpenChoice — pure mapping from a chosen selection-item id (or
|
|
19
|
+
* null on Escape/enter-through) to the trust decision. Kept pure so the
|
|
20
|
+
* consequence-time trust prompt's semantics (raised by trustGatedAsk, wired
|
|
21
|
+
* in main.ts's trustPromptRef.requestTrustDecision) are unit-testable
|
|
22
|
+
* without a live modal. Escape/enter-through (id === null) is the safe
|
|
23
|
+
* default: 'restricted'.
|
|
32
24
|
*/
|
|
33
|
-
export function decodeFirstOpenChoice(
|
|
34
|
-
id
|
|
35
|
-
offered: { readonly trustNeeded: boolean; readonly registerNeeded: boolean },
|
|
36
|
-
): FirstOpenDecision {
|
|
37
|
-
const decision: { trust?: FirstOpenTrustLevel; register?: 'yes' | 'no' } = {};
|
|
38
|
-
if (offered.trustNeeded && offered.registerNeeded) {
|
|
39
|
-
// Combined 2x2 choice.
|
|
40
|
-
const trust: FirstOpenTrustLevel = id === 'trust-register' || id === 'trust-only' ? 'trusted' : 'restricted';
|
|
41
|
-
const register: 'yes' | 'no' = id === 'trust-register' || id === 'restrict-register' ? 'yes' : 'no';
|
|
42
|
-
decision.trust = trust;
|
|
43
|
-
decision.register = register;
|
|
44
|
-
} else if (offered.trustNeeded) {
|
|
45
|
-
decision.trust = id === 'trusted' ? 'trusted' : 'restricted';
|
|
46
|
-
} else if (offered.registerNeeded) {
|
|
47
|
-
decision.register = id === 'register' ? 'yes' : 'no';
|
|
48
|
-
}
|
|
49
|
-
return decision;
|
|
25
|
+
export function decodeFirstOpenChoice(id: string | null): FirstOpenTrustLevel {
|
|
26
|
+
return id === 'trusted' ? 'trusted' : 'restricted';
|
|
50
27
|
}
|
|
51
28
|
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
items: [
|
|
61
|
-
{ id: 'trust-register', label: 'Trust & register this workspace', detail: 'Full capability, and track it in your workspace registry', primaryAction: 'select' },
|
|
62
|
-
{ id: 'trust-only', label: 'Trust, don\'t register', detail: 'Full capability; leave it out of the registry', primaryAction: 'select' },
|
|
63
|
-
{ id: 'restrict-register', label: 'Keep restricted, register', detail: 'Read-only for now; still track it in the registry', primaryAction: 'select' },
|
|
64
|
-
{ id: 'restrict-only', label: 'Keep restricted, don\'t register', detail: 'Read-only exploration; nothing recorded (default)', primaryAction: 'select' },
|
|
65
|
-
],
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
if (offered.trustNeeded) {
|
|
69
|
-
return {
|
|
70
|
-
title: 'New workspace — choose a trust level',
|
|
71
|
-
items: [
|
|
72
|
-
{ id: 'trusted', label: 'Trust this workspace', detail: 'Full capability — all tools may run', primaryAction: 'select' },
|
|
73
|
-
{ id: 'restricted', label: 'Keep restricted (read-only)', detail: 'Explore safely; writes and commands are denied until trusted', primaryAction: 'select' },
|
|
74
|
-
],
|
|
75
|
-
};
|
|
76
|
-
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the selection rows for the trust prompt. This used to also offer a
|
|
31
|
+
* "register this directory?" half (a combined 2x2 when both were needed) —
|
|
32
|
+
* that half is gone: registration self-records (see
|
|
33
|
+
* selfRecordWorkspaceRegistration below) rather than ever asking a question
|
|
34
|
+
* about a registry the user hasn't met yet.
|
|
35
|
+
*/
|
|
36
|
+
export function buildFirstOpenItems(): { readonly title: string; readonly items: SelectionItem[] } {
|
|
77
37
|
return {
|
|
78
|
-
title: '
|
|
38
|
+
title: 'New workspace — choose a trust level',
|
|
79
39
|
items: [
|
|
80
|
-
{ id: '
|
|
81
|
-
{ id: '
|
|
40
|
+
{ id: 'trusted', label: 'Trust this workspace', detail: 'Full capability — all tools may run', primaryAction: 'select' },
|
|
41
|
+
{ id: 'restricted', label: 'Keep restricted (read-only)', detail: 'Explore safely; writes and commands are denied until trusted', primaryAction: 'select' },
|
|
82
42
|
],
|
|
83
43
|
};
|
|
84
44
|
}
|
|
85
45
|
|
|
86
46
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
47
|
+
* Registration self-records: a workspace the user actually works in becomes
|
|
48
|
+
* its own registry entry with no question ever asked (the former "Register
|
|
49
|
+
* this directory as a workspace?" prompt — sometimes a four-option 2x2 when
|
|
50
|
+
* trust was undecided too — is gone entirely).
|
|
51
|
+
*
|
|
52
|
+
* "Actually works in" is anchored to TRUST, not mere directory-open: this is
|
|
53
|
+
* only ever called for a workspace that is (or has just become) trusted,
|
|
54
|
+
* never for one that's merely been glanced at read-only. Labeled 'via TUI'
|
|
55
|
+
* in the shared registry so its provenance is honest.
|
|
91
56
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
57
|
+
* OWNER-BOUNDARY RIDER (binding): self-recording must not widen the
|
|
58
|
+
* checkpoint-owning consumer's boundary. That boundary now reads the SAME
|
|
59
|
+
* shared registration store this writes to, gated by each record's
|
|
60
|
+
* `checkpointEligible` flag (absent means false). So the separation is no
|
|
61
|
+
* longer "a separate list the agent owns" — it is one store, and this self
|
|
62
|
+
* record stays out of scope by stamping its `origin` ('tui') for honest
|
|
63
|
+
* provenance while NEVER setting `checkpointEligible`. Only the consumer that
|
|
64
|
+
* owns checkpointing re-stamps its own roots eligible on boot.
|
|
96
65
|
*/
|
|
97
|
-
async function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const evaluation =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const offered = { trustNeeded, registerNeeded };
|
|
106
|
-
const { title, items } = buildFirstOpenItems(offered);
|
|
107
|
-
commandContext.openSelection?.(
|
|
108
|
-
title,
|
|
109
|
-
items,
|
|
110
|
-
{ allowSearch: false, primaryVerbLabel: 'Choose' },
|
|
111
|
-
(result) => {
|
|
112
|
-
const decision = decodeFirstOpenChoice(result?.item.id ?? null, offered);
|
|
113
|
-
const apply = async () => {
|
|
114
|
-
if (decision.trust && trustManager) await trustManager.setLevel(decision.trust);
|
|
115
|
-
if (decision.register === 'yes' && registrationManager) await registrationManager.register();
|
|
116
|
-
else if (decision.register === 'no' && registrationManager) await registrationManager.decline();
|
|
117
|
-
};
|
|
118
|
-
void apply().finally(render);
|
|
119
|
-
},
|
|
120
|
-
);
|
|
66
|
+
export async function selfRecordWorkspaceRegistration(
|
|
67
|
+
registrationManager: Pick<WorkspaceRegistrationManager, 'evaluate' | 'register'> | undefined,
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
if (!registrationManager) return;
|
|
70
|
+
const evaluation = await registrationManager.evaluate();
|
|
71
|
+
if (!evaluation.offerRegister) return;
|
|
72
|
+
await registrationManager.register('via TUI', 'tui');
|
|
121
73
|
}
|
|
122
74
|
|
|
123
75
|
export function applyInitialTuiCliState(options: {
|
|
@@ -131,6 +83,16 @@ export function applyInitialTuiCliState(options: {
|
|
|
131
83
|
const { cli, input, commandRegistry, commandContext, shellPaths, render } = options;
|
|
132
84
|
const globalOnboardingMarker = readOnboardingCheckMarker(shellPaths, 'user');
|
|
133
85
|
|
|
86
|
+
// Registration self-records on every launch of an already-trusted
|
|
87
|
+
// workspace (fire-and-forget — never blocks or gates startup, never a
|
|
88
|
+
// modal). A workspace that is undecided or restricted is never
|
|
89
|
+
// self-registered here; for one that becomes trusted just now (via the
|
|
90
|
+
// consequence-time trust prompt), main.ts's trustPromptRef wiring calls
|
|
91
|
+
// this same helper right after the decision.
|
|
92
|
+
if (commandContext.workspace?.workspaceTrustManager?.isTrusted()) {
|
|
93
|
+
void selfRecordWorkspaceRegistration(commandContext.workspace.workspaceRegistrationManager);
|
|
94
|
+
}
|
|
95
|
+
|
|
134
96
|
// Seeded prompt is always applied synchronously, regardless of session branch.
|
|
135
97
|
const seededPrompt = cli.flags.prompt ?? (cli.rawCommand === undefined && cli.positionals.length > 0 ? cli.positionals.join(' ') : undefined);
|
|
136
98
|
if (seededPrompt) {
|
|
@@ -183,6 +145,9 @@ export function applyInitialTuiCliState(options: {
|
|
|
183
145
|
} else if (!globalOnboardingMarker.exists) {
|
|
184
146
|
// Fast path: get a brand-new user to a working session in the fewest steps.
|
|
185
147
|
// Falls back to the full wizard when the surface can't detect providers.
|
|
148
|
+
// Trust stays undecided here on purpose — the first non-read tool
|
|
149
|
+
// request is what raises the trust question now (see main.ts's
|
|
150
|
+
// trustPromptRef), not this startup branch.
|
|
186
151
|
startOnboardingFastPath({ input, commandContext, shellPaths, render });
|
|
187
152
|
} else {
|
|
188
153
|
// User has completed onboarding before but left a wizard session in progress.
|
|
@@ -202,11 +167,9 @@ export function applyInitialTuiCliState(options: {
|
|
|
202
167
|
},
|
|
203
168
|
});
|
|
204
169
|
}
|
|
205
|
-
} else {
|
|
206
|
-
// Returning user, no wizard to resume: this is the moment to ask, in one
|
|
207
|
-
// surface, about a genuinely new workspace's trust level and whether to
|
|
208
|
-
// register it — before they run anything here.
|
|
209
|
-
void promptWorkspaceFirstOpen(commandContext, render);
|
|
210
170
|
}
|
|
171
|
+
// Returning user, no wizard to resume, nothing else to do at startup:
|
|
172
|
+
// trust (if still undecided) is asked at the first non-read tool
|
|
173
|
+
// request, not here — see main.ts's trustPromptRef wiring.
|
|
211
174
|
}
|
|
212
175
|
}
|
package/src/cli/types.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface GoodVibesCliFlags {
|
|
|
60
60
|
readonly fork: string | true | undefined;
|
|
61
61
|
readonly yes: boolean;
|
|
62
62
|
readonly nonInteractive: boolean;
|
|
63
|
+
/** `doctor --strict`: advisory findings also fail (exit 1), for CI. Ignored by every other command. */
|
|
64
|
+
readonly strict: boolean;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export interface GoodVibesCliParseResult {
|
package/src/config/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
export { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
10
10
|
export type { DeepReadonly } from '@pellux/goodvibes-sdk/platform/config';
|
|
11
|
-
export type { GoodVibesConfig, ConfigKey, ConfigValue, ConfigSetting, PermissionMode, PermissionAction, PermissionsToolConfig, NotificationsConfig
|
|
11
|
+
export type { GoodVibesConfig, ConfigKey, ConfigValue, ConfigSetting, PermissionMode, PermissionAction, PermissionsToolConfig, NotificationsConfig } from '@pellux/goodvibes-sdk/platform/config';
|
|
12
12
|
export { DEFAULT_CONFIG, CONFIG_SCHEMA } from '@pellux/goodvibes-sdk/platform/config';
|
|
13
13
|
export { ConfigError } from '@pellux/goodvibes-sdk/platform/types';
|
|
14
14
|
|
|
@@ -99,6 +99,39 @@ export function readStatuslineSettings(configManager: Pick<ConfigManager, 'getRa
|
|
|
99
99
|
return out;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// ─── update.* — launch-time self-update behavior ─────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* User-settable launch-time self-update behavior. The feature itself (check at
|
|
106
|
+
* launch, install via the checksum-verified download/verify/swap path, restart
|
|
107
|
+
* onto the new binary) defaults ON for binary installs; the defaults live in
|
|
108
|
+
* the consumer (src/cli/launch-auto-update.ts), matching this file's rule that
|
|
109
|
+
* readers return only what the user actually set.
|
|
110
|
+
*/
|
|
111
|
+
export interface UpdateSettings {
|
|
112
|
+
/** Check for a newer release at TUI launch and install it before starting. Default: true. */
|
|
113
|
+
readonly autoUpdateAtLaunch?: boolean;
|
|
114
|
+
/** How long the launch-time version check may take before it is skipped. Defaults to 2500; clamped to [250, 30000]. */
|
|
115
|
+
readonly launchCheckTimeoutMs?: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const LAUNCH_CHECK_MIN_TIMEOUT_MS = 250;
|
|
119
|
+
const LAUNCH_CHECK_MAX_TIMEOUT_MS = 30_000;
|
|
120
|
+
|
|
121
|
+
/** Read `update.*` from settings.json, validating and clamping the timeout. */
|
|
122
|
+
export function readUpdateSettings(configManager: Pick<ConfigManager, 'getRaw'>): UpdateSettings {
|
|
123
|
+
const src = readNamespace(configManager, 'update');
|
|
124
|
+
if (!src) return {};
|
|
125
|
+
const out: { autoUpdateAtLaunch?: boolean; launchCheckTimeoutMs?: number } = {};
|
|
126
|
+
const autoUpdateAtLaunch = readBoolean(src, 'autoUpdateAtLaunch');
|
|
127
|
+
if (autoUpdateAtLaunch !== undefined) out.autoUpdateAtLaunch = autoUpdateAtLaunch;
|
|
128
|
+
const launchCheckTimeoutMs = readPositiveInt(src, 'launchCheckTimeoutMs');
|
|
129
|
+
if (launchCheckTimeoutMs !== undefined) {
|
|
130
|
+
out.launchCheckTimeoutMs = Math.min(LAUNCH_CHECK_MAX_TIMEOUT_MS, Math.max(LAUNCH_CHECK_MIN_TIMEOUT_MS, launchCheckTimeoutMs));
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
102
135
|
// ─── checkpoints.* — workspace checkpoint root-guard options ─────────────────
|
|
103
136
|
|
|
104
137
|
/**
|
|
@@ -24,7 +24,7 @@ export function deriveComposerState(input: ComposerStateInput): ComposerState {
|
|
|
24
24
|
// status tag: `risk:approval-wait`. It is therefore NOT also duplicated as a
|
|
25
25
|
// `approval` flag, and (below) it suppresses the competing `state:` turn tag —
|
|
26
26
|
// the turn is blocked on the user, not really streaming. Precedence:
|
|
27
|
-
// approval-wait > live turn state. (
|
|
27
|
+
// approval-wait > live turn state. (item 4.)
|
|
28
28
|
const approvalWait = input.pendingApproval === true;
|
|
29
29
|
if (approvalWait) {
|
|
30
30
|
pendingRisk = 'approval-wait';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* conversation-line-cache.ts — per-message Line[] production cache
|
|
2
|
+
* conversation-line-cache.ts — per-message Line[] production cache.
|
|
3
3
|
*
|
|
4
4
|
* The measured defect (perf baseline 2026-07-03, transcript.build_1k): appending
|
|
5
5
|
* ONE message to an N-message conversation re-rendered all N messages
|
|
@@ -98,7 +98,7 @@ interface KeyMeta {
|
|
|
98
98
|
/**
|
|
99
99
|
* True when this (assistant) message has a tool call with no result yet — it
|
|
100
100
|
* renders as pending rather than done. Keyed so the entry invalidates and
|
|
101
|
-
* re-renders ✓ once the tool result arrives. (
|
|
101
|
+
* re-renders ✓ once the tool result arrives. (item 2c.)
|
|
102
102
|
*/
|
|
103
103
|
readonly hasPendingTool: boolean;
|
|
104
104
|
}
|
|
@@ -286,7 +286,7 @@ export class MessageLineCache {
|
|
|
286
286
|
};
|
|
287
287
|
|
|
288
288
|
// Tool calls with no matching tool-result message are still pending; the
|
|
289
|
-
// render context and the cache key both depend on this. (
|
|
289
|
+
// render context and the cache key both depend on this. (item 2c.)
|
|
290
290
|
const completedToolCallIds = collectCompletedToolCallIds(messages);
|
|
291
291
|
const renderContext: ConversationRenderContext = { ...context, completedToolCallIds };
|
|
292
292
|
|
|
@@ -46,7 +46,7 @@ type Message = ConversationMessageSnapshot;
|
|
|
46
46
|
* Collect the ids of tool calls that have a matching tool-result message in the
|
|
47
47
|
* given slice — i.e. the tools that actually ran. Used to decide whether an
|
|
48
48
|
* assistant tool call still shows as pending (awaiting approval) or done.
|
|
49
|
-
* (
|
|
49
|
+
* (item 2c.)
|
|
50
50
|
*/
|
|
51
51
|
export function collectCompletedToolCallIds(messages: readonly Message[]): Set<string> {
|
|
52
52
|
const ids = new Set<string>();
|
|
@@ -79,7 +79,7 @@ export interface ConversationRenderContext {
|
|
|
79
79
|
* awaiting a decision (e.g. an approval prompt) and renders with a pending
|
|
80
80
|
* glyph instead of the completed ✓. When undefined (single-message callers
|
|
81
81
|
* without sibling context), every tool call renders as done — the prior
|
|
82
|
-
* behaviour. (
|
|
82
|
+
* behaviour. (item 2c.)
|
|
83
83
|
*/
|
|
84
84
|
readonly completedToolCallIds?: ReadonlySet<string>;
|
|
85
85
|
}
|
|
@@ -273,7 +273,7 @@ export function renderConversationToolMessage(
|
|
|
273
273
|
// Human one-line summary for tool results (write/read/exec/edit): shown as the
|
|
274
274
|
// collapsed line so the transcript reads "wrote foo.txt (532 B)" instead of a
|
|
275
275
|
// raw JSON blob; the full payload stays behind the expand toggle. Only for
|
|
276
|
-
// 'tool' blocks (diffs render their own view). (
|
|
276
|
+
// 'tool' blocks (diffs render their own view). (item 3.)
|
|
277
277
|
const resultSummary = blockType === 'tool'
|
|
278
278
|
? summarizeToolResult(message.toolName, message.content)
|
|
279
279
|
: null;
|
|
@@ -379,7 +379,7 @@ export function appendConversationMessages(
|
|
|
379
379
|
const lineNumberMode = context.configManager?.get('display.lineNumbers') ?? 'off';
|
|
380
380
|
const collapseThreshold = context.configManager?.get('display.collapseThreshold') ?? 30;
|
|
381
381
|
// Derive pending vs done for tool calls from sibling tool-result messages,
|
|
382
|
-
// unless the caller already supplied the set. (
|
|
382
|
+
// unless the caller already supplied the set. (item 2c.)
|
|
383
383
|
const renderContext: ConversationRenderContext = context.completedToolCallIds !== undefined
|
|
384
384
|
? context
|
|
385
385
|
: { ...context, completedToolCallIds: collectCompletedToolCallIds(messages) };
|
package/src/core/conversation.ts
CHANGED
|
@@ -67,7 +67,7 @@ export interface OrchestratorUsageTotals {
|
|
|
67
67
|
* TUI-side counterpart to SDK Orchestrator.usage: after a session resume
|
|
68
68
|
* replays historical messages into a freshly-constructed Orchestrator (whose
|
|
69
69
|
* `usage` starts at all zeros — bootstrap.ts always constructs a fresh
|
|
70
|
-
* instance, see
|
|
70
|
+
* instance, see), this lets the caller hydrate the footer's token
|
|
71
71
|
* counters from messages that already carry real usage data instead of
|
|
72
72
|
* waiting for the first new turn to populate them.
|
|
73
73
|
*/
|
|
@@ -98,7 +98,7 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
98
98
|
/** When true the buffer needs to be rebuilt before the next display. */
|
|
99
99
|
private dirty = true;
|
|
100
100
|
/**
|
|
101
|
-
* Per-message Line[] cache
|
|
101
|
+
* Per-message Line[] cache. rebuildHistory() reuses cached lines for
|
|
102
102
|
* messages whose complete render inputs are unchanged, so appending one message
|
|
103
103
|
* to an N-message conversation no longer re-renders all N. A cache-served
|
|
104
104
|
* rebuild is byte-identical to a cold one (pure memoisation).
|
|
@@ -18,12 +18,12 @@
|
|
|
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
|
|
21
|
+
* Focus tracking: when `focusTracker` is supplied, notifications are
|
|
22
22
|
* gated the same way as the other unfocused-user alert classes (see
|
|
23
23
|
* alert-gating.ts) — they fire when the terminal is unfocused or focus state
|
|
24
24
|
* was never observed, and are suppressed when it's known to be focused,
|
|
25
25
|
* unless `behavior.notifyOnlyWhenUnfocused` is turned off. `focusTracker` is
|
|
26
|
-
* optional and defaults to "always fire" (the
|
|
26
|
+
* optional and defaults to "always fire" (the behavior before focus gating existed) when
|
|
27
27
|
* omitted, so existing callers that don't have a FocusTracker in scope are
|
|
28
28
|
* unaffected.
|
|
29
29
|
*/
|
|
@@ -82,11 +82,11 @@ export interface MaybeNotifyLongTaskOptions {
|
|
|
82
82
|
readonly webhookNotifier?: WebhookNotifier | null;
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Terminal focus tracker
|
|
85
|
+
* Terminal focus tracker. When supplied together with `configGet`,
|
|
86
86
|
* the notification additionally respects behavior.notifyOnlyWhenUnfocused
|
|
87
87
|
* (default on): suppressed when the terminal is known to be focused, fires
|
|
88
88
|
* when unfocused or when focus was never observed. Omit both to preserve
|
|
89
|
-
*
|
|
89
|
+
* the behavior before focus gating existed (always fire once the threshold is met).
|
|
90
90
|
*/
|
|
91
91
|
readonly focusTracker?: Pick<FocusTracker, 'shouldAlertWhenUnfocused'> | null;
|
|
92
92
|
|
|
@@ -121,9 +121,9 @@ export function maybeNotifyLongTask(opts: MaybeNotifyLongTaskOptions): boolean {
|
|
|
121
121
|
return false;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
// Focus gate
|
|
125
|
-
// are supplied. Absent either one, behavior is unchanged from
|
|
126
|
-
// (always fire once the threshold is met).
|
|
124
|
+
// Focus gate: only applied when both a tracker and a config reader
|
|
125
|
+
// are supplied. Absent either one, behavior is unchanged from before focus
|
|
126
|
+
// gating existed (always fire once the threshold is met).
|
|
127
127
|
if (focusTracker && configGet && readNotifyOnlyWhenUnfocused(configGet) && !focusTracker.shouldAlertWhenUnfocused()) {
|
|
128
128
|
logger.debug('long-task-notifier: suppressed — terminal focused');
|
|
129
129
|
return false;
|
|
@@ -115,11 +115,11 @@ function isStreamRetryLikePayload(payload: unknown): payload is StreamRetryLikeP
|
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* Minimal cost lookup surface for attaching cost-delta information to failover notices.
|
|
118
|
-
* Returns USD-per-1M-token pricing for the given model ID
|
|
119
|
-
*
|
|
118
|
+
* Returns USD-per-1M-token pricing for the given model ID, or null when the
|
|
119
|
+
* catalog honestly does not know the model (never a fabricated zero).
|
|
120
120
|
*/
|
|
121
121
|
export interface FailoverCostLookup {
|
|
122
|
-
getCostFromCatalog(modelId: string): { readonly input: number; readonly output: number };
|
|
122
|
+
getCostFromCatalog(modelId: string): { readonly input: number; readonly output: number } | null;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
export interface WireStreamEventMetricsOptions {
|
|
@@ -219,9 +219,11 @@ function buildCostDeltaSuffix(
|
|
|
219
219
|
// Registry key format: `provider:modelId` — modelId may itself contain `:`.
|
|
220
220
|
const fromModelId = fromRegistryKey ? fromRegistryKey.split(':').slice(1).join(':') : '';
|
|
221
221
|
const toModelId = toRegistryKey.split(':').slice(1).join(':');
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
// A null catalog answer means honestly unknown — treated exactly like the
|
|
223
|
+
// legacy zero-pricing sentinel below.
|
|
224
|
+
const fromCost = (fromModelId ? lookup.getCostFromCatalog(fromModelId) : null) ?? { input: 0, output: 0 };
|
|
225
|
+
const toCost = lookup.getCostFromCatalog(toModelId) ?? { input: 0, output: 0 };
|
|
226
|
+
// Report unavailable when either side has no pricing (unknown model).
|
|
225
227
|
if (fromCost.input === 0 && fromCost.output === 0 && !fromModelId) {
|
|
226
228
|
return ' [cost data unavailable]';
|
|
227
229
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* source these rules catch is emitted (from the SDK) through
|
|
5
5
|
* SystemMessageRouter, so this one funnel is where first-run plumbing is kept
|
|
6
6
|
* out of the user's stream while the information stays reachable elsewhere
|
|
7
|
-
* (fleet panel, /health, /model, activity log). (
|
|
7
|
+
* (fleet panel, /health, /model, activity log). (item 1.)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
export interface NoiseGateDeps {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* that target — plus whether a panel is attached — into a { toPanel,
|
|
10
10
|
* toConversation } decision.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* (the purge): the SystemMessagesPanel this router used to optionally
|
|
13
13
|
* push into was DELETE-disposition (no surviving human surface — a picker
|
|
14
14
|
* over the old panel registry, not something worth a dedicated console) and
|
|
15
15
|
* has been removed entirely, so this router now always resolves with
|
|
@@ -61,7 +61,7 @@ export type {
|
|
|
61
61
|
SystemMessageTarget,
|
|
62
62
|
} from '@/runtime/index.ts';
|
|
63
63
|
|
|
64
|
-
/** Panel emphasis level. Panel delivery was removed in
|
|
64
|
+
/** Panel emphasis level. Panel delivery was removed in (see file doc); kept as the priority vocabulary for callers and for the SDK's delivery-resolution signature. */
|
|
65
65
|
export type SystemMessagePriority = 'high' | 'low';
|
|
66
66
|
|
|
67
67
|
/**
|
|
@@ -88,7 +88,7 @@ function mustReachConversation(message: string): boolean {
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Routes system messages to the conversation based on priority level and
|
|
91
|
-
* configured target. See file doc for the
|
|
91
|
+
* configured target. See file doc for the panel removal.
|
|
92
92
|
*/
|
|
93
93
|
export class SystemMessageRouter {
|
|
94
94
|
/** Buffered provider "from last session" replay lines, folded on a microtask. */
|
|
@@ -123,7 +123,7 @@ export class SystemMessageRouter {
|
|
|
123
123
|
kind: SystemMessageKind,
|
|
124
124
|
): void {
|
|
125
125
|
// Noise gate — keep first-run plumbing out of the transcript while the
|
|
126
|
-
// information stays reachable via other live surfaces. (
|
|
126
|
+
// information stays reachable via other live surfaces. (item 1.)
|
|
127
127
|
const verdict = classifyNoise(message, this.noiseDeps);
|
|
128
128
|
if (verdict.action === 'drop') return;
|
|
129
129
|
if (verdict.action === 'foldProviderReplay') {
|
|
@@ -137,7 +137,7 @@ export class SystemMessageRouter {
|
|
|
137
137
|
/** Post-noise-gate delivery: resolve target and append to the conversation. */
|
|
138
138
|
private deliver(message: string, kind: SystemMessageKind): void {
|
|
139
139
|
const target = this.getTargetForKind(kind);
|
|
140
|
-
// hasPanel is always false
|
|
140
|
+
// hasPanel is always false now that panel delivery was removed — resolveSystemMessageDelivery's own
|
|
141
141
|
// contract means every target ('panel' | 'conversation' | 'both')
|
|
142
142
|
// resolves toConversation: true in that case (see file doc).
|
|
143
143
|
const delivery = resolveSystemMessageDelivery(target, false);
|
|
@@ -153,7 +153,7 @@ export class SystemMessageRouter {
|
|
|
153
153
|
* Buffer a provider "from last session" replay line and schedule a microtask
|
|
154
154
|
* flush. The SDK emits the whole persisted-provider burst synchronously, so a
|
|
155
155
|
* single microtask captures the full burst and folds it to one quiet line.
|
|
156
|
-
* (
|
|
156
|
+
* (item 1b.)
|
|
157
157
|
*/
|
|
158
158
|
private bufferProviderReplay(message: string): void {
|
|
159
159
|
this.providerReplayBuffer.push(message);
|
|
@@ -5,7 +5,7 @@ import type { SpokenTurnRuntime } from '../audio/spoken-turn-wiring.ts';
|
|
|
5
5
|
* Builds the shared "cancel the active turn" action: stops any in-flight
|
|
6
6
|
* spoken output, then aborts the orchestrator's turn if one is actively
|
|
7
7
|
* streaming. Extracted from main.ts so the file can stay under its 800-line
|
|
8
|
-
* cap while still wiring `commandContext.isGenerating` (see
|
|
8
|
+
* cap while still wiring `commandContext.isGenerating` (see FIX 1).
|
|
9
9
|
*
|
|
10
10
|
* Returns whether SPEECH was actively stopped — a Ctrl+C that silenced live
|
|
11
11
|
* TTS is consumed by that job (an earlier replay fix), same as a press that
|
|
@@ -78,12 +78,12 @@ export interface WireTurnEventHandlersOptions {
|
|
|
78
78
|
*/
|
|
79
79
|
readonly webhookNotifier?: WebhookNotifier | null;
|
|
80
80
|
/**
|
|
81
|
-
* Terminal focus tracker
|
|
81
|
+
* Terminal focus tracker. Gates the long-task, budget-breach, and
|
|
82
82
|
* agent/chain-failure desktop alerts wired in this module — see
|
|
83
|
-
* alert-gating.ts. Optional; when absent, none of the new
|
|
84
|
-
* behavior is gated by focus (long-task keeps its
|
|
85
|
-
* behavior, and budget-breach/failure alerts are
|
|
86
|
-
*
|
|
83
|
+
* alert-gating.ts. Optional; when absent, none of the new alert
|
|
84
|
+
* behavior is gated by focus (long-task keeps its always-fire
|
|
85
|
+
* behavior from before focus gating existed, and budget-breach/failure alerts are
|
|
86
|
+
* skipped entirely, since they have no pre-existing unconditional-fire path
|
|
87
87
|
* to fall back to).
|
|
88
88
|
*/
|
|
89
89
|
readonly focusTracker?: Pick<FocusTracker, 'shouldAlertWhenUnfocused'> | null;
|
|
@@ -152,7 +152,7 @@ export function wireTurnEventHandlers(
|
|
|
152
152
|
// Track turn start time for long-task notification threshold.
|
|
153
153
|
let turnStartTime: number | null = null;
|
|
154
154
|
|
|
155
|
-
// Budget-breach edge-trigger checker
|
|
155
|
+
// Budget-breach edge-trigger checker — one instance per session,
|
|
156
156
|
// piggybacking on the same TURN_COMPLETED handler as the long-task
|
|
157
157
|
// notification below rather than adding a second TURN_COMPLETED subscription.
|
|
158
158
|
const budgetBreachNotifier: BudgetBreachNotifier | null = focusTracker
|
|
@@ -193,7 +193,7 @@ export function wireTurnEventHandlers(
|
|
|
193
193
|
focusTracker,
|
|
194
194
|
configGet,
|
|
195
195
|
});
|
|
196
|
-
// Budget-breach alert
|
|
196
|
+
// Budget-breach alert: edge-triggered, piggybacks on this same
|
|
197
197
|
// TURN_COMPLETED handler rather than a second subscription.
|
|
198
198
|
if (budgetBreachNotifier) {
|
|
199
199
|
const sessionModel = providerRegistry.getCurrentModel().id ?? 'unknown';
|
|
@@ -264,7 +264,7 @@ export function wireTurnEventHandlers(
|
|
|
264
264
|
refreshGit();
|
|
265
265
|
}));
|
|
266
266
|
|
|
267
|
-
// Agent/chain-failure desktop alerts
|
|
267
|
+
// Agent/chain-failure desktop alerts. The SDK's WebhookNotifier and
|
|
268
268
|
// Notifier already fire webhook/Slack/Discord notifications for these two
|
|
269
269
|
// events unconditionally (attachToRuntimeBus in the SDK's
|
|
270
270
|
// platform/integrations — pre-existing, not focus-gated: an out-of-band
|
package/src/daemon/cli.ts
CHANGED
|
@@ -4,8 +4,7 @@ import { readFileSync } from 'node:fs';
|
|
|
4
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
|
-
import { createFeatureFlagManager } from '@/runtime/index.ts';
|
|
8
|
-
import type { FlagState } from '@/runtime/index.ts';
|
|
7
|
+
import { bindFeatureSettingsBridge, createFeatureFlagManager, deriveFeatureStates } from '@/runtime/index.ts';
|
|
9
8
|
import { createRuntimeStore } from '../runtime/store/index.ts';
|
|
10
9
|
import { createRuntimeServices } from '../runtime/services.ts';
|
|
11
10
|
import { DaemonServer, HttpListener } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
@@ -28,6 +27,8 @@ import {
|
|
|
28
27
|
} from '@pellux/goodvibes-sdk/platform/discovery';
|
|
29
28
|
import { createSafeHostServeFactory } from './safe-serve.ts';
|
|
30
29
|
import { isDaemonServiceSubcommand, resolveInstalledDaemonBinary, runDaemonServiceCli } from './service-commands.ts';
|
|
30
|
+
import { resolveDaemonUpdateArtifact } from './lifecycle.ts';
|
|
31
|
+
import { VERSION } from '../version.ts';
|
|
31
32
|
|
|
32
33
|
import {
|
|
33
34
|
parseGoodVibesCli,
|
|
@@ -190,10 +191,11 @@ async function main(): Promise<void> {
|
|
|
190
191
|
|
|
191
192
|
const runtimeBus = new RuntimeEventBus();
|
|
192
193
|
const runtimeStore = createRuntimeStore();
|
|
194
|
+
// Gate states derive from the domain settings keys; the bridge keeps live
|
|
195
|
+
// config changes flowing into the manager for the daemon's lifetime.
|
|
193
196
|
const featureFlags = createFeatureFlagManager();
|
|
194
|
-
featureFlags.loadFromConfig({
|
|
195
|
-
|
|
196
|
-
});
|
|
197
|
+
featureFlags.loadFromConfig({ flags: deriveFeatureStates(config) });
|
|
198
|
+
bindFeatureSettingsBridge(config, featureFlags);
|
|
197
199
|
const runtimeServices = createRuntimeServices({
|
|
198
200
|
configManager: config,
|
|
199
201
|
featureFlags,
|
|
@@ -233,6 +235,11 @@ async function main(): Promise<void> {
|
|
|
233
235
|
userAuth,
|
|
234
236
|
runtimeServices,
|
|
235
237
|
serveFactory: createSafeHostServeFactory('Standalone daemon'),
|
|
238
|
+
// Hand the facade this binary's real version + exec path so its self-update
|
|
239
|
+
// lifecycle compares the HOST's version (not the sdk package's). Resolves to
|
|
240
|
+
// undefined for a non-binary install, so a dev run stays host-managed and
|
|
241
|
+
// never swaps the interpreter. See lifecycle.ts.
|
|
242
|
+
updateArtifact: resolveDaemonUpdateArtifact({ version: VERSION }),
|
|
236
243
|
});
|
|
237
244
|
const listener = new HttpListener({
|
|
238
245
|
hookDispatcher: runtimeServices.hookDispatcher,
|
|
@@ -268,6 +275,11 @@ async function main(): Promise<void> {
|
|
|
268
275
|
config.get('danger.httpListener') ? listener.start() : Promise.resolve(),
|
|
269
276
|
]);
|
|
270
277
|
|
|
278
|
+
// The DaemonServer facade owns the hourly self-update loop now (fed this
|
|
279
|
+
// binary's version + exec path via updateArtifact above); it checks hourly,
|
|
280
|
+
// swaps only at an idle moment, keeps the outgoing binary at `<path>.previous`,
|
|
281
|
+
// and stops with the daemon. No separate host loop.
|
|
282
|
+
|
|
271
283
|
const abortController = new AbortController();
|
|
272
284
|
|
|
273
285
|
const shutdown = async (): Promise<void> => {
|
|
@@ -313,6 +325,9 @@ async function main(): Promise<void> {
|
|
|
313
325
|
daemonUrl,
|
|
314
326
|
token: companionTokenRecord.token,
|
|
315
327
|
password: bootstrapPassword,
|
|
328
|
+
// Without this the SDK defaults the printed banner header to v0.0.0 —
|
|
329
|
+
// the visible version must be this binary's real version.
|
|
330
|
+
version: VERSION,
|
|
316
331
|
surface: 'tui',
|
|
317
332
|
});
|
|
318
333
|
const payload = encodeConnectionPayload(connectionInfo);
|