@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,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `/update` — a real self-update path for binary installs
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* `/update` — a real self-update path for binary installs. The
|
|
3
|
+
* download-verify-swap mechanics are the SDK's canonical update policy
|
|
4
|
+
* module (platform/runtime/self-update — hoisted from this file's
|
|
5
|
+
* semantics), the same mechanism the daemon's hourly loop and
|
|
6
|
+
* scripts/install.sh follow: one update mechanism everywhere. This file owns
|
|
7
|
+
* only the /update UX: install-kind gating, target selection, and the
|
|
8
|
+
* printed report.
|
|
6
9
|
*
|
|
7
10
|
* Subcommands:
|
|
8
11
|
* /update [check] — resolve the latest release tag and report
|
|
@@ -12,9 +15,17 @@
|
|
|
12
15
|
* app and daemon binaries, and refresh the
|
|
13
16
|
* sqlite-vec native addon in lockstep so the
|
|
14
17
|
* vector index never goes stale beside a new
|
|
15
|
-
* binary.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
+
* binary. Every swap parks the outgoing file
|
|
19
|
+
* at `<path>.previous`, so the replaced
|
|
20
|
+
* version is always kept. For any other
|
|
21
|
+
* install kind, prints the exact command to
|
|
22
|
+
* run instead — it never attempts a swap it
|
|
23
|
+
* can't do safely.
|
|
24
|
+
* /update rollback — exchange each installed file with its kept
|
|
25
|
+
* `.previous` counterpart: one command back to
|
|
26
|
+
* the version that ran before the last update
|
|
27
|
+
* (and, being an exchange, one more command
|
|
28
|
+
* forward again).
|
|
18
29
|
* /update review — install/subscription/sandbox posture,
|
|
19
30
|
* unrelated to the update mechanics above.
|
|
20
31
|
* /update bundle export|inspect <path> — portable posture bundle, as before.
|
|
@@ -26,9 +37,16 @@
|
|
|
26
37
|
* (scripts/release.ts) does not publish separate stable/preview channels,
|
|
27
38
|
* so there is no real channel selection to wire it to.
|
|
28
39
|
*/
|
|
29
|
-
import {
|
|
40
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
30
41
|
import { dirname, join } from 'node:path';
|
|
31
42
|
import { spawnSync } from 'node:child_process';
|
|
43
|
+
import {
|
|
44
|
+
applyVerifiedUpdate,
|
|
45
|
+
realUpdateFileIo,
|
|
46
|
+
rollbackKeptPrevious,
|
|
47
|
+
type UpdateFileIo,
|
|
48
|
+
type UpdateTarget,
|
|
49
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/self-update';
|
|
32
50
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
33
51
|
import { VERSION } from '../../version.ts';
|
|
34
52
|
import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
|
|
@@ -37,8 +55,6 @@ import {
|
|
|
37
55
|
parseChecksumFile,
|
|
38
56
|
resolveArtifactNames,
|
|
39
57
|
resolveSqliteVecAsset,
|
|
40
|
-
sha256,
|
|
41
|
-
verifyChecksum,
|
|
42
58
|
} from '../../runtime/release-artifacts.ts';
|
|
43
59
|
import {
|
|
44
60
|
compareVersions,
|
|
@@ -66,47 +82,13 @@ async function downloadText(fetchImpl: UpdateFetchLike, url: string): Promise<st
|
|
|
66
82
|
return await response.text();
|
|
67
83
|
}
|
|
68
84
|
|
|
69
|
-
async function downloadBuffer(fetchImpl: UpdateFetchLike, url: string): Promise<Buffer> {
|
|
70
|
-
const response = await fetchImpl(url);
|
|
71
|
-
if (!response.ok) {
|
|
72
|
-
throw new Error(`download failed (${response.status}) for ${url}`);
|
|
73
|
-
}
|
|
74
|
-
return Buffer.from(await response.arrayBuffer());
|
|
75
|
-
}
|
|
76
|
-
|
|
77
85
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* semantics) instead of executing a half-written file. Same approach as
|
|
82
|
-
* scripts/install.sh's `mv -f "$WORKDIR/$artifact" "$target"`.
|
|
86
|
+
* Suffix under which every swap keeps the file it replaced — re-exported
|
|
87
|
+
* from the SDK's canonical update policy module so rollback and swap share
|
|
88
|
+
* one definition everywhere.
|
|
83
89
|
*/
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
writeFileSync(tempPath, buffer);
|
|
87
|
-
if (process.platform !== 'win32') {
|
|
88
|
-
chmodSync(tempPath, 0o755);
|
|
89
|
-
}
|
|
90
|
-
renameSync(tempPath, targetPath);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Writes the sqlite-vec native addon into place with the same temp-write +
|
|
95
|
-
* atomic-rename discipline as the binaries, so a running process never
|
|
96
|
-
* dlopen()s a half-written extension. The addon's parent directory
|
|
97
|
-
* (`<execDir>/lib/sqlite-vec-<os>-<arch>/`) is created first because a fresh
|
|
98
|
-
* install may not have a `lib/` tree yet. A shared library needs no execute
|
|
99
|
-
* bit, so this uses 0o644 rather than the binaries' 0o755.
|
|
100
|
-
*/
|
|
101
|
-
function writeAddonAtomically(targetPath: string, buffer: Buffer): void {
|
|
102
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
103
|
-
const tempPath = `${targetPath}.update-download`;
|
|
104
|
-
writeFileSync(tempPath, buffer);
|
|
105
|
-
if (process.platform !== 'win32') {
|
|
106
|
-
chmodSync(tempPath, 0o644);
|
|
107
|
-
}
|
|
108
|
-
renameSync(tempPath, targetPath);
|
|
109
|
-
}
|
|
90
|
+
export { PREVIOUS_FILE_SUFFIX } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
|
|
91
|
+
import { PREVIOUS_FILE_SUFFIX } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
|
|
110
92
|
|
|
111
93
|
export interface DaemonServiceRestartInfo {
|
|
112
94
|
readonly managed: boolean;
|
|
@@ -169,19 +151,19 @@ export interface ApplyUpdateOptions {
|
|
|
169
151
|
readonly print: (line: string) => void;
|
|
170
152
|
readonly configManager: { get(key: string): unknown };
|
|
171
153
|
readonly runCommand?: RunCommand;
|
|
172
|
-
/** Injectable so tests
|
|
173
|
-
readonly
|
|
174
|
-
/** Injectable so tests can observe/skip writing the sqlite-vec addon. */
|
|
175
|
-
readonly writeAddon?: (targetPath: string, buffer: Buffer) => void;
|
|
176
|
-
readonly fileExists?: (path: string) => boolean;
|
|
154
|
+
/** Injectable filesystem seam (the SDK's UpdateFileIo) so tests observe swaps in memory. */
|
|
155
|
+
readonly io?: UpdateFileIo;
|
|
177
156
|
}
|
|
178
157
|
|
|
179
158
|
/**
|
|
180
|
-
* The real self-update path
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
159
|
+
* The real self-update path, delegating the download-verify-swap mechanics
|
|
160
|
+
* to the SDK's canonical update policy module (applyVerifiedUpdate — the
|
|
161
|
+
* same mechanism the daemon's hourly loop uses). For a binary install:
|
|
162
|
+
* resolve the latest tag, compare to the running version, and if newer,
|
|
163
|
+
* download + checksum-verify EVERY artifact before swapping any one (so a
|
|
164
|
+
* checksum failure never leaves a mismatched pair installed), then
|
|
165
|
+
* atomically swap each in place with the outgoing file kept at
|
|
166
|
+
* `<path>.previous`. For any other install kind, never attempts a swap — it
|
|
185
167
|
* prints the exact command for that install method instead.
|
|
186
168
|
*/
|
|
187
169
|
export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
|
|
@@ -211,22 +193,10 @@ export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
|
|
|
211
193
|
options.print(`Update available: ${latestTag} (running v${normalizeVersion(options.currentVersion)}). Downloading and verifying...`);
|
|
212
194
|
|
|
213
195
|
const baseUrl = releaseDownloadBaseUrl(latestTag);
|
|
214
|
-
const
|
|
215
|
-
const checksums = parseChecksumFile(checksumText);
|
|
216
|
-
|
|
196
|
+
const io = options.io ?? realUpdateFileIo;
|
|
217
197
|
const appBinaryPath = options.execPath;
|
|
218
198
|
const daemonBinaryPath = join(dirname(appBinaryPath), 'goodvibes-daemon');
|
|
219
|
-
const
|
|
220
|
-
const daemonBinaryPresent = fileExists(daemonBinaryPath);
|
|
221
|
-
|
|
222
|
-
const appBuffer = await downloadBuffer(options.fetchImpl, `${baseUrl}/${artifacts.app}`);
|
|
223
|
-
verifyChecksum(artifacts.app, sha256(appBuffer), checksums.get(artifacts.app));
|
|
224
|
-
|
|
225
|
-
let daemonBuffer: Buffer | null = null;
|
|
226
|
-
if (daemonBinaryPresent) {
|
|
227
|
-
daemonBuffer = await downloadBuffer(options.fetchImpl, `${baseUrl}/${artifacts.daemon}`);
|
|
228
|
-
verifyChecksum(artifacts.daemon, sha256(daemonBuffer), checksums.get(artifacts.daemon));
|
|
229
|
-
}
|
|
199
|
+
const daemonBinaryPresent = io.exists(daemonBinaryPath);
|
|
230
200
|
|
|
231
201
|
// The sqlite-vec native addon travels with the binaries: refresh it in the
|
|
232
202
|
// same download-verify-swap pass so /update never leaves a new binary beside a
|
|
@@ -237,28 +207,36 @@ export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
|
|
|
237
207
|
// swap), while an absent entry means the target predates the addon and is
|
|
238
208
|
// skipped rather than blocking an otherwise-valid binary update. On macOS the
|
|
239
209
|
// file is refreshed for consistency even though the platform blocks extension
|
|
240
|
-
// loading.
|
|
210
|
+
// loading. This inclusion decision needs one manifest pre-read; the swap
|
|
211
|
+
// itself re-verifies every included artifact inside applyVerifiedUpdate.
|
|
212
|
+
const checksumText = await downloadText(options.fetchImpl, `${baseUrl}/${CHECKSUM_MANIFEST_NAME}`);
|
|
213
|
+
const checksums = parseChecksumFile(checksumText);
|
|
241
214
|
const addon = resolveSqliteVecAsset(options.platform, options.arch);
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
if (addon && addonExpected !== undefined) {
|
|
245
|
-
addonBuffer = await downloadBuffer(options.fetchImpl, `${baseUrl}/${addon.assetName}`);
|
|
246
|
-
verifyChecksum(addon.assetName, sha256(addonBuffer), addonExpected);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// All downloads verified before any write — an update must not apply partially.
|
|
250
|
-
const swap = options.swap ?? swapBinaryAtomically;
|
|
251
|
-
swap(appBinaryPath, appBuffer);
|
|
252
|
-
if (daemonBuffer) {
|
|
253
|
-
swap(daemonBinaryPath, daemonBuffer);
|
|
254
|
-
}
|
|
255
|
-
const addonTargetPath = addon && addonBuffer
|
|
215
|
+
const addonIncluded = addon !== null && checksums.get(addon.assetName) !== undefined;
|
|
216
|
+
const addonTargetPath = addon && addonIncluded
|
|
256
217
|
? join(dirname(appBinaryPath), 'lib', addon.dirName, addon.fileName)
|
|
257
218
|
: null;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
219
|
+
|
|
220
|
+
const targets: UpdateTarget[] = [
|
|
221
|
+
{ label: 'app binary', path: appBinaryPath, assetName: artifacts.app, executable: true },
|
|
222
|
+
...(daemonBinaryPresent
|
|
223
|
+
? [{ label: 'daemon binary', path: daemonBinaryPath, assetName: artifacts.daemon, executable: true }]
|
|
224
|
+
: []),
|
|
225
|
+
...(addon && addonTargetPath
|
|
226
|
+
? [{ label: 'vector addon', path: addonTargetPath, assetName: addon.assetName, executable: false }]
|
|
227
|
+
: []),
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
// One mechanism everywhere: downloads + verifies ALL targets before any
|
|
231
|
+
// write, then swaps each atomically with the outgoing file kept at
|
|
232
|
+
// `<path>.previous`.
|
|
233
|
+
await applyVerifiedUpdate({
|
|
234
|
+
fetchImpl: options.fetchImpl,
|
|
235
|
+
downloadBaseUrl: baseUrl,
|
|
236
|
+
targets,
|
|
237
|
+
io,
|
|
238
|
+
platform: options.platform,
|
|
239
|
+
});
|
|
262
240
|
|
|
263
241
|
const serviceInfo = detectDaemonServiceManaged(options.platform, options.configManager, options.runCommand);
|
|
264
242
|
|
|
@@ -279,6 +257,70 @@ export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
|
|
|
279
257
|
);
|
|
280
258
|
}
|
|
281
259
|
|
|
260
|
+
export interface RollbackUpdateOptions {
|
|
261
|
+
readonly execPath: string;
|
|
262
|
+
readonly platform: NodeJS.Platform;
|
|
263
|
+
readonly arch: string;
|
|
264
|
+
readonly print: (line: string) => void;
|
|
265
|
+
readonly configManager: { get(key: string): unknown };
|
|
266
|
+
readonly runCommand?: RunCommand;
|
|
267
|
+
/** Injectable filesystem seam (the SDK's UpdateFileIo) so tests observe renames in memory. */
|
|
268
|
+
readonly io?: UpdateFileIo;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* One-command rollback to the version that ran before the last update,
|
|
273
|
+
* delegating the exchange mechanics to the SDK's rollbackKeptPrevious (the
|
|
274
|
+
* same module the swap uses): every installed file (app binary, daemon
|
|
275
|
+
* binary, vector addon) that has a kept `.previous` counterpart is EXCHANGED
|
|
276
|
+
* with it — the previous version becomes live, and the version being rolled
|
|
277
|
+
* back is itself kept at `.previous`, so a second `/update rollback` rolls
|
|
278
|
+
* forward again. Files without a kept counterpart are reported and left
|
|
279
|
+
* untouched; nothing is downloaded.
|
|
280
|
+
*/
|
|
281
|
+
export function rollbackUpdate(options: RollbackUpdateOptions): void {
|
|
282
|
+
const installKind: InstallKind = detectInstallKind(options.execPath);
|
|
283
|
+
if (installKind !== 'binary') {
|
|
284
|
+
options.print(
|
|
285
|
+
[
|
|
286
|
+
`This install is not a self-updatable binary install (detected: ${installKind === 'bun-global-package' ? 'bun/npm package install' : 'running from source'}), so there is no kept previous binary to roll back to.`,
|
|
287
|
+
`Install a specific version with your package manager instead, e.g.: ${fallbackUpdateCommand(installKind)}`,
|
|
288
|
+
].join('\n'),
|
|
289
|
+
);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const io = options.io ?? realUpdateFileIo;
|
|
294
|
+
const addon = resolveSqliteVecAsset(options.platform, options.arch);
|
|
295
|
+
const targets = [
|
|
296
|
+
{ label: 'app binary', path: options.execPath },
|
|
297
|
+
{ label: 'daemon binary', path: join(dirname(options.execPath), 'goodvibes-daemon') },
|
|
298
|
+
...(addon ? [{ label: 'vector addon', path: join(dirname(options.execPath), 'lib', addon.dirName, addon.fileName) }] : []),
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
const result = rollbackKeptPrevious(targets, io);
|
|
302
|
+
if (result.restored.length === 0) {
|
|
303
|
+
options.print(
|
|
304
|
+
`No previous version is kept beside this install (nothing at ${options.execPath}${PREVIOUS_FILE_SUFFIX}). ` +
|
|
305
|
+
'The previous version is kept from the next update onward.',
|
|
306
|
+
);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const serviceInfo = detectDaemonServiceManaged(options.platform, options.configManager, options.runCommand);
|
|
311
|
+
options.print(
|
|
312
|
+
[
|
|
313
|
+
'Rolled back to the previously installed version.',
|
|
314
|
+
...result.restored.map((target) => ` ${target.label}: ${target.path} (the replaced version is kept at ${target.path}${PREVIOUS_FILE_SUFFIX})`),
|
|
315
|
+
'',
|
|
316
|
+
'Restart goodvibes to run the restored version.',
|
|
317
|
+
serviceInfo.managed
|
|
318
|
+
? `The daemon is managed by systemd — restart it with: ${serviceInfo.restartCommand}`
|
|
319
|
+
: 'The daemon restarts automatically the next time goodvibes launches.',
|
|
320
|
+
].join('\n'),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
282
324
|
interface UpdateBundle {
|
|
283
325
|
readonly version: 1;
|
|
284
326
|
readonly exportedAt: number;
|
|
@@ -304,8 +346,8 @@ export function registerUpdateCommand(registry: CommandRegistry): void {
|
|
|
304
346
|
registry.register({
|
|
305
347
|
name: 'update',
|
|
306
348
|
aliases: ['upgrade'],
|
|
307
|
-
description: 'Check for a newer GoodVibes release and, for binary installs, download/verify/apply it',
|
|
308
|
-
usage: '[check|apply|review|bundle export <path>|bundle inspect <path>]',
|
|
349
|
+
description: 'Check for a newer GoodVibes release and, for binary installs, download/verify/apply it or roll back to the kept previous version',
|
|
350
|
+
usage: '[check|apply|rollback|review|bundle export <path>|bundle inspect <path>]',
|
|
309
351
|
async handler(args, ctx) {
|
|
310
352
|
const sub = args[0] ?? 'check';
|
|
311
353
|
|
|
@@ -340,6 +382,21 @@ export function registerUpdateCommand(registry: CommandRegistry): void {
|
|
|
340
382
|
return;
|
|
341
383
|
}
|
|
342
384
|
|
|
385
|
+
if (sub === 'rollback') {
|
|
386
|
+
try {
|
|
387
|
+
rollbackUpdate({
|
|
388
|
+
execPath: process.execPath,
|
|
389
|
+
platform: process.platform,
|
|
390
|
+
arch: process.arch,
|
|
391
|
+
print: ctx.print,
|
|
392
|
+
configManager: ctx.platform.configManager,
|
|
393
|
+
});
|
|
394
|
+
} catch (error) {
|
|
395
|
+
ctx.print(`Rollback failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
396
|
+
}
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
343
400
|
if (sub === 'review') {
|
|
344
401
|
const installKind = detectInstallKind(process.execPath);
|
|
345
402
|
const subscriptions = requireSubscriptionManager(ctx);
|
|
@@ -358,7 +415,7 @@ export function registerUpdateCommand(registry: CommandRegistry): void {
|
|
|
358
415
|
` built-in subscription providers: ${builtinProviders.length}${builtinProviders.length > 0 ? ` (${builtinProviders.join(', ')})` : ''}`,
|
|
359
416
|
` active subscriptions: ${activeSubscriptions.length}${activeSubscriptions.length > 0 ? ` (${activeSubscriptions.join(', ')})` : ''}`,
|
|
360
417
|
` sandbox profile: ${sandboxProfile}`,
|
|
361
|
-
' use /update check to look for a newer release, /update apply to install it',
|
|
418
|
+
' use /update check to look for a newer release, /update apply to install it, /update rollback to return to the kept previous version',
|
|
362
419
|
].join('\n'),
|
|
363
420
|
);
|
|
364
421
|
return;
|
|
@@ -406,7 +463,7 @@ export function registerUpdateCommand(registry: CommandRegistry): void {
|
|
|
406
463
|
}
|
|
407
464
|
}
|
|
408
465
|
|
|
409
|
-
ctx.print('Usage: /update [check|apply|review|bundle export <path>|bundle inspect <path>]');
|
|
466
|
+
ctx.print('Usage: /update [check|apply|rollback|review|bundle export <path>|bundle inspect <path>]');
|
|
410
467
|
},
|
|
411
468
|
});
|
|
412
469
|
}
|
|
@@ -25,7 +25,7 @@ function getStore(ctx: import('../command-registry.ts').CommandContext): WorkPla
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function openPanel(ctx: import('../command-registry.ts').CommandContext): void {
|
|
28
|
-
//
|
|
28
|
+
// work-plan migrated to the 'work-plan' modal — open it via the seam.
|
|
29
29
|
openModalCommand(ctx, 'work-plan-modal');
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -25,6 +25,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
25
25
|
import type { CommandContext } from '../command-registry.ts';
|
|
26
26
|
import type { WorkstreamCommandService } from '../../runtime/workstream-services.ts';
|
|
27
27
|
import { requirePanelManager } from './runtime-services.ts';
|
|
28
|
+
import { describeFailureReason, isTurnBudgetReason } from '../../core/turn-budget-outcome.ts';
|
|
28
29
|
import { type AttemptGraphItem, renderDependentHoldLines } from './workstream-attempt-dependents.ts';
|
|
29
30
|
|
|
30
31
|
/** Resolve a workstream's items for dependent-hold display; null-safe against a stubbed engine. */
|
|
@@ -58,7 +59,11 @@ function resolveCandidate(group: HeldMergeGroup, ref: string): AttemptCandidate
|
|
|
58
59
|
|
|
59
60
|
function candidateLine(c: AttemptCandidate): string {
|
|
60
61
|
const files = c.diff ? `${c.diff.files.length} file(s)` : 'no diff';
|
|
61
|
-
const state = c.state === 'held-merge'
|
|
62
|
+
const state = c.state === 'held-merge'
|
|
63
|
+
? 'held (pick-ready)'
|
|
64
|
+
: isTurnBudgetReason(c.failureReason)
|
|
65
|
+
? describeFailureReason(c.failureReason) // a spent budget, not an infrastructure failure
|
|
66
|
+
: `failed${c.failureReason ? `: ${c.failureReason}` : ''}`;
|
|
62
67
|
return ` ${c.attemptIndex + 1}. [${state}] ${c.title} — ${files}, item ${shortId(c.itemId)}`;
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// workstream-runtime.ts — /workstream
|
|
3
3
|
//
|
|
4
4
|
// UX over the OrchestrationEngine (@pellux/goodvibes-sdk/platform/orchestration,
|
|
5
|
-
//
|
|
5
|
+
//) via its command-facing facade wired onto RuntimeServices as
|
|
6
6
|
// `workstreamCommands` (src/runtime/workstream-services.ts) and threaded
|
|
7
7
|
// through CommandContext.session.workstreamEngine exactly like wrfcController
|
|
8
8
|
// already is (bootstrap-command-context.ts).
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// pre-creation "draft" concept, so approval happens on a TUI-held draft
|
|
22
22
|
// (workstream-services.ts's WorkstreamDraft) before anything is created in
|
|
23
23
|
// the engine at all — see that module's header doc for the full reality-wins
|
|
24
|
-
// divergence from the
|
|
24
|
+
// divergence from the design brief.
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
|
|
27
27
|
import { AdaptivePlanner } from '@pellux/goodvibes-sdk/platform/core';
|
|
@@ -102,6 +102,33 @@ async function checkFanoutQuotaWarning(ctx: CommandContext, draft: WorkstreamDra
|
|
|
102
102
|
].join('\n');
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Launch an already-approved draft through its gates (best-of-N attempt
|
|
107
|
+
* validation, then the fan-out quota warning unless `--force`), and print the
|
|
108
|
+
* outcome. Shared by `launch` and by `approve` (which launches in the same act
|
|
109
|
+
* — the one confirmed step that replaces the old approve-then-retype ceremony).
|
|
110
|
+
*/
|
|
111
|
+
async function launchApprovedDraft(ctx: CommandContext, service: WorkstreamCommandService, id: string, draft: WorkstreamDraft, force: boolean): Promise<void> {
|
|
112
|
+
const attemptsCheck = validateAttempts(draft.spec);
|
|
113
|
+
if (attemptsCheck.violations.length > 0) {
|
|
114
|
+
ctx.print(`Cannot launch ${id} — best-of-N plan constraints are violated:\n${attemptsCheck.violations.map((v) => ` - ${v}`).join('\n')}\nFix the plan (or drop the attempts) and re-approve.`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!force) {
|
|
118
|
+
const quotaWarning = await checkFanoutQuotaWarning(ctx, draft);
|
|
119
|
+
if (quotaWarning) {
|
|
120
|
+
ctx.print(quotaWarning);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const result = service.launchDraft(id);
|
|
125
|
+
if (!result) {
|
|
126
|
+
ctx.print(`Could not launch ${id}.`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
ctx.print(`Launched workstream ${result.workstreamId} — track it with /workstream status ${result.workstreamId} or the Fleet panel.`);
|
|
130
|
+
}
|
|
131
|
+
|
|
105
132
|
/**
|
|
106
133
|
* phase.role for a 'custom' phase is the free-text description passed to
|
|
107
134
|
* `/workstream insert-phase` — it is purely COSMETIC. Neither
|
|
@@ -377,7 +404,7 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
377
404
|
registry.register({
|
|
378
405
|
name: 'workstream',
|
|
379
406
|
description: 'Author and oversee multi-phase agent workstreams (orchestration engine)',
|
|
380
|
-
usage: 'create [--isolation shared|worktree] <task...> | list | status [id] | insert-phase <id> <description...> | edit-item <id> <item#> <brief...> | remove-item <id> <item#> | move-item <id> <item#> <pos> | approve <id> | edit <id> [--isolation shared|worktree] <task...> | launch <id> [--force] | cancel <id> | attempts list|diff|judge|pick',
|
|
407
|
+
usage: 'create [--isolation shared|worktree] <task...> | list | status [id] | insert-phase <id> <description...> | edit-item <id> <item#> <brief...> | remove-item <id> <item#> | move-item <id> <item#> <pos> | approve <id> [--no-launch] [--force] | edit <id> [--isolation shared|worktree] <task...> | launch <id> [--force] | cancel <id> | attempts list|diff|judge|pick',
|
|
381
408
|
argsHint: 'create [--isolation worktree] <task> | list | status [id] | edit-item | remove-item | move-item | approve | edit | launch | cancel | attempts',
|
|
382
409
|
handler: async (args, ctx: CommandContext) => {
|
|
383
410
|
const service = ctx.session.workstreamEngine;
|
|
@@ -470,9 +497,14 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
470
497
|
}
|
|
471
498
|
|
|
472
499
|
if (sub === 'approve') {
|
|
473
|
-
|
|
500
|
+
// Approve IS the one confirmed act: it approves AND launches (typing the
|
|
501
|
+
// verb with an explicit id is the confirmation). --no-launch keeps the
|
|
502
|
+
// old split for scripts; --force skips the fan-out quota warning.
|
|
503
|
+
const noLaunch = args.includes('--no-launch');
|
|
504
|
+
const force = args.includes('--force');
|
|
505
|
+
const id = args.find((arg, index) => index > 0 && arg !== '--no-launch' && arg !== '--force');
|
|
474
506
|
if (!id) {
|
|
475
|
-
ctx.print('Usage: /workstream approve <id>');
|
|
507
|
+
ctx.print('Usage: /workstream approve <id> [--no-launch] [--force]');
|
|
476
508
|
return;
|
|
477
509
|
}
|
|
478
510
|
const draft = service.approveDraft(id);
|
|
@@ -480,7 +512,11 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
480
512
|
ctx.print(draftNotFoundMessage(service, id));
|
|
481
513
|
return;
|
|
482
514
|
}
|
|
483
|
-
|
|
515
|
+
if (noLaunch) {
|
|
516
|
+
ctx.print(`Approved ${id} (held, not launched). Launch it with /workstream launch ${id}.`);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
await launchApprovedDraft(ctx, service, id, draft, force);
|
|
484
520
|
return;
|
|
485
521
|
}
|
|
486
522
|
|
|
@@ -554,27 +590,10 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
554
590
|
return;
|
|
555
591
|
}
|
|
556
592
|
if (!draft.approved) {
|
|
557
|
-
ctx.print(`Proposal ${id} is not approved yet. Run /workstream approve ${id} first.`);
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
const attemptsCheck = validateAttempts(draft.spec);
|
|
561
|
-
if (attemptsCheck.violations.length > 0) {
|
|
562
|
-
ctx.print(`Cannot launch ${id} — best-of-N plan constraints are violated:\n${attemptsCheck.violations.map((v) => ` - ${v}`).join('\n')}\nFix the plan (or drop the attempts) and re-approve.`);
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
if (!force) {
|
|
566
|
-
const quotaWarning = await checkFanoutQuotaWarning(ctx, draft);
|
|
567
|
-
if (quotaWarning) {
|
|
568
|
-
ctx.print(quotaWarning);
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
const result = service.launchDraft(id);
|
|
573
|
-
if (!result) {
|
|
574
|
-
ctx.print(`Could not launch ${id}.`);
|
|
593
|
+
ctx.print(`Proposal ${id} is not approved yet. Run /workstream approve ${id} first (or approve launches it directly).`);
|
|
575
594
|
return;
|
|
576
595
|
}
|
|
577
|
-
ctx
|
|
596
|
+
await launchApprovedDraft(ctx, service, id, draft, force);
|
|
578
597
|
return;
|
|
579
598
|
}
|
|
580
599
|
|
|
@@ -616,9 +635,9 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
616
635
|
+ ' /workstream edit-item <id> <item#> <new brief...>\n'
|
|
617
636
|
+ ' /workstream remove-item <id> <item#>\n'
|
|
618
637
|
+ ' /workstream move-item <id> <item#> <new-position#>\n'
|
|
619
|
-
+ ' /workstream approve <id
|
|
638
|
+
+ ' /workstream approve <id> [--no-launch] [--force] (approves AND launches in one act)\n'
|
|
620
639
|
+ ' /workstream edit <id> [--isolation shared|worktree] <new task...>\n'
|
|
621
|
-
+ ' /workstream launch <id> [--force]\n'
|
|
640
|
+
+ ' /workstream launch <id> [--force] (for a --no-launch-held or re-launched draft)\n'
|
|
622
641
|
+ ' /workstream cancel <id>',
|
|
623
642
|
);
|
|
624
643
|
},
|
|
@@ -145,19 +145,46 @@ export function registerWorktreeRuntimeCommands(registry: CommandRegistry): void
|
|
|
145
145
|
: `${header}: ${targetKind} ${targetId}\n No attached worktrees tracked.`);
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
|
-
if (sub === '
|
|
148
|
+
if (sub === 'discard') {
|
|
149
|
+
// Discard is a real destructive act, not a metadata flip: route it
|
|
150
|
+
// through the worktrees.discard operator verb (preservation commit,
|
|
151
|
+
// git worktree removal, honest receipt) — the SAME behavior as the
|
|
152
|
+
// Fleet panel's D key. Typing the verb with an explicit path is the
|
|
153
|
+
// confirmation.
|
|
154
|
+
const path = args[1];
|
|
155
|
+
if (!path) {
|
|
156
|
+
ctx.print('Usage: /worktree discard <path>');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const rpc = getOperatorRpc(ctx);
|
|
160
|
+
if (!rpc.available) {
|
|
161
|
+
ctx.print(`[worktree discard] ${rpc.reason}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const receipt = await rpc.sdk.operator.invoke('worktrees.discard', { path });
|
|
166
|
+
if (receipt.ok) {
|
|
167
|
+
ctx.print([
|
|
168
|
+
`[worktree discard] Discarded ${receipt.path}`,
|
|
169
|
+
` branch kept: ${receipt.branch || '(unknown)'}`,
|
|
170
|
+
` preservation commit: ${receipt.preservedCommit || '(none — nothing to preserve)'}`,
|
|
171
|
+
` ${receipt.detail}`,
|
|
172
|
+
].join('\n'));
|
|
173
|
+
} else {
|
|
174
|
+
ctx.print(`[worktree discard] refused for ${receipt.path}: ${receipt.detail}`);
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
ctx.print(`[worktree discard] round-trip request failed: ${describeOperatorRpcError(error)}`);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (sub === 'pause' || sub === 'resume' || sub === 'keep') {
|
|
149
182
|
const path = args[1];
|
|
150
183
|
if (!path) {
|
|
151
184
|
ctx.print(`Usage: /worktree ${sub} <path>`);
|
|
152
185
|
return;
|
|
153
186
|
}
|
|
154
|
-
const nextState = sub === 'resume'
|
|
155
|
-
? 'active'
|
|
156
|
-
: sub === 'pause'
|
|
157
|
-
? 'paused'
|
|
158
|
-
: sub === 'keep'
|
|
159
|
-
? 'kept'
|
|
160
|
-
: 'discard';
|
|
187
|
+
const nextState = sub === 'resume' ? 'active' : sub === 'pause' ? 'paused' : 'kept';
|
|
161
188
|
runtime.setState(path, nextState);
|
|
162
189
|
ctx.print(`Updated ${path} to state ${nextState}.`);
|
|
163
190
|
return;
|
package/src/input/commands.ts
CHANGED
|
@@ -17,6 +17,9 @@ import { registerOperatorRuntimeCommands } from './commands/operator-runtime.ts'
|
|
|
17
17
|
import { registerPluginRuntimeCommands } from './commands/plugin-runtime.ts';
|
|
18
18
|
import { registerDiffRuntimeCommands } from './commands/diff-runtime.ts';
|
|
19
19
|
import { registerGitRuntimeCommands } from './commands/git-runtime.ts';
|
|
20
|
+
import { registerQueueRuntimeCommands } from './commands/queue-runtime.ts';
|
|
21
|
+
import { registerPowerRuntimeCommands } from './commands/power-runtime.ts';
|
|
22
|
+
import { registerGraphRuntimeCommands } from './commands/graph-runtime.ts';
|
|
20
23
|
import { registerTestRuntimeCommands } from './commands/test-runtime.ts';
|
|
21
24
|
import { registerNotifyRuntimeCommands } from './commands/notify-runtime.ts';
|
|
22
25
|
import { registerReplayRuntimeCommands } from './commands/replay-runtime.ts';
|
|
@@ -36,7 +39,6 @@ import { registerRemoteRuntimeCommands } from './commands/remote-runtime.ts';
|
|
|
36
39
|
import { registerTeleportRuntimeCommands } from './commands/teleport-runtime.ts';
|
|
37
40
|
import { registerSubscriptionRuntimeCommands } from './commands/subscription-runtime.ts';
|
|
38
41
|
import { registerHooksRuntimeCommands } from './commands/hooks-runtime.ts';
|
|
39
|
-
import { registerFlagsRuntimeCommands } from './commands/flags-runtime.ts';
|
|
40
42
|
import { registerEditorRuntimeCommands } from './commands/editor-runtime.ts';
|
|
41
43
|
import { registerControlRoomRuntimeCommands } from './commands/control-room-runtime.ts';
|
|
42
44
|
import { registerMcpRuntimeCommands } from './commands/mcp-runtime.ts';
|
|
@@ -56,11 +58,13 @@ import { registerLocalProviderRuntimeCommands } from './commands/local-provider-
|
|
|
56
58
|
import { registerHealthRuntimeCommands } from './commands/health-runtime.ts';
|
|
57
59
|
import { registerSettingsSyncRuntimeCommands } from './commands/settings-sync-runtime.ts';
|
|
58
60
|
import { registerWorktreeRuntimeCommands } from './commands/worktree-runtime.ts';
|
|
61
|
+
import { registerAcpRuntimeCommands } from './commands/acp-runtime.ts';
|
|
59
62
|
import { registerProviderAccountsRuntimeCommands } from './commands/provider-accounts-runtime.ts';
|
|
60
63
|
import { registerLocalAuthRuntimeCommands } from './commands/local-auth-runtime.ts';
|
|
61
64
|
import { registerIntelligenceRuntimeCommands } from './commands/intelligence-runtime.ts';
|
|
62
65
|
import { registerConversationRuntimeCommands } from './commands/conversation-runtime.ts';
|
|
63
66
|
import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
|
|
67
|
+
import { registerDevicesRuntimeCommands } from './commands/devices-runtime.ts';
|
|
64
68
|
import { registerRelayRuntimeCommands } from './commands/relay-runtime.ts';
|
|
65
69
|
import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
|
|
66
70
|
import { registerTtsRuntimeCommands } from './commands/tts-runtime.ts';
|
|
@@ -102,6 +106,9 @@ export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
|
|
|
102
106
|
{ category: 'Plugins', register: registerPluginRuntimeCommands },
|
|
103
107
|
{ category: 'Diff & Review', register: registerDiffRuntimeCommands },
|
|
104
108
|
{ category: 'Git', register: registerGitRuntimeCommands },
|
|
109
|
+
{ category: 'Shell & Session', register: registerQueueRuntimeCommands },
|
|
110
|
+
{ category: 'Shell & Session', register: registerPowerRuntimeCommands },
|
|
111
|
+
{ category: 'Automation', register: registerGraphRuntimeCommands },
|
|
105
112
|
{ category: 'Testing', register: registerTestRuntimeCommands },
|
|
106
113
|
{ category: 'Notifications', register: registerNotifyRuntimeCommands },
|
|
107
114
|
{ category: 'Sessions & Replay', register: registerReplayRuntimeCommands },
|
|
@@ -121,7 +128,6 @@ export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
|
|
|
121
128
|
{ category: 'Remote', register: registerTeleportRuntimeCommands },
|
|
122
129
|
{ category: 'Subscriptions', register: registerSubscriptionRuntimeCommands },
|
|
123
130
|
{ category: 'Hooks', register: registerHooksRuntimeCommands },
|
|
124
|
-
{ category: 'Feature Flags', register: registerFlagsRuntimeCommands },
|
|
125
131
|
{ category: 'Editor', register: registerEditorRuntimeCommands },
|
|
126
132
|
{ category: 'Control Room', register: registerControlRoomRuntimeCommands },
|
|
127
133
|
{ category: 'MCP', register: registerMcpRuntimeCommands },
|
|
@@ -135,11 +141,13 @@ export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
|
|
|
135
141
|
{ category: 'Health', register: registerHealthRuntimeCommands },
|
|
136
142
|
{ category: 'Settings Sync', register: registerSettingsSyncRuntimeCommands },
|
|
137
143
|
{ category: 'Worktrees', register: registerWorktreeRuntimeCommands },
|
|
144
|
+
{ category: 'Fleet', register: registerAcpRuntimeCommands },
|
|
138
145
|
{ category: 'Provider Accounts', register: registerProviderAccountsRuntimeCommands },
|
|
139
146
|
{ category: 'Local Auth', register: registerLocalAuthRuntimeCommands },
|
|
140
147
|
{ category: 'Intelligence', register: registerIntelligenceRuntimeCommands },
|
|
141
148
|
{ category: 'Conversation', register: registerConversationRuntimeCommands },
|
|
142
149
|
{ category: 'QR Codes', register: registerQrcodeRuntimeCommands },
|
|
150
|
+
{ category: 'Security', register: registerDevicesRuntimeCommands },
|
|
143
151
|
{ category: 'Relay', register: registerRelayRuntimeCommands },
|
|
144
152
|
{ category: 'Onboarding', register: registerOnboardingRuntimeCommands },
|
|
145
153
|
{ category: 'Voice & TTS', register: registerTtsRuntimeCommands },
|