@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,57 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* recall-files-git.ts — the
|
|
2
|
+
* recall-files-git.ts — the git seam for memory file projection.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* for `add`/`commit` so the seam matches the SDK's synchronous contract.
|
|
4
|
+
* The SDK owns the production seam now (`createMemoryProjectionGit`,
|
|
5
|
+
* platform/state): it shells out to the system git, commits with a neutral
|
|
6
|
+
* projection identity (never the operator's), tolerates an unchanged tree,
|
|
7
|
+
* and — through `projectMemoryToFiles`'s ownership gate — only ever commits
|
|
8
|
+
* to a repository whose toplevel IS the projection directory, initializing
|
|
9
|
+
* one there when needed. A projection directory nested inside some other
|
|
10
|
+
* checkout can never pollute that checkout. This module is a thin local
|
|
11
|
+
* seam factory so command code keeps a single import site.
|
|
13
12
|
*/
|
|
13
|
+
import { createMemoryProjectionGit } from '@pellux/goodvibes-sdk/platform/state';
|
|
14
14
|
import type { MemoryProjectionGit } from '@pellux/goodvibes-sdk/platform/state';
|
|
15
|
-
import { GitService } from '@pellux/goodvibes-sdk/platform/git';
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Build a synchronous git seam rooted at `repoRoot`. `commit()` treats a
|
|
23
|
-
* "nothing to commit" exit as a benign no-op (not an error) so
|
|
24
|
-
* `/recall files sync` stays safely re-runnable when the projection files
|
|
25
|
-
* did not actually change since the last sync — any OTHER commit failure
|
|
26
|
-
* (no git identity configured, hook rejection, etc.) still throws, since the
|
|
27
|
-
* seam's contract has no way to report partial failure back to the caller
|
|
28
|
-
* other than throwing.
|
|
29
|
-
*/
|
|
30
|
-
export function createSyncGitSeam(repoRoot: string): MemoryProjectionGit {
|
|
31
|
-
return {
|
|
32
|
-
add(dir: string): void {
|
|
33
|
-
const result = Bun.spawnSync(['git', 'add', dir], { cwd: repoRoot });
|
|
34
|
-
if (result.exitCode !== 0) {
|
|
35
|
-
throw new Error(`git add failed: ${decode(result.stderr) || 'unknown error'}`);
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
commit(dir: string, message: string): void {
|
|
39
|
-
const result = Bun.spawnSync(['git', 'commit', '-m', message, '--', dir], { cwd: repoRoot });
|
|
40
|
-
if (result.exitCode === 0) return;
|
|
41
|
-
const output = `${decode(result.stdout)} ${decode(result.stderr)}`;
|
|
42
|
-
if (/nothing to commit/i.test(output)) return; // benign — no new/changed files this run
|
|
43
|
-
throw new Error(`git commit failed: ${output.trim() || 'unknown error'}`);
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Resolve the git seam for `dir`, or null when `dir` is not inside a git
|
|
50
|
-
* repository. Never auto-initializes a repository — that is a much bigger
|
|
51
|
-
* side effect than a memory sync command should take unasked.
|
|
52
|
-
*/
|
|
53
|
-
export function resolveSyncGitSeam(dir: string): { seam: MemoryProjectionGit; repoRoot: string } | null {
|
|
54
|
-
const repoRoot = GitService.getRepoRoot(dir);
|
|
55
|
-
if (!repoRoot) return null;
|
|
56
|
-
return { seam: createSyncGitSeam(repoRoot), repoRoot };
|
|
16
|
+
/** The projection git seam (see module note — SDK-owned behavior). */
|
|
17
|
+
export function createSyncGitSeam(): MemoryProjectionGit {
|
|
18
|
+
return createMemoryProjectionGit();
|
|
57
19
|
}
|
|
@@ -39,7 +39,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
39
39
|
import { requireShellPaths } from './runtime-services.ts';
|
|
40
40
|
import { getMemorySpine } from './recall-query.ts';
|
|
41
41
|
import { readMemoryProjectionDir } from './recall-files-config.ts';
|
|
42
|
-
import {
|
|
42
|
+
import { createSyncGitSeam } from './recall-files-git.ts';
|
|
43
43
|
|
|
44
44
|
const PROJECTION_LIST_LIMIT = 5000;
|
|
45
45
|
|
|
@@ -58,19 +58,16 @@ export async function handleRecallFilesSync(args: string[], context: CommandCont
|
|
|
58
58
|
|
|
59
59
|
const dir = resolveProjectionDir(args, context);
|
|
60
60
|
const records = await memory.list({ limit: PROJECTION_LIST_LIMIT });
|
|
61
|
-
// Resolve the git seam from the project working directory, not `dir` itself
|
|
62
|
-
// — `dir` may not exist yet on a first sync, and `git -C <dir>` requires the
|
|
63
|
-
// directory to already exist. The working directory is always real.
|
|
64
|
-
const gitSeam = resolveSyncGitSeam(requireShellPaths(context).workingDirectory);
|
|
65
61
|
|
|
66
62
|
try {
|
|
67
|
-
|
|
63
|
+
// The SDK's ownership gate commits only to a repository whose toplevel IS
|
|
64
|
+
// the projection directory (initializing one there when needed) — never
|
|
65
|
+
// to a checkout the directory merely sits inside.
|
|
66
|
+
const report = projectMemoryToFiles(records, dir, { git: createSyncGitSeam() });
|
|
68
67
|
context.print(`[recall] Projected ${report.written.length} record(s) to ${report.dir}`);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
context.print(` not committed: ${dir} is not inside a git repository`);
|
|
73
|
-
}
|
|
68
|
+
context.print(report.committed
|
|
69
|
+
? ` committed in ${report.dir} (the projection's own repository)`
|
|
70
|
+
: ' not committed (git seam did not run)');
|
|
74
71
|
} catch (error) {
|
|
75
72
|
context.print(`[recall] Projection sync failed: ${summarizeError(error)}`);
|
|
76
73
|
}
|
|
@@ -112,9 +112,9 @@ export function handleRecallExplain(args: string[], context: CommandContext): vo
|
|
|
112
112
|
* `exportState()` returns the same full `AgentRecord[]` (it's what
|
|
113
113
|
* compactConversation() already uses for the same reason).
|
|
114
114
|
*
|
|
115
|
-
* With NO agent id (
|
|
115
|
+
* With NO agent id (wiring), renders the MAIN interactive session's own
|
|
116
116
|
* per-turn injection ring — `Orchestrator.getTurnInjections()`, threaded onto
|
|
117
|
-
* the command context as `session.getMainSessionTurnInjections`. As of
|
|
117
|
+
* the command context as `session.getMainSessionTurnInjections`. As of the
|
|
118
118
|
* main session DOES route through the passive-injection engine, so this is the
|
|
119
119
|
* honest main-session default (previously there was no record source, so the
|
|
120
120
|
* no-id path could only print a usage hint). When the accessor isn't wired
|
|
@@ -47,7 +47,7 @@ async function renderStatus(ctx: CommandContext): Promise<void> {
|
|
|
47
47
|
async function renderPair(ctx: CommandContext): Promise<void> {
|
|
48
48
|
const cfg = relayConfigured(ctx);
|
|
49
49
|
if (!cfg.enabled || !cfg.flagOn) {
|
|
50
|
-
ctx.print('Relay is disabled —
|
|
50
|
+
ctx.print('Relay is disabled — turn on relay.enabled in /config relay first.');
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
const externalServices = ctx.platform.externalServices;
|
|
@@ -48,7 +48,7 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
|
|
|
48
48
|
async handler(args, ctx) {
|
|
49
49
|
if (args.length === 0) {
|
|
50
50
|
if (ctx.openModal) {
|
|
51
|
-
ctx.openModal('remote-modal'); //
|
|
51
|
+
ctx.openModal('remote-modal'); // remote panel -> config modal
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
ctx.print('Remote surface is not available in this runtime.');
|
|
@@ -104,8 +104,8 @@ export function openCommandPanel(
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
108
|
-
* ctx.openModal seam (ui-openers wires it; the
|
|
107
|
+
* (group-B migration): open a config-modal surface by name via the
|
|
108
|
+
* ctx.openModal seam (ui-openers wires it; the host replaces the interim
|
|
109
109
|
* "not available yet" implementation with real dispatch). Front-doors for
|
|
110
110
|
* panels that migrated to modals call this instead of openCommandPanel — the
|
|
111
111
|
* modal is the surface's new home. Stubbed in tests by setting ctx.openModal.
|
|
@@ -256,7 +256,7 @@ export function requireProviderApi(context: CommandContext): ProviderApi {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/** Result of a manual /compact run: the SDK's CompactionEvent, plus an
|
|
259
|
-
* out-of-band quality score (
|
|
259
|
+
* out-of-band quality score () when one could be computed. */
|
|
260
260
|
export interface CompactionRunResult {
|
|
261
261
|
readonly event: CompactionEvent;
|
|
262
262
|
readonly qualityScore: CompactionQualityScore | null;
|
|
@@ -328,7 +328,7 @@ export async function compactConversation(context: CommandContext): Promise<Comp
|
|
|
328
328
|
const eventAfter = getLastCompactionEvent();
|
|
329
329
|
// Return the new event only if it differs from the one recorded before the call.
|
|
330
330
|
if (eventAfter !== null && eventAfter !== eventBefore) {
|
|
331
|
-
|
|
331
|
+
//: score this run out-of-band (see compaction-quality.ts for why
|
|
332
332
|
// this can't just subscribe to the SDK's own CompactionManager pipeline)
|
|
333
333
|
// and keep the score keyed by this event's timestamp for /compact-history.
|
|
334
334
|
const qualityScore = scoreCompactionRun({
|
|
@@ -10,6 +10,7 @@ import type { AutomationManager } from '@pellux/goodvibes-sdk/platform/automatio
|
|
|
10
10
|
import type { AutomationJob } from '@pellux/goodvibes-sdk/platform/automation';
|
|
11
11
|
import type { AutomationScheduleDefinition } from '@pellux/goodvibes-sdk/platform/automation';
|
|
12
12
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
13
|
+
import { buildAutomationEmptyState } from '@pellux/goodvibes-sdk/platform/runtime/feature-announcements';
|
|
13
14
|
import { parseNaturalLanguageSchedule } from './schedule-nl.ts';
|
|
14
15
|
import type {
|
|
15
16
|
AutomationExecutionPolicy,
|
|
@@ -106,13 +107,19 @@ export function registerScheduleRuntimeCommands(registry: CommandRegistry): void
|
|
|
106
107
|
if (!sub || sub === 'list') {
|
|
107
108
|
const jobs = manager.listJobs();
|
|
108
109
|
if (jobs.length === 0) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
// The SDK owns the presence + title of the how-to-create-your-first-
|
|
111
|
+
// routine empty state (shown while automation is on and idle). Its
|
|
112
|
+
// body points at a command name this registry doesn't have, so we
|
|
113
|
+
// render its title with THIS surface's real /schedule commands — a
|
|
114
|
+
// command pointer that actually exists in the registry.
|
|
115
|
+
const emptyState = buildAutomationEmptyState({ enabled: true, routineCount: jobs.length });
|
|
116
|
+
ctx.print([
|
|
117
|
+
emptyState ? emptyState.title : 'No automation jobs.',
|
|
118
|
+
'Automation is on and idle. Create your first routine with:',
|
|
119
|
+
' /schedule add cron "*/30 * * * *" "check build status"',
|
|
120
|
+
' /schedule add every 15m "summarize open PRs"',
|
|
121
|
+
' /schedule add at 2026-04-10T09:00:00 "send release reminder"',
|
|
122
|
+
].join('\n'));
|
|
116
123
|
return;
|
|
117
124
|
}
|
|
118
125
|
const lines = ['Automation jobs:', ''];
|
|
@@ -16,7 +16,7 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
|
|
|
16
16
|
const sub = args[0] ?? 'open';
|
|
17
17
|
const shellPaths = requireShellPaths(ctx);
|
|
18
18
|
if (sub === 'open' || sub === 'panel') {
|
|
19
|
-
ctx.openModal?.('services-modal'); //
|
|
19
|
+
ctx.openModal?.('services-modal'); // services panel -> config modal
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
const svcRegistry = requireServiceRegistry(ctx);
|
|
@@ -89,7 +89,7 @@ export function reopenPanelsFromReturnContext(ctx: CommandContext, summary: Sess
|
|
|
89
89
|
const reopened: string[] = [];
|
|
90
90
|
const movedToModal: string[] = [];
|
|
91
91
|
for (const panelId of summary.openPanels.slice(0, 4)) {
|
|
92
|
-
//
|
|
92
|
+
// (the purge): a MIGRATE-TO-MODAL id has no panel to restore — a modal
|
|
93
93
|
// is not part of the saved panel layout. Skip it (don't pop a modal
|
|
94
94
|
// mid-resume) and note it once, rather than firing openModal + revealing an
|
|
95
95
|
// empty workspace during resume.
|
|
@@ -283,7 +283,7 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
|
|
|
283
283
|
},
|
|
284
284
|
});
|
|
285
285
|
// Hydrate the footer's token counters from the resumed (+ journal-replayed)
|
|
286
|
-
// history now, before ctx.renderRequest() below
|
|
286
|
+
// history now, before ctx.renderRequest() below.
|
|
287
287
|
ctx.session.hydrateSessionUsage?.();
|
|
288
288
|
|
|
289
289
|
if (meta.model) {
|
|
@@ -32,7 +32,7 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
|
|
|
32
32
|
const shellPaths = requireShellPaths(ctx);
|
|
33
33
|
const controlPlaneConfigDir = ctx.platform.configManager.getControlPlaneConfigDir();
|
|
34
34
|
const sub = (args[0] ?? '').toLowerCase();
|
|
35
|
-
//
|
|
35
|
+
// item 3 (report-vs-modal front doors): settings-sync has a full
|
|
36
36
|
// modal surface (settings-sync-modal), so the bare command now opens it
|
|
37
37
|
// — the old bare/`review` transcript report moved to an explicit
|
|
38
38
|
// `report` subcommand (scriptability preserved: /settings-sync report).
|
|
@@ -41,7 +41,7 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
if (sub === 'panel' || sub === 'open') {
|
|
44
|
-
ctx.openModal?.('settings-sync-modal'); //
|
|
44
|
+
ctx.openModal?.('settings-sync-modal'); // settings-sync panel -> config modal
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
if (sub === 'report' || sub === 'review') {
|
|
@@ -115,7 +115,7 @@ export function registerShareRuntimeCommands(registry: CommandRegistry): void {
|
|
|
115
115
|
);
|
|
116
116
|
// The exported document has no field for "this cost is a placeholder" —
|
|
117
117
|
// omit `cost` entirely rather than embed a misleading 0 when the model
|
|
118
|
-
// never resolved to a real price
|
|
118
|
+
// never resolved to a real price.
|
|
119
119
|
const costIsUnpriced = !isModelPriced(sessionModel);
|
|
120
120
|
if (costIsUnpriced) {
|
|
121
121
|
ctx.print(`Note: cost omitted from export — no pricing data for model "${sessionModel}".`);
|
|
@@ -6,6 +6,7 @@ import { executeWriteQuit } from './quit-shared.ts';
|
|
|
6
6
|
import { compactConversation, requireKeybindingsManager, requireProviderApi, requireSessionMemoryStore } from './runtime-services.ts';
|
|
7
7
|
import { buildCompactionPreview, buildCompactionAfterNotice, buildPinUsageText, buildPinSuccessText } from '../../renderer/compaction-preview.ts';
|
|
8
8
|
import { buildCompactionHistoryText } from '../../renderer/compaction-history-modal.ts';
|
|
9
|
+
import { ensureProviderKeyThenSelect } from '../provider-key-intake.ts';
|
|
9
10
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
10
11
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
11
12
|
|
|
@@ -32,11 +33,15 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
32
33
|
const modelId = args[0];
|
|
33
34
|
try {
|
|
34
35
|
const selected = await providerApi.selectModel(modelId);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
ctx
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
// Capture the key for an unconfigured provider inline before the
|
|
37
|
+
// selection is announced, so the switch lands ready to chat.
|
|
38
|
+
ensureProviderKeyThenSelect(ctx, selected.providerId, () => {
|
|
39
|
+
ctx.session.runtime.model = selected.registryKey;
|
|
40
|
+
ctx.session.runtime.provider = selected.providerId;
|
|
41
|
+
ctx.platform.configManager.set('provider.model', selected.registryKey);
|
|
42
|
+
ctx.print(`Switched to model: ${selected.displayName} (${selected.providerId})`);
|
|
43
|
+
void providerApi.recordModelUsage(selected.registryKey).catch((err) => { logger.debug('model usage record failed', { err }); });
|
|
44
|
+
});
|
|
40
45
|
} catch (e) {
|
|
41
46
|
ctx.print(`Error: ${summarizeError(e)}`);
|
|
42
47
|
}
|
|
@@ -112,75 +117,48 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
112
117
|
registry.register({
|
|
113
118
|
name: 'help',
|
|
114
119
|
aliases: ['h', '?'],
|
|
115
|
-
description: '
|
|
116
|
-
argsHint: '[command]',
|
|
120
|
+
description: 'Browse every command with its description; picking one runs it',
|
|
117
121
|
handler(_args, ctx) {
|
|
122
|
+
// Generated live from the command registry — never a hand-maintained
|
|
123
|
+
// list — so /help can never drift from the real command set. Category
|
|
124
|
+
// labels come from the same single source of truth as the generated
|
|
125
|
+
// reference and the palette (ctx.getCommandCategories; see its doc for
|
|
126
|
+
// why it is threaded through the context rather than imported here).
|
|
127
|
+
const commands = registry
|
|
128
|
+
.getAll()
|
|
129
|
+
.slice()
|
|
130
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
118
131
|
if (ctx.openSelection) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
{ id: '/compact', label: '/compact', detail: 'Summarize to free context', category: 'Conversation' },
|
|
132
|
-
{ id: '/export', label: '/export [file]', detail: 'Export as markdown', category: 'Conversation' },
|
|
133
|
-
{ id: '/title', label: '/title [text]', detail: 'Show or set title', category: 'Conversation' },
|
|
134
|
-
{ id: '/save', label: '/save [name]', detail: 'Save session', category: 'Conversation' },
|
|
135
|
-
{ id: '/load', label: '/load <name>', detail: 'Load session', category: 'Conversation' },
|
|
136
|
-
{ id: '/sessions', label: '/sessions', detail: 'List saved sessions', category: 'Conversation' },
|
|
137
|
-
{ id: '/session', label: '/session', detail: 'Current session info', category: 'Conversation' },
|
|
138
|
-
{ id: '/session list', label: '/session list', detail: 'List all sessions', category: 'Conversation' },
|
|
139
|
-
{ id: '/session rename', label: '/session rename <name>', detail: 'Rename current session', category: 'Conversation' },
|
|
140
|
-
{ id: '/resume', label: '/resume [id]', detail: 'Resume a previous session (picker when no id)', category: 'Conversation' },
|
|
141
|
-
{ id: '/session resume', label: '/session resume <id>', detail: 'Load and resume a session', category: 'Conversation' },
|
|
142
|
-
{ id: '/session fork', label: '/session fork [name]', detail: 'Fork current session to new ID', category: 'Conversation' },
|
|
143
|
-
{ id: '/session save', label: '/session save [name]', detail: 'Force-save current session', category: 'Conversation' },
|
|
144
|
-
{ id: '/session info', label: '/session info [id]', detail: 'Show session details', category: 'Conversation' },
|
|
145
|
-
{ id: '/session export', label: '/session export <id> [format]', detail: 'Export session as markdown/text', category: 'Conversation' },
|
|
146
|
-
{ id: '/session search', label: '/session search <query>', detail: 'Search across all sessions', category: 'Conversation' },
|
|
147
|
-
{ id: '/session delete', label: '/session delete <id>', detail: 'Delete a session', category: 'Conversation' },
|
|
148
|
-
{ id: '/undo', label: '/undo', detail: 'Remove last turn', category: 'Conversation' },
|
|
149
|
-
{ id: '/redo', label: '/redo', detail: 'Restore undone turn', category: 'Conversation' },
|
|
150
|
-
{ id: '/retry', label: '/retry [text]', detail: 'Re-send last message', category: 'Conversation' },
|
|
151
|
-
{ id: '/fork', label: '/fork [name]', detail: 'Snapshot conversation as a named branch', category: 'Conversation' },
|
|
152
|
-
{ id: '/branch', label: '/branch [name]', detail: 'List branches or switch to one', category: 'Conversation' },
|
|
153
|
-
{ id: '/merge', label: '/merge <name>', detail: 'Append messages from a branch', category: 'Conversation' },
|
|
154
|
-
{ id: '/template', label: '/template', detail: 'Browse templates', category: 'Templates' },
|
|
155
|
-
{ id: '/template save', label: '/template save <name>', detail: 'Save prompt as template', category: 'Templates' },
|
|
156
|
-
{ id: '/template use', label: '/template use <name>', detail: 'Execute template', category: 'Templates' },
|
|
157
|
-
{ id: '/tools', label: '/tools', detail: 'List available tools', category: 'Tools & System' },
|
|
158
|
-
{ id: '/search', label: '/search <query> [--limit <n>]', detail: 'Search the web and show ranked results', category: 'Tools & System' },
|
|
159
|
-
{ id: '/imagine', label: '/imagine <prompt>', detail: 'Generate an image from a prompt', category: 'Tools & System' },
|
|
160
|
-
{ id: '/paste', label: '/paste', detail: 'Insert clipboard text or image into the prompt', category: 'Tools & System' },
|
|
161
|
-
{ id: '/shortcuts', label: '/shortcuts', detail: 'View keyboard shortcuts reference', category: 'Tools & System' },
|
|
162
|
-
{ id: '/commands', label: '/commands', detail: 'Browse all commands in a scrollable list', category: 'Tools & System' },
|
|
163
|
-
{ id: '/secrets', label: '/secrets set|link|get|test|list|delete', detail: 'Manage encrypted and provider-backed secrets', category: 'Tools & System' },
|
|
164
|
-
{ id: '/help', label: '/help', detail: 'This help', category: 'Tools & System' },
|
|
165
|
-
{ id: '/quit', label: '/quit', detail: 'Exit', category: 'Tools & System' },
|
|
166
|
-
{ id: '/wq', label: '/wq', detail: 'Commit all git changes and then exit', category: 'Tools & System' },
|
|
167
|
-
];
|
|
168
|
-
// UX-C: every item here is a command, and picking one RUNS it — label
|
|
132
|
+
const categoryByName = ctx.getCommandCategories?.() ?? new Map<string, string>();
|
|
133
|
+
const items: SelectionItem[] = commands.map((cmd) => {
|
|
134
|
+
const argsHint = cmd.argsHint ?? cmd.usage;
|
|
135
|
+
const aliases = cmd.aliases ?? [];
|
|
136
|
+
return {
|
|
137
|
+
id: cmd.name,
|
|
138
|
+
label: argsHint ? `/${cmd.name} ${argsHint}` : `/${cmd.name}`,
|
|
139
|
+
detail: aliases.length > 0 ? `${cmd.description} [aka ${aliases.map((a) => `/${a}`).join(', ')}]` : cmd.description,
|
|
140
|
+
category: categoryByName.get(cmd.name) ?? 'Other',
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
// every item here is a command, and picking one RUNS it — label
|
|
169
144
|
// the verb "Run" (matching the slash-command palette) instead of the
|
|
170
145
|
// generic "Select" default.
|
|
171
146
|
ctx.openSelection('Help — Commands', items, { allowSearch: true, primaryVerbLabel: 'Run' }, (result) => {
|
|
172
147
|
if (!result) return;
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
const parts = command.slice(1).trim().split(/\s+/);
|
|
176
|
-
const name = parts[0];
|
|
177
|
-
const cmdArgs = parts.slice(1);
|
|
178
|
-
void (ctx.executeCommand?.(name, cmdArgs) ?? registry.execute(name, cmdArgs, ctx));
|
|
179
|
-
}
|
|
148
|
+
const name = result.item.id;
|
|
149
|
+
void (ctx.executeCommand?.(name, []) ?? registry.execute(name, [], ctx));
|
|
180
150
|
});
|
|
181
151
|
return;
|
|
182
152
|
}
|
|
183
|
-
ctx.print(
|
|
153
|
+
ctx.print(
|
|
154
|
+
[
|
|
155
|
+
'Commands:',
|
|
156
|
+
...commands.map((cmd) => {
|
|
157
|
+
const argsHint = cmd.argsHint ?? cmd.usage;
|
|
158
|
+
return ` /${cmd.name}${argsHint ? ` ${argsHint}` : ''} — ${cmd.description}`;
|
|
159
|
+
}),
|
|
160
|
+
].join('\n'),
|
|
161
|
+
);
|
|
184
162
|
},
|
|
185
163
|
});
|
|
186
164
|
|
|
@@ -214,7 +192,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
214
192
|
description: 'Summarize conversation to free context window',
|
|
215
193
|
async handler(_args, ctx) {
|
|
216
194
|
const messages = ctx.session.conversationManager.getMessagesForLLM();
|
|
217
|
-
//
|
|
195
|
+
// contextWindow IS reachable from CommandContext — the same
|
|
218
196
|
// call compactConversation() (runtime-services.ts) already makes to scale
|
|
219
197
|
// its own behaviour by window size. Previously hardcoded to 0 here, which
|
|
220
198
|
// silently suppressed buildCompactionPreview()'s capacity-% clause even
|
|
@@ -230,7 +208,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
230
208
|
const result = await compactConversation(ctx);
|
|
231
209
|
if (result) {
|
|
232
210
|
// Post-compact notice: uses real CompactionEvent figures, plus an
|
|
233
|
-
// out-of-band quality-score grade line when one was computed (
|
|
211
|
+
// out-of-band quality-score grade line when one was computed ().
|
|
234
212
|
ctx.print(buildCompactionAfterNotice({ event: result.event, pinnedMemoryCount, qualityScore: result.qualityScore }));
|
|
235
213
|
} else {
|
|
236
214
|
ctx.print('[Context] Compact complete.');
|
|
@@ -300,7 +278,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
300
278
|
ctx.session.runtime.debugMode = !ctx.session.runtime.debugMode;
|
|
301
279
|
ctx.print(`Debug mode: ${ctx.session.runtime.debugMode ? 'ON' : 'OFF'}`);
|
|
302
280
|
// Honest quiet counter for direct terminal writes the guard intercepted
|
|
303
|
-
// (routed here instead of spamming the transcript). (
|
|
281
|
+
// (routed here instead of spamming the transcript). (item 1a.)
|
|
304
282
|
const intercepted = ctx.session.runtime.terminalWritesIntercepted ?? 0;
|
|
305
283
|
if (intercepted > 0) {
|
|
306
284
|
ctx.print(`Terminal writes intercepted this session: ${intercepted} (details in the activity log)`);
|
|
@@ -80,7 +80,7 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
|
|
|
80
80
|
async handler(args, ctx) {
|
|
81
81
|
const shellPaths = requireShellPaths(ctx);
|
|
82
82
|
if (args.length === 0 && ctx.openModal) {
|
|
83
|
-
ctx.openModal('subscription-modal'); //
|
|
83
|
+
ctx.openModal('subscription-modal'); // subscription panel -> config modal
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
86
|
const sub = (args[0] ?? 'review').toLowerCase();
|