@pellux/goodvibes-tui 1.16.1 → 1.18.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 +138 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +5490 -1806
- 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/management-commands.ts +82 -15
- package/src/cli/management.ts +0 -2
- 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/consolidation-receipt.ts +41 -0
- package/src/core/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +18 -9
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/memory-provenance.ts +67 -0
- package/src/core/pairing-devices.ts +49 -0
- package/src/core/pairing-handoff.ts +101 -0
- package/src/core/pairing-offers.ts +83 -0
- package/src/core/pairing-origin.ts +78 -0
- package/src/core/pairing-tailscale-gateway.ts +56 -0
- package/src/core/power-chip-source.ts +91 -0
- package/src/core/power-status.ts +95 -0
- package/src/core/stable-host.ts +171 -0
- package/src/core/stream-event-wiring.ts +29 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-budget-outcome.ts +60 -0
- package/src/core/turn-cancellation.ts +40 -1
- package/src/core/turn-event-wiring.ts +14 -8
- package/src/daemon/cli.ts +64 -58
- 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 +55 -9
- package/src/input/commands/acp-runtime.ts +72 -0
- 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/devices-runtime.ts +109 -0
- package/src/input/commands/git-runtime.ts +28 -4
- package/src/input/commands/graph-runtime.ts +45 -0
- 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/operator-rpc.ts +32 -11
- 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/power-runtime.ts +56 -0
- package/src/input/commands/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- package/src/input/commands/qrcode-runtime.ts +14 -19
- package/src/input/commands/queue-runtime.ts +70 -0
- 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-attempts.ts +6 -1
- package/src/input/commands/workstream-runtime.ts +46 -27
- package/src/input/commands/worktree-runtime.ts +35 -8
- package/src/input/commands.ts +10 -2
- package/src/input/config-modal-types.ts +14 -3
- package/src/input/config-modal.ts +75 -13
- 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 +29 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +21 -3
- 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 +52 -57
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +27 -17
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +90 -90
- 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 +83 -11
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +16 -4
- package/src/panels/builtin-modals.ts +59 -42
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +39 -6
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-acts.ts +446 -0
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-gateway.ts +117 -0
- package/src/panels/fleet-observed-render.ts +128 -0
- package/src/panels/fleet-panel-format.ts +163 -11
- package/src/panels/fleet-panel-worktree-detail.ts +14 -0
- package/src/panels/fleet-panel.ts +73 -57
- package/src/panels/fleet-read-model.ts +84 -14
- package/src/panels/fleet-spawn.ts +266 -0
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +41 -5
- package/src/panels/fleet-tabs.ts +15 -16
- 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/memory-consolidation-gateway.ts +94 -0
- package/src/panels/modals/devices-modal.ts +161 -0
- 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 +153 -9
- 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 +198 -68
- 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 +7 -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/panels/workstream-graph-render.ts +123 -0
- 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/composer-fragments.ts +69 -0
- 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/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 +1 -1
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +23 -5
- package/src/renderer/settings-modal.ts +129 -39
- package/src/renderer/shell-surface.ts +6 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +13 -7
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +38 -1
- package/src/runtime/bootstrap-command-context.ts +7 -2
- package/src/runtime/bootstrap-command-parts.ts +9 -5
- package/src/runtime/bootstrap-core.ts +41 -41
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +13 -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 +90 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/idle-power-services.ts +85 -0
- package/src/runtime/index.ts +6 -21
- package/src/runtime/interaction-seams.ts +109 -0
- 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/power-keepawake-remote.ts +75 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- 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 +113 -113
- 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 +6 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +108 -132
- package/src/shell/recovery-input-helpers.ts +53 -166
- 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
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Helper factories for main()'s stdin fast-path: the
|
|
3
|
-
* persistence
|
|
4
|
-
* affordance. Extracted from main.ts so the entrypoint
|
|
5
|
-
* architecture line ceiling; main() wires these with its live
|
|
2
|
+
* Helper factories for main()'s stdin fast-path and startup: the crash-recovery
|
|
3
|
+
* snapshot persistence + panel-reopen callbacks, the SILENT auto-restore, and
|
|
4
|
+
* the one-key error-retry affordance. Extracted from main.ts so the entrypoint
|
|
5
|
+
* stays under the architecture line ceiling; main() wires these with its live
|
|
6
|
+
* services.
|
|
7
|
+
*
|
|
8
|
+
* Recovery is silent (owner ruling): there is no Ctrl+R prompt/banner and no
|
|
9
|
+
* dismiss/preserve dance. At startup autoRestoreRecoverySession restores the
|
|
10
|
+
* newest crash snapshot in place and emits a single one-line receipt. The old
|
|
11
|
+
* `.preserved` sibling machinery is gone — a session's exit deletes only its
|
|
12
|
+
* OWN snapshot (scoped by sessionId), so a concurrent session's snapshot is
|
|
13
|
+
* never touched.
|
|
6
14
|
*/
|
|
7
15
|
|
|
8
|
-
import { copyFileSync, existsSync, readFileSync, unlinkSync } from 'node:fs';
|
|
9
16
|
import type { ConversationMessageSnapshot } from '../core/conversation.ts';
|
|
10
|
-
import type {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
deleteRecoveryFile as deleteLiveRecoveryFile,
|
|
14
|
-
getRecoveryFilePath,
|
|
15
|
-
loadRecoveryConversation as loadLiveRecoveryConversation,
|
|
16
|
-
} from '@/runtime/index.ts';
|
|
17
|
+
import type { SessionSnapshot } from '@/runtime/index.ts';
|
|
18
|
+
import { autoRestoreRecovery } from '@/runtime/index.ts';
|
|
19
|
+
import { replayJournalForSession } from '../core/session-recovery.ts';
|
|
17
20
|
|
|
18
21
|
export interface PersistRecoveryDeps {
|
|
19
22
|
readonly sessionManager: {
|
|
@@ -48,166 +51,50 @@ export function createReopenRecoveryPanels(deps: ReopenPanelsDeps): (snapshot: S
|
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
// main.ts's 60s autosave (writeRecoveryFile) overwrites the single shared
|
|
55
|
-
// recovery.jsonl with the CURRENT session's live state within a minute — so
|
|
56
|
-
// the dismiss banner's promise ("still on disk; you will be asked again next
|
|
57
|
-
// time") went false silently well before the next launch. Fix: the moment
|
|
58
|
-
// the user dismisses (blocking-input.ts), copy recovery.jsonl aside to a
|
|
59
|
-
// preserved sibling file (recovery.jsonl.preserved) so it survives this
|
|
60
|
-
// session's later autosaves. Startup then checks BOTH the live file and the
|
|
61
|
-
// preserved sibling and offers whichever is newer (pickNewestRecoveryInfo),
|
|
62
|
-
// bounded to exactly one preserved file — a later dismiss replaces it, and
|
|
63
|
-
// that replacement is reported honestly (PreserveRecoveryResult.replacedPrevious)
|
|
64
|
-
// rather than silently dropping the older snapshot.
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
export interface RecoveryFileDeps {
|
|
54
|
+
export interface AutoRestoreRecoveryDeps {
|
|
55
|
+
readonly workingDirectory: string;
|
|
68
56
|
readonly homeDirectory: string;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
export interface PreservedRecoveryInfo {
|
|
73
|
-
readonly title: string;
|
|
74
|
-
readonly timestamp: number;
|
|
75
|
-
readonly sessionId: string;
|
|
76
|
-
readonly returnContext?: SessionReturnContextSummary | undefined;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface PreserveRecoveryResult {
|
|
80
|
-
/** True if a live recovery.jsonl existed and was copied to the preserved sibling. */
|
|
81
|
-
readonly preserved: boolean;
|
|
82
|
-
/** True if an existing preserved sibling (from an earlier dismiss) was overwritten. */
|
|
83
|
-
readonly replacedPrevious: boolean;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function preservedRecoveryPath(deps: RecoveryFileDeps): string {
|
|
87
|
-
return `${getRecoveryFilePath(deps.homeDirectory, deps.surfaceRoot)}.preserved`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function readRecoveryMetaLine(path: string): { title?: string; timestamp?: number; sessionId?: string; returnContext?: SessionReturnContextSummary } | null {
|
|
91
|
-
try {
|
|
92
|
-
if (!existsSync(path)) return null;
|
|
93
|
-
const firstLine = readFileSync(path, 'utf-8').split('\n')[0];
|
|
94
|
-
if (!firstLine) return null;
|
|
95
|
-
return JSON.parse(firstLine) as { title?: string; timestamp?: number; sessionId?: string; returnContext?: SessionReturnContextSummary };
|
|
96
|
-
} catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Copy the live recovery.jsonl aside to the preserved sibling. Called once, at dismiss. */
|
|
102
|
-
export function createPreserveRecoveryFile(deps: RecoveryFileDeps): () => PreserveRecoveryResult {
|
|
103
|
-
return () => {
|
|
104
|
-
const live = getRecoveryFilePath(deps.homeDirectory, deps.surfaceRoot);
|
|
105
|
-
const preserved = preservedRecoveryPath(deps);
|
|
106
|
-
if (!existsSync(live)) return { preserved: false, replacedPrevious: false };
|
|
107
|
-
const replacedPrevious = existsSync(preserved);
|
|
108
|
-
try {
|
|
109
|
-
copyFileSync(live, preserved);
|
|
110
|
-
return { preserved: true, replacedPrevious };
|
|
111
|
-
} catch {
|
|
112
|
-
return { preserved: false, replacedPrevious: false };
|
|
113
|
-
}
|
|
57
|
+
/** The live conversation to restore INTO (fromJSON applies the snapshot). */
|
|
58
|
+
readonly conversation: {
|
|
59
|
+
fromJSON(input: { messages: ConversationMessageSnapshot[]; title?: string; titleSource?: SessionSnapshot['titleSource'] }): void;
|
|
114
60
|
};
|
|
61
|
+
/** Persist the post-replay snapshot so the WAL gap is durably closed. */
|
|
62
|
+
readonly persistSnapshot: (msgs: ConversationMessageSnapshot[]) => void;
|
|
63
|
+
/** Reopen the restored session's panels. */
|
|
64
|
+
readonly reopenPanels: (snapshot: SessionSnapshot) => void;
|
|
65
|
+
/** Surface the one-line receipt (the only user-visible sign of a restore). */
|
|
66
|
+
readonly systemMessageRouter: { high(message: string): void };
|
|
115
67
|
}
|
|
116
68
|
|
|
117
69
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
|
|
124
|
-
export function checkPreservedRecoveryFile(deps: RecoveryFileDeps): PreservedRecoveryInfo | null {
|
|
125
|
-
const meta = readRecoveryMetaLine(preservedRecoveryPath(deps));
|
|
126
|
-
if (!meta) return null;
|
|
127
|
-
return { title: meta.title ?? '', timestamp: meta.timestamp ?? 0, sessionId: meta.sessionId ?? '', returnContext: meta.returnContext };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Load the preserved sibling's full snapshot — same JSONL shape as recovery.jsonl since it's a byte-identical copy. */
|
|
131
|
-
export function loadPreservedRecoveryConversation(deps: RecoveryFileDeps): SessionSnapshot | null {
|
|
132
|
-
const path = preservedRecoveryPath(deps);
|
|
133
|
-
try {
|
|
134
|
-
if (!existsSync(path)) return null;
|
|
135
|
-
const lines = readFileSync(path, 'utf-8').split('\n').filter(Boolean);
|
|
136
|
-
if (lines.length < 2) return { messages: [] };
|
|
137
|
-
const metaLine = JSON.parse(lines[0]!) as { title?: string; titleSource?: SessionSnapshot['titleSource']; returnContext?: SessionSnapshot['returnContext'] };
|
|
138
|
-
return {
|
|
139
|
-
title: metaLine.title,
|
|
140
|
-
titleSource: metaLine.titleSource,
|
|
141
|
-
returnContext: metaLine.returnContext,
|
|
142
|
-
messages: lines.slice(1).map((line) => {
|
|
143
|
-
const { type: _type, ...rest } = JSON.parse(line) as { type: string } & Record<string, unknown>;
|
|
144
|
-
return rest;
|
|
145
|
-
}),
|
|
146
|
-
};
|
|
147
|
-
} catch {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Remove the preserved sibling — called after it has been restored or discarded. */
|
|
153
|
-
export function deletePreservedRecoveryFile(deps: RecoveryFileDeps): void {
|
|
154
|
-
try {
|
|
155
|
-
unlinkSync(preservedRecoveryPath(deps));
|
|
156
|
-
} catch {
|
|
157
|
-
// missing file is fine
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Pick the newer of the live vs. preserved recovery info for the startup
|
|
163
|
-
* prompt, tagging which source it came from so the caller knows which
|
|
164
|
-
* load/delete pair to use. Null when neither exists.
|
|
165
|
-
*/
|
|
166
|
-
export function pickNewestRecoveryInfo<T extends { readonly timestamp: number }>(
|
|
167
|
-
live: T | null,
|
|
168
|
-
preserved: T | null,
|
|
169
|
-
): (T & { readonly source: 'live' | 'preserved' }) | null {
|
|
170
|
-
if (!live && !preserved) return null;
|
|
171
|
-
if (!live) return { ...(preserved as T), source: 'preserved' };
|
|
172
|
-
if (!preserved) return { ...live, source: 'live' };
|
|
173
|
-
return preserved.timestamp > live.timestamp
|
|
174
|
-
? { ...(preserved as T), source: 'preserved' }
|
|
175
|
-
: { ...live, source: 'live' };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Startup entry point: resolve which recovery prompt (if any) main.ts should
|
|
180
|
-
* show — the live recovery.jsonl, or an earlier dismiss's preserved sibling,
|
|
181
|
-
* whichever is newer. Bundles checkRecoveryFile + checkPreservedRecoveryFile
|
|
182
|
-
* + pickNewestRecoveryInfo so main.ts's entrypoint stays a one-liner.
|
|
70
|
+
* Silent crash-recovery restore at startup — no prompt, no banner. Loads the
|
|
71
|
+
* newest crash snapshot (the SDK scopes the load AND the follow-up delete to
|
|
72
|
+
* that snapshot's own session id), applies it to the live conversation, replays
|
|
73
|
+
* any journal turns written after the snapshot, reopens its panels, and emits
|
|
74
|
+
* the SDK's one-line receipt. Returns true when a session was restored, false
|
|
75
|
+
* when there was nothing to restore.
|
|
183
76
|
*/
|
|
184
|
-
export function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
export function createRecoveryFileOps(getSource: () => 'live' | 'preserved', deps: RecoveryFileDeps): RecoveryFileOps {
|
|
206
|
-
return {
|
|
207
|
-
loadRecoveryConversation: () => (getSource() === 'preserved' ? loadPreservedRecoveryConversation(deps) : loadLiveRecoveryConversation(deps)),
|
|
208
|
-
deleteRecoveryFile: () => (getSource() === 'preserved' ? deletePreservedRecoveryFile(deps) : deleteLiveRecoveryFile(deps)),
|
|
209
|
-
preserveRecoveryFile: getSource() === 'live' ? createPreserveRecoveryFile(deps) : undefined,
|
|
210
|
-
};
|
|
77
|
+
export function autoRestoreRecoverySession(deps: AutoRestoreRecoveryDeps): boolean {
|
|
78
|
+
const result = autoRestoreRecovery({ workingDirectory: deps.workingDirectory, homeDirectory: deps.homeDirectory });
|
|
79
|
+
if (!result) return false;
|
|
80
|
+
const { snapshot, info, receipt } = result;
|
|
81
|
+
deps.conversation.fromJSON({
|
|
82
|
+
messages: snapshot.messages as ConversationMessageSnapshot[],
|
|
83
|
+
title: snapshot.title,
|
|
84
|
+
titleSource: snapshot.titleSource,
|
|
85
|
+
});
|
|
86
|
+
// Replay journal records that post-date the snapshot so turns written after
|
|
87
|
+
// the last recovery-file write (but before the crash) are not dropped.
|
|
88
|
+
replayJournalForSession({
|
|
89
|
+
homeDirectory: deps.homeDirectory,
|
|
90
|
+
sessionId: info.sessionId,
|
|
91
|
+
snapshotTimestamp: info.timestamp ?? 0,
|
|
92
|
+
conversation: deps.conversation as never,
|
|
93
|
+
persistSnapshot: deps.persistSnapshot,
|
|
94
|
+
});
|
|
95
|
+
deps.reopenPanels(snapshot);
|
|
96
|
+
deps.systemMessageRouter.high(`[Recovery] ${receipt}`);
|
|
97
|
+
return true;
|
|
211
98
|
}
|
|
212
99
|
|
|
213
100
|
export interface ErrorAffordanceDeps {
|
package/src/shell/ui-openers.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { syncServiceSettingToPlatform } from './service-settings-sync.ts';
|
|
|
19
19
|
import { setActiveThemeMode } from '../renderer/theme.ts';
|
|
20
20
|
import { THEME_MODE_CONFIG_KEY, coerceThemeModeSetting } from '../renderer/theme-mode-config.ts';
|
|
21
21
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
22
|
+
import { buildFirstOpenItems, decodeFirstOpenChoice, selfRecordWorkspaceRegistration } from '../cli/tui-startup.ts';
|
|
23
|
+
import type { WorkspaceTrustLevel } from '../runtime/trust/workspace-trust.ts';
|
|
22
24
|
|
|
23
25
|
type WireShellUiOpenersOptions = {
|
|
24
26
|
commandContext: CommandContext;
|
|
@@ -33,13 +35,15 @@ type WireShellUiOpenersOptions = {
|
|
|
33
35
|
subscriptionManager: SubscriptionManager;
|
|
34
36
|
secretsManager?: Pick<SecretsManager, 'delete' | 'get' | 'set'>;
|
|
35
37
|
serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>;
|
|
36
|
-
/**
|
|
38
|
+
/** Backs the model picker's 'embeddings' target and its own item-list mode. */
|
|
37
39
|
memoryEmbeddingRegistry: Pick<MemoryEmbeddingProviderRegistry, 'getDefaultProviderId' | 'setDefaultProvider' | 'status'>;
|
|
38
40
|
workingDirectory: string;
|
|
39
41
|
homeDirectory: string;
|
|
40
42
|
getConfiguredProviderIds: () => string[];
|
|
41
43
|
getPinned: () => Promise<string[]>;
|
|
42
44
|
render: () => void;
|
|
45
|
+
/** Trust-at-consequence-time bridge — patched here with the real modal-driving implementation. */
|
|
46
|
+
trustPromptRef: { requestTrustDecision: () => Promise<WorkspaceTrustLevel> };
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
let commandCategoryMapCache: Map<string, string> | null = null;
|
|
@@ -123,8 +127,25 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
123
127
|
getConfiguredProviderIds,
|
|
124
128
|
getPinned,
|
|
125
129
|
render,
|
|
130
|
+
trustPromptRef,
|
|
126
131
|
} = options;
|
|
127
132
|
|
|
133
|
+
// Trust-at-consequence-time: raised by trustGatedAsk on the first non-read
|
|
134
|
+
// request in an undecided workspace; the answer persists via setLevel().
|
|
135
|
+
// Registration self-records here too when trusted (never for restricted —
|
|
136
|
+
// see selfRecordWorkspaceRegistration's doc).
|
|
137
|
+
trustPromptRef.requestTrustDecision = () =>
|
|
138
|
+
new Promise((resolve) => {
|
|
139
|
+
const { title, items } = buildFirstOpenItems();
|
|
140
|
+
if (!commandContext.openSelection) { resolve('restricted'); return; } // no selection surface (should not happen live)
|
|
141
|
+
commandContext.openSelection(title, items, { allowSearch: false, primaryVerbLabel: 'Choose' }, (result) => {
|
|
142
|
+
const level = decodeFirstOpenChoice(result?.item.id ?? null);
|
|
143
|
+
resolve(level);
|
|
144
|
+
if (level === 'trusted') void selfRecordWorkspaceRegistration(commandContext.workspace?.workspaceRegistrationManager);
|
|
145
|
+
render();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
128
149
|
/**
|
|
129
150
|
* Pre-resolve which provider IDs have secrets-manager keys (async batch, SDK tier pattern).
|
|
130
151
|
* Returns a set of provider IDs (not env var names) that are secrets-backed.
|
|
@@ -248,6 +269,13 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
248
269
|
};
|
|
249
270
|
|
|
250
271
|
commandContext.openModelPicker = () => {
|
|
272
|
+
// Picker-open re-check: re-verify each provider's live model list (TTL-
|
|
273
|
+
// respecting) so a freshly-opened picker reflects models the provider
|
|
274
|
+
// started or stopped serving. Fire-and-forget; a completed refresh re-renders
|
|
275
|
+
// so the list updates in place without blocking the open.
|
|
276
|
+
void providerRegistry.refreshLiveModelDiscovery?.().then((reports) => {
|
|
277
|
+
if (reports.some((report) => report.added.length > 0 || report.removed.length > 0)) render();
|
|
278
|
+
}).catch(() => {});
|
|
251
279
|
void (async () => {
|
|
252
280
|
// getSelectableModels() is catalog-driven and can list models whose
|
|
253
281
|
// `provider` id (e.g. 'google', sourced from the pricing catalog) was never
|
|
@@ -358,7 +386,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
358
386
|
input.modalOpened('settings');
|
|
359
387
|
input.settingsModal.open(configManager, featureFlags, subscriptionManager, serviceRegistry, mcpRegistry, secretsManager, {
|
|
360
388
|
onSettingApplied: (change) => {
|
|
361
|
-
//
|
|
389
|
+
// forced dark/light applies immediately (rebuild palettes + full
|
|
362
390
|
// repaint); auto only re-probes at startup, so it takes effect next launch.
|
|
363
391
|
if (String(change.key) === THEME_MODE_CONFIG_KEY) {
|
|
364
392
|
const next = coerceThemeModeSetting(change.value);
|
|
@@ -379,7 +407,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
379
407
|
render();
|
|
380
408
|
};
|
|
381
409
|
|
|
382
|
-
//
|
|
410
|
+
// (the purge): open a MIGRATE-TO-MODAL surface by name. Fed from both
|
|
383
411
|
// ctx.openModal(name) (migrated command front-doors) and PanelManager's
|
|
384
412
|
// open()-time modal-redirect callback (a legacy panel id resolving to a modal
|
|
385
413
|
// name). Surfaces are registered in builtin-modals.ts; a name with no
|
|
@@ -445,7 +473,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
445
473
|
return;
|
|
446
474
|
}
|
|
447
475
|
if (panelManager.getAllOpen().length === 0) {
|
|
448
|
-
//
|
|
476
|
+
// (the purge): 'panel-list' (a browse-all-panels picker PANEL)
|
|
449
477
|
// was DELETE-disposition — a picker over a handful of panels is dead
|
|
450
478
|
// weight now. Its replacement is this selection MODAL, built from the
|
|
451
479
|
// live registry (PanelManager.getRegisteredTypes()) rather than a
|
|
@@ -475,6 +503,10 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
475
503
|
render();
|
|
476
504
|
};
|
|
477
505
|
|
|
506
|
+
// Registry-driven surfaces built inside command handlers (/help) group by
|
|
507
|
+
// the same categories as the palette and the generated reference.
|
|
508
|
+
commandContext.getCommandCategories = () => getCommandCategoryMap();
|
|
509
|
+
|
|
478
510
|
commandContext.openCommandPalette = () => {
|
|
479
511
|
// The palette is built live from the command registry — never a hardcoded
|
|
480
512
|
// list — so it can never drift from the real command set. Category labels
|
|
@@ -534,11 +566,11 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
534
566
|
};
|
|
535
567
|
|
|
536
568
|
commandContext.showPanel = (panelId, pane, target, opts) => {
|
|
537
|
-
//
|
|
569
|
+
// forward the deep-link target so a jumped-to panel lands on the
|
|
538
570
|
// right row (fleet --target); undefined for ordinary opens.
|
|
539
571
|
panelManager.open(panelId, pane, target);
|
|
540
572
|
panelManager.show();
|
|
541
|
-
//
|
|
573
|
+
// focus rule 1a: every registered caller of showPanel is a slash
|
|
542
574
|
// command (/panel open, /routes, /approval, /tasks, /ops-control, ...) —
|
|
543
575
|
// the command path leaves focus in the composer ("the user is
|
|
544
576
|
// mid-command-flow") unless the caller explicitly asks to grab it.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* formatElapsed floors to integer seconds and lacks the null/'?ms' guards
|
|
7
7
|
* that the latency panels require.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* (the purge): formatShortDuration (used only by incident-review-panel
|
|
10
10
|
* and eval-panel, both removed — RETIRE-INTO-FLEET and DELETE respectively)
|
|
11
11
|
* was removed as a genuinely orphaned export — no remaining caller anywhere
|
|
12
12
|
* in src/.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { CONFIG_SCHEMA } from '@pellux/goodvibes-sdk/platform/config';
|
|
4
|
-
import {
|
|
4
|
+
import { FEATURE_SETTINGS } from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
5
|
+
import { featureEnablementWrite } from '../runtime/feature-settings.ts';
|
|
5
6
|
import { CommandRegistry } from '../input/command-registry.ts';
|
|
6
7
|
import { registerBuiltinCommands } from '../input/commands.ts';
|
|
7
8
|
|
|
@@ -78,8 +79,24 @@ export const FEATURE_KNOB_LOCAL_SETTINGS = [
|
|
|
78
79
|
'runtime.toolBudget.maxCostUsd',
|
|
79
80
|
] as const;
|
|
80
81
|
|
|
81
|
-
/**
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Every feature's enablement settings key whose on/off writes round-trip
|
|
84
|
+
* through a REAL on-disk ConfigManager in
|
|
85
|
+
* `src/test/verification/../input/settings-modal-flag-persistence.test.ts`
|
|
86
|
+
* ("every feature with an off position round-trips both directions"). These
|
|
87
|
+
* are first-class schema keys under the dissolved feature model, so they are
|
|
88
|
+
* genuinely behavior-verified locally. Keys already counted in the
|
|
89
|
+
* feature-knob list are excluded to keep the sum honest.
|
|
90
|
+
*/
|
|
91
|
+
const ENABLEMENT_KEYS_BEHAVIOR_VERIFIED = new Set(
|
|
92
|
+
FEATURE_SETTINGS
|
|
93
|
+
.filter((feature) => featureEnablementWrite(feature.id, true) !== null && featureEnablementWrite(feature.id, false) !== null)
|
|
94
|
+
.map((feature) => feature.enablement.key)
|
|
95
|
+
.filter((key) => !(FEATURE_KNOB_LOCAL_SETTINGS as readonly string[]).includes(key)),
|
|
96
|
+
).size;
|
|
97
|
+
|
|
98
|
+
/** Settings with real local behavior verification: the authored baseline plus the persistence-tested feature-knob and enablement keys. */
|
|
99
|
+
const SETTINGS_BEHAVIOR_VERIFIED = SETTINGS_BEHAVIOR_BASELINE + FEATURE_KNOB_LOCAL_SETTINGS.length + ENABLEMENT_KEYS_BEHAVIOR_VERIFIED;
|
|
83
100
|
|
|
84
101
|
const EXTERNAL_SLASH_COMMANDS = new Set([
|
|
85
102
|
'auth',
|
|
@@ -181,7 +198,7 @@ export function buildVerificationLedger(root: string): VerificationLedger {
|
|
|
181
198
|
const slashCommands = slashCommandNames.length;
|
|
182
199
|
const panels = countBuiltinPanels(root);
|
|
183
200
|
const cliCommands = cliCommandNames.length;
|
|
184
|
-
const featureFlags =
|
|
201
|
+
const featureFlags = FEATURE_SETTINGS.length;
|
|
185
202
|
const settings = CONFIG_SCHEMA.length;
|
|
186
203
|
const externalSlashCommands = slashCommandNames.filter((command) => EXTERNAL_SLASH_COMMANDS.has(command)).length;
|
|
187
204
|
const externalCliCommands = cliCommandNames.filter((command) => EXTERNAL_CLI_COMMANDS.has(command)).length;
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '1.
|
|
9
|
+
let _version = '1.18.0';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|