@pellux/goodvibes-tui 1.16.1 → 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 +89 -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/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 +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
|
@@ -77,6 +77,8 @@ export interface UiPlatformServices {
|
|
|
77
77
|
readonly sessionSpineActive?: boolean;
|
|
78
78
|
readonly sessionSpineStatus?: 'unknown' | 'online' | 'offline';
|
|
79
79
|
};
|
|
80
|
+
/** Undelivered daemon receipts (update/crash/migration) for the attach-time consuming read; empty when no daemon runs. */
|
|
81
|
+
collectDaemonReceipts(): readonly { readonly id: string; readonly text: string; readonly at: number }[];
|
|
80
82
|
restart(): Promise<{
|
|
81
83
|
readonly daemonRunning: boolean;
|
|
82
84
|
readonly daemonPortInUse?: boolean;
|
|
@@ -113,7 +115,7 @@ export interface UiRuntimeSharedServices {
|
|
|
113
115
|
readonly distributedRuntime: RuntimeServices['distributedRuntime'];
|
|
114
116
|
readonly remoteRunnerRegistry: RuntimeServices['remoteRunnerRegistry'] & RemoteRunnerRegistry;
|
|
115
117
|
readonly remoteSupervisor: RuntimeServices['remoteSupervisor'] & RemoteSupervisor;
|
|
116
|
-
/**
|
|
118
|
+
/** the shared live process registry backing the Fleet panel. */
|
|
117
119
|
readonly processRegistry: RuntimeServices['processRegistry'];
|
|
118
120
|
/** The shared runtime event bus — the Fleet panel subscribes to its 'communication' domain for the honest steer-consumed signal. */
|
|
119
121
|
readonly runtimeBus: RuntimeServices['runtimeBus'];
|
|
@@ -1,79 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure logic for `/update`: version comparison, the latest-release-tag
|
|
3
|
-
* redirect lookup, and honest install-kind detection.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* redirect lookup, and honest install-kind detection.
|
|
4
|
+
*
|
|
5
|
+
* Version comparison and the release-tag lookup are re-exported from the
|
|
6
|
+
* SDK's canonical update policy module (platform/runtime/self-update), which
|
|
7
|
+
* was hoisted from this file's semantics — one mechanism everywhere.
|
|
8
|
+
* Install-kind detection stays local: it encodes how THIS package is
|
|
9
|
+
* installed (compiled binary vs bun/npm package vs source run) and what
|
|
10
|
+
* command replaces a swap for each kind.
|
|
8
11
|
*
|
|
9
12
|
* The self-update download/verify/swap orchestration that USES these lives
|
|
10
13
|
* in src/input/commands/update-runtime.ts; this module only decides "is
|
|
11
14
|
* there a newer version" and "can this install be swapped in place".
|
|
12
15
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* integers (a leading "v" is ignored, matching the "vX.Y.Z" release tag
|
|
21
|
-
* format). Returns -1/0/1 for a<b / a==b / a>b. Non-numeric or missing
|
|
22
|
-
* components are treated as 0, so "1.2" and "1.2.0" compare equal.
|
|
23
|
-
*/
|
|
24
|
-
export function compareVersions(a: string, b: string): -1 | 0 | 1 {
|
|
25
|
-
const partsA = normalizeVersion(a).split('.');
|
|
26
|
-
const partsB = normalizeVersion(b).split('.');
|
|
27
|
-
const length = Math.max(partsA.length, partsB.length);
|
|
28
|
-
for (let i = 0; i < length; i++) {
|
|
29
|
-
const x = Number.parseInt(partsA[i] ?? '0', 10) || 0;
|
|
30
|
-
const y = Number.parseInt(partsB[i] ?? '0', 10) || 0;
|
|
31
|
-
if (x < y) return -1;
|
|
32
|
-
if (x > y) return 1;
|
|
33
|
-
}
|
|
34
|
-
return 0;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Extracts the release tag from a GitHub "/releases/latest" redirect
|
|
39
|
-
* Location header, e.g. "https://github.com/mgd34msu/goodvibes-tui/releases/tag/v1.13.2"
|
|
40
|
-
* -> "v1.13.2". Mirrors scripts/install.sh's own redirect-URL tag parsing
|
|
41
|
-
* (it takes everything after the final slash of the redirect target).
|
|
42
|
-
*/
|
|
43
|
-
export function parseReleaseTagFromLocation(location: string | null | undefined): string | null {
|
|
44
|
-
if (!location) return null;
|
|
45
|
-
const segments = location.split('/').filter((segment) => segment.length > 0);
|
|
46
|
-
const tag = segments[segments.length - 1];
|
|
47
|
-
return tag && tag.length > 0 ? tag : null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Minimal shape of the subset of the fetch API this module needs, so tests inject a stub instead of the real network. */
|
|
51
|
-
export interface UpdateFetchLike {
|
|
52
|
-
(url: string, init?: { method?: string; redirect?: 'manual' | 'follow' | 'error' }): Promise<{
|
|
53
|
-
readonly ok: boolean;
|
|
54
|
-
readonly status: number;
|
|
55
|
-
readonly url: string;
|
|
56
|
-
readonly headers: { get(name: string): string | null };
|
|
57
|
-
text(): Promise<string>;
|
|
58
|
-
arrayBuffer(): Promise<ArrayBuffer>;
|
|
59
|
-
}>;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Resolves the latest release tag the same way scripts/install.sh does:
|
|
64
|
-
* a HEAD request with redirects NOT followed, reading the tag out of the
|
|
65
|
-
* response's redirect Location header. Throws if no tag can be resolved —
|
|
66
|
-
* callers must not silently fall back to "already current".
|
|
67
|
-
*/
|
|
68
|
-
export async function resolveLatestReleaseTag(fetchImpl: UpdateFetchLike, releasesLatestUrl: string): Promise<string> {
|
|
69
|
-
const response = await fetchImpl(releasesLatestUrl, { method: 'HEAD', redirect: 'manual' });
|
|
70
|
-
const location = response.headers.get('location');
|
|
71
|
-
const tag = parseReleaseTagFromLocation(location) ?? (response.url !== releasesLatestUrl ? parseReleaseTagFromLocation(response.url) : null);
|
|
72
|
-
if (!tag) {
|
|
73
|
-
throw new Error(`could not resolve the latest release tag from ${releasesLatestUrl} (no redirect Location header)`);
|
|
74
|
-
}
|
|
75
|
-
return tag;
|
|
76
|
-
}
|
|
16
|
+
export {
|
|
17
|
+
compareVersions,
|
|
18
|
+
normalizeVersion,
|
|
19
|
+
parseReleaseTagFromLocation,
|
|
20
|
+
resolveLatestReleaseTag,
|
|
21
|
+
type UpdateFetchLike,
|
|
22
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/self-update';
|
|
77
23
|
|
|
78
24
|
/**
|
|
79
25
|
* How this running process was installed, detected honestly from
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// workstream-services.ts — phase/work-item orchestration engine
|
|
3
3
|
//
|
|
4
4
|
// Constructs the TUI's OrchestrationEngine instance (@pellux/goodvibes-sdk/
|
|
5
|
-
// platform/orchestration, landed on SDK main as
|
|
5
|
+
// platform/orchestration, landed on SDK main as) and a thin
|
|
6
6
|
// command-facing facade around it. Extracted into its own module rather than
|
|
7
7
|
// built inline in services.ts: services.ts sits at the architecture check's
|
|
8
8
|
// 800-line cap (scripts/check-architecture.ts), so any new service gets its
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// (input/commands/workstream-runtime.ts) needs a create -> propose -> approve
|
|
18
18
|
// -> launch flow (Pillar-3 doctrine: render the plan in the transcript before
|
|
19
19
|
// spending anything, mirroring /plan's approve step). REALITY-WINS DIVERGENCE
|
|
20
|
-
// from the
|
|
20
|
+
// from the design brief: the brief recommended an ENGINE-owned draft; the
|
|
21
21
|
// real engine (verified against the linked SDK build) exposes no pre-creation
|
|
22
22
|
// draft concept at all, so WorkstreamDraft below is TUI-owned state, held on
|
|
23
23
|
// this module's facade instance (constructed once, threaded onto
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConversationManager } from '../core/conversation';
|
|
2
|
-
import type { PermissionRequest } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
2
|
+
import type { PermissionRequest, RememberTier } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
3
3
|
import type { SessionSnapshot } from '@/runtime/index.ts';
|
|
4
4
|
import type { SystemMessageRouter } from '../core/system-message-router.ts';
|
|
5
5
|
import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
|
|
@@ -7,11 +7,20 @@ import { replayJournalForSession } from '../core/session-recovery.ts';
|
|
|
7
7
|
import { applyHunkKey, buildModifiedEditArgs, type HunkSelectionState } from '../permissions/hunk-selection.ts';
|
|
8
8
|
|
|
9
9
|
export type PendingPermissionState = PermissionRequest & {
|
|
10
|
-
resolve: (approved: boolean, remember?: boolean, modifiedArgs?: Record<string, unknown
|
|
10
|
+
resolve: (approved: boolean, remember?: boolean, modifiedArgs?: Record<string, unknown>, extras?: { rememberTier?: RememberTier; reason?: string }) => void;
|
|
11
11
|
/** Present only when isHunkSelectable(request) was true when the prompt was opened. */
|
|
12
12
|
hunkState?: HunkSelectionState;
|
|
13
|
-
/** True once the user pressed `d` to expand a condensed low-risk card. (
|
|
13
|
+
/** True once the user pressed `d` to expand a condensed low-risk card. (2b.) */
|
|
14
14
|
detailsExpanded?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Active typed-reply mode: 'deny-reason' (started by typing while the card
|
|
17
|
+
* is up — Enter denies with the typed reason as feedback) or 'exec-answer'
|
|
18
|
+
* (an exec-prompt ask; Enter approves with the typed answer feeding the
|
|
19
|
+
* running command's stdin). Undefined = plain card keys.
|
|
20
|
+
*/
|
|
21
|
+
replyMode?: 'deny-reason' | 'exec-answer';
|
|
22
|
+
/** The reply draft while replyMode is active. */
|
|
23
|
+
replyBuffer?: string;
|
|
15
24
|
/**
|
|
16
25
|
* Epoch ms when the prompt first appeared. Keystrokes that arrive within
|
|
17
26
|
* APPROVAL_INPUT_DEBOUNCE_MS of this are swallowed (not interpreted as an
|
|
@@ -135,6 +144,69 @@ export function handleBlockingShellInput(
|
|
|
135
144
|
return { handled: true, pendingPermission: { ...req, hunkState: state }, recoveryPending };
|
|
136
145
|
}
|
|
137
146
|
|
|
147
|
+
// Ctrl+C is ALWAYS the hard abort: deny and kill the turn, in every mode.
|
|
148
|
+
if (data === '\x03') {
|
|
149
|
+
req.resolve(false, false);
|
|
150
|
+
abortTurn();
|
|
151
|
+
render();
|
|
152
|
+
return { handled: true, pendingPermission: null, recoveryPending };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Typed-reply mode: deny-with-reason (started by typing on any card) or
|
|
156
|
+
// exec-answer (an exec-prompt ask opens in this mode). Every printable
|
|
157
|
+
// key is text here — nothing is a card command.
|
|
158
|
+
if (req.replyMode) {
|
|
159
|
+
const buffer = req.replyBuffer ?? '';
|
|
160
|
+
if (data === '\r' || data === '\n') {
|
|
161
|
+
if (req.replyMode === 'exec-answer') {
|
|
162
|
+
// The typed answer feeds the running command's stdin via the
|
|
163
|
+
// decision's modifiedArgs (SDK exec-prompt wiring contract).
|
|
164
|
+
req.resolve(true, false, { answer: buffer });
|
|
165
|
+
} else {
|
|
166
|
+
// Deny is feedback: the reason rides the structured "user declined"
|
|
167
|
+
// tool result so the model can adapt — the turn is NOT aborted.
|
|
168
|
+
req.resolve(false, false, undefined, { reason: buffer });
|
|
169
|
+
}
|
|
170
|
+
render();
|
|
171
|
+
return { handled: true, pendingPermission: null, recoveryPending };
|
|
172
|
+
}
|
|
173
|
+
if (data === '\x1b') {
|
|
174
|
+
if (req.replyMode === 'exec-answer') {
|
|
175
|
+
// Esc on an exec-prompt: clear a draft first; with nothing typed it
|
|
176
|
+
// declines the prompt (the run gets the honest unanswered result).
|
|
177
|
+
if (buffer.length > 0) {
|
|
178
|
+
render();
|
|
179
|
+
return { handled: true, pendingPermission: { ...req, replyBuffer: '' }, recoveryPending };
|
|
180
|
+
}
|
|
181
|
+
req.resolve(false, false);
|
|
182
|
+
render();
|
|
183
|
+
return { handled: true, pendingPermission: null, recoveryPending };
|
|
184
|
+
}
|
|
185
|
+
// Esc leaves deny-reason mode back to the plain card.
|
|
186
|
+
render();
|
|
187
|
+
return { handled: true, pendingPermission: { ...req, replyMode: undefined, replyBuffer: undefined }, recoveryPending };
|
|
188
|
+
}
|
|
189
|
+
if (data === '\x7f' || data === '\b') {
|
|
190
|
+
render();
|
|
191
|
+
return { handled: true, pendingPermission: { ...req, replyBuffer: buffer.slice(0, -1) }, recoveryPending };
|
|
192
|
+
}
|
|
193
|
+
if (data.length >= 1 && !data.startsWith('\x1b') && data >= ' ') {
|
|
194
|
+
render();
|
|
195
|
+
return { handled: true, pendingPermission: { ...req, replyBuffer: buffer + data }, recoveryPending };
|
|
196
|
+
}
|
|
197
|
+
render();
|
|
198
|
+
return { handled: true, pendingPermission, recoveryPending };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Scroll, mouse, PageUp/Down, arrow, and panel-navigation keys — plus a
|
|
202
|
+
// bare Esc — are not card answers. Pass them through to the normal input
|
|
203
|
+
// handler so the transcript stays scrollable and Esc only drops focus. The
|
|
204
|
+
// request stays pending (answer it with y/n or a remember tier when ready);
|
|
205
|
+
// Ctrl+C above is still the hard abort, and 'n' still denies.
|
|
206
|
+
if (data.startsWith('\x1b')) {
|
|
207
|
+
return { handled: false, pendingPermission, recoveryPending };
|
|
208
|
+
}
|
|
209
|
+
|
|
138
210
|
const key = data.toLowerCase().trim();
|
|
139
211
|
|
|
140
212
|
if (key === 'y') {
|
|
@@ -144,14 +216,31 @@ export function handleBlockingShellInput(
|
|
|
144
216
|
}
|
|
145
217
|
|
|
146
218
|
if (key === 'a') {
|
|
147
|
-
|
|
219
|
+
// Legacy always-this-session choice — the 'session' remember tier.
|
|
220
|
+
req.resolve(true, true, undefined, { rememberTier: 'session' });
|
|
148
221
|
render();
|
|
149
222
|
return { handled: true, pendingPermission: null, recoveryPending };
|
|
150
223
|
}
|
|
151
224
|
|
|
152
|
-
|
|
225
|
+
// Numbered remember tiers ([1]..[N], most specific first) from the SDK's
|
|
226
|
+
// rememberOptions: approve AND remember at that tier. A generalizing tier
|
|
227
|
+
// writes a durable user-origin rule; 'session' only caches in memory.
|
|
228
|
+
if (/^[1-9]$/.test(key) && req.rememberOptions && req.rememberOptions.length > 0) {
|
|
229
|
+
const option = req.rememberOptions[Number(key) - 1];
|
|
230
|
+
if (option) {
|
|
231
|
+
req.resolve(true, option.tier === 'session', undefined, { rememberTier: option.tier });
|
|
232
|
+
render();
|
|
233
|
+
return { handled: true, pendingPermission: null, recoveryPending };
|
|
234
|
+
}
|
|
235
|
+
render();
|
|
236
|
+
return { handled: true, pendingPermission, recoveryPending };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (key === 'n') {
|
|
240
|
+
// Plain deny — feedback to the model (the SDK renders an honest "user
|
|
241
|
+
// declined" result), never a turn abort. Ctrl+C above is the hard stop;
|
|
242
|
+
// Esc drops focus (handled above) rather than denying.
|
|
153
243
|
req.resolve(false, false);
|
|
154
|
-
abortTurn();
|
|
155
244
|
render();
|
|
156
245
|
return { handled: true, pendingPermission: null, recoveryPending };
|
|
157
246
|
}
|
|
@@ -166,6 +255,13 @@ export function handleBlockingShellInput(
|
|
|
166
255
|
};
|
|
167
256
|
}
|
|
168
257
|
|
|
258
|
+
// Any other printable key starts deny-with-reason: what the user types
|
|
259
|
+
// becomes the denial feedback, seeded with this first character.
|
|
260
|
+
if (data.length === 1 && data >= ' ') {
|
|
261
|
+
render();
|
|
262
|
+
return { handled: true, pendingPermission: { ...req, replyMode: 'deny-reason', replyBuffer: data }, recoveryPending };
|
|
263
|
+
}
|
|
264
|
+
|
|
169
265
|
render();
|
|
170
266
|
return { handled: true, pendingPermission, recoveryPending };
|
|
171
267
|
}
|
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.17.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;
|