@phnx-labs/agents-cli 1.20.5 → 1.20.7
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 +13 -0
- package/README.md +1 -1
- package/dist/commands/browser.js +31 -4
- package/dist/commands/computer-actions.d.ts +36 -0
- package/dist/commands/computer-actions.js +328 -0
- package/dist/commands/computer.js +74 -55
- package/dist/commands/defaults.d.ts +7 -0
- package/dist/commands/defaults.js +89 -0
- package/dist/commands/exec.js +24 -6
- package/dist/commands/inspect.d.ts +38 -7
- package/dist/commands/inspect.js +194 -24
- package/dist/commands/rules.js +3 -3
- package/dist/commands/secrets.js +46 -9
- package/dist/commands/sessions.js +9 -12
- package/dist/commands/setup.js +2 -2
- package/dist/commands/teams.js +108 -11
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +121 -38
- package/dist/index.js +61 -22
- package/dist/lib/agents.d.ts +10 -6
- package/dist/lib/agents.js +23 -14
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +84 -3
- package/dist/lib/daemon.js +4 -7
- package/dist/lib/exec.d.ts +9 -0
- package/dist/lib/exec.js +85 -9
- package/dist/lib/migrate.js +6 -4
- package/dist/lib/permissions.d.ts +23 -0
- package/dist/lib/permissions.js +89 -7
- package/dist/lib/platform/exec.d.ts +9 -0
- package/dist/lib/platform/exec.js +24 -0
- package/dist/lib/platform/index.d.ts +20 -0
- package/dist/lib/platform/index.js +20 -0
- package/dist/lib/platform/paths.d.ts +22 -0
- package/dist/lib/platform/paths.js +49 -0
- package/dist/lib/platform/process.d.ts +12 -0
- package/dist/lib/platform/process.js +22 -0
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/project-launch.d.ts +5 -0
- package/dist/lib/project-launch.js +37 -0
- package/dist/lib/pty-client.js +13 -5
- package/dist/lib/pty-server.d.ts +24 -1
- package/dist/lib/pty-server.js +109 -29
- package/dist/lib/resources/rules.js +1 -1
- package/dist/lib/resources/skills.js +1 -1
- package/dist/lib/resources.d.ts +2 -0
- package/dist/lib/resources.js +2 -1
- package/dist/lib/rotate.js +6 -18
- package/dist/lib/run-config.d.ts +9 -0
- package/dist/lib/run-config.js +35 -0
- package/dist/lib/run-defaults.d.ts +42 -0
- package/dist/lib/run-defaults.js +180 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/install-helper.d.ts +11 -3
- package/dist/lib/secrets/install-helper.js +48 -6
- package/dist/lib/secrets/linux.d.ts +12 -0
- package/dist/lib/secrets/linux.js +30 -16
- package/dist/lib/session/artifacts.js +8 -2
- package/dist/lib/shims.d.ts +9 -1
- package/dist/lib/shims.js +80 -3
- package/dist/lib/staleness/detectors/hooks.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/teams/agents.js +5 -7
- package/dist/lib/teams/api.d.ts +67 -0
- package/dist/lib/teams/api.js +78 -0
- package/dist/lib/types.d.ts +15 -6
- package/dist/lib/versions.js +4 -4
- package/package.json +5 -2
- package/scripts/postinstall.js +18 -1
|
@@ -14,14 +14,23 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { fileURLToPath } from 'url';
|
|
16
16
|
import { spawnSync } from 'child_process';
|
|
17
|
+
import { createHash } from 'crypto';
|
|
17
18
|
import * as fs from 'fs';
|
|
18
19
|
import * as os from 'os';
|
|
19
20
|
import * as path from 'path';
|
|
20
21
|
const APP_BUNDLE_NAME = 'Agents CLI.app';
|
|
21
22
|
const INSTALL_DIR_NAME = 'agents-cli';
|
|
23
|
+
let installRootOverride = null;
|
|
24
|
+
/** Redirect the install root (test only). Returns the previous override so callers can restore. */
|
|
25
|
+
export function setInstallRootForTest(dir) {
|
|
26
|
+
const prev = installRootOverride;
|
|
27
|
+
installRootOverride = dir;
|
|
28
|
+
return prev;
|
|
29
|
+
}
|
|
22
30
|
/** Absolute path to the installed `.app` bundle directory (not the executable). */
|
|
23
31
|
function installedAppPath() {
|
|
24
|
-
|
|
32
|
+
const root = installRootOverride ?? os.homedir();
|
|
33
|
+
return path.join(root, 'Library', 'Application Support', INSTALL_DIR_NAME, APP_BUNDLE_NAME);
|
|
25
34
|
}
|
|
26
35
|
/** Absolute path to the executable inside the installed `.app` bundle. */
|
|
27
36
|
function installedExecutablePath() {
|
|
@@ -57,6 +66,33 @@ function assertDarwin() {
|
|
|
57
66
|
throw new Error('Keychain helper is macOS only.');
|
|
58
67
|
}
|
|
59
68
|
}
|
|
69
|
+
function sha256File(filePath) {
|
|
70
|
+
return createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* True when the installed helper executable differs byte-for-byte from the
|
|
74
|
+
* bundled source helper. A stale-but-validly-signed helper is exactly how the
|
|
75
|
+
* broken 1.20.4 build (signed, but missing the keychain-access-groups
|
|
76
|
+
* entitlement) survived upgrades: codesign --verify passes on it, so the
|
|
77
|
+
* "exists and verifies" early-return kept it installed forever. Returns false
|
|
78
|
+
* when there is no bundled source to compare against (dev installs) or no
|
|
79
|
+
* installed copy yet — both cases are decided by the existence checks in the
|
|
80
|
+
* callers, not by staleness.
|
|
81
|
+
*/
|
|
82
|
+
function installedHelperIsStale() {
|
|
83
|
+
let srcApp;
|
|
84
|
+
try {
|
|
85
|
+
srcApp = sourceAppPath();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const srcExec = path.join(srcApp, 'Contents', 'MacOS', 'Agents CLI');
|
|
91
|
+
const destExec = installedExecutablePath();
|
|
92
|
+
if (!fs.existsSync(srcExec) || !fs.existsSync(destExec))
|
|
93
|
+
return false;
|
|
94
|
+
return sha256File(srcExec) !== sha256File(destExec);
|
|
95
|
+
}
|
|
60
96
|
function codesignVerify(appPath) {
|
|
61
97
|
const r = spawnSync('codesign', ['--verify', '--deep', '--strict', appPath], {
|
|
62
98
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -86,8 +122,10 @@ function copyAppBundle(src, dest) {
|
|
|
86
122
|
}
|
|
87
123
|
/**
|
|
88
124
|
* Idempotent install. Copies the bundled `.app` to the stable user path. Skips
|
|
89
|
-
* if the destination already exists
|
|
90
|
-
*
|
|
125
|
+
* if the destination already exists, `codesign --verify` passes, AND the
|
|
126
|
+
* installed executable matches the bundled one byte-for-byte — a valid
|
|
127
|
+
* signature alone is not enough, because an outdated helper signs clean too.
|
|
128
|
+
* `forceReinstall=true` skips all checks and always copies.
|
|
91
129
|
*
|
|
92
130
|
* Notarization is checked via `spctl --assess` after install — a failure is
|
|
93
131
|
* logged as a warning but does NOT throw. Notarization checks require network
|
|
@@ -99,7 +137,7 @@ export function ensureKeychainHelperInstalled(opts = {}) {
|
|
|
99
137
|
const dest = installedAppPath();
|
|
100
138
|
if (!opts.forceReinstall && fs.existsSync(dest)) {
|
|
101
139
|
const { ok } = codesignVerify(dest);
|
|
102
|
-
if (ok)
|
|
140
|
+
if (ok && !installedHelperIsStale())
|
|
103
141
|
return;
|
|
104
142
|
}
|
|
105
143
|
const src = sourceAppPath();
|
|
@@ -119,14 +157,18 @@ export function ensureKeychainHelperInstalled(opts = {}) {
|
|
|
119
157
|
}
|
|
120
158
|
/**
|
|
121
159
|
* Return the absolute path to the helper executable. If the installed bundle
|
|
122
|
-
* is missing,
|
|
160
|
+
* is missing, or is stale relative to the bundled source helper, performs a
|
|
161
|
+
* lazy (re)install first. The staleness check is what lets an upgraded CLI
|
|
162
|
+
* replace a helper a previous version installed — `agents helper install`
|
|
163
|
+
* never runs on `npm i -g`, so this call site is the only one every machine
|
|
164
|
+
* is guaranteed to pass through.
|
|
123
165
|
*
|
|
124
166
|
* Throws on non-darwin.
|
|
125
167
|
*/
|
|
126
168
|
export function getKeychainHelperPath() {
|
|
127
169
|
assertDarwin();
|
|
128
170
|
const exec = installedExecutablePath();
|
|
129
|
-
if (!fs.existsSync(exec)) {
|
|
171
|
+
if (!fs.existsSync(exec) || installedHelperIsStale()) {
|
|
130
172
|
ensureKeychainHelperInstalled();
|
|
131
173
|
}
|
|
132
174
|
return exec;
|
|
@@ -43,6 +43,18 @@ export declare function hasSecretToolToken(item: string): boolean;
|
|
|
43
43
|
export declare function getSecretToolToken(item: string): string;
|
|
44
44
|
export declare function setSecretToolToken(item: string, value: string): void;
|
|
45
45
|
export declare function deleteSecretToolToken(item: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Parse the item names out of `secret-tool search --all` output, keeping only
|
|
48
|
+
* those starting with `prefix`. Exported for tests.
|
|
49
|
+
*
|
|
50
|
+
* `output` must be the combined stdout+stderr of the search: libsecret splits
|
|
51
|
+
* the dump across both streams — the value/label/schema lines go to stdout
|
|
52
|
+
* while the `attribute.*` lines (which carry `attribute.item`, the only place
|
|
53
|
+
* the item name is reliably machine-readable) go to stderr (observed on
|
|
54
|
+
* libsecret 0.21.4). Which stream each line lands on has varied across
|
|
55
|
+
* libsecret versions, so callers concatenate both rather than bet on one.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseSecretToolItems(output: string, prefix: string): string[];
|
|
46
58
|
/**
|
|
47
59
|
* List secrets by prefix. secret-tool doesn't have a list command,
|
|
48
60
|
* so we use secret-tool search which outputs in a specific format.
|
|
@@ -345,6 +345,34 @@ export function deleteSecretToolToken(item) {
|
|
|
345
345
|
// surface that rather than silently swallowing.
|
|
346
346
|
return false;
|
|
347
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* Parse the item names out of `secret-tool search --all` output, keeping only
|
|
350
|
+
* those starting with `prefix`. Exported for tests.
|
|
351
|
+
*
|
|
352
|
+
* `output` must be the combined stdout+stderr of the search: libsecret splits
|
|
353
|
+
* the dump across both streams — the value/label/schema lines go to stdout
|
|
354
|
+
* while the `attribute.*` lines (which carry `attribute.item`, the only place
|
|
355
|
+
* the item name is reliably machine-readable) go to stderr (observed on
|
|
356
|
+
* libsecret 0.21.4). Which stream each line lands on has varied across
|
|
357
|
+
* libsecret versions, so callers concatenate both rather than bet on one.
|
|
358
|
+
*/
|
|
359
|
+
export function parseSecretToolItems(output, prefix) {
|
|
360
|
+
const items = [];
|
|
361
|
+
// Parse output format:
|
|
362
|
+
// [/org/freedesktop/secrets/collection/login/1]
|
|
363
|
+
// label = agents-cli: myitem
|
|
364
|
+
// ...
|
|
365
|
+
// attribute.item = myitem
|
|
366
|
+
const itemRegex = /attribute\.item\s*=\s*(.+)/g;
|
|
367
|
+
let match;
|
|
368
|
+
while ((match = itemRegex.exec(output)) !== null) {
|
|
369
|
+
const itemName = match[1].trim();
|
|
370
|
+
if (itemName.startsWith(prefix)) {
|
|
371
|
+
items.push(itemName);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return [...new Set(items)]; // dedupe
|
|
375
|
+
}
|
|
348
376
|
/**
|
|
349
377
|
* List secrets by prefix. secret-tool doesn't have a list command,
|
|
350
378
|
* so we use secret-tool search which outputs in a specific format.
|
|
@@ -365,22 +393,8 @@ export function listSecretToolItems(prefix) {
|
|
|
365
393
|
}
|
|
366
394
|
return [];
|
|
367
395
|
}
|
|
368
|
-
const output = result.stdout?.toString() || ''
|
|
369
|
-
|
|
370
|
-
// Parse output format:
|
|
371
|
-
// [/org/freedesktop/secrets/collection/login/1]
|
|
372
|
-
// label = agents-cli: myitem
|
|
373
|
-
// ...
|
|
374
|
-
// attribute.item = myitem
|
|
375
|
-
const itemRegex = /attribute\.item\s*=\s*(.+)/g;
|
|
376
|
-
let match;
|
|
377
|
-
while ((match = itemRegex.exec(output)) !== null) {
|
|
378
|
-
const itemName = match[1].trim();
|
|
379
|
-
if (itemName.startsWith(prefix)) {
|
|
380
|
-
items.push(itemName);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
return [...new Set(items)]; // dedupe
|
|
396
|
+
const output = `${result.stdout?.toString() || ''}\n${result.stderr?.toString() || ''}`;
|
|
397
|
+
return parseSecretToolItems(output, prefix);
|
|
384
398
|
}
|
|
385
399
|
/** KeychainBackend implementation for Linux. Routes through secret-tool
|
|
386
400
|
* with a transparent encrypted-file fallback when the default Secret
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { parseSession } from './parse.js';
|
|
11
|
+
import { toComparablePath } from '../platform/index.js';
|
|
11
12
|
/** Tool names that produce file artifacts (writes, edits, patches). */
|
|
12
13
|
const WRITE_TOOLS = new Set([
|
|
13
14
|
'Write', 'Edit', 'write_file', 'edit_file', 'create_file', 'replace', 'patch',
|
|
@@ -87,8 +88,13 @@ export function resolveArtifact(artifacts, name) {
|
|
|
87
88
|
return byBase[0];
|
|
88
89
|
if (byBase.length > 1)
|
|
89
90
|
return byBase[0];
|
|
90
|
-
// Path suffix match (e.g. "src/foo.ts")
|
|
91
|
-
|
|
91
|
+
// Path suffix match (e.g. "src/foo.ts"). Normalize so Windows `\` paths match
|
|
92
|
+
// a forward-slash query too; on POSIX this is identical to the old comparison.
|
|
93
|
+
const target = toComparablePath(name);
|
|
94
|
+
const bySuffix = artifacts.filter(a => {
|
|
95
|
+
const ap = toComparablePath(a.path);
|
|
96
|
+
return ap.endsWith('/' + target) || ap === target;
|
|
97
|
+
});
|
|
92
98
|
if (bySuffix.length >= 1)
|
|
93
99
|
return bySuffix[0];
|
|
94
100
|
return null;
|
package/dist/lib/shims.d.ts
CHANGED
|
@@ -68,8 +68,16 @@ export interface ConflictInfo {
|
|
|
68
68
|
* scoped plugin marketplaces (agents-cli/agents-system/extras-<alias>/
|
|
69
69
|
* agents-project). Version-home reconciliation stays out of the hot
|
|
70
70
|
* path — management commands still own that.
|
|
71
|
+
* v17 — bash-side skip-fast sentinel under ~/.agents/.cache/launch-sync/.
|
|
72
|
+
* When the sentinel mtime is newer than every source dir, exec the
|
|
73
|
+
* agent binary directly without spawning node. Cuts steady-state
|
|
74
|
+
* hot-path latency from ~680ms (node startup + module init) to ~11ms
|
|
75
|
+
* (a few stat calls). Node writes the sentinel after each successful
|
|
76
|
+
* sync. Documented limitation: POSIX dir mtime only updates on
|
|
77
|
+
* top-level entry add/remove — deep edits to plugin contents won't
|
|
78
|
+
* trigger auto-resync, run `agents sync` for that.
|
|
71
79
|
*/
|
|
72
|
-
export declare const SHIM_SCHEMA_VERSION =
|
|
80
|
+
export declare const SHIM_SCHEMA_VERSION = 17;
|
|
73
81
|
/**
|
|
74
82
|
* Generate the full bash shim script for the given agent. The returned string
|
|
75
83
|
* is written to ~/.agents/shims/{cliCommand} and made executable.
|
package/dist/lib/shims.js
CHANGED
|
@@ -13,6 +13,7 @@ import * as path from 'path';
|
|
|
13
13
|
import * as os from 'os';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
15
|
import { confirm, select } from '@inquirer/prompts';
|
|
16
|
+
import { IS_WINDOWS } from './platform/index.js';
|
|
16
17
|
import { getShimsDir, getVersionsDir, getBackupsDir, ensureAgentsDir } from './state.js';
|
|
17
18
|
export { getShimsDir };
|
|
18
19
|
import { AGENTS } from './agents.js';
|
|
@@ -192,8 +193,16 @@ async function promptConflictStrategy(conflictInfos) {
|
|
|
192
193
|
* scoped plugin marketplaces (agents-cli/agents-system/extras-<alias>/
|
|
193
194
|
* agents-project). Version-home reconciliation stays out of the hot
|
|
194
195
|
* path — management commands still own that.
|
|
196
|
+
* v17 — bash-side skip-fast sentinel under ~/.agents/.cache/launch-sync/.
|
|
197
|
+
* When the sentinel mtime is newer than every source dir, exec the
|
|
198
|
+
* agent binary directly without spawning node. Cuts steady-state
|
|
199
|
+
* hot-path latency from ~680ms (node startup + module init) to ~11ms
|
|
200
|
+
* (a few stat calls). Node writes the sentinel after each successful
|
|
201
|
+
* sync. Documented limitation: POSIX dir mtime only updates on
|
|
202
|
+
* top-level entry add/remove — deep edits to plugin contents won't
|
|
203
|
+
* trigger auto-resync, run `agents sync` for that.
|
|
195
204
|
*/
|
|
196
|
-
export const SHIM_SCHEMA_VERSION =
|
|
205
|
+
export const SHIM_SCHEMA_VERSION = 17;
|
|
197
206
|
/** Internal marker string used to embed the schema version in shim scripts. */
|
|
198
207
|
const SHIM_VERSION_MARKER = 'agents-shim-version:';
|
|
199
208
|
function shellQuote(value) {
|
|
@@ -473,8 +482,32 @@ fi
|
|
|
473
482
|
${managedEnv}
|
|
474
483
|
|
|
475
484
|
# Project-scoped compile (rules, workspace resources, scoped plugin marketplaces).
|
|
476
|
-
#
|
|
477
|
-
|
|
485
|
+
# Skip-fast: if a sentinel from the last sync exists and is newer than all
|
|
486
|
+
# source dirs (project .agents/, user plugins, system plugins), exec the
|
|
487
|
+
# agent binary directly without spawning node. Cuts steady-state hot-path
|
|
488
|
+
# latency from ~680ms (node startup + agents-cli module init) to ~11ms (a
|
|
489
|
+
# handful of stat calls). Never blocks launch on failure of the sync itself.
|
|
490
|
+
#
|
|
491
|
+
# Known limitation: POSIX dir mtime updates only on entry add/remove at that
|
|
492
|
+
# level. Deep edits to existing plugin contents (e.g. editing a SKILL.md
|
|
493
|
+
# inside a plugin) won't bump the parent dir's mtime — the marketplace copy
|
|
494
|
+
# stays stale until \`agents sync\` runs explicitly or a top-level entry
|
|
495
|
+
# changes. Advanced users hot-iterating on plugins know to run sync.
|
|
496
|
+
PROJECT_SLUG=\$(printf '%s' "\$PWD" | tr / _ | tr ' ' _)
|
|
497
|
+
LAUNCH_SENTINEL="\$AGENTS_USER_DIR/.cache/launch-sync/\${AGENT}@\${VERSION}@\${PROJECT_SLUG}"
|
|
498
|
+
LAUNCH_SKIP=0
|
|
499
|
+
if [ -f "\$LAUNCH_SENTINEL" ]; then
|
|
500
|
+
LAUNCH_SKIP=1
|
|
501
|
+
for LAUNCH_SRC in "\$PWD/.agents" "\$AGENTS_USER_DIR/plugins" "\$AGENTS_USER_DIR/.system/plugins"; do
|
|
502
|
+
if [ -e "\$LAUNCH_SRC" ] && [ "\$LAUNCH_SRC" -nt "\$LAUNCH_SENTINEL" ]; then
|
|
503
|
+
LAUNCH_SKIP=0
|
|
504
|
+
break
|
|
505
|
+
fi
|
|
506
|
+
done
|
|
507
|
+
fi
|
|
508
|
+
if [ "\$LAUNCH_SKIP" = "0" ]; then
|
|
509
|
+
"\$AGENTS_BIN" sync --agent "\$AGENT" --agent-version "\$VERSION" --launch --cwd "\$PWD" --quiet 2>/dev/null || true
|
|
510
|
+
fi
|
|
478
511
|
|
|
479
512
|
exec "$BINARY"${launchArgs} "$@"
|
|
480
513
|
`;
|
|
@@ -489,8 +522,28 @@ export function createShim(agent) {
|
|
|
489
522
|
const shimPath = path.join(shimsDir, agentConfig.cliCommand);
|
|
490
523
|
const script = generateShimScript(agent);
|
|
491
524
|
fs.writeFileSync(shimPath, script, { mode: 0o755 });
|
|
525
|
+
// Windows can't execute the bash shim directly. Drop a `.cmd` companion next
|
|
526
|
+
// to it that delegates to the node-side transparent resolver (`agents __shim`),
|
|
527
|
+
// so the version resolution stays single-sourced instead of reimplemented in batch.
|
|
528
|
+
if (IS_WINDOWS) {
|
|
529
|
+
writeWindowsCmdShim(shimPath + '.cmd', agentConfig.cliCommand);
|
|
530
|
+
}
|
|
492
531
|
return shimPath;
|
|
493
532
|
}
|
|
533
|
+
/**
|
|
534
|
+
* Generate a Windows `.cmd` launcher that delegates to `agents __shim <spec>`.
|
|
535
|
+
* `spec` is the agent's cliCommand for the default-version shim, or
|
|
536
|
+
* `cliCommand@version` for a versioned alias. node + the dist entrypoint are
|
|
537
|
+
* resolved at generation time so the launcher does not depend on `agents`
|
|
538
|
+
* already being on PATH.
|
|
539
|
+
*/
|
|
540
|
+
function writeWindowsCmdShim(cmdPath, spec) {
|
|
541
|
+
const indexJs = getAgentsBinForGeneratedShim();
|
|
542
|
+
const content = `@echo off\r\n` +
|
|
543
|
+
`rem Auto-generated by agents-cli - do not edit\r\n` +
|
|
544
|
+
`node "${indexJs}" __shim ${spec} %*\r\n`;
|
|
545
|
+
fs.writeFileSync(cmdPath, content);
|
|
546
|
+
}
|
|
494
547
|
/**
|
|
495
548
|
* Remove the shim for an agent.
|
|
496
549
|
*/
|
|
@@ -500,6 +553,12 @@ export function removeShim(agent) {
|
|
|
500
553
|
const shimPath = path.join(shimsDir, agentConfig.cliCommand);
|
|
501
554
|
if (fs.existsSync(shimPath)) {
|
|
502
555
|
fs.unlinkSync(shimPath);
|
|
556
|
+
if (IS_WINDOWS) {
|
|
557
|
+
try {
|
|
558
|
+
fs.unlinkSync(shimPath + '.cmd');
|
|
559
|
+
}
|
|
560
|
+
catch { }
|
|
561
|
+
}
|
|
503
562
|
return true;
|
|
504
563
|
}
|
|
505
564
|
return false;
|
|
@@ -650,6 +709,9 @@ export function createVersionedAlias(agent, version) {
|
|
|
650
709
|
const aliasPath = path.join(shimsDir, `${agentConfig.cliCommand}@${version}`);
|
|
651
710
|
const script = generateVersionedAliasScript(agent, version);
|
|
652
711
|
fs.writeFileSync(aliasPath, script, { mode: 0o755 });
|
|
712
|
+
if (IS_WINDOWS) {
|
|
713
|
+
writeWindowsCmdShim(aliasPath + '.cmd', `${agentConfig.cliCommand}@${version}`);
|
|
714
|
+
}
|
|
653
715
|
return aliasPath;
|
|
654
716
|
}
|
|
655
717
|
/**
|
|
@@ -661,6 +723,12 @@ export function removeVersionedAlias(agent, version) {
|
|
|
661
723
|
const aliasPath = path.join(shimsDir, `${agentConfig.cliCommand}@${version}`);
|
|
662
724
|
if (fs.existsSync(aliasPath)) {
|
|
663
725
|
fs.unlinkSync(aliasPath);
|
|
726
|
+
if (IS_WINDOWS) {
|
|
727
|
+
try {
|
|
728
|
+
fs.unlinkSync(aliasPath + '.cmd');
|
|
729
|
+
}
|
|
730
|
+
catch { }
|
|
731
|
+
}
|
|
664
732
|
return true;
|
|
665
733
|
}
|
|
666
734
|
return false;
|
|
@@ -1456,6 +1524,15 @@ Then restart your shell or run:
|
|
|
1456
1524
|
* Returns true if added, false if already present or failed.
|
|
1457
1525
|
*/
|
|
1458
1526
|
export function addShimsToPath(overrides) {
|
|
1527
|
+
// Windows has no shell rc file to edit: the primary `agents` command is already
|
|
1528
|
+
// on PATH via npm's global bin, and bare shorthands / versioned aliases are
|
|
1529
|
+
// handled by the `.cmd` shims plus the PATH guidance printed at install time.
|
|
1530
|
+
// Report "already present" so callers don't emit a misleading "added to
|
|
1531
|
+
// ~/.bashrc / source ~/.bashrc" message. (The `shell` override is the test hook
|
|
1532
|
+
// for exercising the POSIX path, so it bypasses this short-circuit.)
|
|
1533
|
+
if (IS_WINDOWS && !overrides?.shell) {
|
|
1534
|
+
return { success: true, alreadyPresent: true };
|
|
1535
|
+
}
|
|
1459
1536
|
const shimsDir = overrides?.shimsDir || getShimsDir();
|
|
1460
1537
|
const { rcFile, rcPath, shell } = getShellRcFile(overrides);
|
|
1461
1538
|
// Read current rc file content
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* whose contents match the central source. Mirrors versions.ts:391-421.
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
6
|
import { agentConfigDirName } from '../../agents.js';
|
|
7
|
+
import * as path from 'path';
|
|
8
8
|
import { capableAgents } from '../../capabilities.js';
|
|
9
9
|
import { resolveHookSource } from '../writers/sources.js';
|
|
10
10
|
import { lazyAgentMap } from '../writers/lazy-map.js';
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* stays in the orchestrator since it depends on the broader available set.
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from 'fs';
|
|
8
|
-
import * as path from 'path';
|
|
9
8
|
import { agentConfigDirName } from '../../agents.js';
|
|
9
|
+
import * as path from 'path';
|
|
10
10
|
import { capableAgents } from '../../capabilities.js';
|
|
11
11
|
import { safeJoin } from '../../paths.js';
|
|
12
12
|
import { registerHooksToSettings } from '../../hooks.js';
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -14,6 +14,7 @@ import * as path from 'path';
|
|
|
14
14
|
import * as os from 'os';
|
|
15
15
|
import { randomUUID } from 'crypto';
|
|
16
16
|
import { resolveAgentsDir } from './persistence.js';
|
|
17
|
+
import { findExecutable } from '../platform/index.js';
|
|
17
18
|
import { normalizeEvents } from './parsers.js';
|
|
18
19
|
import { debug } from './debug.js';
|
|
19
20
|
import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from '../gemini-settings.js';
|
|
@@ -300,13 +301,10 @@ export function checkCliAvailable(agentType) {
|
|
|
300
301
|
if (!executable) {
|
|
301
302
|
return [false, `Unknown agent type: ${agentType}`];
|
|
302
303
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
catch {
|
|
308
|
-
return [false, `CLI tool '${executable}' not found in PATH. Install it first.`];
|
|
309
|
-
}
|
|
304
|
+
const resolved = findExecutable(executable);
|
|
305
|
+
return resolved
|
|
306
|
+
? [true, resolved]
|
|
307
|
+
: [false, `CLI tool '${executable}' not found in PATH. Install it first.`];
|
|
310
308
|
}
|
|
311
309
|
/** Check availability of all known agent CLIs. Returns a map of agent type to install status. */
|
|
312
310
|
export function checkAllClis() {
|
package/dist/lib/teams/api.d.ts
CHANGED
|
@@ -64,6 +64,73 @@ export interface TaskStatusResult {
|
|
|
64
64
|
};
|
|
65
65
|
cursor: string;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Compact per-teammate snapshot for the default `teams status` view.
|
|
69
|
+
*
|
|
70
|
+
* The detail shape (`AgentStatusDetail`) carries the full prompt (often many
|
|
71
|
+
* KB), every absolute path the agent has touched, and uncapped message
|
|
72
|
+
* bodies. That's the right shape for programmatic consumers, but it makes
|
|
73
|
+
* `teams status` unreadable for orchestrators who only need: what state are
|
|
74
|
+
* you in, what did you just do, what files have you touched.
|
|
75
|
+
*
|
|
76
|
+
* Use {@link toAgentStatusSummary} to derive this from a detail record.
|
|
77
|
+
*/
|
|
78
|
+
export interface AgentStatusSummary {
|
|
79
|
+
agent_id: string;
|
|
80
|
+
name: string | null;
|
|
81
|
+
agent_type: string;
|
|
82
|
+
status: string;
|
|
83
|
+
duration: string | null;
|
|
84
|
+
tool_count: number;
|
|
85
|
+
has_errors: boolean;
|
|
86
|
+
pr_url: string | null;
|
|
87
|
+
files: {
|
|
88
|
+
/** Count of files modified since the cursor (delta), plus basenames. */
|
|
89
|
+
modified: {
|
|
90
|
+
count: number;
|
|
91
|
+
names: string[];
|
|
92
|
+
};
|
|
93
|
+
created: {
|
|
94
|
+
count: number;
|
|
95
|
+
names: string[];
|
|
96
|
+
};
|
|
97
|
+
deleted: {
|
|
98
|
+
count: number;
|
|
99
|
+
names: string[];
|
|
100
|
+
};
|
|
101
|
+
/** Read is noisy (per-Read events fire constantly); only emit a count. */
|
|
102
|
+
read: {
|
|
103
|
+
count: number;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
/** Already capped at 15 × 120 chars by the detail builder. */
|
|
107
|
+
bash_commands: string[];
|
|
108
|
+
/** Last 3 messages, each body trimmed to ~400 chars. */
|
|
109
|
+
last_messages: string[];
|
|
110
|
+
/** ISO timestamp — feed back via --since for delta polling. */
|
|
111
|
+
cursor: string;
|
|
112
|
+
}
|
|
113
|
+
/** Compact aggregated result; mirrors {@link TaskStatusResult} but agents[] is the summary shape. */
|
|
114
|
+
export interface TaskStatusSummaryResult {
|
|
115
|
+
task_name: string;
|
|
116
|
+
agents: AgentStatusSummary[];
|
|
117
|
+
summary: {
|
|
118
|
+
pending: number;
|
|
119
|
+
running: number;
|
|
120
|
+
completed: number;
|
|
121
|
+
failed: number;
|
|
122
|
+
stopped: number;
|
|
123
|
+
};
|
|
124
|
+
cursor: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Project a full AgentStatusDetail down to a compact AgentStatusSummary.
|
|
128
|
+
* Drops `prompt` entirely (caller knows what they queued), folds file lists
|
|
129
|
+
* to basenames + counts, caps `last_messages` to 3 × {@link SUMMARY_MESSAGE_MAX_CHARS}.
|
|
130
|
+
*/
|
|
131
|
+
export declare function toAgentStatusSummary(detail: AgentStatusDetail): AgentStatusSummary;
|
|
132
|
+
/** Project a full TaskStatusResult down to the compact summary shape. */
|
|
133
|
+
export declare function toTaskStatusSummary(result: TaskStatusResult): TaskStatusSummaryResult;
|
|
67
134
|
/** Result of stopping one or more teammates. */
|
|
68
135
|
export interface StopResult {
|
|
69
136
|
task_name: string;
|
package/dist/lib/teams/api.js
CHANGED
|
@@ -55,6 +55,84 @@ function recentToolCalls(events, max = 10) {
|
|
|
55
55
|
timestamp: typeof event.timestamp === 'string' ? event.timestamp : null,
|
|
56
56
|
}));
|
|
57
57
|
}
|
|
58
|
+
/** Max files to name per category in the summary. Counts are always exact. */
|
|
59
|
+
const SUMMARY_MAX_FILE_NAMES = 6;
|
|
60
|
+
/** Max messages in the summary. */
|
|
61
|
+
const SUMMARY_MAX_MESSAGES = 3;
|
|
62
|
+
/** Max chars per message body in the summary. */
|
|
63
|
+
const SUMMARY_MESSAGE_MAX_CHARS = 400;
|
|
64
|
+
/**
|
|
65
|
+
* Reduce a file path to just its basename for compact rendering. Keeps the
|
|
66
|
+
* orchestrator oriented ("you touched types.ts") without dumping the full
|
|
67
|
+
* absolute path every time. The full paths are still in `AgentStatusDetail`
|
|
68
|
+
* for the verbose path.
|
|
69
|
+
*/
|
|
70
|
+
function basenameOf(p) {
|
|
71
|
+
const ix = p.lastIndexOf('/');
|
|
72
|
+
return ix < 0 ? p : p.slice(ix + 1);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Trim long assistant messages to a fixed budget. We collapse leading
|
|
76
|
+
* whitespace so the budget covers actual content, not indent.
|
|
77
|
+
*/
|
|
78
|
+
function trimMessage(msg, max = SUMMARY_MESSAGE_MAX_CHARS) {
|
|
79
|
+
const s = msg.replace(/^\s+/, '');
|
|
80
|
+
if (s.length <= max)
|
|
81
|
+
return s;
|
|
82
|
+
return s.slice(0, max - 1) + '…';
|
|
83
|
+
}
|
|
84
|
+
/** Pull at most `max` basenames out of a path list, preserving order. */
|
|
85
|
+
function compactFileList(paths, max = SUMMARY_MAX_FILE_NAMES) {
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const names = [];
|
|
88
|
+
for (const p of paths) {
|
|
89
|
+
const base = basenameOf(p);
|
|
90
|
+
if (seen.has(base))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(base);
|
|
93
|
+
names.push(base);
|
|
94
|
+
if (names.length >= max)
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
return { count: paths.length, names };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Project a full AgentStatusDetail down to a compact AgentStatusSummary.
|
|
101
|
+
* Drops `prompt` entirely (caller knows what they queued), folds file lists
|
|
102
|
+
* to basenames + counts, caps `last_messages` to 3 × {@link SUMMARY_MESSAGE_MAX_CHARS}.
|
|
103
|
+
*/
|
|
104
|
+
export function toAgentStatusSummary(detail) {
|
|
105
|
+
return {
|
|
106
|
+
agent_id: detail.agent_id,
|
|
107
|
+
name: detail.name ?? null,
|
|
108
|
+
agent_type: detail.agent_type,
|
|
109
|
+
status: detail.status,
|
|
110
|
+
duration: detail.duration,
|
|
111
|
+
tool_count: detail.tool_count,
|
|
112
|
+
has_errors: detail.has_errors,
|
|
113
|
+
pr_url: detail.pr_url ?? null,
|
|
114
|
+
files: {
|
|
115
|
+
modified: compactFileList(detail.files_modified),
|
|
116
|
+
created: compactFileList(detail.files_created),
|
|
117
|
+
deleted: compactFileList(detail.files_deleted),
|
|
118
|
+
read: { count: detail.files_read.length },
|
|
119
|
+
},
|
|
120
|
+
bash_commands: detail.bash_commands,
|
|
121
|
+
last_messages: detail.last_messages
|
|
122
|
+
.slice(-SUMMARY_MAX_MESSAGES)
|
|
123
|
+
.map((m) => trimMessage(m)),
|
|
124
|
+
cursor: detail.cursor,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** Project a full TaskStatusResult down to the compact summary shape. */
|
|
128
|
+
export function toTaskStatusSummary(result) {
|
|
129
|
+
return {
|
|
130
|
+
task_name: result.task_name,
|
|
131
|
+
agents: result.agents.map(toAgentStatusSummary),
|
|
132
|
+
summary: result.summary,
|
|
133
|
+
cursor: result.cursor,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
58
136
|
/** Spawn a new teammate in a task and return its initial metadata. */
|
|
59
137
|
export async function handleSpawn(manager, taskName, agentType, prompt, cwd, mode, effort = 'medium', parentSessionId = null, workspaceDir = null, version = null, name = null, after = [], model = null, envOverrides = null, taskType = null, cloudProvider = null, cloudSessionId = null, cloudRepo = null, cloudBranch = null, worktreeName = null, worktreePath = null, profileName = null) {
|
|
60
138
|
const defaultMode = manager.getDefaultMode();
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -9,6 +9,19 @@
|
|
|
9
9
|
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'roo' | 'antigravity' | 'grok' | 'kimi';
|
|
10
10
|
/** How `agents run <agent>` chooses an installed version when none is pinned. */
|
|
11
11
|
export type RunStrategy = 'pinned' | 'available' | 'balanced';
|
|
12
|
+
/** Per-agent run strategy config. */
|
|
13
|
+
export interface AgentRunConfig {
|
|
14
|
+
strategy?: RunStrategy;
|
|
15
|
+
}
|
|
16
|
+
/** Default launch options applied by `agents run` when flags are omitted. */
|
|
17
|
+
export interface RunDefaults {
|
|
18
|
+
mode?: Mode;
|
|
19
|
+
model?: string;
|
|
20
|
+
}
|
|
21
|
+
/** `run:` section in agents.yaml. Agent keys keep strategy; `defaults` stores selector rules. */
|
|
22
|
+
export type RunConfig = Partial<Record<AgentId, AgentRunConfig>> & {
|
|
23
|
+
defaults?: Record<string, RunDefaults>;
|
|
24
|
+
};
|
|
12
25
|
/** Preview features that users can opt into via `agents beta`. */
|
|
13
26
|
export type BetaFeatureName = 'drive' | 'factory';
|
|
14
27
|
/** Subset of chalk color names used for agent-specific terminal output. */
|
|
@@ -196,9 +209,7 @@ export interface InstalledHook {
|
|
|
196
209
|
/** Package manifest (agents.yaml) found inside a cloned config repo or package. */
|
|
197
210
|
export interface Manifest {
|
|
198
211
|
agents?: Partial<Record<AgentId, string>>;
|
|
199
|
-
run?:
|
|
200
|
-
strategy?: RunStrategy;
|
|
201
|
-
}>>;
|
|
212
|
+
run?: RunConfig;
|
|
202
213
|
beta?: {
|
|
203
214
|
enabled?: BetaFeatureName[];
|
|
204
215
|
};
|
|
@@ -504,9 +515,7 @@ export interface ExtraRepoConfig {
|
|
|
504
515
|
/** Top-level structure of ~/.agents/.system/agents.yaml -- the CLI's persistent state. */
|
|
505
516
|
export interface Meta {
|
|
506
517
|
agents?: Partial<Record<AgentId, string>>;
|
|
507
|
-
run?:
|
|
508
|
-
strategy?: RunStrategy;
|
|
509
|
-
}>>;
|
|
518
|
+
run?: RunConfig;
|
|
510
519
|
beta?: {
|
|
511
520
|
enabled?: BetaFeatureName[];
|
|
512
521
|
};
|
package/dist/lib/versions.js
CHANGED
|
@@ -25,7 +25,7 @@ import { checkbox, select } from '@inquirer/prompts';
|
|
|
25
25
|
import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getResolvedRulesDir, getUserRulesDir, getVersionResources, ensureVersionResourcePatterns, getProjectAgentsDir, getPromptcutsPath, getUserPromptcutsPath, getEnabledExtraRepos, getAgentsDir, getUserAgentsDir, getTrashVersionsDir, getActiveRulesPreset } from './state.js';
|
|
26
26
|
import { defaultPatterns, expandPatterns } from './resource-patterns.js';
|
|
27
27
|
import { listResources } from './resources.js';
|
|
28
|
-
import { AGENTS, getAccountEmail, resolveAgentName, formatAgentError } from './agents.js';
|
|
28
|
+
import { AGENTS, agentConfigDirName, getAccountEmail, resolveAgentName, formatAgentError } from './agents.js';
|
|
29
29
|
import { discoverPermissionGroups, getActivePermissionPresetName, readPermissionPresetRecipe, PERMISSION_PRESET_ENV_VAR } from './permissions.js';
|
|
30
30
|
import { parseMcpServerConfig } from './mcp.js';
|
|
31
31
|
import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
|
|
@@ -1160,7 +1160,7 @@ export function removeVersion(agent, version) {
|
|
|
1160
1160
|
// Clean up dangling config symlink if it pointed to the removed version
|
|
1161
1161
|
const symlinkVersion = getConfigSymlinkVersion(agent);
|
|
1162
1162
|
if (symlinkVersion === version) {
|
|
1163
|
-
const configPath = path.join(os.homedir(),
|
|
1163
|
+
const configPath = path.join(os.homedir(), agentConfigDirName(agent));
|
|
1164
1164
|
try {
|
|
1165
1165
|
fs.unlinkSync(configPath);
|
|
1166
1166
|
}
|
|
@@ -1351,7 +1351,7 @@ async function getCliVersionFromPath(agent) {
|
|
|
1351
1351
|
export function getResourceDiff(agent, version) {
|
|
1352
1352
|
const agentConfig = AGENTS[agent];
|
|
1353
1353
|
const versionHome = getVersionHomePath(agent, version);
|
|
1354
|
-
const agentDir = path.join(versionHome,
|
|
1354
|
+
const agentDir = path.join(versionHome, agentConfigDirName(agent));
|
|
1355
1355
|
const diff = {
|
|
1356
1356
|
commands: { added: [], dangling: [] },
|
|
1357
1357
|
skills: { added: [], dangling: [] },
|
|
@@ -1500,7 +1500,7 @@ export function getResourceDiff(agent, version) {
|
|
|
1500
1500
|
export function syncResourcesToVersion(agent, version, selection, options = {}) {
|
|
1501
1501
|
const agentConfig = AGENTS[agent];
|
|
1502
1502
|
const versionHome = getVersionHomePath(agent, version);
|
|
1503
|
-
const agentDir = path.join(versionHome,
|
|
1503
|
+
const agentDir = path.join(versionHome, agentConfigDirName(agent));
|
|
1504
1504
|
fs.mkdirSync(agentDir, { recursive: true });
|
|
1505
1505
|
// Capture whether the caller passed a selection. The pattern-expansion
|
|
1506
1506
|
// path below reassigns `selection`, but for manifest write semantics we
|