@phnx-labs/agents-cli 1.22.26 → 1.22.28
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 +293 -0
- package/README.md +80 -27
- package/dist/bin/agents +0 -0
- package/dist/commands/bench.d.ts +2 -0
- package/dist/commands/bench.js +101 -0
- package/dist/commands/doctor.js +24 -10
- package/dist/commands/exec.js +71 -26
- package/dist/commands/hosts.js +4 -3
- package/dist/commands/insights.js +39 -5
- package/dist/commands/output.js +100 -25
- package/dist/commands/perf.d.ts +10 -0
- package/dist/commands/perf.js +14 -6
- package/dist/commands/projects.js +24 -10
- package/dist/commands/resume.d.ts +11 -0
- package/dist/commands/resume.js +51 -0
- package/dist/commands/routines.js +31 -4
- package/dist/commands/run-account-picker.d.ts +37 -0
- package/dist/commands/run-account-picker.js +101 -17
- package/dist/commands/secrets.js +86 -2
- package/dist/commands/sessions-picker.js +12 -7
- package/dist/commands/sessions.d.ts +73 -7
- package/dist/commands/sessions.js +391 -49
- package/dist/commands/ssh.js +184 -11
- package/dist/commands/teams.js +22 -11
- package/dist/index.js +16 -20
- package/dist/lib/activity.js +45 -99
- package/dist/lib/auth-health.d.ts +47 -3
- package/dist/lib/auth-health.js +74 -15
- package/dist/lib/bench/index.d.ts +4 -0
- package/dist/lib/bench/index.js +4 -0
- package/dist/lib/bench/runner.d.ts +16 -0
- package/dist/lib/bench/runner.js +111 -0
- package/dist/lib/bench/schema.d.ts +5 -0
- package/dist/lib/bench/schema.js +91 -0
- package/dist/lib/bench/storage.d.ts +5 -0
- package/dist/lib/bench/storage.js +32 -0
- package/dist/lib/bench/types.d.ts +40 -0
- package/dist/lib/bench/types.js +1 -0
- package/dist/lib/claude-account-token.d.ts +2 -0
- package/dist/lib/claude-account-token.js +56 -3
- package/dist/lib/cloud/host.js +1 -0
- package/dist/lib/crabbox/cli.d.ts +2 -0
- package/dist/lib/crabbox/cli.js +2 -0
- package/dist/lib/crabbox/lease.js +7 -1
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/devices/connect.d.ts +2 -0
- package/dist/lib/devices/connect.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +4 -1
- package/dist/lib/devices/doctor-findings.js +15 -1
- package/dist/lib/devices/harness-inventory.d.ts +97 -0
- package/dist/lib/devices/harness-inventory.js +0 -0
- package/dist/lib/devices/registry.d.ts +2 -0
- package/dist/lib/devices/resolve-target.d.ts +1 -0
- package/dist/lib/devices/resolve-target.js +9 -2
- package/dist/lib/devices/ssh-config.js +3 -0
- package/dist/lib/devices/windows-ssh-enrollment.d.ts +20 -0
- package/dist/lib/devices/windows-ssh-enrollment.js +98 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/events.js +2 -2
- package/dist/lib/exec.js +18 -8
- package/dist/lib/fleet/apply.js +9 -7
- package/dist/lib/fleet/remote-login.d.ts +4 -3
- package/dist/lib/fleet/remote-login.js +11 -9
- package/dist/lib/gemini-settings.d.ts +0 -1
- package/dist/lib/gemini-settings.js +12 -7
- package/dist/lib/git.d.ts +9 -3
- package/dist/lib/git.js +39 -22
- package/dist/lib/hooks/profile.d.ts +12 -1
- package/dist/lib/hooks/profile.js +6 -1
- package/dist/lib/hooks.d.ts +8 -5
- package/dist/lib/hooks.js +14 -7
- package/dist/lib/hosts/dispatch.d.ts +10 -0
- package/dist/lib/hosts/dispatch.js +85 -25
- package/dist/lib/hosts/logs.js +11 -1
- package/dist/lib/hosts/passthrough.d.ts +27 -0
- package/dist/lib/hosts/passthrough.js +19 -10
- package/dist/lib/hosts/progress.d.ts +12 -1
- package/dist/lib/hosts/progress.js +41 -8
- package/dist/lib/hosts/providers/devices.js +1 -0
- package/dist/lib/hosts/ready.d.ts +45 -4
- package/dist/lib/hosts/ready.js +124 -20
- package/dist/lib/hosts/reconcile.d.ts +2 -1
- package/dist/lib/hosts/reconcile.js +24 -7
- package/dist/lib/hosts/reconnect.d.ts +45 -12
- package/dist/lib/hosts/reconnect.js +94 -36
- package/dist/lib/hosts/registry.d.ts +2 -2
- package/dist/lib/hosts/registry.js +4 -5
- package/dist/lib/hosts/remote-cmd.d.ts +17 -0
- package/dist/lib/hosts/remote-cmd.js +29 -0
- package/dist/lib/hosts/tasks.d.ts +3 -0
- package/dist/lib/hosts/types.d.ts +1 -0
- package/dist/lib/hosts/types.js +3 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +9 -6
- package/dist/lib/menubar/install-menubar.js +20 -9
- package/dist/lib/menubar/snapshot.js +15 -2
- package/dist/lib/models.d.ts +42 -6
- package/dist/lib/models.js +55 -153
- package/dist/lib/perf/db.js +16 -3
- package/dist/lib/perf/types.d.ts +12 -1
- package/dist/lib/pricing/cost.d.ts +9 -0
- package/dist/lib/pricing/cost.js +24 -0
- package/dist/lib/pricing/index.d.ts +1 -1
- package/dist/lib/pricing/index.js +1 -1
- package/dist/lib/project-probe.d.ts +22 -3
- package/dist/lib/project-probe.js +105 -17
- package/dist/lib/project-status.d.ts +9 -0
- package/dist/lib/project-status.js +15 -0
- package/dist/lib/redact.js +8 -3
- package/dist/lib/refresh.js +22 -11
- package/dist/lib/remote-agents-json.d.ts +32 -0
- package/dist/lib/remote-agents-json.js +47 -16
- package/dist/lib/resource-profiles.js +1 -2
- package/dist/lib/rotate.d.ts +22 -0
- package/dist/lib/rotate.js +26 -0
- package/dist/lib/routine-notify-owner.d.ts +102 -0
- package/dist/lib/routine-notify-owner.js +232 -0
- package/dist/lib/routines.d.ts +10 -0
- package/dist/lib/runner.d.ts +9 -1
- package/dist/lib/runner.js +182 -17
- package/dist/lib/sandbox.d.ts +0 -2
- package/dist/lib/sandbox.js +2 -19
- 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/agent.d.ts +19 -3
- package/dist/lib/secrets/agent.js +61 -22
- package/dist/lib/secrets/audit.d.ts +1 -1
- package/dist/lib/secrets/audit.js +2 -0
- package/dist/lib/secrets/bundles.d.ts +3 -3
- package/dist/lib/secrets/bundles.js +15 -5
- package/dist/lib/secrets/filestore.d.ts +2 -0
- package/dist/lib/secrets/filestore.js +4 -0
- package/dist/lib/secrets/lease.d.ts +25 -0
- package/dist/lib/secrets/lease.js +44 -0
- package/dist/lib/secrets/session-store.d.ts +7 -0
- package/dist/lib/secrets/session-store.js +21 -0
- package/dist/lib/self-update.d.ts +20 -5
- package/dist/lib/self-update.js +93 -16
- package/dist/lib/session/active.d.ts +49 -0
- package/dist/lib/session/active.js +175 -11
- package/dist/lib/session/bash-command.d.ts +16 -0
- package/dist/lib/session/bash-command.js +65 -0
- package/dist/lib/session/db.d.ts +23 -2
- package/dist/lib/session/db.js +86 -20
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +124 -32
- package/dist/lib/session/insights.d.ts +19 -0
- package/dist/lib/session/insights.js +78 -10
- package/dist/lib/session/recovery.d.ts +17 -1
- package/dist/lib/session/recovery.js +111 -4
- package/dist/lib/session/remote-bundle.js +1 -1
- package/dist/lib/session/remote-list.d.ts +10 -9
- package/dist/lib/session/remote-list.js +25 -23
- package/dist/lib/session/resume-owner.d.ts +55 -0
- package/dist/lib/session/resume-owner.js +69 -0
- package/dist/lib/session/team-filter.d.ts +65 -0
- package/dist/lib/session/team-filter.js +98 -3
- package/dist/lib/session/types.d.ts +33 -0
- package/dist/lib/smart-launch.d.ts +31 -5
- package/dist/lib/smart-launch.js +43 -7
- package/dist/lib/ssh-exec.d.ts +27 -0
- package/dist/lib/ssh-exec.js +34 -1
- package/dist/lib/ssh-tunnel.d.ts +3 -2
- package/dist/lib/ssh-tunnel.js +25 -16
- package/dist/lib/staleness/detectors/skills.d.ts +2 -0
- package/dist/lib/staleness/detectors/skills.js +17 -2
- package/dist/lib/staleness/index.d.ts +10 -1
- package/dist/lib/staleness/index.js +28 -3
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/teams/agents.d.ts +1 -0
- package/dist/lib/teams/agents.js +38 -11
- package/dist/lib/teams/placement-probe.d.ts +1 -1
- package/dist/lib/teams/placement-probe.js +24 -24
- package/dist/lib/teams/remoteWorktree.d.ts +11 -7
- package/dist/lib/teams/remoteWorktree.js +29 -27
- package/dist/lib/tmux/session.d.ts +8 -0
- package/dist/lib/tmux/session.js +22 -0
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/versions.d.ts +1 -0
- package/dist/lib/versions.js +40 -22
- package/package.json +3 -1
|
@@ -40,30 +40,30 @@ export function remotePathExpr(p) {
|
|
|
40
40
|
* downstream remote command (launch `cd`, worktree create, polling) works from an
|
|
41
41
|
* absolute path with no tilde-expansion hazard.
|
|
42
42
|
*/
|
|
43
|
-
export function resolveRemoteRepoRoot(target, repoPath) {
|
|
43
|
+
export function resolveRemoteRepoRoot(target, repoPath, opts = {}) {
|
|
44
44
|
assertValidSshTarget(target);
|
|
45
45
|
const cmd = `git -C ${remotePathExpr(repoPath)} rev-parse --show-toplevel 2>/dev/null`;
|
|
46
|
-
const res = sshExec(target, cmd, { timeoutMs: 15000, multiplex: true });
|
|
46
|
+
const res = sshExec(target, cmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: opts.extraSshArgs });
|
|
47
47
|
const root = res.stdout.trim();
|
|
48
48
|
return res.code === 0 && root ? root : null;
|
|
49
49
|
}
|
|
50
50
|
/** Run one git command in `repoPath` on the host; throw with stderr on failure. */
|
|
51
|
-
function remoteGit(target, repoPath, args, timeoutMs = 60000) {
|
|
51
|
+
function remoteGit(target, repoPath, args, timeoutMs = 60000, opts = {}) {
|
|
52
52
|
const cmd = ['git', '-C', repoPath, ...args].map(shellQuote).join(' ');
|
|
53
|
-
const res = sshExec(target, cmd, { timeoutMs, multiplex: true });
|
|
53
|
+
const res = sshExec(target, cmd, { timeoutMs, multiplex: true, extraSshArgs: opts.extraSshArgs });
|
|
54
54
|
if (res.code !== 0) {
|
|
55
55
|
throw new Error(`remote git failed on ${target} (${args[0]}): ${(res.stderr || res.stdout).trim() || 'ssh error'}`);
|
|
56
56
|
}
|
|
57
57
|
return res.stdout.trim();
|
|
58
58
|
}
|
|
59
59
|
/** True when `repoPath` is a git working tree on the host. */
|
|
60
|
-
export function isRemoteGitRepo(target, repoPath) {
|
|
60
|
+
export function isRemoteGitRepo(target, repoPath, opts = {}) {
|
|
61
61
|
assertValidSshTarget(target);
|
|
62
62
|
// remotePathExpr so a `~`/`$HOME`-relative path (the canonical repos dir) expands
|
|
63
63
|
// on the host — plain shellQuote would single-quote the tilde into a literal,
|
|
64
64
|
// making an existing checkout look absent and forcing a doomed re-clone.
|
|
65
65
|
const cmd = `git -C ${remotePathExpr(repoPath)} rev-parse --git-dir 2>/dev/null`;
|
|
66
|
-
const res = sshExec(target, cmd, { timeoutMs: 15000, multiplex: true });
|
|
66
|
+
const res = sshExec(target, cmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: opts.extraSshArgs });
|
|
67
67
|
return res.code === 0;
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
@@ -71,12 +71,12 @@ export function isRemoteGitRepo(target, repoPath) {
|
|
|
71
71
|
* Falls back to `main` when origin/HEAD isn't set, matching the local recipe's
|
|
72
72
|
* `remote set-head` step.
|
|
73
73
|
*/
|
|
74
|
-
export function remoteDefaultBranch(target, repoPath) {
|
|
74
|
+
export function remoteDefaultBranch(target, repoPath, opts = {}) {
|
|
75
75
|
assertValidSshTarget(target);
|
|
76
76
|
try {
|
|
77
77
|
// Refresh origin/HEAD first so a repo cloned before the default was set resolves.
|
|
78
|
-
sshExec(target, ['git', '-C', repoPath, 'remote', 'set-head', 'origin', '--auto'].map(shellQuote).join(' '), { timeoutMs: 20000, multiplex: true });
|
|
79
|
-
const ref = remoteGit(target, repoPath, ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']);
|
|
78
|
+
sshExec(target, ['git', '-C', repoPath, 'remote', 'set-head', 'origin', '--auto'].map(shellQuote).join(' '), { timeoutMs: 20000, multiplex: true, extraSshArgs: opts.extraSshArgs });
|
|
79
|
+
const ref = remoteGit(target, repoPath, ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], 60000, opts);
|
|
80
80
|
return ref.replace(/^origin\//, '') || 'main';
|
|
81
81
|
}
|
|
82
82
|
catch {
|
|
@@ -91,15 +91,15 @@ export function remoteDefaultBranch(target, repoPath) {
|
|
|
91
91
|
* `worktree add -b … origin/<default>` so a stale checkout can't fork the
|
|
92
92
|
* teammate off old code.
|
|
93
93
|
*/
|
|
94
|
-
export function createRemoteWorktree(target, repoPath, worktreeName) {
|
|
94
|
+
export function createRemoteWorktree(target, repoPath, worktreeName, opts = {}) {
|
|
95
95
|
assertValidSshTarget(target);
|
|
96
96
|
assertName(worktreeName);
|
|
97
|
-
const gitRoot = remoteGit(target, repoPath, ['rev-parse', '--show-toplevel']);
|
|
98
|
-
const base = remoteDefaultBranch(target, gitRoot);
|
|
97
|
+
const gitRoot = remoteGit(target, repoPath, ['rev-parse', '--show-toplevel'], 60000, opts);
|
|
98
|
+
const base = remoteDefaultBranch(target, gitRoot, opts);
|
|
99
99
|
const worktreePath = `${gitRoot}/.agents/worktrees/${worktreeName}`;
|
|
100
100
|
const branchName = `agents/${worktreeName}`;
|
|
101
|
-
remoteGit(target, gitRoot, ['fetch', 'origin'], 120000);
|
|
102
|
-
remoteGit(target, gitRoot, ['worktree', 'add', '-b', branchName, worktreePath, `origin/${base}`], 120000);
|
|
101
|
+
remoteGit(target, gitRoot, ['fetch', 'origin'], 120000, opts);
|
|
102
|
+
remoteGit(target, gitRoot, ['worktree', 'add', '-b', branchName, worktreePath, `origin/${base}`], 120000, opts);
|
|
103
103
|
return worktreePath;
|
|
104
104
|
}
|
|
105
105
|
// Team-name → repos-dir slug: same allowlist a worktree name uses, so it lands
|
|
@@ -126,24 +126,24 @@ function repoSlug(name) {
|
|
|
126
126
|
* expands `$HOME` — single-quoting the tilde would defeat expansion, the same
|
|
127
127
|
* hazard dispatch.ts avoids.
|
|
128
128
|
*/
|
|
129
|
-
export function ensureRemoteRepo(target, repo, slug) {
|
|
129
|
+
export function ensureRemoteRepo(target, repo, slug, opts = {}) {
|
|
130
130
|
assertValidSshTarget(target);
|
|
131
131
|
const safeSlug = repoSlug(slug);
|
|
132
132
|
const canonical = `~/.agents/repos/${safeSlug}`;
|
|
133
133
|
// 1. Canonical checkout already exists → fetch and reuse.
|
|
134
|
-
if (isRemoteGitRepo(target, canonical)) {
|
|
134
|
+
if (isRemoteGitRepo(target, canonical, opts)) {
|
|
135
135
|
// Best-effort refresh; a fetch failure (offline origin) shouldn't block reuse.
|
|
136
|
-
sshExec(target, `git -C ${remotePathExpr(canonical)} fetch origin`, { timeoutMs: 120000, multiplex: true });
|
|
137
|
-
const root = resolveRemoteRepoRoot(target, canonical);
|
|
136
|
+
sshExec(target, `git -C ${remotePathExpr(canonical)} fetch origin`, { timeoutMs: 120000, multiplex: true, extraSshArgs: opts.extraSshArgs });
|
|
137
|
+
const root = resolveRemoteRepoRoot(target, canonical, opts);
|
|
138
138
|
if (!root)
|
|
139
139
|
throw new Error(`Repo at ${canonical} on ${target} vanished mid-provision.`);
|
|
140
140
|
return root;
|
|
141
141
|
}
|
|
142
142
|
// 2. `repo` is a path that already exists on the host as a git repo → use it.
|
|
143
143
|
if (repo && !looksLikeUrl(repo)) {
|
|
144
|
-
const existing = resolveRemoteRepoRoot(target, repo);
|
|
144
|
+
const existing = resolveRemoteRepoRoot(target, repo, opts);
|
|
145
145
|
if (existing) {
|
|
146
|
-
sshExec(target, `git -C ${remotePathExpr(existing)} fetch origin`, { timeoutMs: 120000, multiplex: true });
|
|
146
|
+
sshExec(target, `git -C ${remotePathExpr(existing)} fetch origin`, { timeoutMs: 120000, multiplex: true, extraSshArgs: opts.extraSshArgs });
|
|
147
147
|
return existing;
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -159,12 +159,12 @@ export function ensureRemoteRepo(target, repo, slug) {
|
|
|
159
159
|
// blocks *shell* injection and is orthogonal to *git-transport* injection.
|
|
160
160
|
assertSafeGitTransport(repo);
|
|
161
161
|
const clone = sshExec(target, `mkdir -p ${remotePathExpr('~/.agents/repos')} && ` +
|
|
162
|
-
`git clone -- ${shellQuote(repo)} ${remotePathExpr(canonical)}`, { timeoutMs: 600000, multiplex: true });
|
|
162
|
+
`git clone -- ${shellQuote(repo)} ${remotePathExpr(canonical)}`, { timeoutMs: 600000, multiplex: true, extraSshArgs: opts.extraSshArgs });
|
|
163
163
|
if (clone.code !== 0) {
|
|
164
164
|
throw new Error(`git clone ${repo} into ${canonical} on ${target} failed: ` +
|
|
165
165
|
`${(clone.stderr || clone.stdout).trim() || 'ssh error'}`);
|
|
166
166
|
}
|
|
167
|
-
const root = resolveRemoteRepoRoot(target, canonical);
|
|
167
|
+
const root = resolveRemoteRepoRoot(target, canonical, opts);
|
|
168
168
|
if (!root)
|
|
169
169
|
throw new Error(`Cloned ${repo} into ${canonical} on ${target} but it isn't a git repo.`);
|
|
170
170
|
return root;
|
|
@@ -176,10 +176,10 @@ function looksLikeUrl(repo) {
|
|
|
176
176
|
repo.startsWith('git@'));
|
|
177
177
|
}
|
|
178
178
|
/** True when the host worktree has uncommitted changes (staged or unstaged). */
|
|
179
|
-
export function remoteWorktreeDirty(target, worktreePath) {
|
|
179
|
+
export function remoteWorktreeDirty(target, worktreePath, opts = {}) {
|
|
180
180
|
assertValidSshTarget(target);
|
|
181
181
|
const cmd = ['git', '-C', worktreePath, 'status', '--porcelain'].map(shellQuote).join(' ');
|
|
182
|
-
const res = sshExec(target, cmd, { timeoutMs: 20000, multiplex: true });
|
|
182
|
+
const res = sshExec(target, cmd, { timeoutMs: 20000, multiplex: true, extraSshArgs: opts.extraSshArgs });
|
|
183
183
|
if (res.code !== 0)
|
|
184
184
|
return false;
|
|
185
185
|
return res.stdout.trim().length > 0;
|
|
@@ -189,17 +189,18 @@ export function remoteWorktreeDirty(target, worktreePath) {
|
|
|
189
189
|
* "not a working tree" error and ignores a missing branch, matching local
|
|
190
190
|
* `removeWorktree` semantics so cleanup never throws on a half-gone worktree.
|
|
191
191
|
*/
|
|
192
|
-
export function removeRemoteWorktree(target, repoPath, worktreeName, deleteBranch = true) {
|
|
192
|
+
export function removeRemoteWorktree(target, repoPath, worktreeName, deleteBranch = true, opts = {}) {
|
|
193
193
|
assertValidSshTarget(target);
|
|
194
194
|
assertName(worktreeName);
|
|
195
|
-
const gitRoot = remoteGit(target, repoPath, ['rev-parse', '--show-toplevel']);
|
|
195
|
+
const gitRoot = remoteGit(target, repoPath, ['rev-parse', '--show-toplevel'], 60000, opts);
|
|
196
196
|
const worktreePath = `${gitRoot}/.agents/worktrees/${worktreeName}`;
|
|
197
197
|
const branchName = `agents/${worktreeName}`;
|
|
198
|
-
const rm = sshExec(target, ['git', '-C', gitRoot, 'worktree', 'remove', '--force', worktreePath].map(shellQuote).join(' '), { timeoutMs: 60000, multiplex: true });
|
|
198
|
+
const rm = sshExec(target, ['git', '-C', gitRoot, 'worktree', 'remove', '--force', worktreePath].map(shellQuote).join(' '), { timeoutMs: 60000, multiplex: true, extraSshArgs: opts.extraSshArgs });
|
|
199
199
|
if (rm.code !== 0 && /is not a working tree/.test(rm.stderr + rm.stdout)) {
|
|
200
200
|
sshExec(target, ['git', '-C', gitRoot, 'worktree', 'prune'].map(shellQuote).join(' '), {
|
|
201
201
|
timeoutMs: 30000,
|
|
202
202
|
multiplex: true,
|
|
203
|
+
extraSshArgs: opts.extraSshArgs,
|
|
203
204
|
});
|
|
204
205
|
}
|
|
205
206
|
if (deleteBranch) {
|
|
@@ -207,6 +208,7 @@ export function removeRemoteWorktree(target, repoPath, worktreeName, deleteBranc
|
|
|
207
208
|
sshExec(target, ['git', '-C', gitRoot, 'branch', '-D', branchName].map(shellQuote).join(' '), {
|
|
208
209
|
timeoutMs: 20000,
|
|
209
210
|
multiplex: true,
|
|
211
|
+
extraSshArgs: opts.extraSshArgs,
|
|
210
212
|
});
|
|
211
213
|
}
|
|
212
214
|
}
|
|
@@ -179,6 +179,14 @@ export declare const AGENT_HOOK_SCHEMA = 5;
|
|
|
179
179
|
export declare function agentPaneDiedHook(sessionName: string, agentPane: string): string;
|
|
180
180
|
/** Stamp a session's hook-schema marker to the current version. */
|
|
181
181
|
export declare function markSessionHookSchema(name: string, socket?: string): Promise<void>;
|
|
182
|
+
/**
|
|
183
|
+
* Decide whether a native resume may attach an existing managed tmux session.
|
|
184
|
+
* Only a positively resolved, living agent pane is reusable. Metadata-less
|
|
185
|
+
* legacy sessions and stale metadata resolve their real first pane through
|
|
186
|
+
* tmux before the decision; a dead or unreadable session is reaped so the
|
|
187
|
+
* caller can create a fresh wrapper for the resumed harness.
|
|
188
|
+
*/
|
|
189
|
+
export declare function prepareSessionForResume(name: string, socket?: string): Promise<'attach' | 'create'>;
|
|
182
190
|
/**
|
|
183
191
|
* Retrofit the current guarded `pane-died` hook onto every managed `agents run`
|
|
184
192
|
* session whose hook predates AGENT_HOOK_SCHEMA. Idempotent and NON-DESTRUCTIVE:
|
package/dist/lib/tmux/session.js
CHANGED
|
@@ -359,6 +359,28 @@ async function lowestPaneId(name, socket) {
|
|
|
359
359
|
return undefined;
|
|
360
360
|
return ids.reduce((lo, id) => (parseInt(id.slice(1), 10) < parseInt(lo.slice(1), 10) ? id : lo));
|
|
361
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Decide whether a native resume may attach an existing managed tmux session.
|
|
364
|
+
* Only a positively resolved, living agent pane is reusable. Metadata-less
|
|
365
|
+
* legacy sessions and stale metadata resolve their real first pane through
|
|
366
|
+
* tmux before the decision; a dead or unreadable session is reaped so the
|
|
367
|
+
* caller can create a fresh wrapper for the resumed harness.
|
|
368
|
+
*/
|
|
369
|
+
export async function prepareSessionForResume(name, socket) {
|
|
370
|
+
const sock = socket ?? getDefaultSocketPath();
|
|
371
|
+
if (!(await hasSession(name, sock)))
|
|
372
|
+
return 'create';
|
|
373
|
+
const recordedPane = readSessionMeta(name)?.pane;
|
|
374
|
+
const recordedState = recordedPane ? await paneExitStatus(recordedPane, sock) : undefined;
|
|
375
|
+
const pane = recordedPane && recordedState?.found
|
|
376
|
+
? recordedPane
|
|
377
|
+
: await lowestPaneId(name, sock);
|
|
378
|
+
const state = pane ? await paneExitStatus(pane, sock) : undefined;
|
|
379
|
+
if (pane && state?.found && !state.dead)
|
|
380
|
+
return 'attach';
|
|
381
|
+
await killSession(name, sock);
|
|
382
|
+
return 'create';
|
|
383
|
+
}
|
|
362
384
|
/**
|
|
363
385
|
* Retrofit the current guarded `pane-died` hook onto every managed `agents run`
|
|
364
386
|
* session whose hook predates AGENT_HOOK_SCHEMA. Idempotent and NON-DESTRUCTIVE:
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1064,6 +1064,8 @@ export interface HostEntry {
|
|
|
1064
1064
|
address?: string;
|
|
1065
1065
|
/** SSH user — inline hosts only. */
|
|
1066
1066
|
user?: string;
|
|
1067
|
+
/** Explicit private key inherited from a fleet device profile. */
|
|
1068
|
+
identityFile?: string;
|
|
1067
1069
|
/** Captured at enroll probe. */
|
|
1068
1070
|
os?: string;
|
|
1069
1071
|
/** Free-form capability tags for routing (e.g. ['gpu']). */
|
package/dist/lib/versions.d.ts
CHANGED
|
@@ -525,6 +525,7 @@ export declare function syncResourcesToVersion(agent: AgentId, version: string,
|
|
|
525
525
|
projectDir?: string;
|
|
526
526
|
cwd?: string;
|
|
527
527
|
force?: boolean;
|
|
528
|
+
available?: AvailableResources;
|
|
528
529
|
}): SyncResult;
|
|
529
530
|
/**
|
|
530
531
|
* Get the effective HOME directory for an agent.
|
package/dist/lib/versions.js
CHANGED
|
@@ -346,11 +346,21 @@ function skillDirsMatch(src, dest) {
|
|
|
346
346
|
return false;
|
|
347
347
|
}
|
|
348
348
|
else {
|
|
349
|
-
|
|
349
|
+
// Stat-first (RUSH-2320 #2): size mismatch = definitive miss, no reads.
|
|
350
|
+
// Equal mtimes across different trees are accidental — content-compare
|
|
351
|
+
// whenever sizes match.
|
|
352
|
+
let srcStat;
|
|
353
|
+
let destStat;
|
|
354
|
+
try {
|
|
355
|
+
srcStat = fs.statSync(srcPath);
|
|
356
|
+
destStat = fs.statSync(destPath);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
350
359
|
return false;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
360
|
+
}
|
|
361
|
+
if (srcStat.size !== destStat.size)
|
|
362
|
+
return false;
|
|
363
|
+
if (fs.readFileSync(srcPath, 'utf-8') !== fs.readFileSync(destPath, 'utf-8'))
|
|
354
364
|
return false;
|
|
355
365
|
}
|
|
356
366
|
}
|
|
@@ -2580,7 +2590,28 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2580
2590
|
// Extra DotAgent repos registered via `agents repo add`. Looked up last so
|
|
2581
2591
|
// project/user/system repos win on name collisions.
|
|
2582
2592
|
const extraRepos = getEnabledExtraRepos();
|
|
2583
|
-
|
|
2593
|
+
// Project-layer fan-out always runs — even on the early guard hit — so the
|
|
2594
|
+
// `projectSkipped` contract is preserved for callers (RUSH-2320 #4).
|
|
2595
|
+
if (projectAgentsDir) {
|
|
2596
|
+
result.projectSkipped = syncProjectResourcesToAgent(agent, version, projectAgentsDir).skipped;
|
|
2597
|
+
}
|
|
2598
|
+
// Fast guard BEFORE getAvailableResources / pattern expansion /
|
|
2599
|
+
// ensureVersionResourcePatterns. Guard needs only loadManifest + isStale
|
|
2600
|
+
// (~6 ms); the work that used to sit ahead of it was ~12.5 ms of the 21.5 ms
|
|
2601
|
+
// guard-hit path and is discarded when nothing changed (RUSH-2320 #4).
|
|
2602
|
+
// Behavior note: a valid manifest implies a prior full sync already wrote
|
|
2603
|
+
// version resource patterns, so skipping ensureVersionResourcePatterns on
|
|
2604
|
+
// the hit path is safe in practice but is not byte-identical to the old
|
|
2605
|
+
// order (that call always wrote missing pattern defaults).
|
|
2606
|
+
if (!userPassedSelection && !options.force) {
|
|
2607
|
+
const manifest = loadManifest(agent, version);
|
|
2608
|
+
if (manifest && !isStale(manifest, agent, version, cwd)) {
|
|
2609
|
+
return { ...result };
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
// Prefer a caller-supplied inventory (refresh already built one) so multi-
|
|
2613
|
+
// version fan-out does not re-scan resource trees per version (RUSH-2320 #5).
|
|
2614
|
+
const available = options.available ?? getAvailableResources(cwd);
|
|
2584
2615
|
// Write default resource selection patterns for this version (idempotent —
|
|
2585
2616
|
// only sets fields that aren't already present, preserving user edits).
|
|
2586
2617
|
{
|
|
@@ -2640,21 +2671,6 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2640
2671
|
}
|
|
2641
2672
|
}
|
|
2642
2673
|
}
|
|
2643
|
-
if (projectAgentsDir) {
|
|
2644
|
-
result.projectSkipped = syncProjectResourcesToAgent(agent, version, projectAgentsDir).skipped;
|
|
2645
|
-
}
|
|
2646
|
-
// Fast guard: skip the entire sync when the caller requested a full sync and
|
|
2647
|
-
// nothing has changed since the last full sync. Pattern-derived selections
|
|
2648
|
-
// still count as full syncs because they are the persisted intended scope,
|
|
2649
|
-
// not a one-off caller override.
|
|
2650
|
-
if (!userPassedSelection && !options.force) {
|
|
2651
|
-
const manifest = loadManifest(agent, version);
|
|
2652
|
-
if (manifest && !isStale(manifest, agent, version, cwd)) {
|
|
2653
|
-
// Nothing synced, but the project sync above already ran — carry its
|
|
2654
|
-
// skipped files out so the caller can still report them.
|
|
2655
|
-
return { ...result };
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
2674
|
// Helper: remove a path (symlink or real) if it exists
|
|
2659
2675
|
const removePath = (p) => {
|
|
2660
2676
|
try {
|
|
@@ -3023,9 +3039,11 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
3023
3039
|
// launch can skip the slow path. Pattern-derived selections still count as
|
|
3024
3040
|
// "full" — the agents.yaml patterns describe the intended scope, not a
|
|
3025
3041
|
// one-off override, so the resulting state matches what the manifest
|
|
3026
|
-
// records as the synced set.
|
|
3042
|
+
// records as the synced set. Carry forward still-fresh fingerprints from
|
|
3043
|
+
// the previous manifest so we do not re-hash an unchanged tree (RUSH-2320 #3).
|
|
3027
3044
|
if (!userPassedSelection) {
|
|
3028
|
-
const
|
|
3045
|
+
const previous = loadManifest(agent, version);
|
|
3046
|
+
const manifest = buildSyncManifest(agent, version, cwd, previous);
|
|
3029
3047
|
manifest.writtenCommands = writtenCommands;
|
|
3030
3048
|
saveManifest(agent, version, manifest);
|
|
3031
3049
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.28",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
"changelog": "bun scripts/gen-changelog.ts",
|
|
55
55
|
"start": "node dist/index.js",
|
|
56
56
|
"test": "node ./node_modules/vitest/vitest.mjs run",
|
|
57
|
+
"bench": "node ./node_modules/vitest/vitest.mjs bench --run",
|
|
58
|
+
"typecheck:bench": "tsc --noEmit --ignoreConfig --skipLibCheck --module esnext --moduleResolution bundler --target es2022 --strict --esModuleInterop --lib es2022 --types node src/lib/*.bench.ts src/lib/**/*.bench.ts",
|
|
57
59
|
"test:remote": "scripts/sandbox.sh 'bun install && bun run build && bun run test'",
|
|
58
60
|
"verify-docs": "scripts/verify-docs.sh",
|
|
59
61
|
"test:watch": "node ./node_modules/vitest/vitest.mjs"
|