@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
package/dist/lib/ssh-tunnel.js
CHANGED
|
@@ -28,7 +28,7 @@ import { sshExec, SSH_OPTS, assertValidSshTarget } from './ssh-exec.js';
|
|
|
28
28
|
import { backgroundSpawnOptions } from './platform/process.js';
|
|
29
29
|
import { encodePowerShell } from './browser/drivers/ssh.js';
|
|
30
30
|
import { getDevice } from './devices/registry.js';
|
|
31
|
-
import { sshTargetFor } from './devices/connect.js';
|
|
31
|
+
import { deviceIdentityArgs, sshTargetFor } from './devices/connect.js';
|
|
32
32
|
import { hostNameFor } from './devices/ssh-config.js';
|
|
33
33
|
import { getCacheDir } from './state.js';
|
|
34
34
|
import { getCliVersion } from './version.js';
|
|
@@ -38,12 +38,13 @@ import { openComputerClient, resolveTcpEndpoint } from './computer-rpc.js';
|
|
|
38
38
|
* Composes the shared hardened baseline (`SSH_OPTS`) rather than re-listing it,
|
|
39
39
|
* so the tunnel inherits the same options — crucially the keepalive, which lets
|
|
40
40
|
* a dropped `-N` tunnel exit instead of lingering as a zombie on the laptop. */
|
|
41
|
-
export function buildTunnelArgs(user, host, localPort, remotePort) {
|
|
41
|
+
export function buildTunnelArgs(user, host, localPort, remotePort, extraSshArgs = []) {
|
|
42
42
|
return [
|
|
43
43
|
'-L',
|
|
44
44
|
`${localPort}:127.0.0.1:${remotePort}`,
|
|
45
45
|
`${user}@${host}`,
|
|
46
46
|
'-N',
|
|
47
|
+
...extraSshArgs,
|
|
47
48
|
...SSH_OPTS,
|
|
48
49
|
];
|
|
49
50
|
}
|
|
@@ -69,7 +70,7 @@ export function startSSHTunnel(user, host, localPort, remotePort, opts = {}) {
|
|
|
69
70
|
reject(err);
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
|
-
const args = buildTunnelArgs(user, host, localPort, remotePort);
|
|
73
|
+
const args = buildTunnelArgs(user, host, localPort, remotePort, opts.extraSshArgs);
|
|
73
74
|
const tunnel = spawn('ssh', args, {
|
|
74
75
|
stdio: opts.detached ? 'ignore' : ['ignore', 'ignore', 'pipe'],
|
|
75
76
|
...(opts.detached ? backgroundSpawnOptions() : { detached: false, windowsHide: true }),
|
|
@@ -360,16 +361,16 @@ export function scpRemotePath(remotePath) {
|
|
|
360
361
|
* assert the real binary copy path keeps BatchMode and does not route bytes
|
|
361
362
|
* through a PowerShell decoder.
|
|
362
363
|
*/
|
|
363
|
-
export function buildScpArgs(target, remotePath, filePath) {
|
|
364
|
-
return [...SSH_OPTS, filePath, `${target}:${scpRemotePath(remotePath)}`];
|
|
364
|
+
export function buildScpArgs(target, remotePath, filePath, extraSshArgs = []) {
|
|
365
|
+
return [...extraSshArgs, ...SSH_OPTS, filePath, `${target}:${scpRemotePath(remotePath)}`];
|
|
365
366
|
}
|
|
366
367
|
/**
|
|
367
368
|
* Copy a local file to the remote destination with scp. This is a binary
|
|
368
369
|
* transfer; no base64 transform runs on either side.
|
|
369
370
|
*/
|
|
370
|
-
function copyFileOverScp(target, remotePath, filePath, timeoutMs = 600_000) {
|
|
371
|
+
function copyFileOverScp(target, remotePath, filePath, extraSshArgs = [], timeoutMs = 600_000) {
|
|
371
372
|
return new Promise((resolve, reject) => {
|
|
372
|
-
const child = spawn('scp', buildScpArgs(target, remotePath, filePath), {
|
|
373
|
+
const child = spawn('scp', buildScpArgs(target, remotePath, filePath, extraSshArgs), {
|
|
373
374
|
stdio: ['ignore', 'ignore', 'pipe'],
|
|
374
375
|
});
|
|
375
376
|
let stderr = '';
|
|
@@ -397,11 +398,12 @@ function copyFileOverScp(target, remotePath, filePath, timeoutMs = 600_000) {
|
|
|
397
398
|
* scp transfer. Throws with the remote stderr on any failure.
|
|
398
399
|
*/
|
|
399
400
|
export async function setupRemoteHelper(name) {
|
|
400
|
-
const { target } = await resolveRemoteDevice(name);
|
|
401
|
+
const { device, target } = await resolveRemoteDevice(name);
|
|
402
|
+
const identityArgs = deviceIdentityArgs(device);
|
|
401
403
|
// Local build output, else the checksum-verified GitHub release asset for
|
|
402
404
|
// this CLI version. Throws naming the tag it checked when neither exists.
|
|
403
405
|
const exe = await ensureWinHelperExe();
|
|
404
|
-
const prep = sshExec(target, encodePowerShell(buildPushScript()), { timeoutMs: 60_000 });
|
|
406
|
+
const prep = sshExec(target, encodePowerShell(buildPushScript()), { timeoutMs: 60_000, extraSshArgs: identityArgs });
|
|
405
407
|
if (prep.code !== 0) {
|
|
406
408
|
throw new Error(`preparing helper exe path on '${name}' failed (exit ${prep.code ?? 'null'}): ${prep.stderr.trim() || prep.stdout.trim()}`);
|
|
407
409
|
}
|
|
@@ -409,12 +411,12 @@ export async function setupRemoteHelper(name) {
|
|
|
409
411
|
if (!remotePath) {
|
|
410
412
|
throw new Error(`preparing helper exe path on '${name}' did not return a destination path`);
|
|
411
413
|
}
|
|
412
|
-
const push = await copyFileOverScp(target, remotePath, exe);
|
|
414
|
+
const push = await copyFileOverScp(target, remotePath, exe, identityArgs);
|
|
413
415
|
if (push.code !== 0) {
|
|
414
416
|
throw new Error(`pushing helper exe to '${name}' failed (exit ${push.code ?? 'null'}): ${push.stderr.trim()}`);
|
|
415
417
|
}
|
|
416
418
|
const expectedBytes = fs.statSync(exe).size;
|
|
417
|
-
const verify = sshExec(target, encodePowerShell(buildVerifyPushScript(remotePath, expectedBytes)), { timeoutMs: 60_000 });
|
|
419
|
+
const verify = sshExec(target, encodePowerShell(buildVerifyPushScript(remotePath, expectedBytes)), { timeoutMs: 60_000, extraSshArgs: identityArgs });
|
|
418
420
|
if (verify.code !== 0) {
|
|
419
421
|
throw new Error(`verifying helper exe on '${name}' failed (exit ${verify.code ?? 'null'}): ${verify.stderr.trim() || verify.stdout.trim()}`);
|
|
420
422
|
}
|
|
@@ -423,7 +425,7 @@ export async function setupRemoteHelper(name) {
|
|
|
423
425
|
// refuses to start without a token, so a token-less (open-to-any-local-process)
|
|
424
426
|
// daemon can no longer be stood up through the CLI.
|
|
425
427
|
const token = generateToken();
|
|
426
|
-
const tokWrite = sshExec(target, encodePowerShell(buildWriteTokenScript(token)), { timeoutMs: 60_000 });
|
|
428
|
+
const tokWrite = sshExec(target, encodePowerShell(buildWriteTokenScript(token)), { timeoutMs: 60_000, extraSshArgs: identityArgs });
|
|
427
429
|
if (tokWrite.code !== 0) {
|
|
428
430
|
throw new Error(`writing helper token on '${name}' failed (exit ${tokWrite.code ?? 'null'}): ${tokWrite.stderr.trim() || tokWrite.stdout.trim()}`);
|
|
429
431
|
}
|
|
@@ -434,6 +436,7 @@ export async function setupRemoteHelper(name) {
|
|
|
434
436
|
// Register + start the LOGON task, pointing it at the token file.
|
|
435
437
|
const reg = sshExec(target, encodePowerShell(buildRegisterTaskScript(REMOTE_HELPER_PORT, REMOTE_TASK_NAME, remoteTokenPath)), {
|
|
436
438
|
timeoutMs: 60_000,
|
|
439
|
+
extraSshArgs: identityArgs,
|
|
437
440
|
});
|
|
438
441
|
if (reg.code !== 0) {
|
|
439
442
|
throw new Error(`registering scheduled task on '${name}' failed (exit ${reg.code ?? 'null'}): ${reg.stderr.trim() || reg.stdout.trim()}`);
|
|
@@ -460,10 +463,13 @@ export function pickFreePort() {
|
|
|
460
463
|
* the state (and leaves the tunnel running in the background).
|
|
461
464
|
*/
|
|
462
465
|
export async function startRemoteTunnel(name) {
|
|
463
|
-
const { target, user, host } = await resolveRemoteDevice(name);
|
|
466
|
+
const { device, target, user, host } = await resolveRemoteDevice(name);
|
|
464
467
|
const remotePort = REMOTE_HELPER_PORT;
|
|
465
468
|
const localPort = await pickFreePort();
|
|
466
|
-
const tunnel = await startSSHTunnel(user, host, localPort, remotePort, {
|
|
469
|
+
const tunnel = await startSSHTunnel(user, host, localPort, remotePort, {
|
|
470
|
+
detached: true,
|
|
471
|
+
extraSshArgs: deviceIdentityArgs(device),
|
|
472
|
+
});
|
|
467
473
|
const tunnelPid = tunnel.pid ?? 0;
|
|
468
474
|
// Verify the daemon answers through the tunnel before we record it. This is
|
|
469
475
|
// the real end-to-end check: tunnel up + daemon listening + RPC round-trips.
|
|
@@ -543,8 +549,11 @@ export async function stopRemoteHelper(name) {
|
|
|
543
549
|
}
|
|
544
550
|
let taskRemoved = false;
|
|
545
551
|
try {
|
|
546
|
-
const { target } = await resolveRemoteDevice(name);
|
|
547
|
-
const res = sshExec(target, encodePowerShell(buildUnregisterTaskScript(REMOTE_TASK_NAME)), {
|
|
552
|
+
const { device, target } = await resolveRemoteDevice(name);
|
|
553
|
+
const res = sshExec(target, encodePowerShell(buildUnregisterTaskScript(REMOTE_TASK_NAME)), {
|
|
554
|
+
timeoutMs: 60_000,
|
|
555
|
+
extraSshArgs: deviceIdentityArgs(device),
|
|
556
|
+
});
|
|
548
557
|
taskRemoved = res.code === 0;
|
|
549
558
|
}
|
|
550
559
|
catch {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { AgentId } from '../../types.js';
|
|
2
2
|
import type { ResourceDetector } from './types.js';
|
|
3
|
+
/** Exported for unit tests (RUSH-2320 #2). Production callers: list() below. */
|
|
4
|
+
export declare function skillDirsMatch(src: string, dest: string): boolean;
|
|
3
5
|
export declare const skillsDetectors: Partial<Record<AgentId, ResourceDetector>>;
|
|
@@ -9,7 +9,8 @@ import { capableAgents } from '../../capabilities.js';
|
|
|
9
9
|
import { resolveSkillSource } from '../writers/sources.js';
|
|
10
10
|
import { lazyAgentMap } from '../writers/lazy-map.js';
|
|
11
11
|
const SKILL_COPY_IGNORE = new Set(['.DS_Store', '.git', '.gitignore', '.venv', '__pycache__', 'node_modules']);
|
|
12
|
-
|
|
12
|
+
/** Exported for unit tests (RUSH-2320 #2). Production callers: list() below. */
|
|
13
|
+
export function skillDirsMatch(src, dest) {
|
|
13
14
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
14
15
|
for (const entry of entries) {
|
|
15
16
|
if (entry.isSymbolicLink())
|
|
@@ -25,7 +26,21 @@ function skillDirsMatch(src, dest) {
|
|
|
25
26
|
return false;
|
|
26
27
|
}
|
|
27
28
|
else {
|
|
28
|
-
|
|
29
|
+
// Stat-first (RUSH-2320 #2): size mismatch is a definitive miss with no
|
|
30
|
+
// content reads. Do NOT treat equal mtimes as equal content — src and
|
|
31
|
+
// dest are different trees (version home vs source), and copyFileSync
|
|
32
|
+
// does not preserve mtime, so mtime equality is accidental and unsafe.
|
|
33
|
+
// Content compare only when sizes match.
|
|
34
|
+
let srcStat;
|
|
35
|
+
let destStat;
|
|
36
|
+
try {
|
|
37
|
+
srcStat = fs.statSync(srcPath);
|
|
38
|
+
destStat = fs.statSync(destPath);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (srcStat.size !== destStat.size)
|
|
29
44
|
return false;
|
|
30
45
|
if (fs.readFileSync(srcPath, 'utf-8') !== fs.readFileSync(destPath, 'utf-8'))
|
|
31
46
|
return false;
|
|
@@ -16,7 +16,16 @@ export type { SyncManifest } from './types.js';
|
|
|
16
16
|
export { MANIFEST_VERSION } from './types.js';
|
|
17
17
|
export declare function loadManifest(agent: AgentId, version: string): SyncManifest | null;
|
|
18
18
|
export declare function saveManifest(agent: AgentId, version: string, manifest: SyncManifest): void;
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Snapshot current resource fingerprints for `agent@version`.
|
|
21
|
+
*
|
|
22
|
+
* When `previous` is supplied (the still-loaded post-guard manifest), any
|
|
23
|
+
* entry whose stored path/mtime/size still match via the checker's `isFresh`
|
|
24
|
+
* is carried forward without re-hashing. Measured: full rebuild ~716 ms →
|
|
25
|
+
* carry-forward near the 5.67 ms `isStale` cost on an unchanged tree
|
|
26
|
+
* (RUSH-2320 #3).
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildManifest(agent: AgentId, version: string, cwd: string, previous?: SyncManifest | null): SyncManifest;
|
|
20
29
|
/**
|
|
21
30
|
* True when any tracked resource has drifted from the stored manifest.
|
|
22
31
|
* Walks every resource type in turn and returns true at the first miss —
|
|
@@ -69,7 +69,16 @@ export function saveManifest(agent, version, manifest) {
|
|
|
69
69
|
catch { /* ignore */ }
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Snapshot current resource fingerprints for `agent@version`.
|
|
74
|
+
*
|
|
75
|
+
* When `previous` is supplied (the still-loaded post-guard manifest), any
|
|
76
|
+
* entry whose stored path/mtime/size still match via the checker's `isFresh`
|
|
77
|
+
* is carried forward without re-hashing. Measured: full rebuild ~716 ms →
|
|
78
|
+
* carry-forward near the 5.67 ms `isStale` cost on an unchanged tree
|
|
79
|
+
* (RUSH-2320 #3).
|
|
80
|
+
*/
|
|
81
|
+
export function buildManifest(agent, version, cwd, previous) {
|
|
73
82
|
const manifest = {
|
|
74
83
|
v: MANIFEST_VERSION,
|
|
75
84
|
syncedAt: new Date().toISOString(),
|
|
@@ -85,14 +94,30 @@ export function buildManifest(agent, version, cwd) {
|
|
|
85
94
|
};
|
|
86
95
|
for (const { checker, field } of STANDARD_CHECKERS) {
|
|
87
96
|
const target = manifest[field];
|
|
97
|
+
const prevMap = (previous?.[field] ?? {});
|
|
88
98
|
for (const name of checker.listNames(cwd)) {
|
|
99
|
+
const prev = prevMap[name];
|
|
100
|
+
if (prev !== undefined && checker.isFresh(name, prev, cwd)) {
|
|
101
|
+
target[name] = prev;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
89
104
|
const entry = checker.build(name, cwd);
|
|
90
105
|
if (entry !== null)
|
|
91
106
|
target[name] = entry;
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
if (previous?.rules && !isRulesStale(previous.rules, agent, version, cwd)) {
|
|
110
|
+
manifest.rules = previous.rules;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
manifest.rules = buildRules(agent, version, cwd);
|
|
114
|
+
}
|
|
115
|
+
if (previous?.permissions && !isPermissionsStale(previous.permissions)) {
|
|
116
|
+
manifest.permissions = previous.permissions;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
manifest.permissions = buildPermissions();
|
|
120
|
+
}
|
|
96
121
|
return manifest;
|
|
97
122
|
}
|
|
98
123
|
/**
|
|
@@ -76,6 +76,7 @@ export declare const loadUsage: ModuleLoader;
|
|
|
76
76
|
export declare const loadCost: ModuleLoader;
|
|
77
77
|
export declare const loadInsights: ModuleLoader;
|
|
78
78
|
export declare const loadPerf: ModuleLoader;
|
|
79
|
+
export declare const loadBench: ModuleLoader;
|
|
79
80
|
export declare const loadTrends: ModuleLoader;
|
|
80
81
|
export declare const loadOutput: ModuleLoader;
|
|
81
82
|
export declare const loadBudget: ModuleLoader;
|
|
@@ -54,6 +54,7 @@ export const loadUsage = async () => (await import('../../commands/usage.js')).r
|
|
|
54
54
|
export const loadCost = async () => (await import('../../commands/cost.js')).registerCostCommand;
|
|
55
55
|
export const loadInsights = async () => (await import('../../commands/insights.js')).registerInsightsCommand;
|
|
56
56
|
export const loadPerf = async () => (await import('../../commands/perf.js')).registerPerfCommand;
|
|
57
|
+
export const loadBench = async () => (await import('../../commands/bench.js')).registerBenchCommand;
|
|
57
58
|
// Thin deprecated alias of `agents insights mix` — no second mix implementation.
|
|
58
59
|
export const loadTrends = async () => (await import('../../commands/trends.js')).registerTrendsCommand;
|
|
59
60
|
export const loadOutput = async () => (await import('../../commands/output.js')).registerOutputCommand;
|
|
@@ -191,6 +192,7 @@ export const COMMAND_LOADERS = {
|
|
|
191
192
|
cost: [loadCost],
|
|
192
193
|
insights: [loadInsights],
|
|
193
194
|
perf: [loadPerf],
|
|
195
|
+
bench: [loadBench],
|
|
194
196
|
trends: [loadTrends],
|
|
195
197
|
output: [loadOutput],
|
|
196
198
|
budget: [loadBudget],
|
|
@@ -156,6 +156,7 @@ export declare class AgentProcess {
|
|
|
156
156
|
worktreePath: string | null;
|
|
157
157
|
hostName: string | null;
|
|
158
158
|
hostTarget: string | null;
|
|
159
|
+
hostIdentityFile: string | null;
|
|
159
160
|
repoPath: string | null;
|
|
160
161
|
remotePid: number | null;
|
|
161
162
|
remoteLog: string | null;
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -493,6 +493,7 @@ export class AgentProcess {
|
|
|
493
493
|
// (remotePid/remoteLog/remoteExit) — so the giant constructor stays untouched.
|
|
494
494
|
hostName = null;
|
|
495
495
|
hostTarget = null;
|
|
496
|
+
hostIdentityFile = null;
|
|
496
497
|
repoPath = null;
|
|
497
498
|
remotePid = null;
|
|
498
499
|
remoteLog = null;
|
|
@@ -692,6 +693,7 @@ export class AgentProcess {
|
|
|
692
693
|
const delta = pullRemoteLogDelta(this.hostTarget, {
|
|
693
694
|
remoteLog: this.remoteLog,
|
|
694
695
|
offset: this.remoteLogOffset,
|
|
696
|
+
extraSshArgs: this.hostIdentityFile ? ['-i', this.hostIdentityFile, '-o', 'IdentitiesOnly=yes'] : [],
|
|
695
697
|
});
|
|
696
698
|
if (delta && delta.bytes.length > 0) {
|
|
697
699
|
const stdoutPath = await this.getStdoutPath();
|
|
@@ -717,6 +719,7 @@ export class AgentProcess {
|
|
|
717
719
|
const res = sshExec(this.hostTarget, `cat ${this.remoteExit} 2>/dev/null`, {
|
|
718
720
|
timeoutMs: 8000,
|
|
719
721
|
multiplex: true,
|
|
722
|
+
extraSshArgs: this.hostIdentityFile ? ['-i', this.hostIdentityFile, '-o', 'IdentitiesOnly=yes'] : [],
|
|
720
723
|
});
|
|
721
724
|
exit = res.code === 0 && res.stdout.trim() !== '' ? res.stdout.trim() : null;
|
|
722
725
|
}
|
|
@@ -898,6 +901,7 @@ export class AgentProcess {
|
|
|
898
901
|
worktree_path: this.worktreePath,
|
|
899
902
|
host_name: this.hostName,
|
|
900
903
|
host_target: this.hostTarget,
|
|
904
|
+
host_identity_file: this.hostIdentityFile,
|
|
901
905
|
repo_path: this.repoPath,
|
|
902
906
|
remote_pid: this.remotePid,
|
|
903
907
|
remote_log: this.remoteLog,
|
|
@@ -952,6 +956,7 @@ export class AgentProcess {
|
|
|
952
956
|
// constructor signature stays fixed. Null on every pre-existing teammate.
|
|
953
957
|
agent.hostName = meta.host_name || null;
|
|
954
958
|
agent.hostTarget = meta.host_target || null;
|
|
959
|
+
agent.hostIdentityFile = meta.host_identity_file || null;
|
|
955
960
|
agent.repoPath = meta.repo_path || null;
|
|
956
961
|
agent.remotePid = typeof meta.remote_pid === 'number' ? meta.remote_pid : null;
|
|
957
962
|
agent.remoteLog = meta.remote_log || null;
|
|
@@ -980,7 +985,11 @@ export class AgentProcess {
|
|
|
980
985
|
// interpolate UNQUOTED so `$HOME` expands (shellQuote would defeat it).
|
|
981
986
|
const probe = `test -f ${this.remoteExit} && echo DEAD || ` +
|
|
982
987
|
`(kill -0 ${this.remotePid} 2>/dev/null && echo ALIVE || echo DEAD)`;
|
|
983
|
-
const res = sshExec(this.hostTarget, probe, {
|
|
988
|
+
const res = sshExec(this.hostTarget, probe, {
|
|
989
|
+
timeoutMs: 8000,
|
|
990
|
+
multiplex: true,
|
|
991
|
+
extraSshArgs: this.hostIdentityFile ? ['-i', this.hostIdentityFile, '-o', 'IdentitiesOnly=yes'] : [],
|
|
992
|
+
});
|
|
984
993
|
if (res.code === null)
|
|
985
994
|
return true; // transient ssh failure — don't reap early
|
|
986
995
|
return res.stdout.trim().endsWith('ALIVE');
|
|
@@ -1640,10 +1649,16 @@ export class AgentManager {
|
|
|
1640
1649
|
if (!host) {
|
|
1641
1650
|
throw new Error(`Cannot launch remote teammate ${agent.agentId}: device "${agent.hostName}" no longer resolves.`);
|
|
1642
1651
|
}
|
|
1643
|
-
|
|
1644
|
-
//
|
|
1652
|
+
agent.hostIdentityFile = host.identityFile ?? null;
|
|
1653
|
+
// Ensure agents-cli is present + the pin is installed on the host. A bare
|
|
1654
|
+
// agent name still warns (like dispatch.ts); a concrete agent.version pin
|
|
1655
|
+
// fails loud so the teammate never reports launched against a missing pin
|
|
1656
|
+
// (RUSH-2313).
|
|
1645
1657
|
try {
|
|
1646
|
-
const { warnings } = ensureHostReady(host, {
|
|
1658
|
+
const { warnings } = ensureHostReady(host, {
|
|
1659
|
+
agent: agent.agentType,
|
|
1660
|
+
version: agent.version ?? undefined,
|
|
1661
|
+
});
|
|
1647
1662
|
for (const w of warnings)
|
|
1648
1663
|
process.stderr.write(`[teams] warning: ${w}\n`);
|
|
1649
1664
|
}
|
|
@@ -1661,7 +1676,9 @@ export class AgentManager {
|
|
|
1661
1676
|
remoteCwd = agent.worktreePath;
|
|
1662
1677
|
}
|
|
1663
1678
|
else {
|
|
1664
|
-
const worktreePath = createRemoteWorktree(agent.hostTarget, agent.repoPath, agent.worktreeName
|
|
1679
|
+
const worktreePath = createRemoteWorktree(agent.hostTarget, agent.repoPath, agent.worktreeName, {
|
|
1680
|
+
extraSshArgs: agent.hostIdentityFile ? ['-i', agent.hostIdentityFile, '-o', 'IdentitiesOnly=yes'] : [],
|
|
1681
|
+
});
|
|
1665
1682
|
agent.worktreePath = worktreePath;
|
|
1666
1683
|
remoteCwd = worktreePath;
|
|
1667
1684
|
}
|
|
@@ -1743,9 +1760,12 @@ export class AgentManager {
|
|
|
1743
1760
|
}
|
|
1744
1761
|
const target = sshTargetFor(host);
|
|
1745
1762
|
const teamMeta = await getTeam(taskName);
|
|
1746
|
-
const repoRoot = ensureRemoteRepo(target, teamMeta?.repo ?? '', taskName
|
|
1763
|
+
const repoRoot = ensureRemoteRepo(target, teamMeta?.repo ?? '', taskName, {
|
|
1764
|
+
extraSshArgs: host.identityFile ? ['-i', host.identityFile, '-o', 'IdentitiesOnly=yes'] : [],
|
|
1765
|
+
});
|
|
1747
1766
|
agent.hostName = host.name;
|
|
1748
1767
|
agent.hostTarget = target;
|
|
1768
|
+
agent.hostIdentityFile = host.identityFile ?? null;
|
|
1749
1769
|
agent.repoPath = repoRoot;
|
|
1750
1770
|
await agent.saveMeta();
|
|
1751
1771
|
}
|
|
@@ -1864,11 +1884,12 @@ export class AgentManager {
|
|
|
1864
1884
|
return;
|
|
1865
1885
|
const byTarget = new Map();
|
|
1866
1886
|
for (const a of teammates) {
|
|
1867
|
-
const
|
|
1868
|
-
|
|
1869
|
-
|
|
1887
|
+
const key = `${a.hostTarget}\0${a.hostIdentityFile ?? ''}`;
|
|
1888
|
+
const group = byTarget.get(key) || { target: a.hostTarget, agents: [] };
|
|
1889
|
+
group.agents.push(a);
|
|
1890
|
+
byTarget.set(key, group);
|
|
1870
1891
|
}
|
|
1871
|
-
for (const
|
|
1892
|
+
for (const { target, agents } of byTarget.values()) {
|
|
1872
1893
|
// Emit one line per teammate: "<agentId> ALIVE|DEAD <exitOrEmpty>". A single
|
|
1873
1894
|
// round-trip over the multiplexed socket, regardless of teammate count.
|
|
1874
1895
|
const parts = agents.map((a) => {
|
|
@@ -1883,7 +1904,12 @@ export class AgentManager {
|
|
|
1883
1904
|
`elif kill -0 ${a.remotePid} 2>/dev/null; then printf 'ALIVE\\n'; ` +
|
|
1884
1905
|
`else printf 'DEAD\\n'; fi`);
|
|
1885
1906
|
});
|
|
1886
|
-
const
|
|
1907
|
+
const identityFile = agents[0]?.hostIdentityFile;
|
|
1908
|
+
const res = sshExec(target, parts.join('; '), {
|
|
1909
|
+
timeoutMs: 12000,
|
|
1910
|
+
multiplex: true,
|
|
1911
|
+
extraSshArgs: identityFile ? ['-i', identityFile, '-o', 'IdentitiesOnly=yes'] : [],
|
|
1912
|
+
});
|
|
1887
1913
|
if (res.code === null)
|
|
1888
1914
|
continue; // transient ssh failure — skip this wave, no snapshot
|
|
1889
1915
|
const snapshots = new Map();
|
|
@@ -2155,6 +2181,7 @@ export class AgentManager {
|
|
|
2155
2181
|
sshExec(agent.hostTarget, `kill -TERM -- -${agent.remotePid} 2>/dev/null`, {
|
|
2156
2182
|
timeoutMs: 10000,
|
|
2157
2183
|
multiplex: true,
|
|
2184
|
+
extraSshArgs: agent.hostIdentityFile ? ['-i', agent.hostIdentityFile, '-o', 'IdentitiesOnly=yes'] : [],
|
|
2158
2185
|
});
|
|
2159
2186
|
}
|
|
2160
2187
|
catch {
|
|
@@ -15,7 +15,7 @@ export declare function clearPlacementSignalCache(): void;
|
|
|
15
15
|
* excludes nor prefers them). Never throws — a probe failure degrades to a
|
|
16
16
|
* missing/partial signal.
|
|
17
17
|
*/
|
|
18
|
-
export declare function probePoolSignals(pool: string[], agent
|
|
18
|
+
export declare function probePoolSignals(pool: string[], agent?: AgentType, opts?: {
|
|
19
19
|
force?: boolean;
|
|
20
20
|
now?: number;
|
|
21
21
|
}): Promise<Map<string, DevicePlacementSignal>>;
|
|
@@ -7,26 +7,25 @@
|
|
|
7
7
|
*
|
|
8
8
|
* - reachability + headroom + load ← {@link probeFleetStats} (one parallel
|
|
9
9
|
* SSH fan-out over the pool; the local box is measured directly).
|
|
10
|
-
* - requested agent installed
|
|
10
|
+
* - requested agent installed + signed in (when known) ← a one-shot readiness
|
|
11
11
|
* probe per remote device ({@link buildReadyProbeCommand} → `agents view`),
|
|
12
12
|
* the local box via {@link checkCliAvailable}/{@link checkCliSignedIn}.
|
|
13
13
|
*
|
|
14
|
-
* The result is cached briefly per (pool, agent) so a `teams start` wave that
|
|
14
|
+
* The result is cached briefly per (pool, agent-or-any) so a `teams start` wave that
|
|
15
15
|
* places N teammates probes the pool ONCE, not N times — the roster-count part
|
|
16
16
|
* of the rank stays live (the pure pick recounts the roster each call), only the
|
|
17
17
|
* SSH-measured load/harness snapshot is reused within the TTL.
|
|
18
18
|
*
|
|
19
19
|
* All SSH here is via `execFile` (async, bounded) so the whole pool is probed in
|
|
20
20
|
* parallel; a slow or wedged box degrades to "no signal" instead of blocking the
|
|
21
|
-
* launch.
|
|
22
|
-
*
|
|
23
|
-
* only, and the install gate (not sign-in) is what drives the fail-loud.
|
|
21
|
+
* launch. The readiness payload is `agents view --json`, so remote and local
|
|
22
|
+
* candidates carry the same installed/sign-in verdict.
|
|
24
23
|
*/
|
|
25
24
|
import { execFile } from 'child_process';
|
|
26
25
|
import { probeFleetStats, headroom } from '../devices/health.js';
|
|
27
26
|
import { loadDevicesSync } from '../devices/registry.js';
|
|
28
27
|
import { buildSshInvocation, writeAskpassShim } from '../devices/connect.js';
|
|
29
|
-
import { buildReadyProbeCommand, parseReadyProbe, viewHasAgent } from '../hosts/ready.js';
|
|
28
|
+
import { buildReadyProbeCommand, parseReadyProbe, viewAgentSignedIn, viewHasAgent, } from '../hosts/ready.js';
|
|
30
29
|
import { localMachineId } from '../session/origin-machine.js';
|
|
31
30
|
import { normalizeHost } from '../machine-id.js';
|
|
32
31
|
import { checkCliAvailable, checkCliSignedIn } from './agents.js';
|
|
@@ -39,7 +38,7 @@ export const READY_PROBE_TIMEOUT_MS = 8_000;
|
|
|
39
38
|
export const SIGNAL_TTL_MS = 15_000;
|
|
40
39
|
const cache = new Map();
|
|
41
40
|
function cacheKey(pool, agent) {
|
|
42
|
-
return `${agent}::${[...pool].map(normalizeHost).sort().join(',')}`;
|
|
41
|
+
return `${agent ?? 'any-agent'}::${[...pool].map(normalizeHost).sort().join(',')}`;
|
|
43
42
|
}
|
|
44
43
|
/** Clear the probe cache — for tests and after a device-registry change. */
|
|
45
44
|
export function clearPlacementSignalCache() {
|
|
@@ -51,7 +50,7 @@ export function clearPlacementSignalCache() {
|
|
|
51
50
|
* failure) — reachability is then left to {@link probeFleetStats}; `false` when
|
|
52
51
|
* the box answered but agents-cli or the agent is absent (a genuine can't-run).
|
|
53
52
|
*/
|
|
54
|
-
function
|
|
53
|
+
function probeRemoteReadiness(device, agent) {
|
|
55
54
|
let args;
|
|
56
55
|
let env;
|
|
57
56
|
try {
|
|
@@ -62,18 +61,19 @@ function probeRemoteInstalled(device, agent) {
|
|
|
62
61
|
({ args, env } = buildSshInvocation(device, [cmd], shim, {}, { agentOnly: true }));
|
|
63
62
|
}
|
|
64
63
|
catch {
|
|
65
|
-
return Promise.resolve(undefined);
|
|
64
|
+
return Promise.resolve({ installed: undefined, signedIn: undefined });
|
|
66
65
|
}
|
|
67
66
|
return new Promise((resolve) => {
|
|
68
67
|
execFile('ssh', args, { encoding: 'utf-8', env: { ...process.env, ...env }, timeout: READY_PROBE_TIMEOUT_MS }, (err, stdout) => {
|
|
69
68
|
if (err || !stdout)
|
|
70
|
-
return resolve(undefined);
|
|
69
|
+
return resolve({ installed: undefined, signedIn: undefined });
|
|
71
70
|
const probe = parseReadyProbe(stdout);
|
|
72
71
|
if (!probe.reachable)
|
|
73
|
-
return resolve(
|
|
72
|
+
return resolve({ installed: undefined, signedIn: undefined });
|
|
74
73
|
if (!probe.version)
|
|
75
|
-
return resolve(
|
|
76
|
-
|
|
74
|
+
return resolve({ installed: false, signedIn: false });
|
|
75
|
+
const installed = viewHasAgent(probe.view, agent);
|
|
76
|
+
resolve({ installed, signedIn: installed ? viewAgentSignedIn(probe.view, agent) : false });
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
79
|
}
|
|
@@ -103,17 +103,17 @@ export async function probePoolSignals(pool, agent, opts = {}) {
|
|
|
103
103
|
}
|
|
104
104
|
const selfProfile = profiles.find((d) => normalizeHost(d.name) === self);
|
|
105
105
|
const stats = await probeFleetStats(profiles, { selfName: selfProfile?.name });
|
|
106
|
-
const installed = new Map(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
const installed = new Map(agent
|
|
107
|
+
? await Promise.all(profiles.map(async (d) => {
|
|
108
|
+
const isSelf = normalizeHost(d.name) === self;
|
|
109
|
+
if (isSelf) {
|
|
110
|
+
const inst = checkCliAvailable(agent)[0];
|
|
111
|
+
const signedIn = inst ? await checkCliSignedIn(agent) : undefined;
|
|
112
|
+
return [d.name, { installed: inst, signedIn }];
|
|
113
|
+
}
|
|
114
|
+
return [d.name, await probeRemoteReadiness(d, agent)];
|
|
115
|
+
}))
|
|
116
|
+
: []);
|
|
117
117
|
const signals = new Map();
|
|
118
118
|
for (const name of pool) {
|
|
119
119
|
const d = lookup(name);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
interface RemoteSshOptions {
|
|
2
|
+
extraSshArgs?: string[];
|
|
3
|
+
}
|
|
1
4
|
/**
|
|
2
5
|
* Render a path for safe interpolation into a REMOTE shell, expanding a leading
|
|
3
6
|
* `~`/`~/` to `"$HOME"` so it resolves on the host. A plain `shellQuote('~/x')`
|
|
@@ -13,15 +16,15 @@ export declare function remotePathExpr(p: string): string;
|
|
|
13
16
|
* downstream remote command (launch `cd`, worktree create, polling) works from an
|
|
14
17
|
* absolute path with no tilde-expansion hazard.
|
|
15
18
|
*/
|
|
16
|
-
export declare function resolveRemoteRepoRoot(target: string, repoPath: string): string | null;
|
|
19
|
+
export declare function resolveRemoteRepoRoot(target: string, repoPath: string, opts?: RemoteSshOptions): string | null;
|
|
17
20
|
/** True when `repoPath` is a git working tree on the host. */
|
|
18
|
-
export declare function isRemoteGitRepo(target: string, repoPath: string): boolean;
|
|
21
|
+
export declare function isRemoteGitRepo(target: string, repoPath: string, opts?: RemoteSshOptions): boolean;
|
|
19
22
|
/**
|
|
20
23
|
* Resolve the host repo's default branch name (origin/HEAD → `main`/`master`/…).
|
|
21
24
|
* Falls back to `main` when origin/HEAD isn't set, matching the local recipe's
|
|
22
25
|
* `remote set-head` step.
|
|
23
26
|
*/
|
|
24
|
-
export declare function remoteDefaultBranch(target: string, repoPath: string): string;
|
|
27
|
+
export declare function remoteDefaultBranch(target: string, repoPath: string, opts?: RemoteSshOptions): string;
|
|
25
28
|
/**
|
|
26
29
|
* Create a worktree on the host for a teammate, branched off the freshly-fetched
|
|
27
30
|
* default branch. Returns the absolute worktree path on the host.
|
|
@@ -30,7 +33,7 @@ export declare function remoteDefaultBranch(target: string, repoPath: string): s
|
|
|
30
33
|
* `worktree add -b … origin/<default>` so a stale checkout can't fork the
|
|
31
34
|
* teammate off old code.
|
|
32
35
|
*/
|
|
33
|
-
export declare function createRemoteWorktree(target: string, repoPath: string, worktreeName: string): string;
|
|
36
|
+
export declare function createRemoteWorktree(target: string, repoPath: string, worktreeName: string, opts?: RemoteSshOptions): string;
|
|
34
37
|
/**
|
|
35
38
|
* Ensure the team's repo is present on the host and return its absolute git
|
|
36
39
|
* root. The canonical location is `~/.agents/repos/<slug>` (slug = sanitized
|
|
@@ -45,12 +48,13 @@ export declare function createRemoteWorktree(target: string, repoPath: string, w
|
|
|
45
48
|
* expands `$HOME` — single-quoting the tilde would defeat expansion, the same
|
|
46
49
|
* hazard dispatch.ts avoids.
|
|
47
50
|
*/
|
|
48
|
-
export declare function ensureRemoteRepo(target: string, repo: string, slug: string): string;
|
|
51
|
+
export declare function ensureRemoteRepo(target: string, repo: string, slug: string, opts?: RemoteSshOptions): string;
|
|
49
52
|
/** True when the host worktree has uncommitted changes (staged or unstaged). */
|
|
50
|
-
export declare function remoteWorktreeDirty(target: string, worktreePath: string): boolean;
|
|
53
|
+
export declare function remoteWorktreeDirty(target: string, worktreePath: string, opts?: RemoteSshOptions): boolean;
|
|
51
54
|
/**
|
|
52
55
|
* Remove a host worktree and (optionally) its branch. Best-effort: prunes on a
|
|
53
56
|
* "not a working tree" error and ignores a missing branch, matching local
|
|
54
57
|
* `removeWorktree` semantics so cleanup never throws on a half-gone worktree.
|
|
55
58
|
*/
|
|
56
|
-
export declare function removeRemoteWorktree(target: string, repoPath: string, worktreeName: string, deleteBranch?: boolean): void;
|
|
59
|
+
export declare function removeRemoteWorktree(target: string, repoPath: string, worktreeName: string, deleteBranch?: boolean, opts?: RemoteSshOptions): void;
|
|
60
|
+
export {};
|