@phnx-labs/agents-cli 1.22.42 → 1.22.44
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 +149 -0
- package/README.md +15 -13
- package/dist/bootstrap.js +33 -13
- package/dist/cli/command-registry.d.ts +0 -8
- package/dist/cli/command-registry.js +1 -25
- package/dist/commands/alias.d.ts +7 -4
- package/dist/commands/alias.js +33 -8
- package/dist/commands/apply.d.ts +7 -10
- package/dist/commands/apply.js +15 -35
- package/dist/commands/artifacts.js +4 -5
- package/dist/commands/audit.d.ts +6 -6
- package/dist/commands/audit.js +12 -12
- package/dist/commands/auth.js +14 -6
- package/dist/commands/beta.d.ts +7 -1
- package/dist/commands/beta.js +16 -9
- package/dist/commands/commands.js +1 -1
- package/dist/commands/computer.js +20 -6
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/doctor.d.ts +4 -2
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/events.d.ts +2 -2
- package/dist/commands/events.js +6 -5
- package/dist/commands/exec.d.ts +1 -1
- package/dist/commands/exec.js +11 -6
- package/dist/commands/factory.d.ts +1 -1
- package/dist/commands/feed.js +1 -30
- package/dist/commands/fleet-capture.d.ts +1 -1
- package/dist/commands/fleet-capture.js +4 -5
- package/dist/commands/harness.js +4 -52
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +3 -2
- package/dist/commands/menubar.js +58 -1
- package/dist/commands/monitors.js +1 -1
- package/dist/commands/org.d.ts +5 -0
- package/dist/commands/org.js +83 -29
- package/dist/commands/output.js +2 -2
- package/dist/commands/profiles.js +2 -2
- package/dist/commands/prune.js +5 -4
- package/dist/commands/routines.js +43 -25
- package/dist/commands/routines.test-fixture.d.ts +86 -0
- package/dist/commands/routines.test-fixture.js +345 -0
- package/dist/commands/sessions-picker.js +124 -122
- package/dist/commands/sessions.test-fixture.d.ts +24 -0
- package/dist/commands/sessions.test-fixture.js +224 -0
- package/dist/commands/setup.js +9 -1
- package/dist/commands/share.d.ts +9 -10
- package/dist/commands/share.js +68 -50
- package/dist/commands/skills.js +1 -1
- package/dist/commands/snapshot.d.ts +1 -1
- package/dist/commands/snapshot.js +4 -4
- package/dist/commands/ssh.js +15 -62
- package/dist/commands/status.d.ts +4 -2
- package/dist/commands/status.js +10 -7
- package/dist/commands/subagents.js +1 -1
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +29 -2
- package/dist/commands/view.js +11 -2
- package/dist/commands/webhook.js +1 -1
- package/dist/lib/accounting/rotate.d.ts +5 -3
- package/dist/lib/accounting/rotate.js +36 -7
- package/dist/lib/accounting/usage.d.ts +18 -4
- package/dist/lib/accounting/usage.js +138 -27
- package/dist/lib/agent-spec/agents.d.ts +7 -7
- package/dist/lib/agent-spec/agents.js +15 -15
- package/dist/lib/analytics/mix-commands.d.ts +6 -28
- package/dist/lib/analytics/mix-commands.js +14 -40
- package/dist/lib/audit/log.d.ts +2 -2
- package/dist/lib/audit/log.js +2 -2
- package/dist/lib/beta.js +1 -1
- package/dist/lib/browser/domain-skills.d.ts +26 -6
- package/dist/lib/browser/domain-skills.js +81 -43
- package/dist/lib/cloud/rush.d.ts +0 -15
- package/dist/lib/cloud/rush.js +0 -35
- package/dist/lib/crabbox/runtimes.d.ts +6 -2
- package/dist/lib/crabbox/runtimes.js +21 -5
- package/dist/lib/daemon/daemon.js +97 -58
- package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
- package/dist/lib/daemon/daemon.test-fixture.js +71 -0
- package/dist/lib/daemon/runner.d.ts +18 -1
- package/dist/lib/daemon/runner.js +105 -11
- package/dist/lib/daemon-ticks.d.ts +38 -1
- package/dist/lib/daemon-ticks.js +52 -5
- package/dist/lib/device-config.js +1 -2
- package/dist/lib/devices/discovery-policy.d.ts +3 -3
- package/dist/lib/devices/discovery-policy.js +3 -3
- package/dist/lib/devices/doctor-findings.js +1 -1
- package/dist/lib/devices/fleet.d.ts +4 -8
- package/dist/lib/devices/fleet.js +3 -15
- package/dist/lib/devices/health-report.js +4 -4
- package/dist/lib/devices/pool.d.ts +0 -6
- package/dist/lib/devices/pool.js +0 -6
- package/dist/lib/devices/registry.d.ts +0 -15
- package/dist/lib/devices/registry.js +0 -9
- package/dist/lib/devices/stats-cache.d.ts +12 -0
- package/dist/lib/devices/stats-cache.js +24 -5
- package/dist/lib/drift-sync.d.ts +1 -1
- package/dist/lib/drift-sync.js +2 -2
- package/dist/lib/drift.d.ts +1 -1
- package/dist/lib/exec.js +13 -3
- package/dist/lib/fleet/capture.d.ts +1 -1
- package/dist/lib/fleet/manifest.js +1 -1
- package/dist/lib/fleet/remote-login.d.ts +2 -2
- package/dist/lib/fleet/remote-login.js +3 -4
- package/dist/lib/format.d.ts +9 -0
- package/dist/lib/format.js +16 -0
- package/dist/lib/github/pr-mergeable.d.ts +49 -0
- package/dist/lib/github/pr-mergeable.js +157 -0
- package/dist/lib/github/pr-verdict.d.ts +67 -0
- package/dist/lib/github/pr-verdict.js +72 -0
- package/dist/lib/harness/adapter.d.ts +6 -5
- package/dist/lib/hosts/dispatch.d.ts +2 -0
- package/dist/lib/hosts/dispatch.js +8 -1
- package/dist/lib/hosts/passthrough.d.ts +10 -0
- package/dist/lib/hosts/passthrough.js +28 -7
- package/dist/lib/hosts/providers/devices.js +1 -13
- package/dist/lib/hosts/ready.d.ts +12 -3
- package/dist/lib/hosts/ready.js +27 -12
- package/dist/lib/hosts/registry.d.ts +4 -5
- package/dist/lib/hosts/registry.js +3 -7
- package/dist/lib/hosts/session-index.js +1 -1
- package/dist/lib/installations/shims.js +1 -1
- package/dist/lib/installations/versions.d.ts +40 -0
- package/dist/lib/installations/versions.js +78 -4
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
- package/dist/lib/menubar/install-menubar.d.ts +109 -0
- package/dist/lib/menubar/install-menubar.js +218 -12
- package/dist/lib/monitors/config.d.ts +2 -2
- package/dist/lib/monitors/config.js +3 -2
- package/dist/lib/origin-machine.d.ts +18 -0
- package/dist/lib/origin-machine.js +34 -0
- package/dist/lib/perf/db.js +1 -1
- package/dist/lib/perf/spool.js +1 -1
- package/dist/lib/prix-account.d.ts +2 -1
- package/dist/lib/prix-account.js +2 -1
- package/dist/lib/profiles.d.ts +7 -0
- package/dist/lib/profiles.js +26 -1
- package/dist/lib/remote-agents-json.js +1 -7
- package/dist/lib/routines-placement.d.ts +1 -1
- package/dist/lib/routines-placement.js +1 -1
- package/dist/lib/sandbox.d.ts +24 -0
- package/dist/lib/sandbox.js +101 -0
- package/dist/lib/scheduling/routines.d.ts +18 -9
- package/dist/lib/scheduling/routines.js +16 -8
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
- package/dist/lib/secrets/agent.js +9 -1
- package/dist/lib/secrets/index.js +1 -1
- package/dist/lib/secrets/list-filter.js +1 -1
- package/dist/lib/service-manifest.d.ts +20 -0
- package/dist/lib/service-manifest.js +34 -0
- package/dist/lib/session/active.d.ts +4 -16
- package/dist/lib/session/active.js +2 -6
- package/dist/lib/session/cloud.js +1 -1
- package/dist/lib/session/db.d.ts +13 -98
- package/dist/lib/session/db.js +5 -12
- package/dist/lib/session/discover.d.ts +5 -81
- package/dist/lib/session/discover.js +5 -5
- package/dist/lib/session/fork.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -1
- package/dist/lib/session/origin-machine.d.ts +1 -18
- package/dist/lib/session/origin-machine.js +4 -34
- package/dist/lib/session/relative-time.d.ts +1 -44
- package/dist/lib/session/relative-time.js +4 -120
- package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
- package/dist/lib/session/remote/remote-bundle.js +61 -0
- package/dist/lib/session/remote/remote-list.d.ts +148 -0
- package/dist/lib/session/remote/remote-list.js +607 -0
- package/dist/lib/session/remote/remote.d.ts +132 -0
- package/dist/lib/session/remote/remote.js +314 -0
- package/dist/lib/session/remote/watch.d.ts +101 -0
- package/dist/lib/session/remote/watch.js +241 -0
- package/dist/lib/session/remote-bundle.d.ts +1 -12
- package/dist/lib/session/remote-bundle.js +3 -61
- package/dist/lib/session/remote-list.d.ts +1 -148
- package/dist/lib/session/remote-list.js +3 -607
- package/dist/lib/session/remote.d.ts +1 -132
- package/dist/lib/session/remote.js +3 -314
- package/dist/lib/session/short-id.d.ts +1 -17
- package/dist/lib/session/short-id.js +4 -20
- package/dist/lib/session/watch.d.ts +1 -101
- package/dist/lib/session/watch.js +3 -242
- package/dist/lib/session/width.d.ts +1 -29
- package/dist/lib/session/width.js +4 -101
- package/dist/lib/share/config.d.ts +12 -2
- package/dist/lib/share/config.js +47 -11
- package/dist/lib/share/delete.js +1 -1
- package/dist/lib/share/publish.js +1 -1
- package/dist/lib/share/worker-template.js +2 -2
- package/dist/lib/smart-launch.d.ts +4 -4
- package/dist/lib/smart-launch.js +8 -18
- package/dist/lib/snapshot.d.ts +2 -2
- package/dist/lib/snapshot.js +1 -1
- package/dist/lib/staleness/index.d.ts +3 -1
- package/dist/lib/staleness/index.js +21 -1
- package/dist/lib/staleness/types.d.ts +11 -0
- package/dist/lib/staleness/writers/commands.js +13 -4
- package/dist/lib/staleness/writers/hooks.js +3 -1
- package/dist/lib/staleness/writers/mcp.d.ts +0 -7
- package/dist/lib/staleness/writers/mcp.js +16 -1
- package/dist/lib/staleness/writers/rules.js +1 -1
- package/dist/lib/staleness/writers/skills.js +3 -1
- package/dist/lib/staleness/writers/subagents.d.ts +0 -9
- package/dist/lib/staleness/writers/subagents.js +18 -1
- package/dist/lib/staleness/writers/types.d.ts +10 -0
- package/dist/lib/startup/command-registry.d.ts +10 -1
- package/dist/lib/startup/command-registry.js +26 -7
- package/dist/lib/startup/root-command.d.ts +18 -1
- package/dist/lib/startup/root-command.js +18 -1
- package/dist/lib/state.js +7 -0
- package/dist/lib/teams/placement-probe.js +28 -13
- package/dist/lib/teams/scheduler.d.ts +3 -1
- package/dist/lib/teams/scheduler.js +1 -1
- package/dist/lib/text/relative-time.d.ts +44 -0
- package/dist/lib/text/relative-time.js +120 -0
- package/dist/lib/text/short-id.d.ts +17 -0
- package/dist/lib/text/short-id.js +20 -0
- package/dist/lib/text/width.d.ts +29 -0
- package/dist/lib/text/width.js +101 -0
- package/dist/lib/triggers/handlers.d.ts +7 -0
- package/dist/lib/triggers/handlers.js +8 -0
- package/dist/lib/triggers/webhook.d.ts +1 -0
- package/dist/lib/triggers/webhook.js +2 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/view-types.d.ts +7 -0
- package/package.json +2 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/serve.d.ts +0 -10
- package/dist/commands/serve.js +0 -68
- package/dist/commands/tickets.d.ts +0 -2
- package/dist/commands/tickets.js +0 -43
- package/dist/commands/trends.d.ts +0 -10
- package/dist/commands/trends.js +0 -12
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/observe-aliases.d.ts +0 -30
- package/dist/lib/observe-aliases.js +0 -38
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/serve/control.d.ts +0 -95
- package/dist/lib/serve/control.js +0 -260
- package/dist/lib/serve/data.d.ts +0 -81
- package/dist/lib/serve/data.js +0 -91
- package/dist/lib/serve/page.d.ts +0 -7
- package/dist/lib/serve/page.js +0 -140
- package/dist/lib/serve/server.d.ts +0 -80
- package/dist/lib/serve/server.js +0 -145
- package/dist/lib/serve/stream.d.ts +0 -43
- package/dist/lib/serve/stream.js +0 -116
- package/dist/lib/serve/token.d.ts +0 -35
- package/dist/lib/serve/token.js +0 -85
- package/dist/lib/tickets/list.d.ts +0 -53
- package/dist/lib/tickets/list.js +0 -153
|
@@ -1,120 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* `remote.ts`, so a back-import would cycle.
|
|
6
|
-
*/
|
|
7
|
-
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
8
|
-
/**
|
|
9
|
-
* Calendar label for a timestamp older than the relative window. A 2-digit year
|
|
10
|
-
* is appended outside the current year so "Jun 28" is never ambiguous across
|
|
11
|
-
* years (e.g. "Jun 28 '25").
|
|
12
|
-
*/
|
|
13
|
-
function calendarLabel(thenMs, nowMs) {
|
|
14
|
-
const d = new Date(thenMs);
|
|
15
|
-
const label = `${MONTHS[d.getMonth()]} ${d.getDate()}`;
|
|
16
|
-
return d.getFullYear() === new Date(nowMs).getFullYear()
|
|
17
|
-
? label
|
|
18
|
-
: `${label} '${String(d.getFullYear()).slice(-2)}`;
|
|
19
|
-
}
|
|
20
|
-
export function formatRelativeTime(isoTimestamp) {
|
|
21
|
-
const now = Date.now();
|
|
22
|
-
const then = new Date(isoTimestamp).getTime();
|
|
23
|
-
if (isNaN(then))
|
|
24
|
-
return isoTimestamp;
|
|
25
|
-
const diffMs = now - then;
|
|
26
|
-
const diffMin = Math.floor(diffMs / 60_000);
|
|
27
|
-
const diffHrs = Math.floor(diffMs / 3_600_000);
|
|
28
|
-
const diffDays = Math.floor(diffMs / 86_400_000);
|
|
29
|
-
if (diffMin < 1)
|
|
30
|
-
return 'just now';
|
|
31
|
-
if (diffMin < 60)
|
|
32
|
-
return `${diffMin} min ago`;
|
|
33
|
-
if (diffHrs < 24)
|
|
34
|
-
return `${diffHrs} hour${diffHrs === 1 ? '' : 's'} ago`;
|
|
35
|
-
if (diffDays < 7)
|
|
36
|
-
return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
|
|
37
|
-
// Older: show the date.
|
|
38
|
-
return calendarLabel(then, now);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Compact age — `now`, `45m`, `3h`, `2d`, then the calendar date. This is the
|
|
42
|
-
* *creation* half of a session's time cell: it renders right next to the long
|
|
43
|
-
* last-activity label, and one row can afford one long form, not two.
|
|
44
|
-
*/
|
|
45
|
-
export function formatCompactAge(isoTimestamp) {
|
|
46
|
-
const now = Date.now();
|
|
47
|
-
const then = new Date(isoTimestamp).getTime();
|
|
48
|
-
if (isNaN(then))
|
|
49
|
-
return isoTimestamp;
|
|
50
|
-
const diffMs = now - then;
|
|
51
|
-
const diffMin = Math.floor(diffMs / 60_000);
|
|
52
|
-
const diffHrs = Math.floor(diffMs / 3_600_000);
|
|
53
|
-
const diffDays = Math.floor(diffMs / 86_400_000);
|
|
54
|
-
if (diffMin < 1)
|
|
55
|
-
return 'now';
|
|
56
|
-
if (diffMin < 60)
|
|
57
|
-
return `${diffMin}m`;
|
|
58
|
-
if (diffHrs < 24)
|
|
59
|
-
return `${diffHrs}h`;
|
|
60
|
-
if (diffDays < 7)
|
|
61
|
-
return `${diffDays}d`;
|
|
62
|
-
return calendarLabel(then, now);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Shortest creation→last-activity gap worth rendering as two fields. Below it
|
|
66
|
-
* both halves name the same minute, so the row would carry a duplicate instead
|
|
67
|
-
* of information.
|
|
68
|
-
*/
|
|
69
|
-
const SPAN_MIN_MS = 60_000;
|
|
70
|
-
/**
|
|
71
|
-
* Split a session's timestamps into the two fields a listing renders: when it
|
|
72
|
-
* was created and when it was last active. One last-activity label answers
|
|
73
|
-
* neither "is this the old session I'm looking for" nor "how long did it run" —
|
|
74
|
-
* a session touched an hour ago may have started last week.
|
|
75
|
-
*/
|
|
76
|
-
export function sessionAgeParts(createdIso, lastActivityIso) {
|
|
77
|
-
const lastIso = lastActivityIso ?? createdIso;
|
|
78
|
-
const last = formatRelativeTime(lastIso);
|
|
79
|
-
const createdMs = new Date(createdIso).getTime();
|
|
80
|
-
const lastMs = new Date(lastIso).getTime();
|
|
81
|
-
if (isNaN(createdMs) || isNaN(lastMs) || lastMs - createdMs < SPAN_MIN_MS)
|
|
82
|
-
return { last };
|
|
83
|
-
return { created: formatCompactAge(createdIso), last };
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Parse a time filter string (relative like '7d' or an ISO timestamp) into epoch
|
|
87
|
-
* milliseconds. Backs `--since` on `sessions`, `cost`, `teams`, and `output`, and
|
|
88
|
-
* `--unused` on `secrets list`.
|
|
89
|
-
*
|
|
90
|
-
* It lives here, in a module with no imports, rather than in `discover.ts` where
|
|
91
|
-
* it started: `discover.ts` loads `../sqlite.js`, so importing this one function
|
|
92
|
-
* from there pulls `node:sqlite` into the caller's module graph and Node prints
|
|
93
|
-
* `ExperimentalWarning: SQLite …` on stderr. That is invisible in a command that
|
|
94
|
-
* already touches the session DB, and a broken contract in one that doesn't —
|
|
95
|
-
* `monitors --json` asserts a clean stderr. `discover.ts` re-exports it, so
|
|
96
|
-
* existing importers are unaffected.
|
|
97
|
-
*/
|
|
98
|
-
export function parseTimeFilter(input) {
|
|
99
|
-
// Units: m=minute, h=hour, d=day, w=week, mo=month(30d), y=year(365d). `mo`
|
|
100
|
-
// must precede the single-letter alternatives so "1mo" isn't read as "1m"+"o".
|
|
101
|
-
const relativeMatch = input.match(/^(\d+)(mo|[mhdwy])$/i);
|
|
102
|
-
if (relativeMatch) {
|
|
103
|
-
const value = parseInt(relativeMatch[1], 10);
|
|
104
|
-
const unit = relativeMatch[2].toLowerCase();
|
|
105
|
-
if (unit === 'm')
|
|
106
|
-
return Date.now() - value * 60_000;
|
|
107
|
-
if (unit === 'h')
|
|
108
|
-
return Date.now() - value * 3_600_000;
|
|
109
|
-
if (unit === 'd')
|
|
110
|
-
return Date.now() - value * 86_400_000;
|
|
111
|
-
if (unit === 'w')
|
|
112
|
-
return Date.now() - value * 7 * 86_400_000;
|
|
113
|
-
if (unit === 'mo')
|
|
114
|
-
return Date.now() - value * 30 * 86_400_000;
|
|
115
|
-
if (unit === 'y')
|
|
116
|
-
return Date.now() - value * 365 * 86_400_000;
|
|
117
|
-
}
|
|
118
|
-
const ts = new Date(input).getTime();
|
|
119
|
-
return Number.isNaN(ts) ? 0 : ts;
|
|
120
|
-
}
|
|
1
|
+
// Moved to ../text/relative-time.ts (move 1 of the core-module refactor — see
|
|
2
|
+
// .agents/artifacts/2026-08-20/plan-*.md). Kept as a re-export shim so
|
|
3
|
+
// existing importers don't break.
|
|
4
|
+
export * from '../text/relative-time.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ParsedBundle } from '../bundle.js';
|
|
2
|
+
export interface RemotePullResult {
|
|
3
|
+
bundles: ParsedBundle[];
|
|
4
|
+
errors: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Run `agents sessions export …exportArgs --stdout` on each host and parse the
|
|
8
|
+
* streamed bundle. A host that fails (unreachable, remote error, bad output) is
|
|
9
|
+
* collected in `errors` and skipped — one asleep peer never aborts the pull.
|
|
10
|
+
* `exportArgs` must NOT contain --device (the remote export runs for itself only).
|
|
11
|
+
*/
|
|
12
|
+
export declare function pullBundlesFromHosts(hosts: string[], exportArgs: string[]): Promise<RemotePullResult>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-device session transfer over the EXISTING SSH fleet transport
|
|
3
|
+
* (RUSH-1712) — no R2, no daemon. `agents sessions export --device <h>` and
|
|
4
|
+
* `agents sessions import --from-host <h>` both run `agents sessions export
|
|
5
|
+
* … --stdout` ON the peer and stream the bundle back over the same SSH path the
|
|
6
|
+
* cross-machine listing already uses (resolveExplicitTargets + ssh-exec), then
|
|
7
|
+
* either write it (export) or import it (import) locally.
|
|
8
|
+
*
|
|
9
|
+
* This deliberately reuses ssh-exec / resolve-target rather than adding a second
|
|
10
|
+
* transport: the raw form `agents ssh boxA 'agents sessions export --stdout' |
|
|
11
|
+
* agents sessions import -` works with plain export/import; this module is just
|
|
12
|
+
* the one-shot wrapper around it.
|
|
13
|
+
*/
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import { sshExec } from '../../ssh-exec.js';
|
|
16
|
+
import { shellQuote } from '../../ssh-exec.js';
|
|
17
|
+
import { resolveExplicitTargets } from '../../devices/resolve-target.js';
|
|
18
|
+
import { remoteShellFor, buildWindowsAgentsCommand } from '../../hosts/remote-cmd.js';
|
|
19
|
+
import { parseBundle } from '../bundle.js';
|
|
20
|
+
/** Remote export can traverse many sessions; give it a generous ceiling. */
|
|
21
|
+
const REMOTE_EXPORT_TIMEOUT_MS = 300_000;
|
|
22
|
+
/** Build `agents <args>` for the peer's login shell (bash or PowerShell). */
|
|
23
|
+
function remoteAgentsCommand(args, os) {
|
|
24
|
+
if (remoteShellFor(os) === 'powershell') {
|
|
25
|
+
return buildWindowsAgentsCommand({ args });
|
|
26
|
+
}
|
|
27
|
+
const inner = ['agents', ...args].map((t, i) => (i === 0 ? t : shellQuote(t))).join(' ');
|
|
28
|
+
return `bash -lc ${shellQuote(inner)}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run `agents sessions export …exportArgs --stdout` on each host and parse the
|
|
32
|
+
* streamed bundle. A host that fails (unreachable, remote error, bad output) is
|
|
33
|
+
* collected in `errors` and skipped — one asleep peer never aborts the pull.
|
|
34
|
+
* `exportArgs` must NOT contain --device (the remote export runs for itself only).
|
|
35
|
+
*/
|
|
36
|
+
export async function pullBundlesFromHosts(hosts, exportArgs) {
|
|
37
|
+
const targets = await resolveExplicitTargets(hosts);
|
|
38
|
+
const bundles = [];
|
|
39
|
+
const errors = [];
|
|
40
|
+
for (const t of targets) {
|
|
41
|
+
const cmd = remoteAgentsCommand(['sessions', 'export', ...exportArgs, '--stdout'], t.os);
|
|
42
|
+
process.stderr.write(chalk.dim(`Pulling sessions from ${t.name}…\n`));
|
|
43
|
+
const res = sshExec(t.target, cmd, { timeoutMs: REMOTE_EXPORT_TIMEOUT_MS, extraSshArgs: t.extraSshArgs });
|
|
44
|
+
if (res.timedOut) {
|
|
45
|
+
errors.push(`${t.name}: timed out after ${Math.round(REMOTE_EXPORT_TIMEOUT_MS / 1000)}s`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (res.code !== 0) {
|
|
49
|
+
const tail = res.stderr.trim().split('\n').filter(Boolean).pop();
|
|
50
|
+
errors.push(`${t.name}: remote export failed (${res.code ?? 'ssh error'})${tail ? ': ' + tail : ''}`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
bundles.push(parseBundle(res.stdout));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
errors.push(`${t.name}: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { bundles, errors };
|
|
61
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { type DeviceProfile } from '../../devices/registry.js';
|
|
2
|
+
import { type RemoteAgentsJsonParseResult } from '../../remote-agents-json.js';
|
|
3
|
+
import type { SessionMeta } from '../types.js';
|
|
4
|
+
import { type ToolProgramCountEnvelope, type ToolSearchEnvelope } from '../tool-index.js';
|
|
5
|
+
export { REMOTE_STDOUT_MAX_BYTES, RemoteUtf8Accumulator } from '../../ssh-exec.js';
|
|
6
|
+
export declare const REMOTE_TOOL_AGGREGATE_MAX_BYTES: number;
|
|
7
|
+
export interface RemoteToolByteBudget {
|
|
8
|
+
remainingBytes: number;
|
|
9
|
+
exhausted: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Claim received bytes against one fleet-query budget before retaining them. */
|
|
12
|
+
export declare function consumeRemoteToolByteBudget(budget: RemoteToolByteBudget, bytes: number): boolean;
|
|
13
|
+
/** Charge sanitized, machine-stamped evidence because redaction may expand it. */
|
|
14
|
+
export declare function consumeParsedRemoteToolSearchBudget(budget: RemoteToolByteBudget, envelope: ToolSearchEnvelope): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The command run on each peer: answer for itself, as JSON, without recursing.
|
|
17
|
+
* `forwardedArgs` carry the caller's own query/filters (already including the
|
|
18
|
+
* leading `sessions` and a `--json`) so each peer returns a comparable slice.
|
|
19
|
+
* A Windows peer gets a PowerShell invocation (ssh lands in cmd.exe/PowerShell
|
|
20
|
+
* there, where `bash -lc` is not a command); every other OS keeps `bash -lc`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function remoteListCommand(forwardedArgs: string[], os?: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Parse a peer's `sessions --json` stdout into `SessionMeta[]`, tagging each
|
|
25
|
+
* with `machine`. Defensive against version skew / partial output: non-JSON or
|
|
26
|
+
* a non-array yields `[]`, and non-object entries are dropped rather than
|
|
27
|
+
* throwing. The `machine` we dialed always wins over any value the peer set on
|
|
28
|
+
* its own rows, so grouping keys off the computer we asked. Exported for unit
|
|
29
|
+
* testing without a live tailnet.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseRemoteList(stdout: string, machine: string): SessionMeta[];
|
|
32
|
+
export declare function parseRemoteListPayload(stdout: string, machine: string, safeResolver?: boolean): {
|
|
33
|
+
items: SessionMeta[];
|
|
34
|
+
valid: boolean;
|
|
35
|
+
};
|
|
36
|
+
/** Run one remote `agents sessions … --json` and capture stdout. Resolves
|
|
37
|
+
* `{ code: null }` on spawn error or timeout (host treated as dead). */
|
|
38
|
+
export declare function sshCapture(target: string, remoteCmd: string, timeoutMs: number, aggregateBudget?: RemoteToolByteBudget, options?: {
|
|
39
|
+
multiplex?: boolean;
|
|
40
|
+
port?: number;
|
|
41
|
+
hostKeyOpts?: string[];
|
|
42
|
+
}): Promise<{
|
|
43
|
+
code: number | null;
|
|
44
|
+
stdout: string;
|
|
45
|
+
aggregateBudgetExceeded?: boolean;
|
|
46
|
+
}>;
|
|
47
|
+
export interface RemoteListResult {
|
|
48
|
+
sessions: SessionMeta[];
|
|
49
|
+
/** How many peer machines we attempted to reach (drives the empty-fleet tip). */
|
|
50
|
+
deviceCount: number;
|
|
51
|
+
/**
|
|
52
|
+
* Peers that failed to answer, by display name. The stderr note above is
|
|
53
|
+
* enough for a printed listing, but the interactive browser repaints over it —
|
|
54
|
+
* so callers rendering a full-screen UI need the outcome as data to tell
|
|
55
|
+
* "that box is asleep" apart from "that box has no matching sessions".
|
|
56
|
+
*/
|
|
57
|
+
unreachable: string[];
|
|
58
|
+
}
|
|
59
|
+
/** Keep browse and tool-search fan-out on the same automatic peer set. */
|
|
60
|
+
export declare function isAutomaticSessionPeer(d: DeviceProfile, self: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Gather listing sessions from other machines. With an explicit `hosts` list
|
|
63
|
+
* (from `--device`), fan out to exactly those. Otherwise sweep the registered,
|
|
64
|
+
* online devices from `ag devices`, excluding this machine and any without an
|
|
65
|
+
* address. `forwardedArgs` are the caller's own sessions args (query + filters,
|
|
66
|
+
* already `--json`) so every peer returns the same slice this machine asked for.
|
|
67
|
+
*/
|
|
68
|
+
export interface GatherRemoteListOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Opt-in early-exit for a globally-unique id lookup (a full UUID): the first
|
|
71
|
+
* peer to return the matching row resolves the fan-out and cancels the rest.
|
|
72
|
+
* Omitted for browse/label/prefix sweeps, which must wait for every peer to
|
|
73
|
+
* know whether the match is unique or conflicting.
|
|
74
|
+
*/
|
|
75
|
+
isDefinitive?: (session: SessionMeta, machine: string) => boolean;
|
|
76
|
+
/** Per-peer deadline for slower indexed browse queries. */
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
}
|
|
79
|
+
export declare function gatherRemoteList(forwardedArgs: string[], hosts?: string[], opts?: GatherRemoteListOptions): Promise<RemoteListResult>;
|
|
80
|
+
export interface RemoteToolSearchResult {
|
|
81
|
+
envelopes: Array<{
|
|
82
|
+
machine: string;
|
|
83
|
+
envelope: ToolSearchEnvelope;
|
|
84
|
+
}>;
|
|
85
|
+
deviceCount: number;
|
|
86
|
+
unreachable: string[];
|
|
87
|
+
truncated: string[];
|
|
88
|
+
}
|
|
89
|
+
export interface RemoteToolProgramCountResult {
|
|
90
|
+
envelopes: Array<{
|
|
91
|
+
machine: string;
|
|
92
|
+
envelope: ToolProgramCountEnvelope;
|
|
93
|
+
}>;
|
|
94
|
+
deviceCount: number;
|
|
95
|
+
unreachable: string[];
|
|
96
|
+
}
|
|
97
|
+
export declare function parseRemoteToolProgramCount(stdout: string, machine: string, expectedProgram: string): RemoteAgentsJsonParseResult<{
|
|
98
|
+
machine: string;
|
|
99
|
+
envelope: ToolProgramCountEnvelope;
|
|
100
|
+
}>;
|
|
101
|
+
export declare function gatherRemoteToolProgramCounts(forwardedArgs: string[], hosts: string[] | undefined, expectedProgram: string): Promise<RemoteToolProgramCountResult>;
|
|
102
|
+
export declare function parseRemoteToolSearch(stdout: string, machine: string, expectedClauses?: string[]): ToolSearchEnvelope | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Fleet sibling of {@link gatherRemoteList} for the versioned tool-search
|
|
105
|
+
* envelope. Each peer executes the same local-only query against its own
|
|
106
|
+
* SQLite index; only compact matches cross SSH.
|
|
107
|
+
*/
|
|
108
|
+
export declare function gatherRemoteToolSearch(forwardedArgs: string[], hosts?: string[], maxAggregateBytes?: number, expectedClauses?: string[]): Promise<RemoteToolSearchResult>;
|
|
109
|
+
/** Resolve a peer's SSH target (and OS) from the device registry by its
|
|
110
|
+
* normalized machine id — the same id the fan-out tags rows with. Returns
|
|
111
|
+
* undefined when no registered device with an address matches. */
|
|
112
|
+
export declare function resolvePeerTarget(machine: string): Promise<{
|
|
113
|
+
target: string;
|
|
114
|
+
os?: string;
|
|
115
|
+
} | undefined>;
|
|
116
|
+
/**
|
|
117
|
+
* Fetch one remote session's preview digest from its owning peer — the data
|
|
118
|
+
* behind the picker pane for a `_remote` row, whose transcript file this
|
|
119
|
+
* machine cannot parse. Runs the peer's own `agents sessions preview <id>
|
|
120
|
+
* --local --json` (the same envelope `agents sessions preview` already
|
|
121
|
+
* delegates to for a remote id) and returns its `preview` object, verbatim and
|
|
122
|
+
* UNSANITIZED — the caller owns scrubbing peer-supplied strings before any of
|
|
123
|
+
* them reach a TTY. Undefined on every failure: unregistered machine, SSH
|
|
124
|
+
* error, timeout, version-skewed peer with no `--json` preview envelope.
|
|
125
|
+
*/
|
|
126
|
+
export declare function fetchPeerPreviewDigest(sessionId: string, machine: string, timeoutMs?: number): Promise<unknown | undefined>;
|
|
127
|
+
/**
|
|
128
|
+
* Run `agents <args>` ON a peer over SSH, attached to this terminal (inherited
|
|
129
|
+
* stdio). `args` is the full arg vector after the binary — callers pass e.g.
|
|
130
|
+
* `['sessions', id, '--markdown']` or `['sessions', 'resume', id]`. Used when a
|
|
131
|
+
* picked session lives on another machine: its transcript and agent binary are
|
|
132
|
+
* there, so both reading (no TTY) and resuming (TTY) must execute on the peer —
|
|
133
|
+
* not via a local `--device` hop, which would discover locally and dead-end for a
|
|
134
|
+
* session that exists only on the peer. Resolves 'no-target' when the machine
|
|
135
|
+
* isn't a dialable registered device; the caller surfaces a clear message.
|
|
136
|
+
*
|
|
137
|
+
* `opts.env` adds variables to the remote command. It deliberately does NOT
|
|
138
|
+
* carry `AGENTS_FLEET_REMOTE` the way the `--device` passthrough does: that marker
|
|
139
|
+
* gates consent-sensitive actions on the far side
|
|
140
|
+
* (lib/browser/remote-control.ts), and a resumed agent is a long-lived session
|
|
141
|
+
* that would inherit it for its whole life — `agents browser start` inside it
|
|
142
|
+
* would then be refused as a cross-machine drive. A one-shot `--device` command
|
|
143
|
+
* can carry the marker; a session cannot.
|
|
144
|
+
*/
|
|
145
|
+
export declare function runOnPeer(args: string[], machine: string, opts?: {
|
|
146
|
+
tty?: boolean;
|
|
147
|
+
env?: Record<string, string>;
|
|
148
|
+
}): Promise<'ok' | 'no-target'>;
|