@phnx-labs/agents-cli 1.20.34 → 1.20.36
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 +113 -0
- package/LICENSE +185 -21
- package/README.md +13 -5
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/computer.js +81 -0
- package/dist/commands/cost.js +17 -12
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +54 -97
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +283 -12
- package/dist/commands/hosts.js +37 -29
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +62 -16
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +196 -91
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +221 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +143 -1
- package/dist/commands/sessions.js +628 -80
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/view.d.ts +3 -0
- package/dist/commands/view.js +19 -8
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +82 -32
- package/dist/lib/agents.js +30 -9
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.d.ts +8 -0
- package/dist/lib/browser/drivers/ssh.js +77 -16
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +37 -12
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +183 -23
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +92 -0
- package/dist/lib/daemon.js +222 -18
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +10 -3
- package/dist/lib/events.js +101 -24
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +100 -12
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +135 -10
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +45 -13
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +74 -0
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +51 -7
- package/dist/lib/hosts/ready.js +98 -25
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +22 -0
- package/dist/lib/hosts/tasks.js +29 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +26 -0
- package/dist/lib/picker.js +127 -0
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +61 -10
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +61 -0
- package/dist/lib/session/active.js +196 -17
- package/dist/lib/session/discover.d.ts +37 -0
- package/dist/lib/session/discover.js +204 -27
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +286 -2
- package/dist/lib/session/pid-registry.d.ts +22 -0
- package/dist/lib/session/pid-registry.js +104 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +153 -0
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +213 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +18 -8
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +1 -7
- package/dist/lib/session/sync/config.js +4 -11
- package/dist/lib/session/types.d.ts +18 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +44 -6
- package/dist/lib/shims.js +143 -57
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +47 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +62 -63
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +8 -0
- package/dist/lib/startup/command-registry.js +17 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +17 -9
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/registry.js +25 -9
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +19 -0
- package/dist/lib/terminal/backends/index.js +31 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +17 -0
- package/dist/lib/terminal/index.js +8 -0
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/types.d.ts +7 -0
- package/dist/lib/versions.d.ts +30 -3
- package/dist/lib/versions.js +129 -18
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-machine fan-out for `agents sessions --active`.
|
|
3
|
+
*
|
|
4
|
+
* A single `getActiveSessions()` only sees the local machine. To show the whole
|
|
5
|
+
* fleet in one view, we run `agents sessions --active --json --local` on each
|
|
6
|
+
* peer over SSH and merge the parsed results, tagging every row with the machine
|
|
7
|
+
* it came from so the renderer can bucket by computer.
|
|
8
|
+
*
|
|
9
|
+
* Peers are the registered, online devices from `ag devices` (or an explicit
|
|
10
|
+
* `--host` list). `--local` on the remote invocation is critical: it stops the
|
|
11
|
+
* peer from fanning out to *its* devices, so the sweep never recurses.
|
|
12
|
+
*
|
|
13
|
+
* A dead or slow host is skipped with a stderr note, never fatal — one asleep
|
|
14
|
+
* laptop must not blank the whole view. SSH runs are async + parallel (a fresh
|
|
15
|
+
* `spawn`, not the sync `sshExec`) so N peers cost one round-trip, not N.
|
|
16
|
+
*/
|
|
17
|
+
import { spawn } from 'child_process';
|
|
18
|
+
import chalk from 'chalk';
|
|
19
|
+
import { SSH_OPTS, controlOpts, assertValidSshTarget, shellQuote } from '../ssh-exec.js';
|
|
20
|
+
import { sshTargetFor } from '../devices/connect.js';
|
|
21
|
+
import { loadDevices } from '../devices/registry.js';
|
|
22
|
+
import { remoteShellFor, buildWindowsAgentsCommand } from '../hosts/remote-cmd.js';
|
|
23
|
+
import { resolveRemoteOsSync } from '../hosts/remote-os.js';
|
|
24
|
+
import { machineId, normalizeHost } from './sync/config.js';
|
|
25
|
+
/** Per-host SSH budget. Slightly above SSH_OPTS' ConnectTimeout=10 so a
|
|
26
|
+
* reachable-but-slow remote still answers before we give up. */
|
|
27
|
+
const REMOTE_TIMEOUT_MS = 12_000;
|
|
28
|
+
/**
|
|
29
|
+
* Recursion guard, passed as an env var (not a CLI flag) so an OLDER remote
|
|
30
|
+
* `agents` that predates this feature ignores it harmlessly instead of erroring
|
|
31
|
+
* on an unknown option. A remote new enough to fan out reads it and stays local.
|
|
32
|
+
*/
|
|
33
|
+
export const NO_FANOUT_ENV = 'AGENTS_SESSIONS_LOCAL';
|
|
34
|
+
/** The command run on each peer: answer for itself, as JSON, without recursing.
|
|
35
|
+
* A Windows peer gets a PowerShell invocation (ssh lands in cmd.exe/PowerShell
|
|
36
|
+
* there, where `bash -lc` is not a command); every other OS keeps `bash -lc`. */
|
|
37
|
+
function remoteActiveCommand(os) {
|
|
38
|
+
if (remoteShellFor(os) === 'powershell') {
|
|
39
|
+
return buildWindowsAgentsCommand({
|
|
40
|
+
args: ['sessions', '--active', '--json'],
|
|
41
|
+
env: { [NO_FANOUT_ENV]: '1' },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const inner = `${NO_FANOUT_ENV}=1 agents sessions --active --json`;
|
|
45
|
+
return `bash -lc ${shellQuote(inner)}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse a peer's `--active --json` stdout into active sessions, tagging each
|
|
49
|
+
* with `machine`. Defensive against version skew / partial output: non-JSON or
|
|
50
|
+
* a non-array yields `[]`, and non-object entries are dropped rather than
|
|
51
|
+
* throwing. Exported for unit testing without a live tailnet.
|
|
52
|
+
*/
|
|
53
|
+
export function parseRemoteActive(stdout, machine) {
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = JSON.parse(stdout);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
if (!Array.isArray(parsed))
|
|
62
|
+
return [];
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const x of parsed) {
|
|
65
|
+
if (x && typeof x === 'object' && !Array.isArray(x)) {
|
|
66
|
+
out.push({ ...x, machine });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** Run one remote `agents sessions --active --json --local` and capture stdout.
|
|
72
|
+
* Resolves `{ code: null }` on spawn error or timeout (host treated as dead). */
|
|
73
|
+
function sshCapture(target, remoteCmd, timeoutMs) {
|
|
74
|
+
assertValidSshTarget(target);
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
const args = [...SSH_OPTS, ...controlOpts(), target, remoteCmd];
|
|
77
|
+
const child = spawn('ssh', args, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
78
|
+
let stdout = '';
|
|
79
|
+
let settled = false;
|
|
80
|
+
const done = (code) => {
|
|
81
|
+
if (settled)
|
|
82
|
+
return;
|
|
83
|
+
settled = true;
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
resolve({ code, stdout });
|
|
86
|
+
};
|
|
87
|
+
const timer = setTimeout(() => { child.kill('SIGKILL'); done(null); }, timeoutMs);
|
|
88
|
+
child.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
89
|
+
child.on('error', () => done(null));
|
|
90
|
+
child.on('close', (code) => done(code));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function fetchByTarget(target, machine, display, os) {
|
|
94
|
+
const { code, stdout } = await sshCapture(target, remoteActiveCommand(os), REMOTE_TIMEOUT_MS);
|
|
95
|
+
if (code !== 0) {
|
|
96
|
+
process.stderr.write(chalk.gray(` ${display}: unreachable or no agents CLI — skipped\n`));
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return parseRemoteActive(stdout, machine);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Gather active sessions from other machines. With an explicit `hosts` list
|
|
103
|
+
* (from `--host`), fan out to exactly those. Otherwise sweep the registered,
|
|
104
|
+
* online devices from `ag devices`, excluding this machine and any without an
|
|
105
|
+
* address. Results from all peers run in parallel and are flattened.
|
|
106
|
+
*/
|
|
107
|
+
export async function gatherRemoteActive(hosts) {
|
|
108
|
+
const self = machineId();
|
|
109
|
+
const targets = [];
|
|
110
|
+
if (hosts && hosts.length > 0) {
|
|
111
|
+
for (const h of hosts) {
|
|
112
|
+
try {
|
|
113
|
+
assertValidSshTarget(h);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
process.stderr.write(chalk.gray(` ${h}: not a valid ssh target — skipped\n`));
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const bareHost = h.split('@').pop() || h;
|
|
120
|
+
// Resolve the OS by the name the user passed (a bare alias like `win-mini`
|
|
121
|
+
// matches a device-registry entry; a raw `user@host` falls back to POSIX).
|
|
122
|
+
targets.push({ target: h, machine: normalizeHost(bareHost), name: h, os: resolveRemoteOsSync(h) });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
let reg;
|
|
127
|
+
try {
|
|
128
|
+
reg = await loadDevices();
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return { sessions: [], deviceCount: 0 };
|
|
132
|
+
}
|
|
133
|
+
for (const d of Object.values(reg)) {
|
|
134
|
+
if (d.tailscale?.online !== true)
|
|
135
|
+
continue;
|
|
136
|
+
if (normalizeHost(d.name) === self)
|
|
137
|
+
continue;
|
|
138
|
+
// Only machines that can actually run the CLI. iOS/tablet nodes register as
|
|
139
|
+
// `unknown` platform and can never answer, so skip them rather than burn a
|
|
140
|
+
// full ConnectTimeout on each.
|
|
141
|
+
if (d.platform !== 'windows' && d.platform !== 'linux' && d.platform !== 'macos')
|
|
142
|
+
continue;
|
|
143
|
+
try {
|
|
144
|
+
targets.push({ target: sshTargetFor(d), machine: normalizeHost(d.name), name: d.name, os: d.platform });
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// No address on the profile — nothing to dial; skip silently.
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const results = await Promise.all(targets.map((t) => fetchByTarget(t.target, t.machine, t.name, t.os)));
|
|
152
|
+
return { sessions: results.flat(), deviceCount: targets.length };
|
|
153
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SessionMeta } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The command run on each peer: answer for itself, as JSON, without recursing.
|
|
4
|
+
* `forwardedArgs` carry the caller's own query/filters (already including the
|
|
5
|
+
* leading `sessions` and a `--json`) so each peer returns a comparable slice.
|
|
6
|
+
* A Windows peer gets a PowerShell invocation (ssh lands in cmd.exe/PowerShell
|
|
7
|
+
* there, where `bash -lc` is not a command); every other OS keeps `bash -lc`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function remoteListCommand(forwardedArgs: string[], os?: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Parse a peer's `sessions --json` stdout into `SessionMeta[]`, tagging each
|
|
12
|
+
* with `machine`. Defensive against version skew / partial output: non-JSON or
|
|
13
|
+
* a non-array yields `[]`, and non-object entries are dropped rather than
|
|
14
|
+
* throwing. The `machine` we dialed always wins over any value the peer set on
|
|
15
|
+
* its own rows, so grouping keys off the computer we asked. Exported for unit
|
|
16
|
+
* testing without a live tailnet.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseRemoteList(stdout: string, machine: string): SessionMeta[];
|
|
19
|
+
export interface RemoteListResult {
|
|
20
|
+
sessions: SessionMeta[];
|
|
21
|
+
/** How many peer machines we attempted to reach (drives the empty-fleet tip). */
|
|
22
|
+
deviceCount: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Gather listing sessions from other machines. With an explicit `hosts` list
|
|
26
|
+
* (from `--host`), fan out to exactly those. Otherwise sweep the registered,
|
|
27
|
+
* online devices from `ag devices`, excluding this machine and any without an
|
|
28
|
+
* address. `forwardedArgs` are the caller's own sessions args (query + filters,
|
|
29
|
+
* already `--json`) so every peer returns the same slice this machine asked for.
|
|
30
|
+
*/
|
|
31
|
+
export declare function gatherRemoteList(forwardedArgs: string[], hosts?: string[]): Promise<RemoteListResult>;
|
|
32
|
+
/** Resolve a peer's SSH target (and OS) from the device registry by its
|
|
33
|
+
* normalized machine id — the same id the fan-out tags rows with. Returns
|
|
34
|
+
* undefined when no registered device with an address matches. */
|
|
35
|
+
export declare function resolvePeerTarget(machine: string): Promise<{
|
|
36
|
+
target: string;
|
|
37
|
+
os?: string;
|
|
38
|
+
} | undefined>;
|
|
39
|
+
/**
|
|
40
|
+
* Run `agents <args>` ON a peer over SSH, attached to this terminal (inherited
|
|
41
|
+
* stdio). `args` is the full arg vector after the binary — callers pass e.g.
|
|
42
|
+
* `['sessions', id, '--markdown']` or `['sessions', 'resume', id]`. Used when a
|
|
43
|
+
* picked session lives on another machine: its transcript and agent binary are
|
|
44
|
+
* there, so both reading (no TTY) and resuming (TTY) must execute on the peer —
|
|
45
|
+
* not via a local `--host` hop, which would discover locally and dead-end for a
|
|
46
|
+
* session that exists only on the peer. Resolves 'no-target' when the machine
|
|
47
|
+
* isn't a dialable registered device; the caller surfaces a clear message.
|
|
48
|
+
*/
|
|
49
|
+
export declare function runOnPeer(args: string[], machine: string, opts?: {
|
|
50
|
+
tty?: boolean;
|
|
51
|
+
}): Promise<'ok' | 'no-target'>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-machine fan-out for the default `agents sessions` listing.
|
|
3
|
+
*
|
|
4
|
+
* `discoverSessions()` only scans the local disk. To browse the whole fleet in
|
|
5
|
+
* one list — without syncing anything — we run `agents sessions <same query>
|
|
6
|
+
* --json` on each peer over SSH and merge the parsed `SessionMeta[]`, tagging
|
|
7
|
+
* every row with the machine it came from so the picker/table can label and
|
|
8
|
+
* group by computer.
|
|
9
|
+
*
|
|
10
|
+
* This is the browse-listing sibling of `remote-active.ts` (which fans out
|
|
11
|
+
* `--active`): same transport, same device set, same recursion guard. The peer
|
|
12
|
+
* runs with `AGENTS_SESSIONS_LOCAL=1` so it answers only for itself and the
|
|
13
|
+
* sweep never recurses. A dead or slow host is skipped with a stderr note,
|
|
14
|
+
* never fatal — one asleep laptop must not blank the list.
|
|
15
|
+
*/
|
|
16
|
+
import { spawn } from 'child_process';
|
|
17
|
+
import chalk from 'chalk';
|
|
18
|
+
import { SSH_OPTS, controlOpts, assertValidSshTarget, shellQuote } from '../ssh-exec.js';
|
|
19
|
+
import { sshTargetFor } from '../devices/connect.js';
|
|
20
|
+
import { loadDevices } from '../devices/registry.js';
|
|
21
|
+
import { remoteShellFor, buildWindowsAgentsCommand } from '../hosts/remote-cmd.js';
|
|
22
|
+
import { resolveRemoteOsSync } from '../hosts/remote-os.js';
|
|
23
|
+
import { machineId, normalizeHost } from './sync/config.js';
|
|
24
|
+
import { NO_FANOUT_ENV } from './remote-active.js';
|
|
25
|
+
import { terminalWidth } from './width.js';
|
|
26
|
+
/** Per-host SSH budget. Slightly above SSH_OPTS' ConnectTimeout=10 so a
|
|
27
|
+
* reachable-but-slow remote still answers before we give up. */
|
|
28
|
+
const REMOTE_TIMEOUT_MS = 12_000;
|
|
29
|
+
/**
|
|
30
|
+
* The command run on each peer: answer for itself, as JSON, without recursing.
|
|
31
|
+
* `forwardedArgs` carry the caller's own query/filters (already including the
|
|
32
|
+
* leading `sessions` and a `--json`) so each peer returns a comparable slice.
|
|
33
|
+
* A Windows peer gets a PowerShell invocation (ssh lands in cmd.exe/PowerShell
|
|
34
|
+
* there, where `bash -lc` is not a command); every other OS keeps `bash -lc`.
|
|
35
|
+
*/
|
|
36
|
+
export function remoteListCommand(forwardedArgs, os) {
|
|
37
|
+
if (remoteShellFor(os) === 'powershell') {
|
|
38
|
+
return buildWindowsAgentsCommand({
|
|
39
|
+
args: forwardedArgs,
|
|
40
|
+
env: { [NO_FANOUT_ENV]: '1' },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const inner = [`${NO_FANOUT_ENV}=1`, 'agents', ...forwardedArgs].map((t, i) => i === 0 ? t : shellQuote(t)).join(' ');
|
|
44
|
+
return `bash -lc ${shellQuote(inner)}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse a peer's `sessions --json` stdout into `SessionMeta[]`, tagging each
|
|
48
|
+
* with `machine`. Defensive against version skew / partial output: non-JSON or
|
|
49
|
+
* a non-array yields `[]`, and non-object entries are dropped rather than
|
|
50
|
+
* throwing. The `machine` we dialed always wins over any value the peer set on
|
|
51
|
+
* its own rows, so grouping keys off the computer we asked. Exported for unit
|
|
52
|
+
* testing without a live tailnet.
|
|
53
|
+
*/
|
|
54
|
+
export function parseRemoteList(stdout, machine) {
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(stdout);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(parsed))
|
|
63
|
+
return [];
|
|
64
|
+
const out = [];
|
|
65
|
+
for (const x of parsed) {
|
|
66
|
+
if (x && typeof x === 'object' && !Array.isArray(x)) {
|
|
67
|
+
// `_remote` marks these as living on the peer's disk (not a local mirror),
|
|
68
|
+
// so the picker routes read/resume back over SSH instead of the local FS.
|
|
69
|
+
out.push({ ...x, machine, _remote: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/** Run one remote `agents sessions … --json` and capture stdout. Resolves
|
|
75
|
+
* `{ code: null }` on spawn error or timeout (host treated as dead). */
|
|
76
|
+
function sshCapture(target, remoteCmd, timeoutMs) {
|
|
77
|
+
assertValidSshTarget(target);
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
const args = [...SSH_OPTS, ...controlOpts(), target, remoteCmd];
|
|
80
|
+
const child = spawn('ssh', args, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
81
|
+
let stdout = '';
|
|
82
|
+
let settled = false;
|
|
83
|
+
const done = (code) => {
|
|
84
|
+
if (settled)
|
|
85
|
+
return;
|
|
86
|
+
settled = true;
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
resolve({ code, stdout });
|
|
89
|
+
};
|
|
90
|
+
const timer = setTimeout(() => { child.kill('SIGKILL'); done(null); }, timeoutMs);
|
|
91
|
+
child.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
92
|
+
child.on('error', () => done(null));
|
|
93
|
+
child.on('close', (code) => done(code));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async function fetchByTarget(target, machine, display, forwardedArgs, os) {
|
|
97
|
+
const { code, stdout } = await sshCapture(target, remoteListCommand(forwardedArgs, os), REMOTE_TIMEOUT_MS);
|
|
98
|
+
if (code !== 0) {
|
|
99
|
+
process.stderr.write(chalk.gray(` ${display}: unreachable or no agents CLI — skipped\n`));
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
return parseRemoteList(stdout, machine);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Gather listing sessions from other machines. With an explicit `hosts` list
|
|
106
|
+
* (from `--host`), fan out to exactly those. Otherwise sweep the registered,
|
|
107
|
+
* online devices from `ag devices`, excluding this machine and any without an
|
|
108
|
+
* address. `forwardedArgs` are the caller's own sessions args (query + filters,
|
|
109
|
+
* already `--json`) so every peer returns the same slice this machine asked for.
|
|
110
|
+
*/
|
|
111
|
+
export async function gatherRemoteList(forwardedArgs, hosts) {
|
|
112
|
+
const self = machineId();
|
|
113
|
+
const targets = [];
|
|
114
|
+
if (hosts && hosts.length > 0) {
|
|
115
|
+
for (const h of hosts) {
|
|
116
|
+
try {
|
|
117
|
+
assertValidSshTarget(h);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
process.stderr.write(chalk.gray(` ${h}: not a valid ssh target — skipped\n`));
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const bareHost = h.split('@').pop() || h;
|
|
124
|
+
targets.push({ target: h, machine: normalizeHost(bareHost), name: h, os: resolveRemoteOsSync(h) });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
let reg;
|
|
129
|
+
try {
|
|
130
|
+
reg = await loadDevices();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return { sessions: [], deviceCount: 0 };
|
|
134
|
+
}
|
|
135
|
+
for (const d of Object.values(reg)) {
|
|
136
|
+
if (d.tailscale?.online !== true)
|
|
137
|
+
continue;
|
|
138
|
+
if (normalizeHost(d.name) === self)
|
|
139
|
+
continue;
|
|
140
|
+
// Only machines that can actually run the CLI. iOS/tablet nodes register as
|
|
141
|
+
// `unknown` platform and can never answer, so skip them rather than burn a
|
|
142
|
+
// full ConnectTimeout on each.
|
|
143
|
+
if (d.platform !== 'windows' && d.platform !== 'linux' && d.platform !== 'macos')
|
|
144
|
+
continue;
|
|
145
|
+
try {
|
|
146
|
+
targets.push({ target: sshTargetFor(d), machine: normalizeHost(d.name), name: d.name, os: d.platform });
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// No address on the profile — nothing to dial; skip silently.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const results = await Promise.all(targets.map((t) => fetchByTarget(t.target, t.machine, t.name, forwardedArgs, t.os)));
|
|
154
|
+
return { sessions: results.flat(), deviceCount: targets.length };
|
|
155
|
+
}
|
|
156
|
+
/** Resolve a peer's SSH target (and OS) from the device registry by its
|
|
157
|
+
* normalized machine id — the same id the fan-out tags rows with. Returns
|
|
158
|
+
* undefined when no registered device with an address matches. */
|
|
159
|
+
export async function resolvePeerTarget(machine) {
|
|
160
|
+
let reg;
|
|
161
|
+
try {
|
|
162
|
+
reg = await loadDevices();
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
for (const d of Object.values(reg)) {
|
|
168
|
+
if (normalizeHost(d.name) !== machine)
|
|
169
|
+
continue;
|
|
170
|
+
try {
|
|
171
|
+
return { target: sshTargetFor(d), os: d.platform };
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return undefined; // matched the machine, but it has no address to dial
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Run `agents <args>` ON a peer over SSH, attached to this terminal (inherited
|
|
181
|
+
* stdio). `args` is the full arg vector after the binary — callers pass e.g.
|
|
182
|
+
* `['sessions', id, '--markdown']` or `['sessions', 'resume', id]`. Used when a
|
|
183
|
+
* picked session lives on another machine: its transcript and agent binary are
|
|
184
|
+
* there, so both reading (no TTY) and resuming (TTY) must execute on the peer —
|
|
185
|
+
* not via a local `--host` hop, which would discover locally and dead-end for a
|
|
186
|
+
* session that exists only on the peer. Resolves 'no-target' when the machine
|
|
187
|
+
* isn't a dialable registered device; the caller surfaces a clear message.
|
|
188
|
+
*/
|
|
189
|
+
export async function runOnPeer(args, machine, opts = {}) {
|
|
190
|
+
const peer = await resolvePeerTarget(machine);
|
|
191
|
+
if (!peer)
|
|
192
|
+
return 'no-target';
|
|
193
|
+
assertValidSshTarget(peer.target); // registry-sourced, but validate like the fan-out does
|
|
194
|
+
const cols = terminalWidth();
|
|
195
|
+
const remoteCmd = remoteShellFor(peer.os) === 'powershell'
|
|
196
|
+
? buildWindowsAgentsCommand({ args, env: cols > 0 ? { COLUMNS: String(cols) } : undefined })
|
|
197
|
+
: `bash -lc ${shellQuote((cols > 0 ? [`COLUMNS=${cols}`] : []).concat(['agents', ...args].map(shellQuote)).join(' '))}`;
|
|
198
|
+
const sshArgs = [...SSH_OPTS, ...controlOpts()];
|
|
199
|
+
if (opts.tty)
|
|
200
|
+
sshArgs.push('-tt'); // force a PTY so the resumed agent is interactive
|
|
201
|
+
sshArgs.push(peer.target, remoteCmd);
|
|
202
|
+
return new Promise((resolve) => {
|
|
203
|
+
const child = spawn('ssh', sshArgs, { stdio: 'inherit' });
|
|
204
|
+
// ssh prints its own connection errors to the inherited stderr; a spawn
|
|
205
|
+
// failure (e.g. ssh not on PATH) has no such output, so name it. Either way
|
|
206
|
+
// we resolve once it settles so the picker flow completes.
|
|
207
|
+
child.on('error', (err) => {
|
|
208
|
+
process.stderr.write(chalk.red(`Failed to reach ${machine}: ${err?.message ?? 'ssh failed to launch'}\n`));
|
|
209
|
+
resolve('ok');
|
|
210
|
+
});
|
|
211
|
+
child.on('close', () => resolve('ok'));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
@@ -22,8 +22,14 @@ export declare function buildForwardedArgs(argv: string[], hosts?: Set<string>):
|
|
|
22
22
|
* Build the single remote command string for `ssh <host> <cmd>`. Forwarded args
|
|
23
23
|
* are quoted for the inner login shell, then the whole `agents …` invocation is
|
|
24
24
|
* quoted again so it survives `bash -lc <...>`.
|
|
25
|
+
*
|
|
26
|
+
* `os` selects the remote shell: a Windows host gets a PowerShell invocation
|
|
27
|
+
* (ssh lands in cmd.exe/PowerShell there, where `bash -lc` does not exist);
|
|
28
|
+
* anything else — including unknown/absent — keeps the POSIX form unchanged.
|
|
29
|
+
* The forwarded terminal width rides across as an env var either way so the
|
|
30
|
+
* remote renders its table to the local screen.
|
|
25
31
|
*/
|
|
26
|
-
export declare function buildRemoteCommand(forwardedArgs: string[], columns?: number): string;
|
|
32
|
+
export declare function buildRemoteCommand(forwardedArgs: string[], columns?: number, os?: string): string;
|
|
27
33
|
/** The four outcomes of one `ssh <host> agents sessions …` invocation. */
|
|
28
34
|
export type SshOutcome = 'ok' | 'unreachable' | 'query-failed' | 'spawn-error';
|
|
29
35
|
/**
|
|
@@ -27,6 +27,9 @@ import { join } from 'path';
|
|
|
27
27
|
import { createHash } from 'crypto';
|
|
28
28
|
import chalk from 'chalk';
|
|
29
29
|
import { getCacheDir } from '../state.js';
|
|
30
|
+
import { SSH_OPTS, controlOpts } from '../ssh-exec.js';
|
|
31
|
+
import { remoteShellFor, buildWindowsAgentsCommand } from '../hosts/remote-cmd.js';
|
|
32
|
+
import { resolveRemoteOsSync } from '../hosts/remote-os.js';
|
|
30
33
|
import { formatRelativeTime } from './relative-time.js';
|
|
31
34
|
import { terminalWidth } from './width.js';
|
|
32
35
|
/**
|
|
@@ -87,8 +90,18 @@ export function buildForwardedArgs(argv, hosts = new Set()) {
|
|
|
87
90
|
* Build the single remote command string for `ssh <host> <cmd>`. Forwarded args
|
|
88
91
|
* are quoted for the inner login shell, then the whole `agents …` invocation is
|
|
89
92
|
* quoted again so it survives `bash -lc <...>`.
|
|
93
|
+
*
|
|
94
|
+
* `os` selects the remote shell: a Windows host gets a PowerShell invocation
|
|
95
|
+
* (ssh lands in cmd.exe/PowerShell there, where `bash -lc` does not exist);
|
|
96
|
+
* anything else — including unknown/absent — keeps the POSIX form unchanged.
|
|
97
|
+
* The forwarded terminal width rides across as an env var either way so the
|
|
98
|
+
* remote renders its table to the local screen.
|
|
90
99
|
*/
|
|
91
|
-
export function buildRemoteCommand(forwardedArgs, columns) {
|
|
100
|
+
export function buildRemoteCommand(forwardedArgs, columns, os) {
|
|
101
|
+
if (remoteShellFor(os) === 'powershell') {
|
|
102
|
+
const env = columns && columns > 0 ? { COLUMNS: String(columns) } : undefined;
|
|
103
|
+
return buildWindowsAgentsCommand({ args: forwardedArgs, env });
|
|
104
|
+
}
|
|
92
105
|
const inner = ['agents', ...forwardedArgs].map(shellQuote).join(' ');
|
|
93
106
|
// Forward the caller's terminal width so the remote renders the table to the
|
|
94
107
|
// local screen (over SSH the remote's own COLUMNS is unset/wrong). `VAR=val
|
|
@@ -96,11 +109,6 @@ export function buildRemoteCommand(forwardedArgs, columns) {
|
|
|
96
109
|
const withCols = columns && columns > 0 ? `COLUMNS=${columns} ${inner}` : inner;
|
|
97
110
|
return `bash -lc ${shellQuote(withCols)}`;
|
|
98
111
|
}
|
|
99
|
-
const SSH_OPTS = [
|
|
100
|
-
'-o', 'BatchMode=yes',
|
|
101
|
-
'-o', 'StrictHostKeyChecking=accept-new',
|
|
102
|
-
'-o', 'ConnectTimeout=10',
|
|
103
|
-
];
|
|
104
112
|
/**
|
|
105
113
|
* Classify an ssh `spawnSync` result. ssh(1) reserves exit 255 for its own
|
|
106
114
|
* connection-layer failures (host down, timeout, refused, auth, changed host
|
|
@@ -180,13 +188,15 @@ export function runRemoteSessions(hosts, argv = process.argv) {
|
|
|
180
188
|
for (const host of hosts)
|
|
181
189
|
assertValidSshTarget(host); // fail fast on any bad target
|
|
182
190
|
const forwarded = buildForwardedArgs(argv, new Set(hosts));
|
|
183
|
-
const
|
|
191
|
+
const cols = terminalWidth();
|
|
184
192
|
const multi = hosts.length > 1;
|
|
185
193
|
let failures = 0;
|
|
186
194
|
for (const host of hosts) {
|
|
187
195
|
if (multi)
|
|
188
196
|
process.stdout.write(chalk.cyan(`\n── ${host} ──\n`));
|
|
189
|
-
|
|
197
|
+
// Per-host: a Windows peer needs a PowerShell command, POSIX peers `bash -lc`.
|
|
198
|
+
const remoteCmd = buildRemoteCommand(forwarded, cols, resolveRemoteOsSync(host));
|
|
199
|
+
const res = spawnSync('ssh', [...SSH_OPTS, ...controlOpts(), host, remoteCmd], {
|
|
190
200
|
encoding: 'utf8',
|
|
191
201
|
maxBuffer: 64 * 1024 * 1024,
|
|
192
202
|
});
|
|
@@ -541,7 +541,7 @@ export function renderSummary(events, cwd) {
|
|
|
541
541
|
if (p)
|
|
542
542
|
filesReadAbs.add(p);
|
|
543
543
|
}
|
|
544
|
-
else if (['Write', 'Edit', 'write_file', 'edit_file', 'create_file', 'replace', 'patch'].includes(tool)) {
|
|
544
|
+
else if (['Write', 'Edit', 'Create', 'write_file', 'edit_file', 'create_file', 'replace', 'patch'].includes(tool)) {
|
|
545
545
|
if (p) {
|
|
546
546
|
if (p.includes('.claude/plans/') && p.endsWith('.md')) {
|
|
547
547
|
planFilePath = p;
|
|
@@ -33,10 +33,4 @@ export declare function loadR2Config(): R2Config;
|
|
|
33
33
|
* does not re-storm every cycle. `now` is injectable for tests.
|
|
34
34
|
*/
|
|
35
35
|
export declare function isSyncConfigured(now?: number): boolean;
|
|
36
|
-
|
|
37
|
-
* This machine's stable, human-readable id, used as its R2 prefix and mirror
|
|
38
|
-
* directory name. Tailnet hostnames (zion, yosemite-s0, mac-mini) are already
|
|
39
|
-
* unique and readable; we lowercase and strip any domain suffix. Overridable
|
|
40
|
-
* via AGENTS_SYNC_MACHINE_ID for tests and unusual setups.
|
|
41
|
-
*/
|
|
42
|
-
export declare function machineId(): string;
|
|
36
|
+
export { machineId, normalizeHost } from '../../machine-id.js';
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* machine's stable identity. Credentials come from the `r2.backups` secrets
|
|
4
4
|
* bundle (OS keychain on macOS, libsecret on Linux) — never from env or disk.
|
|
5
5
|
*/
|
|
6
|
-
import * as os from 'os';
|
|
7
6
|
import { readAndResolveBundleEnv } from '../../secrets/bundles.js';
|
|
8
7
|
/** Secrets bundle holding the R2 credentials. */
|
|
9
8
|
export const SYNC_BUNDLE = 'r2.backups';
|
|
@@ -93,13 +92,7 @@ export function isSyncConfigured(now = Date.now()) {
|
|
|
93
92
|
return false;
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
* via AGENTS_SYNC_MACHINE_ID for tests and unusual setups.
|
|
101
|
-
*/
|
|
102
|
-
export function machineId() {
|
|
103
|
-
const raw = process.env.AGENTS_SYNC_MACHINE_ID || os.hostname();
|
|
104
|
-
return raw.split('.')[0].trim().toLowerCase().replace(/[^a-z0-9_-]/g, '-') || 'unknown';
|
|
105
|
-
}
|
|
95
|
+
// machineId() and normalizeHost() now live in the dependency-free leaf
|
|
96
|
+
// ../../machine-id.ts so low-level modules (state.ts) can use them without an
|
|
97
|
+
// import cycle. Re-exported here for existing importers.
|
|
98
|
+
export { machineId, normalizeHost } from '../../machine-id.js';
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* speaks these types.
|
|
8
8
|
*/
|
|
9
9
|
/** Agents that store session data on disk and can be discovered by `agents sessions`. */
|
|
10
|
-
export type SessionAgentId = 'claude' | 'codex' | 'gemini' | 'opencode' | 'openclaw' | 'rush' | 'hermes' | 'grok' | 'kimi' | 'droid';
|
|
10
|
+
export type SessionAgentId = 'claude' | 'codex' | 'gemini' | 'antigravity' | 'opencode' | 'openclaw' | 'rush' | 'hermes' | 'grok' | 'kimi' | 'droid';
|
|
11
11
|
/** All agents with session discovery support, in display order. */
|
|
12
12
|
export declare const SESSION_AGENTS: SessionAgentId[];
|
|
13
13
|
/** A single normalized event within a session (message, tool call, thinking, etc.). */
|
|
@@ -78,6 +78,23 @@ export interface SessionMeta {
|
|
|
78
78
|
* `entrypoint` field ('sdk-cli' for team spawns, 'cli' for real sessions).
|
|
79
79
|
*/
|
|
80
80
|
isTeamOrigin?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* The machine (normalized hostname) this session's transcript originated on:
|
|
83
|
+
* the local machine for live-home sessions, or the origin machine parsed from
|
|
84
|
+
* the cross-machine mirror path (backups/<agent>/<machine>/…, see
|
|
85
|
+
* sync/agents.ts). Populated by discoverSessions for the listing/picker;
|
|
86
|
+
* undefined for sessions obtained outside that path.
|
|
87
|
+
*/
|
|
88
|
+
machine?: string;
|
|
89
|
+
/**
|
|
90
|
+
* True only for rows pulled from another machine over the live cross-machine
|
|
91
|
+
* fan-out (`remote-list.ts`) — their transcript is on that peer's disk, so
|
|
92
|
+
* reading/resuming has to hop back over SSH. Distinct from `machine`, which is
|
|
93
|
+
* also set on locally-readable synced mirrors: a mirror is machine-tagged but
|
|
94
|
+
* its `filePath` is a local path, so it must NOT be treated as remote. Set by
|
|
95
|
+
* `parseRemoteList`; transient (never persisted, stripped from --json).
|
|
96
|
+
*/
|
|
97
|
+
_remote?: boolean;
|
|
81
98
|
/** Terms that matched the current search query */
|
|
82
99
|
_matchedTerms?: string[];
|
|
83
100
|
/** BM25 relevance score from the most recent content-index search */
|
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
* speaks these types.
|
|
8
8
|
*/
|
|
9
9
|
/** All agents with session discovery support, in display order. */
|
|
10
|
-
export const SESSION_AGENTS = ['claude', 'codex', 'gemini', 'opencode', 'openclaw', 'rush', 'hermes', 'grok', 'kimi', 'droid'];
|
|
10
|
+
export const SESSION_AGENTS = ['claude', 'codex', 'gemini', 'antigravity', 'opencode', 'openclaw', 'rush', 'hermes', 'grok', 'kimi', 'droid'];
|