@pellux/goodvibes-agent 1.9.1 → 1.10.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 +10 -0
- package/README.md +12 -1
- package/dist/package/main.js +36712 -28803
- package/dist/package/{web-tree-sitter-jbz042ba.wasm → web-tree-sitter-e011xaqr.wasm} +0 -0
- package/docs/README.md +1 -1
- package/docs/connected-host.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/tools-and-commands.md +1 -0
- package/package.json +3 -3
- package/release/live-verification/live-verification.json +11 -11
- package/release/live-verification/live-verification.md +12 -12
- package/src/agent/email/email-service.ts +1 -1
- package/src/agent/email/imap-client.ts +4 -4
- package/src/agent/email/smtp-client.ts +5 -5
- package/src/cli/config-overrides.ts +29 -14
- package/src/cli/entrypoint.ts +12 -4
- package/src/cli/launch-auto-update.ts +218 -0
- package/src/cli/relay-command.ts +4 -4
- package/src/cli/service-posture.ts +2 -2
- package/src/cli/tui-startup.ts +31 -0
- package/src/cli/workspaces-command.ts +5 -1
- package/src/cli-flags.ts +1 -1
- package/src/config/index.ts +1 -1
- package/src/config/update-settings.ts +45 -0
- package/src/config/workspace-registration.ts +214 -15
- package/src/input/commands/runtime-services.ts +0 -5
- package/src/input/commands/update-runtime.ts +313 -0
- package/src/input/commands.ts +2 -0
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/handler-feed.ts +8 -8
- package/src/input/handler.ts +1 -1
- package/src/input/mcp-workspace.ts +5 -1
- package/src/input/panel-paste-flood-guard.ts +1 -1
- package/src/input/settings-modal-types.ts +11 -5
- package/src/input/settings-modal.ts +56 -61
- package/src/main.ts +19 -20
- package/src/renderer/activity-sidebar.ts +53 -4
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +37 -25
- package/src/runtime/bootstrap-core.ts +16 -5
- package/src/runtime/bootstrap-external-services.ts +107 -11
- package/src/runtime/bootstrap-hook-bridge.ts +2 -2
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +34 -12
- package/src/runtime/connected-host-autostart.ts +269 -0
- package/src/runtime/daemon-receipts.ts +66 -0
- package/src/runtime/diagnostics/panels/index.ts +0 -2
- package/src/runtime/feature-enablement.ts +176 -0
- package/src/runtime/index.ts +12 -29
- package/src/runtime/memory-spine-adoption.ts +18 -0
- package/src/runtime/onboarding/apply.ts +50 -37
- package/src/runtime/onboarding/types.ts +2 -2
- package/src/runtime/onboarding/verify.ts +22 -4
- package/src/runtime/release-artifacts.ts +113 -0
- package/src/runtime/services.ts +228 -19
- package/src/runtime/session-spine-rest-transport.ts +84 -4
- package/src/runtime/ui-services.ts +1 -1
- package/src/runtime/update-check.ts +64 -0
- package/src/shell/ui-openers.ts +8 -0
- package/src/tools/agent-harness-metadata.ts +8 -0
- package/src/tools/agent-harness-setup-connected-host.ts +1 -1
- package/src/tools/agent-harness-setup-posture.ts +1 -1
- package/src/version.ts +1 -1
- package/src/runtime/diagnostics/panels/ops.ts +0 -156
- package/src/runtime/surface-feature-flags.ts +0 -100
|
@@ -16,7 +16,6 @@ import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
|
16
16
|
import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
17
17
|
import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
18
18
|
import type { OperatorClient } from '@/runtime/index.ts';
|
|
19
|
-
import type { OpsApi } from '@/runtime/index.ts';
|
|
20
19
|
import type { PeerClient } from '@/runtime/index.ts';
|
|
21
20
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
|
|
22
21
|
import type { ProviderApi } from '@pellux/goodvibes-sdk/platform/providers';
|
|
@@ -239,10 +238,6 @@ export function requireMcpApi(context: CommandContext): McpApi {
|
|
|
239
238
|
return requireContextValue(context.clients?.mcpApi, 'clients.mcpApi');
|
|
240
239
|
}
|
|
241
240
|
|
|
242
|
-
export function requireOpsApi(context: CommandContext): OpsApi {
|
|
243
|
-
return requireContextValue(context.clients?.opsApi, 'clients.opsApi');
|
|
244
|
-
}
|
|
245
|
-
|
|
246
241
|
export function requireDirectTransport(context: CommandContext): DirectTransport {
|
|
247
242
|
return requireContextValue(context.clients?.transport, 'clients.transport');
|
|
248
243
|
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/update` — a real self-update path for binary installs. The
|
|
3
|
+
* download-verify-swap mechanics are the SDK's canonical update policy module
|
|
4
|
+
* (platform/runtime/self-update), the same mechanism the TUI's `/update` and
|
|
5
|
+
* the daemon's hourly loop follow: one update mechanism everywhere. This file
|
|
6
|
+
* owns only the agent's /update UX: install-kind gating, this repo's release
|
|
7
|
+
* asset layout, and the printed report.
|
|
8
|
+
*
|
|
9
|
+
* Subcommands:
|
|
10
|
+
* /update [check] — resolve the latest release tag and report whether this
|
|
11
|
+
* build is already current.
|
|
12
|
+
* /update apply — for a binary install (a directly-downloaded release
|
|
13
|
+
* binary), download + verify + atomically swap the agent
|
|
14
|
+
* binary, and refresh the sqlite-vec native addon in
|
|
15
|
+
* lockstep so the vector index never goes stale beside a
|
|
16
|
+
* new binary. Every swap parks the outgoing file at
|
|
17
|
+
* `<path>.previous`, so the replaced version is always
|
|
18
|
+
* kept. For any other install kind, prints the exact
|
|
19
|
+
* command to run instead — it never attempts a swap it
|
|
20
|
+
* can't do safely.
|
|
21
|
+
* /update rollback — exchange each installed file with its kept `.previous`
|
|
22
|
+
* counterpart: one command back to the version that ran
|
|
23
|
+
* before the last update (and, being an exchange, one
|
|
24
|
+
* more command forward again).
|
|
25
|
+
*
|
|
26
|
+
* The agent ships ONE binary and no daemon binary, and its addon travels as a
|
|
27
|
+
* tar.gz ARCHIVE asset (see release-artifacts.ts) rather than a bare file —
|
|
28
|
+
* so the addon bytes are downloaded and checksum-verified as the archive,
|
|
29
|
+
* extracted in memory, and only then swapped with the same keep-previous
|
|
30
|
+
* mechanics as the binary. Verification order is strict: the addon archive is
|
|
31
|
+
* fully verified and extracted BEFORE the binary swap begins, so a corrupted
|
|
32
|
+
* addon download can never leave a new binary beside a stale (or half-written)
|
|
33
|
+
* addon.
|
|
34
|
+
*/
|
|
35
|
+
import { dirname, join } from 'node:path';
|
|
36
|
+
import {
|
|
37
|
+
applyVerifiedUpdate,
|
|
38
|
+
realUpdateFileIo,
|
|
39
|
+
rollbackKeptPrevious,
|
|
40
|
+
sha256,
|
|
41
|
+
swapFileAtomically,
|
|
42
|
+
verifyChecksum,
|
|
43
|
+
type UpdateFileIo,
|
|
44
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/self-update';
|
|
45
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
46
|
+
import { VERSION } from '../../version.ts';
|
|
47
|
+
import {
|
|
48
|
+
CHECKSUM_MANIFEST_NAME,
|
|
49
|
+
extractTarGzEntry,
|
|
50
|
+
parseChecksumFile,
|
|
51
|
+
resolveAgentBinaryAssetName,
|
|
52
|
+
resolveSqliteVecArchive,
|
|
53
|
+
} from '../../runtime/release-artifacts.ts';
|
|
54
|
+
import {
|
|
55
|
+
compareVersions,
|
|
56
|
+
detectInstallKind,
|
|
57
|
+
fallbackUpdateCommand,
|
|
58
|
+
normalizeVersion,
|
|
59
|
+
resolveLatestReleaseTag,
|
|
60
|
+
type InstallKind,
|
|
61
|
+
type UpdateFetchLike,
|
|
62
|
+
} from '../../runtime/update-check.ts';
|
|
63
|
+
|
|
64
|
+
const REPO_RELEASES_LATEST_URL = 'https://github.com/mgd34msu/goodvibes-agent/releases/latest';
|
|
65
|
+
|
|
66
|
+
function releaseDownloadBaseUrl(tag: string): string {
|
|
67
|
+
return `https://github.com/mgd34msu/goodvibes-agent/releases/download/${tag}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Suffix under which every swap keeps the file it replaced — re-exported from
|
|
72
|
+
* the SDK's canonical update policy module so rollback and swap share one
|
|
73
|
+
* definition everywhere.
|
|
74
|
+
*/
|
|
75
|
+
export { PREVIOUS_FILE_SUFFIX } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
|
|
76
|
+
import { PREVIOUS_FILE_SUFFIX } from '@pellux/goodvibes-sdk/platform/runtime/self-update';
|
|
77
|
+
|
|
78
|
+
async function downloadBytes(fetchImpl: UpdateFetchLike, url: string): Promise<Buffer> {
|
|
79
|
+
const response = await fetchImpl(url);
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`download failed (${response.status}) for ${url}`);
|
|
82
|
+
}
|
|
83
|
+
return Buffer.from(await response.arrayBuffer());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface CheckForUpdateResult {
|
|
87
|
+
readonly latestTag: string;
|
|
88
|
+
readonly isCurrent: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function checkForUpdate(fetchImpl: UpdateFetchLike, currentVersion: string): Promise<CheckForUpdateResult> {
|
|
92
|
+
const latestTag = await resolveLatestReleaseTag(fetchImpl, REPO_RELEASES_LATEST_URL);
|
|
93
|
+
const isCurrent = compareVersions(currentVersion, latestTag) >= 0;
|
|
94
|
+
return { latestTag, isCurrent };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ApplyUpdateOptions {
|
|
98
|
+
readonly fetchImpl: UpdateFetchLike;
|
|
99
|
+
readonly execPath: string;
|
|
100
|
+
readonly platform: NodeJS.Platform;
|
|
101
|
+
readonly arch: string;
|
|
102
|
+
readonly currentVersion: string;
|
|
103
|
+
readonly print: (line: string) => void;
|
|
104
|
+
/** Injectable filesystem seam (the SDK's UpdateFileIo) so tests observe swaps in memory. */
|
|
105
|
+
readonly io?: UpdateFileIo;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The real self-update path, delegating the download-verify-swap mechanics to
|
|
110
|
+
* the SDK's canonical update policy module. For a binary install: resolve the
|
|
111
|
+
* latest tag, compare to the running version, and if newer, verify and stage
|
|
112
|
+
* the addon archive first (bytes in memory, no writes), run the binary
|
|
113
|
+
* download-verify-swap, then swap the extracted addon file — each swap keeps
|
|
114
|
+
* the outgoing file at `<path>.previous`. For any other install kind, never
|
|
115
|
+
* attempts a swap — it prints the exact command for that install method
|
|
116
|
+
* instead.
|
|
117
|
+
*/
|
|
118
|
+
export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
|
|
119
|
+
const installKind: InstallKind = detectInstallKind(options.execPath);
|
|
120
|
+
if (installKind !== 'binary') {
|
|
121
|
+
options.print(
|
|
122
|
+
[
|
|
123
|
+
`This install is not a self-updatable binary install (detected: ${installKind === 'bun-global-package' ? 'bun/npm package install' : 'running from source'}).`,
|
|
124
|
+
`Update with: ${fallbackUpdateCommand(installKind)}`,
|
|
125
|
+
].join('\n'),
|
|
126
|
+
);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const latestTag = await resolveLatestReleaseTag(options.fetchImpl, REPO_RELEASES_LATEST_URL);
|
|
131
|
+
if (compareVersions(options.currentVersion, latestTag) >= 0) {
|
|
132
|
+
options.print(`Already current: running v${normalizeVersion(options.currentVersion)}, latest release is ${latestTag}.`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const binaryAsset = resolveAgentBinaryAssetName(options.platform, options.arch);
|
|
137
|
+
if (!binaryAsset) {
|
|
138
|
+
options.print(`No prebuilt binaries are published for ${options.platform}-${options.arch}; cannot self-update.`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
options.print(`Update available: ${latestTag} (running v${normalizeVersion(options.currentVersion)}). Downloading and verifying...`);
|
|
143
|
+
|
|
144
|
+
const baseUrl = releaseDownloadBaseUrl(latestTag);
|
|
145
|
+
const io = options.io ?? realUpdateFileIo;
|
|
146
|
+
const appBinaryPath = options.execPath;
|
|
147
|
+
|
|
148
|
+
// The sqlite-vec native addon travels with the binary as a tar.gz archive
|
|
149
|
+
// asset: refresh it in the same update so /update never leaves a new binary
|
|
150
|
+
// beside a stale addon. The manifest entry decides whether the target
|
|
151
|
+
// release ships it — an entry that IS present makes the download, checksum,
|
|
152
|
+
// and extraction mandatory (any failure is fatal, verified before the
|
|
153
|
+
// binary swap begins), while an absent entry means the target predates the
|
|
154
|
+
// addon archives and is skipped rather than blocking an otherwise-valid
|
|
155
|
+
// binary update.
|
|
156
|
+
const manifestBytes = await downloadBytes(options.fetchImpl, `${baseUrl}/${CHECKSUM_MANIFEST_NAME}`);
|
|
157
|
+
const checksums = parseChecksumFile(manifestBytes.toString('utf-8'));
|
|
158
|
+
const addon = resolveSqliteVecArchive(options.platform, options.arch);
|
|
159
|
+
const addonIncluded = addon !== null && checksums.get(addon.assetName) !== undefined;
|
|
160
|
+
let addonFileBytes: Buffer | null = null;
|
|
161
|
+
let addonTargetPath: string | null = null;
|
|
162
|
+
if (addon && addonIncluded) {
|
|
163
|
+
const archiveBytes = await downloadBytes(options.fetchImpl, `${baseUrl}/${addon.assetName}`);
|
|
164
|
+
verifyChecksum(addon.assetName, sha256(archiveBytes), checksums.get(addon.assetName));
|
|
165
|
+
addonFileBytes = extractTarGzEntry(archiveBytes, addon.entryPath);
|
|
166
|
+
if (addonFileBytes === null) {
|
|
167
|
+
throw new Error(`addon archive ${addon.assetName} verified but holds no ${addon.entryPath} — refusing a partial update`);
|
|
168
|
+
}
|
|
169
|
+
addonTargetPath = join(dirname(appBinaryPath), 'lib', addon.dirName, addon.fileName);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// One mechanism everywhere: downloads + verifies the binary against the
|
|
173
|
+
// same manifest, then swaps it atomically with the outgoing file kept at
|
|
174
|
+
// `<path>.previous`. The addon bytes above are already verified, so the
|
|
175
|
+
// addon swap after this cannot fail on bad data.
|
|
176
|
+
await applyVerifiedUpdate({
|
|
177
|
+
fetchImpl: options.fetchImpl,
|
|
178
|
+
downloadBaseUrl: baseUrl,
|
|
179
|
+
targets: [{ label: 'agent binary', path: appBinaryPath, assetName: binaryAsset, executable: true }],
|
|
180
|
+
io,
|
|
181
|
+
platform: options.platform,
|
|
182
|
+
});
|
|
183
|
+
if (addonFileBytes !== null && addonTargetPath !== null) {
|
|
184
|
+
swapFileAtomically(addonTargetPath, addonFileBytes, { executable: false, io, platform: options.platform });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
options.print(
|
|
188
|
+
[
|
|
189
|
+
`Updated to ${latestTag}.`,
|
|
190
|
+
` agent binary: ${appBinaryPath}`,
|
|
191
|
+
...(addonTargetPath
|
|
192
|
+
? [` vector addon: ${addonTargetPath}`]
|
|
193
|
+
: [` vector addon: the ${latestTag} release ships no ${addon?.assetName ?? 'addon archive'} for this platform — left untouched`]),
|
|
194
|
+
'',
|
|
195
|
+
'Restart goodvibes-agent to run the new version.',
|
|
196
|
+
].join('\n'),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface RollbackUpdateOptions {
|
|
201
|
+
readonly execPath: string;
|
|
202
|
+
readonly platform: NodeJS.Platform;
|
|
203
|
+
readonly arch: string;
|
|
204
|
+
readonly print: (line: string) => void;
|
|
205
|
+
/** Injectable filesystem seam (the SDK's UpdateFileIo) so tests observe renames in memory. */
|
|
206
|
+
readonly io?: UpdateFileIo;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* One-command rollback to the version that ran before the last update,
|
|
211
|
+
* delegating the exchange mechanics to the SDK's rollbackKeptPrevious (the
|
|
212
|
+
* same module the swap uses): every installed file (agent binary, vector
|
|
213
|
+
* addon) that has a kept `.previous` counterpart is EXCHANGED with it — the
|
|
214
|
+
* previous version becomes live, and the version being rolled back is itself
|
|
215
|
+
* kept at `.previous`, so a second `/update rollback` rolls forward again.
|
|
216
|
+
* Files without a kept counterpart are reported and left untouched; nothing
|
|
217
|
+
* is downloaded.
|
|
218
|
+
*/
|
|
219
|
+
export function rollbackUpdate(options: RollbackUpdateOptions): void {
|
|
220
|
+
const installKind: InstallKind = detectInstallKind(options.execPath);
|
|
221
|
+
if (installKind !== 'binary') {
|
|
222
|
+
options.print(
|
|
223
|
+
[
|
|
224
|
+
`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.`,
|
|
225
|
+
`Install a specific version with your package manager instead, e.g.: ${fallbackUpdateCommand(installKind)}`,
|
|
226
|
+
].join('\n'),
|
|
227
|
+
);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const io = options.io ?? realUpdateFileIo;
|
|
232
|
+
const addon = resolveSqliteVecArchive(options.platform, options.arch);
|
|
233
|
+
const targets = [
|
|
234
|
+
{ label: 'agent binary', path: options.execPath },
|
|
235
|
+
...(addon ? [{ label: 'vector addon', path: join(dirname(options.execPath), 'lib', addon.dirName, addon.fileName) }] : []),
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const result = rollbackKeptPrevious(targets, io);
|
|
239
|
+
if (result.restored.length === 0) {
|
|
240
|
+
options.print(
|
|
241
|
+
`No previous version is kept beside this install (nothing at ${options.execPath}${PREVIOUS_FILE_SUFFIX}). ` +
|
|
242
|
+
'The previous version is kept from the next update onward.',
|
|
243
|
+
);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
options.print(
|
|
248
|
+
[
|
|
249
|
+
'Rolled back to the previously installed version.',
|
|
250
|
+
...result.restored.map((target) => ` ${target.label}: ${target.path} (the replaced version is kept at ${target.path}${PREVIOUS_FILE_SUFFIX})`),
|
|
251
|
+
'',
|
|
252
|
+
'Restart goodvibes-agent to run the restored version.',
|
|
253
|
+
].join('\n'),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function registerUpdateCommand(registry: CommandRegistry): void {
|
|
258
|
+
registry.register({
|
|
259
|
+
name: 'update',
|
|
260
|
+
aliases: ['upgrade'],
|
|
261
|
+
description: 'Check for a newer GoodVibes Agent release and, for binary installs, download/verify/apply it or roll back to the kept previous version',
|
|
262
|
+
usage: '[check|apply|rollback]',
|
|
263
|
+
async handler(args, ctx) {
|
|
264
|
+
const sub = args[0] ?? 'check';
|
|
265
|
+
|
|
266
|
+
if (sub === 'check') {
|
|
267
|
+
try {
|
|
268
|
+
const result = await checkForUpdate(fetch as UpdateFetchLike, VERSION);
|
|
269
|
+
ctx.print(
|
|
270
|
+
result.isCurrent
|
|
271
|
+
? `Already current: running v${normalizeVersion(VERSION)} (latest release is ${result.latestTag}).`
|
|
272
|
+
: `Update available: ${result.latestTag} (running v${normalizeVersion(VERSION)}). Run /update apply to install it.`,
|
|
273
|
+
);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
ctx.print(`Could not check for updates: ${error instanceof Error ? error.message : String(error)}`);
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (sub === 'apply') {
|
|
281
|
+
try {
|
|
282
|
+
await applyUpdate({
|
|
283
|
+
fetchImpl: fetch as UpdateFetchLike,
|
|
284
|
+
execPath: process.execPath,
|
|
285
|
+
platform: process.platform,
|
|
286
|
+
arch: process.arch,
|
|
287
|
+
currentVersion: VERSION,
|
|
288
|
+
print: ctx.print,
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
ctx.print(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
292
|
+
}
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (sub === 'rollback') {
|
|
297
|
+
try {
|
|
298
|
+
rollbackUpdate({
|
|
299
|
+
execPath: process.execPath,
|
|
300
|
+
platform: process.platform,
|
|
301
|
+
arch: process.arch,
|
|
302
|
+
print: ctx.print,
|
|
303
|
+
});
|
|
304
|
+
} catch (error) {
|
|
305
|
+
ctx.print(`Rollback failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
ctx.print('Usage: /update [check|apply|rollback]');
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
}
|
package/src/input/commands.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { registerOperatorActionRuntimeCommands } from './commands/operator-actio
|
|
|
41
41
|
import { registerConnectedHostAdminCommands } from './commands/connected-host-admin-runtime.ts';
|
|
42
42
|
import { registerEmailRuntimeCommands } from './commands/email-runtime.ts';
|
|
43
43
|
import { registerCalendarRuntimeCommands } from './commands/calendar-runtime.ts';
|
|
44
|
+
import { registerUpdateCommand } from './commands/update-runtime.ts';
|
|
44
45
|
|
|
45
46
|
function registerAgentMemoryCommand(registry: CommandRegistry): void {
|
|
46
47
|
registry.register({
|
|
@@ -90,6 +91,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
|
90
91
|
registerLocalProviderRuntimeCommands(registry);
|
|
91
92
|
registerNetworkScanRuntimeCommands(registry);
|
|
92
93
|
registerHealthRuntimeCommands(registry);
|
|
94
|
+
registerUpdateCommand(registry);
|
|
93
95
|
registerProviderAccountsRuntimeCommands(registry);
|
|
94
96
|
registerConversationRuntimeCommands(registry);
|
|
95
97
|
registerQrcodeRuntimeCommands(registry);
|
|
@@ -95,9 +95,9 @@ export interface FeedContextStableRefs {
|
|
|
95
95
|
selection: SelectionManager;
|
|
96
96
|
pasteRegistry: Map<string, string>;
|
|
97
97
|
imageRegistry: Map<string, { data: string; mediaType: string }>;
|
|
98
|
-
/** Ported from goodvibes-tui's
|
|
98
|
+
/** Ported from goodvibes-tui's unbracketed-paste-flood guard; mutated in place, never reallocated. */
|
|
99
99
|
burstGuard: PanelBurstGuardState;
|
|
100
|
-
/** OS-level terminal focus tracker, ported from goodvibes-tui's
|
|
100
|
+
/** OS-level terminal focus tracker, ported from goodvibes-tui's core/focus-tracker.ts. */
|
|
101
101
|
focusTracker: FocusTracker;
|
|
102
102
|
projectRoot: string;
|
|
103
103
|
selectionModal: SelectionModal;
|
|
@@ -64,13 +64,13 @@ import type { FocusTracker } from '../core/focus-tracker.ts';
|
|
|
64
64
|
* - `inputHistory`, `conversationManager` — late-wired service handles; synced at
|
|
65
65
|
* feed() entry only since no in-feed action rewires them
|
|
66
66
|
* - `pasteRegistry`, `imageRegistry` — owned Maps, never replaced
|
|
67
|
-
* - `burstGuard` (ported from goodvibes-tui's
|
|
67
|
+
* - `burstGuard` (ported from goodvibes-tui's panel-paste-flood-guard.ts) — the
|
|
68
68
|
* unbracketed-paste-flood guard's sliding-window state, mutated in place
|
|
69
69
|
* across tokens by trackPanelPasteFloodGuard (see panel-paste-flood-guard.ts).
|
|
70
70
|
* Never reallocated. `burstSuppressedCount` is this wiring layer's own
|
|
71
71
|
* bookkeeping (not part of the ported module) for the honest resolution
|
|
72
72
|
* notice — see feedInputTokens below.
|
|
73
|
-
* - `focusTracker` (ported from goodvibes-tui's
|
|
73
|
+
* - `focusTracker` (ported from goodvibes-tui's core/focus-tracker.ts) — tracks OS-level
|
|
74
74
|
* terminal focus from `\x1b[I`/`\x1b[O` tokens (DECSET ?1004h, enabled in
|
|
75
75
|
* main.ts). Shared instance from RuntimeServices, threaded via
|
|
76
76
|
* uiServices.platform.focusTracker (mirrors the TUI's own wiring).
|
|
@@ -105,11 +105,11 @@ export interface InputFeedContext {
|
|
|
105
105
|
contentWidth: number;
|
|
106
106
|
readonly pasteRegistry: Map<string, string>;
|
|
107
107
|
readonly imageRegistry: Map<string, { data: string; mediaType: string }>;
|
|
108
|
-
/** Ported from goodvibes-tui
|
|
108
|
+
/** Ported from goodvibes-tui's paste-flood guard — mutated in place, never reallocated. */
|
|
109
109
|
readonly burstGuard: PanelBurstGuardState;
|
|
110
110
|
/** Wiring-layer bookkeeping (not part of the ported module) for the honest suppressed-count notice. */
|
|
111
111
|
burstSuppressedCount: number;
|
|
112
|
-
/** Ported from goodvibes-tui
|
|
112
|
+
/** Ported from goodvibes-tui's focus tracker — OS-level terminal focus, fed from 'focus' tokens below. */
|
|
113
113
|
readonly focusTracker: FocusTracker;
|
|
114
114
|
readonly projectRoot: string;
|
|
115
115
|
readonly selection: SelectionManager;
|
|
@@ -178,11 +178,11 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
178
178
|
const keybindings = context.keybindingsManager;
|
|
179
179
|
// One `now` per feed() call (not per token) — a genuine unbracketed-paste
|
|
180
180
|
// flood delivers many tokens in one drain, and they should all measure as
|
|
181
|
-
// arriving "at once" (mirrors goodvibes-tui's handler-feed.ts
|
|
181
|
+
// arriving "at once" (mirrors the same doc note in goodvibes-tui's handler-feed.ts).
|
|
182
182
|
const now = Date.now();
|
|
183
183
|
|
|
184
184
|
for (const token of tokens) {
|
|
185
|
-
// Ported from goodvibes-tui
|
|
185
|
+
// Ported from goodvibes-tui's focus tracking: focus-reporting tokens (CSI ?1004h)
|
|
186
186
|
// never reach the composer or any modal route — consumed here, first,
|
|
187
187
|
// unconditionally. No render needed.
|
|
188
188
|
if (token.type === 'focus') {
|
|
@@ -294,8 +294,8 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
// Ported from goodvibes-tui's panel-paste-flood-guard.ts
|
|
298
|
-
//
|
|
297
|
+
// Ported from goodvibes-tui's panel-paste-flood-guard.ts: guards
|
|
298
|
+
// command-mode's key-driven dispatch (handleCommandModeToken,
|
|
299
299
|
// below) from an unbracketed-paste-replay or control-character-injection
|
|
300
300
|
// burst.
|
|
301
301
|
//
|
package/src/input/handler.ts
CHANGED
|
@@ -94,7 +94,7 @@ export class InputHandler {
|
|
|
94
94
|
public pasteRegistry = new Map<string, string>();
|
|
95
95
|
public nextPasteId = 1;
|
|
96
96
|
public lastCtrlCTime = 0;
|
|
97
|
-
/** Ported from goodvibes-tui
|
|
97
|
+
/** Ported from goodvibes-tui — unbracketed-paste-flood guard state, mutated in place. */
|
|
98
98
|
public burstGuard: PanelBurstGuardState = { timestamps: [], suspended: false, hintShown: false };
|
|
99
99
|
/** Long-lived feed context — reused across every feed() call to avoid per-keystroke allocation. */
|
|
100
100
|
public feedContext!: import('./handler-feed.ts').InputFeedContext;
|
|
@@ -150,7 +150,11 @@ function serverConfigToForm(server?: McpServerConfig): McpWorkspaceForm {
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
// The workspace form edits stdio (command-launched) servers only, so the
|
|
154
|
+
// converted config always carries a command; streamable-HTTP (url) servers
|
|
155
|
+
// from the SDK's widened McpServerConfig appear in the list but are managed
|
|
156
|
+
// through the /mcp command line.
|
|
157
|
+
function formToServerConfig(form: McpWorkspaceForm): McpServerConfig & { command: string } {
|
|
154
158
|
const name = form.name.trim();
|
|
155
159
|
const command = form.command.trim();
|
|
156
160
|
if (!name) throw new Error('Server name is required.');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// panel-paste-flood-guard.ts — ported from goodvibes-tui (commit 90eb3a26,
|
|
3
|
-
//
|
|
3
|
+
// src/input/panel-paste-flood-guard.ts).
|
|
4
4
|
//
|
|
5
5
|
// A terminal WITHOUT bracketed paste delivers a pasted block as a burst of
|
|
6
6
|
// discrete 1-char 'text' tokens (isPasteToken stays false for every one of
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
2
|
import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
3
|
import type { ProviderAuthFreshness, ProviderAuthRoute } from '@/runtime/index.ts';
|
|
4
|
-
import type {
|
|
4
|
+
import type { FeatureSetting, FlagState } from '@/runtime/index.ts';
|
|
5
5
|
|
|
6
6
|
export interface SettingsModalChange {
|
|
7
7
|
readonly key: ConfigKey;
|
|
@@ -61,7 +61,9 @@ export type SettingsCategory =
|
|
|
61
61
|
| 'policy'
|
|
62
62
|
| 'fetch'
|
|
63
63
|
| 'security'
|
|
64
|
-
| 'integrations'
|
|
64
|
+
| 'integrations'
|
|
65
|
+
| 'update'
|
|
66
|
+
| 'pricing';
|
|
65
67
|
|
|
66
68
|
export type SettingsFocusPane = 'categories' | 'settings';
|
|
67
69
|
|
|
@@ -70,10 +72,10 @@ export const SETTINGS_CATEGORY_GROUPS: ReadonlyArray<{
|
|
|
70
72
|
readonly categories: readonly SettingsCategory[];
|
|
71
73
|
}> = [
|
|
72
74
|
{ label: 'Agent Experience', categories: ['display', 'ui', 'behavior', 'agents', 'notifications', 'permissions', 'policy', 'fetch', 'diagnostics'] },
|
|
73
|
-
{ label: 'Models and Providers', categories: ['provider', 'subscriptions', 'helper', 'tools', 'tts'] },
|
|
75
|
+
{ label: 'Models and Providers', categories: ['provider', 'subscriptions', 'helper', 'tools', 'tts', 'pricing'] },
|
|
74
76
|
{ label: 'Agent-local state', categories: ['storage', 'cache', 'telemetry', 'atRest', 'security', 'learning'] },
|
|
75
77
|
{ label: 'Channels and Tools', categories: ['surfaces', 'mcp', 'automation', 'checkin', 'integrations'] },
|
|
76
|
-
{ label: 'Daemon Runtime', categories: ['daemon', 'service', 'controlPlane', 'httpListener', 'web', 'watchers', 'network', 'relay'] },
|
|
78
|
+
{ label: 'Daemon Runtime', categories: ['daemon', 'service', 'controlPlane', 'httpListener', 'web', 'watchers', 'network', 'relay', 'update'] },
|
|
77
79
|
{ label: 'Advanced Runtime', categories: ['orchestration', 'planner', 'runtime', 'sandbox', 'batch', 'cloudflare', 'wrfc'] },
|
|
78
80
|
{ label: 'Advanced', categories: ['flags', 'release'] },
|
|
79
81
|
];
|
|
@@ -92,8 +94,12 @@ export interface SettingEntry {
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
export interface FlagEntry {
|
|
95
|
-
|
|
97
|
+
/** The feature as FEATURE_SETTINGS describes it: domain, enablement shape, settings keys, real description. */
|
|
98
|
+
feature: FeatureSetting;
|
|
99
|
+
/** Live gate state from the manager (kill-switch aware); config-derived when no manager is attached. */
|
|
96
100
|
state: FlagState;
|
|
101
|
+
/** The enablement settings key's current config value, rendered on the row. */
|
|
102
|
+
enablementValue: string;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
export interface McpEntry {
|