@phnx-labs/agents-cli 1.20.73 → 1.20.76
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 +989 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +31 -1
- package/dist/commands/exec.js +31 -3
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +87 -9
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +15 -2
- package/dist/commands/secrets.js +44 -21
- package/dist/commands/sessions-browser.js +1 -1
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.js +16 -1
- package/dist/commands/sessions.d.ts +36 -13
- package/dist/commands/sessions.js +210 -40
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/setup.js +6 -1
- package/dist/commands/ssh.js +71 -35
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +51 -11
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +99 -31
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +14 -4
- package/dist/lib/activity.js +17 -5
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +56 -0
- package/dist/lib/agents.js +133 -7
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +60 -0
- package/dist/lib/daemon.js +18 -2
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +21 -0
- package/dist/lib/devices/fleet.js +26 -0
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +66 -0
- package/dist/lib/exec.js +195 -6
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +2 -0
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +13 -5
- package/dist/lib/hosts/run-target.js +26 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +37 -2
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +25 -0
- package/dist/lib/import.js +107 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.d.ts +20 -0
- package/dist/lib/menubar/install-menubar.js +45 -1
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +7 -2
- package/dist/lib/overdue.js +18 -11
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/project-resources.js +20 -5
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routines.d.ts +26 -0
- package/dist/lib/routines.js +19 -2
- package/dist/lib/runner.js +104 -7
- package/dist/lib/secrets/agent.d.ts +66 -1
- package/dist/lib/secrets/agent.js +154 -90
- package/dist/lib/secrets/bundles.d.ts +34 -0
- package/dist/lib/secrets/bundles.js +179 -19
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +21 -3
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +114 -7
- package/dist/lib/session/active.js +323 -52
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +48 -0
- package/dist/lib/session/db.js +238 -14
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +465 -2
- package/dist/lib/session/discover.js +1648 -468
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +130 -30
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +22 -10
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.js +6 -2
- package/dist/lib/shims.d.ts +27 -1
- package/dist/lib/shims.js +88 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +15 -36
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +48 -2
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +22 -2
- package/dist/lib/versions.js +98 -10
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host provider registry.
|
|
2
|
+
* Host provider registry + the single host/device resolver.
|
|
3
3
|
*
|
|
4
4
|
* Mirrors the cloud provider registry: a Map of provider id → implementation,
|
|
5
|
-
* instantiated once. Registers `local` then `devices
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* instantiated once. Registers `local` then `devices`; adding `rush`/`crabbox`
|
|
6
|
+
* later is a one-line `providers.set(...)`.
|
|
7
|
+
*
|
|
8
|
+
* Resolution used to fork into two disagreeing chains — a local-provider-first
|
|
9
|
+
* `resolveHost` (this file) and a devices-only `resolveSshTarget`
|
|
10
|
+
* (`../devices/resolve-target.ts`). They dialed different boxes for the same
|
|
11
|
+
* token (RUSH-1967). Now every caller goes through one core, {@link matchHost}:
|
|
12
|
+
* it merges the two directories per-FIELD instead of letting one provider
|
|
13
|
+
* shadow the other — the live devices registry supplies address/OS/presence,
|
|
14
|
+
* the agents.yaml overlay supplies caps/hints, and ssh_config supplies hosts
|
|
15
|
+
* Tailscale has never seen. The typed wrappers below (`resolveHost`) and in
|
|
16
|
+
* resolve-target.ts (`resolveExplicitTargets`, `resolveDeviceTarget`) differ
|
|
17
|
+
* only in what shape they hand back and which literal fallbacks they permit.
|
|
8
18
|
*/
|
|
9
19
|
import { DeviceOffloadUnsupportedError } from './types.js';
|
|
10
20
|
import { LocalHostProvider } from './providers/local.js';
|
|
11
21
|
import { DevicesHostProvider } from './providers/devices.js';
|
|
12
22
|
import { assertValidSshTarget } from '../ssh-exec.js';
|
|
23
|
+
import { normalizeHost } from '../machine-id.js';
|
|
24
|
+
import { readMeta } from '../state.js';
|
|
25
|
+
import { isSshConfigHost } from './ssh-config.js';
|
|
26
|
+
import { resolveRemoteOsSync } from './remote-os.js';
|
|
27
|
+
import { loadDevices, isControlDevice } from '../devices/registry.js';
|
|
13
28
|
// Re-export so existing importers (tests, commands) keep their path; the class
|
|
14
29
|
// itself lives in types.ts so providers can throw it without a circular import.
|
|
15
30
|
export { DeviceOffloadUnsupportedError };
|
|
@@ -32,44 +47,204 @@ export function getAllProviders() {
|
|
|
32
47
|
initProviders();
|
|
33
48
|
return [...providers.values()];
|
|
34
49
|
}
|
|
35
|
-
/**
|
|
50
|
+
/** Split a `user@host` / `host` token into its login user (if any) and host part. */
|
|
51
|
+
export function splitUserHost(token) {
|
|
52
|
+
const at = token.indexOf('@');
|
|
53
|
+
return at === -1 ? { host: token } : { user: token.slice(0, at), host: token.slice(at + 1) };
|
|
54
|
+
}
|
|
55
|
+
/** True when a token is clearly a network target (a `user@`, or a dotted/IPv6
|
|
56
|
+
* host / IP) rather than a bare alias. A bare unknown word is a typo, so a
|
|
57
|
+
* strict caller (`agents ssh foo`) reports "Unknown device" instead of dialing
|
|
58
|
+
* a literal `foo`. */
|
|
59
|
+
function looksLikeHostLiteral(token) {
|
|
60
|
+
return token.includes('@') || token.includes('.') || token.includes(':');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Match a host part (the piece after any `user@`) to a registered device: exact
|
|
64
|
+
* registry key first, then a normalized-host match so `yosemite-s0` and
|
|
65
|
+
* `yosemite-s0.<tailnet>.ts.net` land on the same profile. This normalized
|
|
66
|
+
* grammar used to live only in the devices chain (RUSH-1967 divergence #3/#4);
|
|
67
|
+
* it is now shared by every caller.
|
|
68
|
+
*/
|
|
69
|
+
function matchDevice(host, reg) {
|
|
70
|
+
return reg[host] ?? Object.values(reg).find((d) => normalizeHost(d.name) === normalizeHost(host));
|
|
71
|
+
}
|
|
72
|
+
/** Tailscale's own presence bit for a device, when the sync captured one. */
|
|
73
|
+
function deviceStatus(device) {
|
|
74
|
+
if (!device.tailscale)
|
|
75
|
+
return 'unknown';
|
|
76
|
+
return device.tailscale.online ? 'online' : 'offline';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Merge a matched device with any same-name agents.yaml overlay into one Host,
|
|
80
|
+
* per-FIELD (the core of the RUSH-1967 fix). The live registry owns address, OS
|
|
81
|
+
* and presence — so `agents devices sync` takes effect without re-enrolling and
|
|
82
|
+
* an enrolled route can never freeze — while the overlay contributes capability
|
|
83
|
+
* tags (the reason to enroll at all) and an OS hint when the device platform is
|
|
84
|
+
* unknown. `dispatchable` follows the device's auth method, so a password-auth
|
|
85
|
+
* device can never be made dispatchable by shadowing it with an inline entry.
|
|
86
|
+
*/
|
|
87
|
+
function deviceHost(device, user, overlay) {
|
|
88
|
+
const address = device.address.dnsName ?? device.address.ip;
|
|
89
|
+
return {
|
|
90
|
+
name: device.name,
|
|
91
|
+
provider: 'devices',
|
|
92
|
+
source: 'inline',
|
|
93
|
+
...(address ? { address } : {}),
|
|
94
|
+
user: user ?? device.user,
|
|
95
|
+
os: device.platform !== 'unknown' ? device.platform : overlay?.os,
|
|
96
|
+
...(overlay?.caps?.length ? { caps: overlay.caps } : {}),
|
|
97
|
+
enrolled: true,
|
|
98
|
+
status: deviceStatus(device),
|
|
99
|
+
dispatchable: device.auth.method !== 'password',
|
|
100
|
+
device,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Build a Host from a bare overlay entry (inline or ssh-config), applying any
|
|
104
|
+
* `user@` override. Inline/ssh-config hosts authenticate over key / ssh-config,
|
|
105
|
+
* so they are dispatchable; presence is unknown without an explicit probe. */
|
|
106
|
+
function overlayHost(name, entry, user) {
|
|
107
|
+
return {
|
|
108
|
+
name,
|
|
109
|
+
provider: 'local',
|
|
110
|
+
enrolled: true,
|
|
111
|
+
source: entry.source,
|
|
112
|
+
...(entry.address ? { address: entry.address } : {}),
|
|
113
|
+
user: user ?? entry.user,
|
|
114
|
+
...(entry.os ? { os: entry.os } : {}),
|
|
115
|
+
...(entry.caps?.length ? { caps: entry.caps } : {}),
|
|
116
|
+
...(entry.addedAt ? { addedAt: entry.addedAt } : {}),
|
|
117
|
+
status: 'unknown',
|
|
118
|
+
dispatchable: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Synthesize an ad-hoc inline Host for a `user@host` / `host` literal so a box
|
|
122
|
+
* that was never registered still dials. sshTargetFor emits `user@address`. */
|
|
123
|
+
function literalHost(token, host, user) {
|
|
124
|
+
return {
|
|
125
|
+
name: token,
|
|
126
|
+
provider: 'local',
|
|
127
|
+
source: 'inline',
|
|
128
|
+
address: host,
|
|
129
|
+
...(user ? { user } : {}),
|
|
130
|
+
status: 'unknown',
|
|
131
|
+
dispatchable: true,
|
|
132
|
+
adhoc: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The one place a `--host` / `--device` token becomes a resolved host. Reads the
|
|
137
|
+
* devices registry, the agents.yaml overlay, and ssh_config, and merges them
|
|
138
|
+
* per-field (see {@link deviceHost}). One grammar for every caller: `name`,
|
|
139
|
+
* `user@name`, a tailnet FQDN, an ssh_config alias, and a literal `user@host`
|
|
140
|
+
* all resolve the same way regardless of which subcommand called.
|
|
141
|
+
*
|
|
142
|
+
* Non-throwing: returns null on an injection-guard failure or an unresolved bare
|
|
143
|
+
* token. Device-only refusals (control device, password auth) are NOT applied
|
|
144
|
+
* here — that is the dispatch wrapper's job ({@link resolveHost}), so the fan-out
|
|
145
|
+
* and `agents ssh` paths, which handle those cases differently, aren't forced
|
|
146
|
+
* into a dispatch verdict.
|
|
147
|
+
*/
|
|
148
|
+
export async function matchHost(name, opts = {}) {
|
|
149
|
+
try {
|
|
150
|
+
assertValidSshTarget(name);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const { user, host } = splitUserHost(name);
|
|
156
|
+
let reg;
|
|
157
|
+
try {
|
|
158
|
+
reg = await loadDevices();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
reg = {};
|
|
162
|
+
}
|
|
163
|
+
const overlay = readMeta().hosts?.[host];
|
|
164
|
+
// 1. A registered device (normalized match) — its live address/OS/presence win.
|
|
165
|
+
const device = matchDevice(host, reg);
|
|
166
|
+
if (device)
|
|
167
|
+
return deviceHost(device, user, overlay);
|
|
168
|
+
// 2. An agents.yaml overlay entry keyed by the host part (inline or ssh-config).
|
|
169
|
+
if (overlay)
|
|
170
|
+
return overlayHost(host, overlay, user);
|
|
171
|
+
// 3. A bare ssh_config alias Tailscale has never seen — dial by name (ssh
|
|
172
|
+
// applies the stanza). A `user@alias` is handled as a literal below so the
|
|
173
|
+
// `user@` reaches ssh, which overrides the stanza's User.
|
|
174
|
+
if (!user && isSshConfigHost(host)) {
|
|
175
|
+
return { name: host, provider: 'local', source: 'ssh-config', os: resolveRemoteOsSync(host), status: 'unknown', dispatchable: true };
|
|
176
|
+
}
|
|
177
|
+
// 4. An ad-hoc literal target. `user@host` always; a bare IP/FQDN only when the
|
|
178
|
+
// caller opted in (`agents ssh`). A bare unknown word is a miss (null).
|
|
179
|
+
if (name.includes('@') || (opts.allowBareLiteral && looksLikeHostLiteral(name))) {
|
|
180
|
+
assertValidSshTarget(name);
|
|
181
|
+
return literalHost(name, host, user);
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
/** Every host across all registered providers, merged by name so a device's
|
|
186
|
+
* presence/dispatchable/address and an overlay's caps coexist on one row
|
|
187
|
+
* (RUSH-1967: first-wins dedup used to drop the device row, and its status/caps
|
|
188
|
+
* with it). Provider order still decides the base row (`local` first). */
|
|
36
189
|
export async function listAllHosts() {
|
|
37
|
-
const
|
|
38
|
-
const out = [];
|
|
190
|
+
const byName = new Map();
|
|
39
191
|
for (const provider of getAllProviders()) {
|
|
40
192
|
for (const host of await provider.list()) {
|
|
41
|
-
|
|
193
|
+
const prev = byName.get(host.name);
|
|
194
|
+
if (!prev) {
|
|
195
|
+
byName.set(host.name, host);
|
|
42
196
|
continue;
|
|
43
|
-
|
|
44
|
-
|
|
197
|
+
}
|
|
198
|
+
// Same name from a later provider (a device behind an enrolled overlay):
|
|
199
|
+
// `prev` is the local/overlay row (`local` registers first), `host` is the
|
|
200
|
+
// live device row. Keep the overlay's caps/source/addedAt as the base, but
|
|
201
|
+
// the DEVICE wins every field it owns — address, user, OS, presence and
|
|
202
|
+
// dispatchable — so this matches `deviceHost()`'s per-field precedence and
|
|
203
|
+
// an enrolled device can never serve a frozen address. Getting this
|
|
204
|
+
// backwards reintroduced the frozen route through `resolveHostByCap`,
|
|
205
|
+
// which hands a `listAllHosts()` row straight to dispatch.
|
|
206
|
+
byName.set(host.name, {
|
|
207
|
+
...prev,
|
|
208
|
+
address: host.address ?? prev.address,
|
|
209
|
+
user: host.user ?? prev.user,
|
|
210
|
+
os: host.os ?? prev.os,
|
|
211
|
+
status: host.status ?? prev.status,
|
|
212
|
+
dispatchable: host.dispatchable ?? prev.dispatchable,
|
|
213
|
+
});
|
|
45
214
|
}
|
|
46
215
|
}
|
|
47
|
-
return
|
|
216
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
48
217
|
}
|
|
49
218
|
/**
|
|
50
|
-
* Resolve a host
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
219
|
+
* Resolve a `--host`/`--device` token for DISPATCH — the shape every offload
|
|
220
|
+
* caller consumes (`run --host`, the generic passthrough, teams placement, the
|
|
221
|
+
* cloud host provider, doctor, funnel, remote secrets). Grammar and merge come
|
|
222
|
+
* from {@link matchHost}; on top, this layer applies the device-only dispatch
|
|
223
|
+
* refusals so they hold even when an inline overlay shadows the device:
|
|
224
|
+
* - a control device (a paired iPhone) can never be a dispatch target;
|
|
225
|
+
* - a password-auth device throws the typed {@link DeviceOffloadUnsupportedError}
|
|
226
|
+
* (offload rides `BatchMode=yes` ssh, which can't answer a prompt);
|
|
227
|
+
* - an addressless device has nothing to dial.
|
|
56
228
|
*
|
|
57
|
-
* A bare unknown name returns null so capability-tag routing (`resolveHostByCap
|
|
58
|
-
*
|
|
59
|
-
* as an ad-hoc target.
|
|
229
|
+
* A bare unknown name returns null so capability-tag routing (`resolveHostByCap`,
|
|
230
|
+
* e.g. `--host gpu`) stays reachable, and an ad-hoc `user@host` still resolves.
|
|
60
231
|
*/
|
|
61
232
|
export async function resolveHost(name) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
233
|
+
const host = await matchHost(name);
|
|
234
|
+
if (!host)
|
|
235
|
+
return null;
|
|
236
|
+
if (host.device) {
|
|
237
|
+
if (isControlDevice(host.device)) {
|
|
238
|
+
throw new Error(`Device "${host.device.name}" is a control device (a cockpit), not an executor — it can't run agents. Dispatch to a worker device instead.`);
|
|
239
|
+
}
|
|
240
|
+
if (host.device.auth.method === 'password') {
|
|
241
|
+
throw new DeviceOffloadUnsupportedError(host.device.name);
|
|
242
|
+
}
|
|
243
|
+
if (!host.address) {
|
|
244
|
+
throw new Error(`Device "${host.device.name}" has no address (Tailscale DNS name or IP) to reach it by.`);
|
|
245
|
+
}
|
|
71
246
|
}
|
|
72
|
-
return
|
|
247
|
+
return host;
|
|
73
248
|
}
|
|
74
249
|
/**
|
|
75
250
|
* Resolve a host by capability tag (e.g. `--host gpu`). Returns the single
|
|
@@ -86,6 +86,7 @@ export const RUN_OPTION_FORWARDING = {
|
|
|
86
86
|
yes: 'forward', // a detached remote run can't answer the budget-confirm prompt
|
|
87
87
|
acp: 'forward', // the remote CLI routes through ACP on ITS side of the wire
|
|
88
88
|
autoSecrets: 'forward', // workflow frontmatter secrets resolve on the REMOTE keychain
|
|
89
|
+
emitSessionId: 'forward', // remote prints its session id as a stdout sentinel the launcher captures (session-marker.ts)
|
|
89
90
|
// rejected — cannot cross the SSH boundary; fail loud, never degrade
|
|
90
91
|
secrets: 'reject',
|
|
91
92
|
secretsKeys: 'reject',
|
|
@@ -75,10 +75,18 @@ export interface HostPromptRun {
|
|
|
75
75
|
copyCreds?: HostCredentials;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Dispatch a headless prompt run onto a resolved host
|
|
79
|
-
* id
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
78
|
+
* Dispatch a headless prompt run onto a resolved host, then relate the run's
|
|
79
|
+
* session id back to this launcher for EVERY agent — not just Claude.
|
|
80
|
+
*
|
|
81
|
+
* Claude is the only agent that accepts a forced `--session-id`, so we mint one
|
|
82
|
+
* up front and know it immediately. For every other agent the remote run coins
|
|
83
|
+
* its OWN id; we forward `--emit-session-id` so the remote prints that id as a
|
|
84
|
+
* stdout sentinel (hosts/session-marker.ts), and once the follow returns we parse
|
|
85
|
+
* it out of the mirrored local log and stamp it on the task before registering —
|
|
86
|
+
* so `agents sessions`/resume-by-id can map the discovered session home. On
|
|
87
|
+
* resume the remote session keeps its existing id (passed through, no capture).
|
|
88
|
+
*
|
|
89
|
+
* Returns the task record and the remote exit code (`-1` = follow window closed
|
|
90
|
+
* while the run continues).
|
|
83
91
|
*/
|
|
84
92
|
export declare function dispatchPromptToHost(host: Host, opts: HostPromptRun): Promise<DispatchResult>;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { randomUUID } from 'crypto';
|
|
18
18
|
import { resolveHost, resolveHostByCap } from './registry.js';
|
|
19
19
|
import { dispatchToHost } from './dispatch.js';
|
|
20
|
-
import { registerHostSession } from './session-index.js';
|
|
20
|
+
import { registerHostSession, captureRemoteSessionId } from './session-index.js';
|
|
21
21
|
/**
|
|
22
22
|
* Resolution failed with a user-actionable message the caller should print
|
|
23
23
|
* verbatim. Distinct from `DeviceOffloadUnsupportedError` (which propagates to
|
|
@@ -54,14 +54,26 @@ export async function resolveHostRunTarget(name, opts = {}) {
|
|
|
54
54
|
return host;
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
* Dispatch a headless prompt run onto a resolved host
|
|
58
|
-
* id
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
57
|
+
* Dispatch a headless prompt run onto a resolved host, then relate the run's
|
|
58
|
+
* session id back to this launcher for EVERY agent — not just Claude.
|
|
59
|
+
*
|
|
60
|
+
* Claude is the only agent that accepts a forced `--session-id`, so we mint one
|
|
61
|
+
* up front and know it immediately. For every other agent the remote run coins
|
|
62
|
+
* its OWN id; we forward `--emit-session-id` so the remote prints that id as a
|
|
63
|
+
* stdout sentinel (hosts/session-marker.ts), and once the follow returns we parse
|
|
64
|
+
* it out of the mirrored local log and stamp it on the task before registering —
|
|
65
|
+
* so `agents sessions`/resume-by-id can map the discovered session home. On
|
|
66
|
+
* resume the remote session keeps its existing id (passed through, no capture).
|
|
67
|
+
*
|
|
68
|
+
* Returns the task record and the remote exit code (`-1` = follow window closed
|
|
69
|
+
* while the run continues).
|
|
62
70
|
*/
|
|
63
71
|
export async function dispatchPromptToHost(host, opts) {
|
|
64
|
-
const
|
|
72
|
+
const forcedSessionId = opts.agent === 'claude' && !opts.resume ? randomUUID() : undefined;
|
|
73
|
+
// Ask the remote to print its resolved id whenever we did NOT force one (every
|
|
74
|
+
// non-Claude agent, and Claude-on-resume where the id is already known). No-op
|
|
75
|
+
// when the run isn't followed — nothing tails the log to catch the marker.
|
|
76
|
+
const emitSessionId = !forcedSessionId && !opts.resume && opts.follow !== false;
|
|
65
77
|
const result = await dispatchToHost(host, {
|
|
66
78
|
agent: opts.agent,
|
|
67
79
|
prompt: opts.prompt,
|
|
@@ -69,7 +81,8 @@ export async function dispatchPromptToHost(host, opts) {
|
|
|
69
81
|
mode: opts.mode,
|
|
70
82
|
model: opts.model,
|
|
71
83
|
remoteCwd: opts.remoteCwd,
|
|
72
|
-
sessionId,
|
|
84
|
+
sessionId: forcedSessionId,
|
|
85
|
+
emitSessionId,
|
|
73
86
|
name: opts.name,
|
|
74
87
|
resume: opts.resume,
|
|
75
88
|
follow: opts.follow !== false,
|
|
@@ -94,6 +107,9 @@ export async function dispatchPromptToHost(host, opts) {
|
|
|
94
107
|
passthroughArgs: opts.passthroughArgs,
|
|
95
108
|
copyCreds: opts.copyCreds,
|
|
96
109
|
});
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
// Capture the remote-coined id from the followed log (non-Claude); harmlessly a
|
|
111
|
+
// no-op when the task already carries a forced/resumed id or no marker landed.
|
|
112
|
+
const task = emitSessionId ? captureRemoteSessionId(result.task) ?? result.task : result.task;
|
|
113
|
+
registerHostSession(task, { cwd: opts.cwd ?? process.cwd(), prompt: opts.prompt });
|
|
114
|
+
return { ...result, task };
|
|
99
115
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* `[cloud/<status>]` convention.
|
|
14
14
|
*/
|
|
15
15
|
import type { SessionMeta } from '../session/types.js';
|
|
16
|
-
import type
|
|
16
|
+
import { type HostTask } from './tasks.js';
|
|
17
17
|
export interface HostSessionContext {
|
|
18
18
|
/** Local directory the `agents run --host` was invoked from. */
|
|
19
19
|
cwd: string;
|
|
@@ -32,6 +32,23 @@ export declare function hostSessionMeta(task: HostTask, ctx: HostSessionContext)
|
|
|
32
32
|
* break the dispatch itself, which has already been launched on the host.
|
|
33
33
|
*/
|
|
34
34
|
export declare function registerHostSession(task: HostTask, ctx: HostSessionContext): void;
|
|
35
|
+
/**
|
|
36
|
+
* Relate a remote-created session id back to a followed host dispatch.
|
|
37
|
+
*
|
|
38
|
+
* The remote run (dispatched with `--emit-session-id`) prints its resolved id as
|
|
39
|
+
* a stdout sentinel (see session-marker.ts) that rides the followed log into the
|
|
40
|
+
* task's local mirror. Read that mirror, parse the id, and stamp it on the task
|
|
41
|
+
* so `findTaskBySessionId` and the session-index registration work for agents
|
|
42
|
+
* that never take a forced `--session-id` — closing the gap where every
|
|
43
|
+
* non-Claude host run was orphaned.
|
|
44
|
+
*
|
|
45
|
+
* Returns the updated task when an id was captured (and differs from what's on
|
|
46
|
+
* the record), else null. A task that already carries an id (Claude's forced id,
|
|
47
|
+
* a resume) is left untouched — the marker only fills a genuinely empty slot, so
|
|
48
|
+
* it can never overwrite an authoritative id with a stale echo. Best-effort: a
|
|
49
|
+
* missing/unreadable mirror or absent marker yields null, never an exception.
|
|
50
|
+
*/
|
|
51
|
+
export declare function captureRemoteSessionId(task: HostTask): HostTask | null;
|
|
35
52
|
export interface InteractiveHostSessionContext {
|
|
36
53
|
cwd: string;
|
|
37
54
|
host: string;
|
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
* local run. The `[host/<name>]` label mirrors the cloud path's
|
|
13
13
|
* `[cloud/<status>]` convention.
|
|
14
14
|
*/
|
|
15
|
+
import * as fs from 'fs';
|
|
15
16
|
import { upsertSession } from '../session/db.js';
|
|
16
17
|
import { SESSION_AGENTS } from '../session/types.js';
|
|
18
|
+
import { localLogPath, updateTask } from './tasks.js';
|
|
19
|
+
import { parseSessionIdMarker } from './session-marker.js';
|
|
20
|
+
import { deriveShortId } from '../session/short-id.js';
|
|
17
21
|
/**
|
|
18
22
|
* Build the SessionMeta for a host-dispatched run. Returns null when the run has
|
|
19
23
|
* no captured session id (nothing stable to key/resume on) or its agent isn't a
|
|
@@ -27,7 +31,7 @@ export function hostSessionMeta(task, ctx) {
|
|
|
27
31
|
return null;
|
|
28
32
|
return {
|
|
29
33
|
id,
|
|
30
|
-
shortId: id
|
|
34
|
+
shortId: deriveShortId(id),
|
|
31
35
|
agent: task.agent,
|
|
32
36
|
timestamp: task.createdAt,
|
|
33
37
|
cwd: ctx.cwd,
|
|
@@ -57,6 +61,37 @@ export function registerHostSession(task, ctx) {
|
|
|
57
61
|
/* index write is best-effort; the run is already live on the host */
|
|
58
62
|
}
|
|
59
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Relate a remote-created session id back to a followed host dispatch.
|
|
66
|
+
*
|
|
67
|
+
* The remote run (dispatched with `--emit-session-id`) prints its resolved id as
|
|
68
|
+
* a stdout sentinel (see session-marker.ts) that rides the followed log into the
|
|
69
|
+
* task's local mirror. Read that mirror, parse the id, and stamp it on the task
|
|
70
|
+
* so `findTaskBySessionId` and the session-index registration work for agents
|
|
71
|
+
* that never take a forced `--session-id` — closing the gap where every
|
|
72
|
+
* non-Claude host run was orphaned.
|
|
73
|
+
*
|
|
74
|
+
* Returns the updated task when an id was captured (and differs from what's on
|
|
75
|
+
* the record), else null. A task that already carries an id (Claude's forced id,
|
|
76
|
+
* a resume) is left untouched — the marker only fills a genuinely empty slot, so
|
|
77
|
+
* it can never overwrite an authoritative id with a stale echo. Best-effort: a
|
|
78
|
+
* missing/unreadable mirror or absent marker yields null, never an exception.
|
|
79
|
+
*/
|
|
80
|
+
export function captureRemoteSessionId(task) {
|
|
81
|
+
if (task.sessionId)
|
|
82
|
+
return null;
|
|
83
|
+
let text;
|
|
84
|
+
try {
|
|
85
|
+
text = fs.readFileSync(localLogPath(task.id), 'utf8');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null; // no local mirror (unfollowed run, or read raced the follow)
|
|
89
|
+
}
|
|
90
|
+
const captured = parseSessionIdMarker(text);
|
|
91
|
+
if (!captured)
|
|
92
|
+
return null;
|
|
93
|
+
return updateTask(task.id, { sessionId: captured });
|
|
94
|
+
}
|
|
60
95
|
/**
|
|
61
96
|
* Register an interactive host run (no prompt, TTY forwarded over SSH) in the
|
|
62
97
|
* local session index. Unlike detached host runs, there is no remote log/exit
|
|
@@ -69,7 +104,7 @@ export function registerInteractiveHostSession(ctx) {
|
|
|
69
104
|
try {
|
|
70
105
|
upsertSession({
|
|
71
106
|
id: ctx.sessionId,
|
|
72
|
-
shortId: ctx.sessionId
|
|
107
|
+
shortId: deriveShortId(ctx.sessionId),
|
|
73
108
|
agent: ctx.agent,
|
|
74
109
|
timestamp: ctx.createdAt ?? new Date().toISOString(),
|
|
75
110
|
cwd: ctx.cwd,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relate a remote-created session id back to the launching agent.
|
|
3
|
+
*
|
|
4
|
+
* A host dispatch (`agents run --host`) launches `agents run` on the remote box.
|
|
5
|
+
* Only Claude accepts a forced `--session-id`, so for every OTHER agent the
|
|
6
|
+
* remote run coins its OWN session id (via its SessionStart hook) and the
|
|
7
|
+
* launcher never learns it — the run is orphaned in `agents sessions`.
|
|
8
|
+
*
|
|
9
|
+
* This module closes that gap without an extra SSH round-trip: the dispatch
|
|
10
|
+
* forwards `--emit-session-id`, which makes the remote `agents run` print its
|
|
11
|
+
* resolved session id as a stable one-line sentinel to stdout. That line rides
|
|
12
|
+
* the same combined log the follow already mirrors locally, so the launcher
|
|
13
|
+
* parses the id straight out of the streamed output and stamps it on the task.
|
|
14
|
+
*
|
|
15
|
+
* The marker is a fixed ASCII frame with the id on its own line. A real session
|
|
16
|
+
* id (`[A-Za-z0-9._-]`) never contains the marker's own bytes, so the parser can
|
|
17
|
+
* scan for the LAST occurrence and stay robust to an agent echoing the token in
|
|
18
|
+
* its own output.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The single line the remote run prints to stdout so the launcher can capture
|
|
22
|
+
* the session id the remote coined. Newline-framed on both sides so it lands on
|
|
23
|
+
* its own line regardless of what the agent last wrote.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sessionIdMarkerLine(sessionId: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Extract the session id from a chunk of followed remote output, or null when no
|
|
28
|
+
* marker is present. Scans for the LAST marker (so an id echoed into the log
|
|
29
|
+
* earlier can never mask the real trailing sentinel) and validates the captured
|
|
30
|
+
* token against the session-id charset — a malformed frame yields null rather
|
|
31
|
+
* than a bogus id that could never match a real session.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseSessionIdMarker(text: string): string | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relate a remote-created session id back to the launching agent.
|
|
3
|
+
*
|
|
4
|
+
* A host dispatch (`agents run --host`) launches `agents run` on the remote box.
|
|
5
|
+
* Only Claude accepts a forced `--session-id`, so for every OTHER agent the
|
|
6
|
+
* remote run coins its OWN session id (via its SessionStart hook) and the
|
|
7
|
+
* launcher never learns it — the run is orphaned in `agents sessions`.
|
|
8
|
+
*
|
|
9
|
+
* This module closes that gap without an extra SSH round-trip: the dispatch
|
|
10
|
+
* forwards `--emit-session-id`, which makes the remote `agents run` print its
|
|
11
|
+
* resolved session id as a stable one-line sentinel to stdout. That line rides
|
|
12
|
+
* the same combined log the follow already mirrors locally, so the launcher
|
|
13
|
+
* parses the id straight out of the streamed output and stamps it on the task.
|
|
14
|
+
*
|
|
15
|
+
* The marker is a fixed ASCII frame with the id on its own line. A real session
|
|
16
|
+
* id (`[A-Za-z0-9._-]`) never contains the marker's own bytes, so the parser can
|
|
17
|
+
* scan for the LAST occurrence and stay robust to an agent echoing the token in
|
|
18
|
+
* its own output.
|
|
19
|
+
*/
|
|
20
|
+
const MARKER_PREFIX = '@@AGENTS_SESSION_ID ';
|
|
21
|
+
const MARKER_SUFFIX = '@@';
|
|
22
|
+
/** Only characters a real agent session id can hold — no marker bytes, no spaces. */
|
|
23
|
+
const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;
|
|
24
|
+
/**
|
|
25
|
+
* The single line the remote run prints to stdout so the launcher can capture
|
|
26
|
+
* the session id the remote coined. Newline-framed on both sides so it lands on
|
|
27
|
+
* its own line regardless of what the agent last wrote.
|
|
28
|
+
*/
|
|
29
|
+
export function sessionIdMarkerLine(sessionId) {
|
|
30
|
+
return `\n${MARKER_PREFIX}${sessionId}${MARKER_SUFFIX}\n`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract the session id from a chunk of followed remote output, or null when no
|
|
34
|
+
* marker is present. Scans for the LAST marker (so an id echoed into the log
|
|
35
|
+
* earlier can never mask the real trailing sentinel) and validates the captured
|
|
36
|
+
* token against the session-id charset — a malformed frame yields null rather
|
|
37
|
+
* than a bogus id that could never match a real session.
|
|
38
|
+
*/
|
|
39
|
+
export function parseSessionIdMarker(text) {
|
|
40
|
+
const last = text.lastIndexOf(MARKER_PREFIX);
|
|
41
|
+
if (last === -1)
|
|
42
|
+
return null;
|
|
43
|
+
const start = last + MARKER_PREFIX.length;
|
|
44
|
+
const end = text.indexOf(MARKER_SUFFIX, start);
|
|
45
|
+
if (end === -1)
|
|
46
|
+
return null;
|
|
47
|
+
const id = text.slice(start, end).trim();
|
|
48
|
+
if (!SESSION_ID_RE.test(id))
|
|
49
|
+
return null;
|
|
50
|
+
return id;
|
|
51
|
+
}
|
|
@@ -24,10 +24,14 @@ export interface HostTask {
|
|
|
24
24
|
*/
|
|
25
25
|
name?: string;
|
|
26
26
|
/**
|
|
27
|
-
* The agent session id
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
27
|
+
* The remote run's agent session id, so `agents sessions`/resume-by-id can map
|
|
28
|
+
* a discovered session back to the host it lives on. Two sources: for Claude
|
|
29
|
+
* (the only agent that accepts `--session-id`) it's the id we FORCED at
|
|
30
|
+
* dispatch; for every other agent it's the id the remote COINED, captured from
|
|
31
|
+
* the run's stdout sentinel (`--emit-session-id`, see session-marker.ts) once
|
|
32
|
+
* the follow returns and stamped on the record via `captureRemoteSessionId`.
|
|
33
|
+
* Absent only until that capture lands — e.g. an unfollowed (`--no-follow`)
|
|
34
|
+
* non-Claude run whose id the reconcile path fills in later.
|
|
31
35
|
*/
|
|
32
36
|
sessionId?: string;
|
|
33
37
|
/** Remote paths (under the host's ~/.agents/.cache/hosts/). */
|
package/dist/lib/import.d.ts
CHANGED
|
@@ -111,3 +111,28 @@ export declare function importInstallScriptBinary(spec: AgentBinarySpec, version
|
|
|
111
111
|
* /opt/homebrew/bin/{cli} -> ../lib/node_modules/{pkg}/dist/index.js
|
|
112
112
|
*/
|
|
113
113
|
export declare function resolvePackageDirFromBinary(binaryPath: string): string | null;
|
|
114
|
+
/**
|
|
115
|
+
* Seed an ISOLATED version's home from the user's real `~/.<agent>` — the mirror of
|
|
116
|
+
* {@link importAgentConfig}, which adopts.
|
|
117
|
+
*
|
|
118
|
+
* The difference is the whole point: this COPIES and leaves the original in place,
|
|
119
|
+
* never symlinks it, never sets a default, never creates a shim. So an isolated copy
|
|
120
|
+
* can start from the setup the user already has instead of from nothing, which was
|
|
121
|
+
* the only way to get a working sandbox before.
|
|
122
|
+
*
|
|
123
|
+
* Credentials are skipped by default and reported, not silently included. An isolated
|
|
124
|
+
* copy is a separate principal — `agents add --isolated` already tells the user to
|
|
125
|
+
* sign in on first run — and copying tokens into it should be a choice, not a side
|
|
126
|
+
* effect of wanting your settings. `--with-auth` opts in.
|
|
127
|
+
*/
|
|
128
|
+
export declare function seedIsolatedConfigFromLocal(agentId: AgentId, version: string, opts?: {
|
|
129
|
+
withAuth?: boolean;
|
|
130
|
+
all?: boolean;
|
|
131
|
+
}): {
|
|
132
|
+
seeded: boolean;
|
|
133
|
+
from: string;
|
|
134
|
+
to: string;
|
|
135
|
+
skippedAuth: string[];
|
|
136
|
+
skippedRuntime: string[];
|
|
137
|
+
error?: string;
|
|
138
|
+
};
|