@phnx-labs/agents-cli 1.22.78 → 1.22.80
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 +15 -0
- package/README.md +28 -1
- package/dist/bootstrap.js +40 -12
- package/dist/commands/accounts.d.ts +22 -0
- package/dist/commands/accounts.js +158 -35
- package/dist/commands/config.js +37 -0
- package/dist/commands/exec.js +21 -10
- package/dist/commands/update.js +169 -18
- package/dist/commands/versions.d.ts +11 -0
- package/dist/commands/versions.js +30 -4
- package/dist/commands/view.d.ts +69 -3
- package/dist/commands/view.js +235 -75
- package/dist/index.js +35 -2
- package/dist/lib/account-catalog.d.ts +97 -1
- package/dist/lib/account-catalog.js +134 -6
- package/dist/lib/account-registry.d.ts +43 -0
- package/dist/lib/account-registry.js +97 -2
- package/dist/lib/accounting/rotate.d.ts +2 -2
- package/dist/lib/accounting/rotate.js +5 -5
- package/dist/lib/accounts/auth-operation-lock.d.ts +9 -0
- package/dist/lib/accounts/auth-operation-lock.js +55 -0
- package/dist/lib/accounts/connect.d.ts +170 -0
- package/dist/lib/accounts/connect.js +383 -0
- package/dist/lib/capabilities.js +2 -0
- package/dist/lib/commands.js +2 -0
- package/dist/lib/config-keys.d.ts +11 -2
- package/dist/lib/config-keys.js +21 -1
- package/dist/lib/daemon/daemon.js +5 -0
- package/dist/lib/daemon/harness-update-service.d.ts +110 -0
- package/dist/lib/daemon/harness-update-service.js +216 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +0 -1
- package/dist/lib/device-config.js +50 -5
- package/dist/lib/exec.js +26 -2
- package/dist/lib/fs-atomic.d.ts +2 -0
- package/dist/lib/fs-atomic.js +2 -0
- package/dist/lib/hooks/install.js +7 -2
- package/dist/lib/installations/active-check.d.ts +48 -0
- package/dist/lib/installations/active-check.js +84 -0
- package/dist/lib/installations/index.d.ts +5 -1
- package/dist/lib/installations/index.js +4 -0
- package/dist/lib/installations/installation-lock.d.ts +6 -0
- package/dist/lib/installations/installation-lock.js +29 -0
- package/dist/lib/installations/launch-gate.d.ts +69 -0
- package/dist/lib/installations/launch-gate.js +133 -0
- package/dist/lib/installations/native-command.d.ts +5 -0
- package/dist/lib/installations/native-command.js +52 -0
- package/dist/lib/installations/shims.d.ts +8 -2
- package/dist/lib/installations/shims.js +105 -2
- package/dist/lib/installations/store.d.ts +5 -1
- package/dist/lib/installations/store.js +24 -3
- package/dist/lib/installations/strategies.js +55 -35
- package/dist/lib/installations/types.d.ts +17 -0
- package/dist/lib/installations/update-cancellation.d.ts +82 -0
- package/dist/lib/installations/update-cancellation.js +122 -0
- package/dist/lib/installations/update-policy.d.ts +69 -0
- package/dist/lib/installations/update-policy.js +114 -0
- package/dist/lib/installations/update-runtime.d.ts +118 -0
- package/dist/lib/installations/update-runtime.js +321 -0
- package/dist/lib/installations/update.d.ts +25 -0
- package/dist/lib/installations/update.js +141 -2
- package/dist/lib/installations/versions.d.ts +1 -0
- package/dist/lib/installations/versions.js +166 -131
- package/dist/lib/platform/process.d.ts +3 -1
- package/dist/lib/platform/process.js +2 -2
- package/dist/lib/staleness/detectors/commands.d.ts +1 -2
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -2
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -2
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -2
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -7
- package/dist/lib/staleness/detectors/rules.d.ts +1 -2
- package/dist/lib/staleness/detectors/skills.d.ts +1 -2
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -7
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -2
- package/dist/lib/staleness/writers/commands.d.ts +1 -2
- package/dist/lib/staleness/writers/hooks.d.ts +1 -2
- package/dist/lib/staleness/writers/mcp.d.ts +1 -2
- package/dist/lib/staleness/writers/permissions.d.ts +1 -12
- package/dist/lib/staleness/writers/plugins.d.ts +1 -6
- package/dist/lib/staleness/writers/rules.d.ts +1 -2
- package/dist/lib/staleness/writers/skills.d.ts +1 -2
- package/dist/lib/staleness/writers/subagents.d.ts +1 -2
- package/dist/lib/staleness/writers/workflows.d.ts +1 -8
- package/dist/lib/state.d.ts +3 -1
- package/dist/lib/state.js +38 -13
- package/dist/lib/types.d.ts +26 -1
- package/dist/lib/types.js +5 -0
- package/dist/lib/view-types.d.ts +6 -0
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ import { discoverPermissionGroups, getActivePermissionPresetName, readPermission
|
|
|
22
22
|
import { parseMcpConfigForScan, isProjectMcpTrusted } from '../mcp.js';
|
|
23
23
|
import { createVersionedAlias, removeVersionedAlias, switchConfigSymlink, getConfigSymlinkVersion, ensureClaudeInsideSymlink, assertIsolationBoundary, isIsolationProtected, } from './shims.js';
|
|
24
24
|
import { importInstallScriptBinary } from '../import.js';
|
|
25
|
-
import { createInstallation, getBinaryPath, getCliVersionFromPath, getGlobalDefault, getIsolatedDefault, getLiveVersion, getVersionDir, getVersionHomePath, invalidateInstalledVersionsCache, invalidateLiveVersionCache, isGlobalBinaryAgent, isVersionInstalled, isVersionIsolated, listInstalledVersions, pickCanonicalGlobalBinaryVersion, resolveGrokFallbackBinary, resolveVersion, } from './store.js';
|
|
25
|
+
import { createInstallation, readInstallation, getBinaryPath, getCliVersionFromPath, getGlobalDefault, getIsolatedDefault, getLiveVersion, getVersionDir, getVersionHomePath, invalidateInstalledVersionsCache, invalidateLiveVersionCache, isGlobalBinaryAgent, isVersionInstalled, isVersionIsolated, listInstalledVersions, pickCanonicalGlobalBinaryVersion, resolveGrokFallbackBinary, resolveVersion, } from './store.js';
|
|
26
26
|
export * from './store.js';
|
|
27
27
|
import { INSTALLATION_RECORD_FILE } from './types.js';
|
|
28
28
|
import { composeWin32CommandLine } from '../platform/index.js';
|
|
@@ -34,6 +34,9 @@ import { discoverPlugins, syncPluginToVersion, pluginSupportsAgent, cleanOrphane
|
|
|
34
34
|
import { loadManifest, saveManifest, buildManifest as buildSyncManifest, isStale } from '../staleness/index.js';
|
|
35
35
|
import { pruneRemovedResources } from '../staleness/prune.js';
|
|
36
36
|
import { emit } from '../feed/events.js';
|
|
37
|
+
import { withFileLockAsync } from '../fs-atomic.js';
|
|
38
|
+
import { installationLockTarget, INSTALLATION_LOCK_OPTIONS } from './installation-lock.js';
|
|
39
|
+
import { isInstallationLikelyActive } from './active-check.js';
|
|
37
40
|
import { safeJoin } from '../paths.js';
|
|
38
41
|
import { readSkillSourceCommandMarker, shouldAlsoInstallCommandAsSkill, shouldInstallCommandAsSkill, } from '../command-skills.js';
|
|
39
42
|
import { getWriter, getDetector } from '../staleness/registry.js';
|
|
@@ -1031,7 +1034,8 @@ async function checkGrokAccountCollision(installedVersion) {
|
|
|
1031
1034
|
/** Install a specific version of an agent. */
|
|
1032
1035
|
export async function installVersion(agent, version, onProgress, opts) {
|
|
1033
1036
|
const agentConfig = AGENTS[agent];
|
|
1034
|
-
const requestedLabel = version;
|
|
1037
|
+
const requestedLabel = opts?.installationLabel ?? version;
|
|
1038
|
+
const initialUpdatePolicy = version === 'latest' ? 'latest' : 'pinned';
|
|
1035
1039
|
if (isAgentHardDeprecated(agent)) {
|
|
1036
1040
|
return { success: false, installedVersion: version, error: hardDeprecationError(agent) };
|
|
1037
1041
|
}
|
|
@@ -1039,6 +1043,20 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1039
1043
|
if (!VERSION_RE.test(version)) {
|
|
1040
1044
|
throw new Error(`Invalid version: ${JSON.stringify(version)}`);
|
|
1041
1045
|
}
|
|
1046
|
+
// A caller-supplied installation label decouples the addressable version-dir
|
|
1047
|
+
// name from the vendor release (PHNX-3940): `agents accounts connect` mints an
|
|
1048
|
+
// opaque stable label so ten accounts can share the same upstream `latest`
|
|
1049
|
+
// under distinct isolated homes, each with its own native login. It follows
|
|
1050
|
+
// the same identity/release split the installScript branch already uses via
|
|
1051
|
+
// `requestedLabel`, and must be a real label, never a release alias.
|
|
1052
|
+
if (opts?.installationLabel !== undefined) {
|
|
1053
|
+
if (!VERSION_RE.test(opts.installationLabel)) {
|
|
1054
|
+
throw new Error(`Invalid installation label: ${JSON.stringify(opts.installationLabel)}`);
|
|
1055
|
+
}
|
|
1056
|
+
if (opts.installationLabel === 'latest' || opts.installationLabel === 'oldest') {
|
|
1057
|
+
throw new Error(`Installation label cannot be a release alias ('${opts.installationLabel}').`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1042
1060
|
if (!agentConfig.npmPackage) {
|
|
1043
1061
|
if (!agentConfig.installScript) {
|
|
1044
1062
|
return { success: false, installedVersion: version, error: 'Agent has no npm package' };
|
|
@@ -1149,7 +1167,7 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1149
1167
|
// Freeze this installation's identity. The dir name is its stable label from
|
|
1150
1168
|
// here on; the release it carries is recorded separately so `agents update`
|
|
1151
1169
|
// can move the release without invalidating any reference to the label.
|
|
1152
|
-
createInstallation(agent, installationLabel, releaseVersion);
|
|
1170
|
+
createInstallation(agent, installationLabel, releaseVersion, initialUpdatePolicy);
|
|
1153
1171
|
const trackerInstall = await installSessionTrackerHook(agent, installationLabel);
|
|
1154
1172
|
if (!trackerInstall.installed && trackerInstall.error) {
|
|
1155
1173
|
console.warn(`agents: SessionStart hook not installed for ${agent}@${installationLabel}: ${trackerInstall.error}`);
|
|
@@ -1183,142 +1201,158 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1183
1201
|
}
|
|
1184
1202
|
version = resolved;
|
|
1185
1203
|
}
|
|
1204
|
+
// `version` is now the concrete vendor release. The addressable slot is the
|
|
1205
|
+
// caller's opaque installation label when given (connect), else the release
|
|
1206
|
+
// itself — the identity/release split (PHNX-3940). Everything on disk (dir,
|
|
1207
|
+
// alias, record, tracker) keys on `label`; only the npm spec uses `release`.
|
|
1208
|
+
const releaseVersion = version;
|
|
1209
|
+
const label = opts?.installationLabel ?? releaseVersion;
|
|
1186
1210
|
ensureAgentsDir();
|
|
1187
|
-
const versionDir = getVersionDir(agent,
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
name: `agents-${agent}-${version}`,
|
|
1202
|
-
version: '1.0.0',
|
|
1203
|
-
private: true,
|
|
1204
|
-
};
|
|
1205
|
-
fs.writeFileSync(path.join(versionDir, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
1206
|
-
// Install the package. `version` is always concrete here (`latest`/`oldest`
|
|
1207
|
-
// were resolved above), so the spec is always pinned. The `@` prefix is
|
|
1208
|
-
// load-bearing: it ensures `version` (which VERSION_RE permits to start with
|
|
1209
|
-
// `-`) is never passed as a standalone npm CLI flag.
|
|
1210
|
-
const packageSpec = `${agentConfig.npmPackage}@${version}`;
|
|
1211
|
-
// Set once the install has passed its integrity gate; read after the try so
|
|
1212
|
-
// the success path's bookkeeping sits outside the catch's cleanup.
|
|
1213
|
-
let healthyVersion;
|
|
1214
|
-
try {
|
|
1215
|
-
// Check npm is available
|
|
1216
|
-
const winShell = process.platform === 'win32';
|
|
1217
|
-
try {
|
|
1218
|
-
await execFileAsync('npm', ['--version'], { shell: winShell });
|
|
1219
|
-
}
|
|
1220
|
-
catch {
|
|
1221
|
-
return {
|
|
1222
|
-
success: false,
|
|
1223
|
-
installedVersion: version,
|
|
1224
|
-
error: 'npm is not installed. Install Node.js and npm first: https://nodejs.org/',
|
|
1225
|
-
};
|
|
1211
|
+
const versionDir = getVersionDir(agent, label);
|
|
1212
|
+
return withFileLockAsync(installationLockTarget(agent, label), async () => {
|
|
1213
|
+
// Installs and repairs mutate the same executable as updates. Hold the same
|
|
1214
|
+
// lock before touching artifacts, even before the first record exists.
|
|
1215
|
+
if (await isInstallationLikelyActive({ agent, label })) {
|
|
1216
|
+
return { success: false, installedVersion: label, error: `${agent} account home ${label} is in use. Retry after its sessions finish.` };
|
|
1217
|
+
}
|
|
1218
|
+
// A `clean` (repair) reinstall wipes a possibly partially-extracted
|
|
1219
|
+
// node_modules first. npm treats a present-but-gutted platform package (its
|
|
1220
|
+
// package.json landed, its vendored native binary did not) as already
|
|
1221
|
+
// installed and would skip re-fetching it — so without this the corrupt
|
|
1222
|
+
// vendor/ survives the reinstall and the ENOENT persists. home/ is preserved.
|
|
1223
|
+
if (opts?.clean && fs.existsSync(versionDir)) {
|
|
1224
|
+
removeInstallArtifacts(versionDir);
|
|
1226
1225
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
//
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
//
|
|
1238
|
-
//
|
|
1239
|
-
//
|
|
1240
|
-
|
|
1226
|
+
// Create version directory and isolated home
|
|
1227
|
+
fs.mkdirSync(versionDir, { recursive: true });
|
|
1228
|
+
fs.mkdirSync(path.join(versionDir, 'home'), { recursive: true });
|
|
1229
|
+
// Initialize package.json (only for real npm agents)
|
|
1230
|
+
const packageJson = {
|
|
1231
|
+
name: `agents-${agent}-${version}`,
|
|
1232
|
+
version: '1.0.0',
|
|
1233
|
+
private: true,
|
|
1234
|
+
};
|
|
1235
|
+
fs.writeFileSync(path.join(versionDir, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
1236
|
+
// Install the package. `version` is always concrete here (`latest`/`oldest`
|
|
1237
|
+
// were resolved above), so the spec is always pinned. The `@` prefix is
|
|
1238
|
+
// load-bearing: it ensures `version` (which VERSION_RE permits to start with
|
|
1239
|
+
// `-`) is never passed as a standalone npm CLI flag.
|
|
1240
|
+
const packageSpec = `${agentConfig.npmPackage}@${version}`;
|
|
1241
|
+
// Set once the install has passed its integrity gate; read after the try so
|
|
1242
|
+
// the success path's bookkeeping sits outside the catch's cleanup.
|
|
1243
|
+
let healthyVersion;
|
|
1244
|
+
try {
|
|
1245
|
+
// Check npm is available
|
|
1246
|
+
const winShell = process.platform === 'win32';
|
|
1241
1247
|
try {
|
|
1242
|
-
|
|
1248
|
+
await execFileAsync('npm', ['--version'], { shell: winShell });
|
|
1243
1249
|
}
|
|
1244
1250
|
catch {
|
|
1245
|
-
|
|
1251
|
+
return {
|
|
1252
|
+
success: false,
|
|
1253
|
+
installedVersion: version,
|
|
1254
|
+
error: 'npm is not installed. Install Node.js and npm first: https://nodejs.org/',
|
|
1255
|
+
};
|
|
1246
1256
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
try {
|
|
1267
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf-8'));
|
|
1268
|
-
const postinstall = pkg?.scripts?.postinstall;
|
|
1269
|
-
if (typeof postinstall === 'string' && postinstall.trim()) {
|
|
1270
|
-
onProgress?.(`Running ${agentConfig.name} postinstall...`);
|
|
1271
|
-
// The declared postinstall is a shell command string (e.g. `node
|
|
1272
|
-
// install.cjs`), so it must run through a shell on ALL platforms —
|
|
1273
|
-
// shell:true, empty args. cwd is the package root; install.cjs anchors
|
|
1274
|
-
// its paths to __dirname, so that is correct and sufficient.
|
|
1275
|
-
await execFileAsync(postinstall, [], { cwd: pkgRoot, shell: true });
|
|
1257
|
+
onProgress?.(`Installing ${packageSpec}...`);
|
|
1258
|
+
await execFileAsync('npm', ['install', packageSpec, '--ignore-scripts'], { cwd: versionDir, shell: winShell });
|
|
1259
|
+
// The release installed directly into its final labeled dir (`versionDir` is
|
|
1260
|
+
// keyed on `label`) — no post-install rename, no shared `latest/` dir for a
|
|
1261
|
+
// concurrent process to move out from under us. The addressable slot is the
|
|
1262
|
+
// label; `releaseVersion` is what npm actually staged.
|
|
1263
|
+
const installedVersion = label;
|
|
1264
|
+
// Create versioned alias (e.g., claude@2.0.65, or claude@ins_… for connect)
|
|
1265
|
+
createVersionedAlias(agent, installedVersion);
|
|
1266
|
+
// Claude reads its global config from CLAUDE_CONFIG_DIR/.claude.json —
|
|
1267
|
+
// i.e. inside the per-version .claude dir — while the rest of agents-cli
|
|
1268
|
+
// manages the home-level file. Symlink INSIDE to OUTSIDE so Claude and
|
|
1269
|
+
// agents-cli see the same content.
|
|
1270
|
+
if (agent === 'claude') {
|
|
1271
|
+
try {
|
|
1272
|
+
ensureClaudeInsideSymlink(installedVersion);
|
|
1273
|
+
}
|
|
1274
|
+
catch {
|
|
1275
|
+
/* non-fatal; the install itself succeeded */
|
|
1276
1276
|
}
|
|
1277
1277
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1278
|
+
// The `npm install` above ran with `--ignore-scripts` — the right posture for
|
|
1279
|
+
// the dependency TREE (never run arbitrary transitive postinstalls), but it
|
|
1280
|
+
// also skips the agent package's OWN postinstall, which for some agents is a
|
|
1281
|
+
// required install step. @anthropic-ai/claude-code ships a ~500-byte stub at
|
|
1282
|
+
// `bin/claude.exe` plus per-arch native binaries as optional deps; its
|
|
1283
|
+
// `postinstall` (`node install.cjs`) is what copies the correct native binary
|
|
1284
|
+
// over the stub. Skip it and every launch dies with "native binary not
|
|
1285
|
+
// installed". So run the first-party package's declared postinstall here —
|
|
1286
|
+
// scoped to that one package, never `prepare` (claude-code's `prepare` is an
|
|
1287
|
+
// unconditional `exit 1` publish guard). Same precedent as the keychain-helper
|
|
1288
|
+
// postinstall re-run after a `--ignore-scripts` upgrade (see index.ts).
|
|
1289
|
+
// Best-effort: the integrity gate below is the real backstop — a still-broken
|
|
1290
|
+
// binary (postinstall failed, or a platform with no published native dep)
|
|
1291
|
+
// fails there with the correct message rather than throwing here.
|
|
1292
|
+
if (agentConfig.npmPackage) {
|
|
1293
|
+
// The install landed in `versionDir` (the labeled dir) with no rename to
|
|
1294
|
+
// chase, so the package root is exactly there.
|
|
1295
|
+
const pkgRoot = path.join(versionDir, 'node_modules', agentConfig.npmPackage);
|
|
1296
|
+
try {
|
|
1297
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf-8'));
|
|
1298
|
+
const postinstall = pkg?.scripts?.postinstall;
|
|
1299
|
+
if (typeof postinstall === 'string' && postinstall.trim()) {
|
|
1300
|
+
onProgress?.(`Running ${agentConfig.name} postinstall...`);
|
|
1301
|
+
// The declared postinstall is a shell command string (e.g. `node
|
|
1302
|
+
// install.cjs`), so it must run through a shell on ALL platforms —
|
|
1303
|
+
// shell:true, empty args. cwd is the package root; install.cjs anchors
|
|
1304
|
+
// its paths to __dirname, so that is correct and sufficient.
|
|
1305
|
+
await execFileAsync(postinstall, [], { cwd: pkgRoot, shell: true });
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
catch {
|
|
1309
|
+
/* non-fatal; the integrity gate below catches a still-broken binary */
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
// Integrity gate: confirm the install actually launches, not just that the
|
|
1313
|
+
// JS wrapper landed. A gutted install (wrapper present, native platform
|
|
1314
|
+
// binary missing) otherwise gets silently pinned as the default and crashes
|
|
1315
|
+
// with ENOENT on run. Fail loudly here so `agents add` never records a
|
|
1316
|
+
// broken version as healthy — the caller then won't set it as default.
|
|
1317
|
+
const health = await verifyInstalledBinaryLaunches(agent, installedVersion);
|
|
1318
|
+
if (!health.ok) {
|
|
1319
|
+
if (fs.existsSync(versionDir))
|
|
1320
|
+
removeInstallArtifacts(versionDir);
|
|
1321
|
+
const detail = health.detail ? ` (${health.detail})` : '';
|
|
1322
|
+
emit('version.install', { agent, version: installedVersion, error: `binary failed to launch${detail}` });
|
|
1323
|
+
return {
|
|
1324
|
+
success: false,
|
|
1325
|
+
installedVersion,
|
|
1326
|
+
error: `${agentConfig.name}@${installedVersion} installed but its binary failed to launch${detail}. `
|
|
1327
|
+
+ `The install is incomplete — the platform binary is missing. Re-run: agents add ${agent}@${installedVersion}`,
|
|
1328
|
+
};
|
|
1280
1329
|
}
|
|
1330
|
+
// The install is healthy from here. Identity is frozen AFTER the try (see
|
|
1331
|
+
// below) so a bookkeeping write failure cannot fall into the catch and wipe
|
|
1332
|
+
// a working install.
|
|
1333
|
+
healthyVersion = installedVersion;
|
|
1281
1334
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
// broken version as healthy — the caller then won't set it as default.
|
|
1287
|
-
const health = await verifyInstalledBinaryLaunches(agent, installedVersion);
|
|
1288
|
-
if (!health.ok) {
|
|
1289
|
-
if (fs.existsSync(versionDir))
|
|
1335
|
+
catch (err) {
|
|
1336
|
+
// Clean up on failure — preserve `home/` in case a prior install left
|
|
1337
|
+
// conversation history behind that we must not wipe on a failed reinstall.
|
|
1338
|
+
if (fs.existsSync(versionDir)) {
|
|
1290
1339
|
removeInstallArtifacts(versionDir);
|
|
1291
|
-
|
|
1292
|
-
emit('version.install', { agent, version
|
|
1293
|
-
return {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
healthyVersion = installedVersion;
|
|
1304
|
-
}
|
|
1305
|
-
catch (err) {
|
|
1306
|
-
// Clean up on failure — preserve `home/` in case a prior install left
|
|
1307
|
-
// conversation history behind that we must not wipe on a failed reinstall.
|
|
1308
|
-
if (fs.existsSync(versionDir)) {
|
|
1309
|
-
removeInstallArtifacts(versionDir);
|
|
1340
|
+
}
|
|
1341
|
+
emit('version.install', { agent, version, error: err.message });
|
|
1342
|
+
return { success: false, installedVersion: version, error: err.message };
|
|
1343
|
+
}
|
|
1344
|
+
// Freeze this installation's identity (see the installScript branch above).
|
|
1345
|
+
// The label is the frozen identity; the release it carries is recorded
|
|
1346
|
+
// separately so a connect home under an opaque label records the real vendor
|
|
1347
|
+
// release it installed rather than the label string.
|
|
1348
|
+
createInstallation(agent, healthyVersion, releaseVersion, initialUpdatePolicy);
|
|
1349
|
+
const trackerInstall = await installSessionTrackerHook(agent, healthyVersion);
|
|
1350
|
+
if (!trackerInstall.installed && trackerInstall.error) {
|
|
1351
|
+
console.warn(`agents: SessionStart hook not installed for ${agent}@${healthyVersion}: ${trackerInstall.error}`);
|
|
1310
1352
|
}
|
|
1311
|
-
emit('version.install', { agent, version
|
|
1312
|
-
return { success:
|
|
1313
|
-
}
|
|
1314
|
-
// Freeze this installation's identity (see the installScript branch above).
|
|
1315
|
-
createInstallation(agent, healthyVersion, healthyVersion);
|
|
1316
|
-
const trackerInstall = await installSessionTrackerHook(agent, healthyVersion);
|
|
1317
|
-
if (!trackerInstall.installed && trackerInstall.error) {
|
|
1318
|
-
console.warn(`agents: SessionStart hook not installed for ${agent}@${healthyVersion}: ${trackerInstall.error}`);
|
|
1319
|
-
}
|
|
1320
|
-
emit('version.install', { agent, version: healthyVersion });
|
|
1321
|
-
return { success: true, installedVersion: healthyVersion };
|
|
1353
|
+
emit('version.install', { agent, version: healthyVersion });
|
|
1354
|
+
return { success: true, installedVersion: healthyVersion };
|
|
1355
|
+
}, { ...INSTALLATION_LOCK_OPTIONS, realpath: false });
|
|
1322
1356
|
}
|
|
1323
1357
|
// Version-dir entries that are STATE, not install output, and so must survive a
|
|
1324
1358
|
// clean reinstall: `home/`, the `.isolated` marker that is the single source
|
|
@@ -1328,7 +1362,7 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1328
1362
|
// self-heal would hand it a bare `<agent>` shim and a PATH entry. Losing the
|
|
1329
1363
|
// installation record would mint a NEW id for the same install on its first
|
|
1330
1364
|
// repair, discarding its release history.
|
|
1331
|
-
const PRESERVED_ON_CLEAN_REINSTALL = new Set(['home', '.isolated', INSTALLATION_RECORD_FILE]);
|
|
1365
|
+
const PRESERVED_ON_CLEAN_REINSTALL = new Set(['home', '.isolated', '.launch-leases', INSTALLATION_RECORD_FILE, `${INSTALLATION_RECORD_FILE}.lock`]);
|
|
1332
1366
|
/**
|
|
1333
1367
|
* Remove install artifacts from a version directory, preserving `home/` which
|
|
1334
1368
|
* contains the user's conversation history, sessions, history.jsonl, tasks,
|
|
@@ -1339,7 +1373,7 @@ const PRESERVED_ON_CLEAN_REINSTALL = new Set(['home', '.isolated', INSTALLATION_
|
|
|
1339
1373
|
*/
|
|
1340
1374
|
function removeInstallArtifacts(versionDir) {
|
|
1341
1375
|
for (const entry of fs.readdirSync(versionDir)) {
|
|
1342
|
-
if (PRESERVED_ON_CLEAN_REINSTALL.has(entry))
|
|
1376
|
+
if (PRESERVED_ON_CLEAN_REINSTALL.has(entry) || entry.startsWith('.rollback-'))
|
|
1343
1377
|
continue;
|
|
1344
1378
|
fs.rmSync(path.join(versionDir, entry), { recursive: true, force: true });
|
|
1345
1379
|
}
|
|
@@ -1906,7 +1940,8 @@ export async function ensureAgentRunnable(agent, version, log, opts) {
|
|
|
1906
1940
|
// so isolation must be decided from the pre-repair state, not re-read after.
|
|
1907
1941
|
const targetIsolated = isVersionIsolated(agent, version);
|
|
1908
1942
|
log?.(`${cfg.name}@${version} is broken (platform binary missing) — repairing…`);
|
|
1909
|
-
const
|
|
1943
|
+
const record = readInstallation(agent, version);
|
|
1944
|
+
const repair = await installVersion(agent, record?.releaseVersion ?? version, undefined, { clean: true, installationLabel: version });
|
|
1910
1945
|
if (repair.success && (await verifyInstalledBinaryLaunches(agent, version)).ok) {
|
|
1911
1946
|
log?.(`repaired ${cfg.name}@${version}.`);
|
|
1912
1947
|
return version;
|
|
@@ -1914,7 +1949,7 @@ export async function ensureAgentRunnable(agent, version, log, opts) {
|
|
|
1914
1949
|
// An isolated copy is walled off from the rest of the setup: repairing it in
|
|
1915
1950
|
// place is the ONLY thing we may do. No fallback, no install, no default
|
|
1916
1951
|
// switch — surface the failure and let the caller tell the user.
|
|
1917
|
-
if (targetIsolated) {
|
|
1952
|
+
if (targetIsolated || (record && record.label !== record.releaseVersion)) {
|
|
1918
1953
|
log?.(`${cfg.name}@${version} is an isolated install and could not be repaired — leaving your default ${cfg.name} untouched.`);
|
|
1919
1954
|
return null;
|
|
1920
1955
|
}
|
|
@@ -78,4 +78,6 @@ export declare function isAlive(pid: number): boolean;
|
|
|
78
78
|
* caller there silently ran with NO pid-reuse protection — including
|
|
79
79
|
* `agents teams stop`, which is how it could SIGKILL an unrelated process group.
|
|
80
80
|
*/
|
|
81
|
-
export declare function captureProcessStartTime(pid: number
|
|
81
|
+
export declare function captureProcessStartTime(pid: number, opts?: {
|
|
82
|
+
fresh?: boolean;
|
|
83
|
+
}): string | null;
|
|
@@ -158,11 +158,11 @@ const startTimeByPid = new Map();
|
|
|
158
158
|
* caller there silently ran with NO pid-reuse protection — including
|
|
159
159
|
* `agents teams stop`, which is how it could SIGKILL an unrelated process group.
|
|
160
160
|
*/
|
|
161
|
-
export function captureProcessStartTime(pid) {
|
|
161
|
+
export function captureProcessStartTime(pid, opts = {}) {
|
|
162
162
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
163
163
|
return null;
|
|
164
164
|
const cached = startTimeByPid.get(pid);
|
|
165
|
-
if (cached !== undefined)
|
|
165
|
+
if (!opts.fresh && cached !== undefined)
|
|
166
166
|
return cached;
|
|
167
167
|
const value = readProcessStartTime(pid);
|
|
168
168
|
startTimeByPid.set(pid, value);
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceDetector } from './types.js';
|
|
3
|
-
export declare const commandsDetectors: Partial<Record<
|
|
2
|
+
export declare const commandsDetectors: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceDetector>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceDetector } from './types.js';
|
|
3
|
-
export declare const hooksDetectors: Partial<Record<
|
|
2
|
+
export declare const hooksDetectors: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceDetector>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceDetector } from './types.js';
|
|
3
|
-
export declare const mcpDetectors: Partial<Record<
|
|
2
|
+
export declare const mcpDetectors: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceDetector>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceDetector } from './types.js';
|
|
3
|
-
export declare const permissionsDetectors: Partial<Record<
|
|
2
|
+
export declare const permissionsDetectors: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceDetector>>;
|
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugins detector — for each discovered plugin, ask `isPluginSynced` whether
|
|
3
|
-
* its expected artifacts are present in the version home. Mirrors
|
|
4
|
-
* versions.ts:541-549.
|
|
5
|
-
*/
|
|
6
|
-
import type { AgentId } from '../../types.js';
|
|
7
1
|
import type { ResourceDetector } from './types.js';
|
|
8
|
-
export declare const pluginsDetectors: Partial<Record<
|
|
2
|
+
export declare const pluginsDetectors: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceDetector>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceDetector } from './types.js';
|
|
3
|
-
export declare const rulesDetectors: Partial<Record<
|
|
2
|
+
export declare const rulesDetectors: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceDetector>>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceDetector } from './types.js';
|
|
3
2
|
/** Exported for unit tests (RUSH-2320 #2). Production callers: list() below. */
|
|
4
3
|
export declare function skillDirsMatch(src: string, dest: string): boolean;
|
|
5
|
-
export declare const skillsDetectors: Partial<Record<
|
|
4
|
+
export declare const skillsDetectors: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceDetector>>;
|
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Subagents detector. The installed-name enumeration for every agent's on-disk
|
|
3
|
-
* layout is declared once in the subagent registry; this detector is generic
|
|
4
|
-
* and delegates to `listInstalledSubagentNames` instead of a per-agent builder.
|
|
5
|
-
*/
|
|
6
|
-
import type { AgentId } from '../../types.js';
|
|
7
1
|
import type { ResourceDetector } from './types.js';
|
|
8
|
-
export declare const subagentsDetectors: Partial<Record<
|
|
2
|
+
export declare const subagentsDetectors: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceDetector>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceDetector } from './types.js';
|
|
3
|
-
export declare const workflowsDetectors: Partial<Record<
|
|
2
|
+
export declare const workflowsDetectors: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceDetector>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceWriter } from './types.js';
|
|
3
|
-
export declare const commandsWriters: Partial<Record<
|
|
2
|
+
export declare const commandsWriters: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceWriter<string[]>>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceWriter } from './types.js';
|
|
3
|
-
export declare const hooksWriters: Partial<Record<
|
|
2
|
+
export declare const hooksWriters: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceWriter<string[]>>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceWriter } from './types.js';
|
|
3
|
-
export declare const mcpWriters: Partial<Record<
|
|
2
|
+
export declare const mcpWriters: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceWriter<string[]>>>;
|
|
@@ -1,13 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Permissions writer — selection is a list of permission GROUP names. We
|
|
3
|
-
* build the PermissionSet from the discovered groups, then dispatch into the
|
|
4
|
-
* per-agent format writer in `lib/permissions.ts:applyPermissionsToVersion`.
|
|
5
|
-
*
|
|
6
|
-
* The per-agent format work lives in lib/permissions.ts because the format
|
|
7
|
-
* conversions (Claude settings.json vs Codex TOML+rules vs Gemini tools vs
|
|
8
|
-
* Antigravity permissions{} vs Grok [permission].rules) are tightly coupled
|
|
9
|
-
* to the converters defined alongside them.
|
|
10
|
-
*/
|
|
11
|
-
import type { AgentId } from '../../types.js';
|
|
12
1
|
import type { ResourceWriter } from './types.js';
|
|
13
|
-
export declare const permissionsWriters: Partial<Record<
|
|
2
|
+
export declare const permissionsWriters: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceWriter<string[]>>>;
|
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugins writer — thin wrapper around `syncPluginToVersion`. Discovery and
|
|
3
|
-
* per-agent format work lives in lib/plugins.ts.
|
|
4
|
-
*/
|
|
5
|
-
import type { AgentId } from '../../types.js';
|
|
6
1
|
import type { ResourceWriter } from './types.js';
|
|
7
|
-
export declare const pluginsWriters: Partial<Record<
|
|
2
|
+
export declare const pluginsWriters: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceWriter<string[]>>>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceWriter } from './types.js';
|
|
3
2
|
export interface RulesSelection {
|
|
4
3
|
/** Preset name to compose. Empty string falls through to `"default"` in the composer. */
|
|
5
4
|
preset: string;
|
|
6
5
|
}
|
|
7
|
-
export declare const rulesWriters: Partial<Record<
|
|
6
|
+
export declare const rulesWriters: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceWriter<RulesSelection>>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceWriter } from './types.js';
|
|
3
|
-
export declare const skillsWriters: Partial<Record<
|
|
2
|
+
export declare const skillsWriters: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceWriter<string[]>>>;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { AgentId } from '../../types.js';
|
|
2
1
|
import type { ResourceWriter } from './types.js';
|
|
3
|
-
export declare const subagentsWriters: Partial<Record<
|
|
2
|
+
export declare const subagentsWriters: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceWriter<string[]>>>;
|
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workflows writer — copies each selected workflow into the agent-specific
|
|
3
|
-
* on-disk layout via `syncWorkflowToVersion` (Claude: WORKFLOW.md tree under
|
|
4
|
-
* `{versionHome}/workflows/`; Grok: native `.rhai` under
|
|
5
|
-
* `{versionHome}/.grok/workflows/`).
|
|
6
|
-
*/
|
|
7
|
-
import type { AgentId } from '../../types.js';
|
|
8
1
|
import type { ResourceWriter } from './types.js';
|
|
9
|
-
export declare const workflowsWriters: Partial<Record<
|
|
2
|
+
export declare const workflowsWriters: Partial<Record<"claude" | "codex" | "gemini" | "cursor" | "opencode" | "openclaw" | "copilot" | "amp" | "goose" | "antigravity" | "grok" | "kimi" | "droid" | "hermes" | "muse" | "warp", ResourceWriter<string[]>>>;
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -475,7 +475,9 @@ export declare function writeMetaUnlocked(meta: Meta): boolean;
|
|
|
475
475
|
* mtime check below catches it on the next read (assuming the mtime advanced).
|
|
476
476
|
* - The cache stores the merged system+user meta; both files' mtimes contribute.
|
|
477
477
|
*/
|
|
478
|
-
export declare function readMeta(
|
|
478
|
+
export declare function readMeta(options?: {
|
|
479
|
+
migrate?: boolean;
|
|
480
|
+
}): Meta;
|
|
479
481
|
/** Serialize and write agents.yaml to the user repo, invalidating the in-memory cache. */
|
|
480
482
|
export declare function writeMeta(meta: Meta): void;
|
|
481
483
|
/** Update agents.yaml under lock and return the new state. */
|