@phnx-labs/agents-cli 1.22.114 → 1.22.116
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 +245 -0
- package/README.md +22 -102
- package/dist/browser.js +0 -0
- package/dist/cli/command-registry.js +0 -7
- package/dist/commands/accounts.d.ts +3 -77
- package/dist/commands/accounts.js +13 -366
- package/dist/commands/audit.js +1 -1
- package/dist/commands/auth.js +0 -2
- package/dist/commands/browser.d.ts +53 -0
- package/dist/commands/browser.js +210 -9
- package/dist/commands/doctor.d.ts +2 -3
- package/dist/commands/doctor.js +0 -11
- package/dist/commands/events.d.ts +1 -3
- package/dist/commands/events.js +4 -7
- package/dist/commands/exec.d.ts +0 -1
- package/dist/commands/exec.js +214 -148
- package/dist/commands/feed-watch.js +52 -8
- package/dist/commands/feed.js +65 -14
- package/dist/commands/focus.js +1 -1
- package/dist/commands/go.d.ts +0 -17
- package/dist/commands/go.js +2 -19
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mcp.js +8 -83
- package/dist/commands/memory.js +4 -47
- package/dist/commands/message.js +4 -4
- package/dist/commands/plugins.js +1 -93
- package/dist/commands/repo.js +0 -44
- package/dist/commands/resume.js +4 -1
- package/dist/commands/secrets-passthrough.js +2 -2
- package/dist/commands/send.d.ts +4 -4
- package/dist/commands/send.js +6 -51
- package/dist/commands/sessions-backup-setup.js +1 -1
- package/dist/commands/sessions-picker.d.ts +11 -0
- package/dist/commands/sessions-picker.js +88 -7
- package/dist/commands/sessions-resume.js +0 -1
- package/dist/commands/sessions-share.d.ts +5 -7
- package/dist/commands/sessions-share.js +98 -49
- package/dist/commands/sessions.d.ts +21 -2
- package/dist/commands/sessions.js +158 -11
- package/dist/commands/setup-browser.js +18 -2
- package/dist/commands/setup-computer.js +20 -6
- package/dist/commands/setup-secrets.d.ts +2 -2
- package/dist/commands/setup-secrets.js +24 -4
- package/dist/commands/setup-term.d.ts +24 -0
- package/dist/commands/setup-term.js +70 -0
- package/dist/commands/setup-terminal.d.ts +3 -0
- package/dist/commands/setup-terminal.js +22 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +29 -11
- package/dist/commands/skills.js +0 -8
- package/dist/commands/ssh.d.ts +6 -0
- package/dist/commands/ssh.js +93 -302
- package/dist/commands/sync.js +14 -5
- package/dist/commands/teams.js +1 -1
- package/dist/commands/traces.js +1 -1
- package/dist/lib/accounting/rotate.d.ts +63 -1
- package/dist/lib/accounting/rotate.js +56 -0
- package/dist/lib/accounts/add.d.ts +0 -5
- package/dist/lib/accounts/add.js +3 -9
- package/dist/lib/accounts/slots.js +32 -2
- package/dist/lib/answer-router.d.ts +11 -2
- package/dist/lib/answer-router.js +26 -2
- package/dist/lib/artifacts-client.d.ts +20 -0
- package/dist/lib/artifacts-client.js +46 -0
- package/dist/lib/auth-mint.d.ts +5 -4
- package/dist/lib/auth-mint.js +6 -5
- package/dist/lib/browser/drivers/arc.d.ts +1 -1
- package/dist/lib/browser/runtime-state.d.ts +55 -0
- package/dist/lib/browser/runtime-state.js +99 -18
- package/dist/lib/browser/service.d.ts +10 -0
- package/dist/lib/browser/service.js +229 -37
- package/dist/lib/browser/types.d.ts +18 -0
- package/dist/lib/cli-resources.js +3 -1
- package/dist/lib/cloud/dispatch.js +1 -1
- package/dist/lib/cloudflare/creds.d.ts +10 -0
- package/dist/lib/cloudflare/creds.js +46 -0
- package/dist/lib/cloudflare/provision.d.ts +35 -0
- package/dist/lib/cloudflare/provision.js +144 -0
- package/dist/lib/computer/sessions-list.d.ts +55 -0
- package/dist/lib/computer/sessions-list.js +168 -1
- package/dist/lib/config-keys.d.ts +1 -1
- package/dist/lib/config-keys.js +5 -0
- package/dist/lib/daemon/daemon.js +8 -1
- package/dist/lib/daemon/feed-stream-service.d.ts +23 -0
- package/dist/lib/daemon/feed-stream-service.js +40 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.js +61 -0
- package/dist/lib/devices/connect.d.ts +49 -5
- package/dist/lib/devices/connect.js +169 -21
- package/dist/lib/devices/doctor-findings.js +2 -6
- package/dist/lib/feed/answer.d.ts +153 -4
- package/dist/lib/feed/answer.js +716 -105
- package/dist/lib/feed/envelope.d.ts +79 -0
- package/dist/lib/feed/envelope.js +23 -0
- package/dist/lib/feed/events.d.ts +6 -0
- package/dist/lib/feed/events.js +8 -0
- package/dist/lib/feed/feed.d.ts +61 -1
- package/dist/lib/feed/feed.js +226 -14
- package/dist/lib/feed/hub-server.d.ts +141 -0
- package/dist/lib/feed/hub-server.js +586 -0
- package/dist/lib/feed/hub.d.ts +95 -0
- package/dist/lib/feed/hub.js +255 -0
- package/dist/lib/feed/pr-status.d.ts +8 -0
- package/dist/lib/feed/pr-status.js +9 -1
- package/dist/lib/feed/tool-activity.d.ts +108 -0
- package/dist/lib/feed/tool-activity.js +313 -0
- package/dist/lib/feed/tools.d.ts +198 -0
- package/dist/lib/feed/tools.js +265 -0
- package/dist/lib/feed/watch.d.ts +50 -50
- package/dist/lib/feed/watch.js +147 -16
- package/dist/lib/feed-outcome.d.ts +1 -1
- package/dist/lib/feed-outcome.js +9 -2
- package/dist/lib/feed-policy.js +9 -3
- package/dist/lib/fleet/auth-sync.d.ts +2 -55
- package/dist/lib/fleet/auth-sync.js +2 -89
- package/dist/lib/format.d.ts +1 -1
- package/dist/lib/format.js +1 -1
- package/dist/lib/git.d.ts +0 -16
- package/dist/lib/git.js +0 -58
- package/dist/lib/harness-auth-capabilities.js +7 -2
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +20 -1
- package/dist/lib/hosts/dispatch.js +52 -30
- package/dist/lib/hosts/remote-cmd.d.ts +72 -1
- package/dist/lib/hosts/remote-cmd.js +151 -10
- package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +2 -0
- package/dist/lib/hosts/remote-cmd.test-fixture.js +22 -0
- package/dist/lib/mailbox.d.ts +12 -0
- package/dist/lib/mailbox.js +16 -2
- package/dist/lib/mcp.js +17 -11
- package/dist/lib/menubar/snapshot.d.ts +51 -0
- package/dist/lib/menubar/snapshot.js +42 -3
- package/dist/lib/open-url.js +2 -2
- package/dist/lib/probe.d.ts +4 -1
- package/dist/lib/probe.js +5 -2
- package/dist/lib/projects.d.ts +23 -0
- package/dist/lib/projects.js +78 -0
- package/dist/lib/pwsh.d.ts +33 -0
- package/dist/lib/pwsh.js +56 -0
- package/dist/lib/redact.d.ts +8 -0
- package/dist/lib/redact.js +11 -0
- package/dist/lib/refresh.d.ts +6 -2
- package/dist/lib/refresh.js +92 -72
- package/dist/lib/secrets-cli.d.ts +11 -0
- package/dist/lib/secrets-cli.js +30 -0
- package/dist/lib/secrets-client.js +3 -2
- package/dist/lib/session/active.d.ts +1 -0
- package/dist/lib/session/active.js +8 -0
- package/dist/lib/session/db.d.ts +67 -3
- package/dist/lib/session/db.js +381 -126
- package/dist/lib/session/detached.d.ts +7 -0
- package/dist/lib/session/detached.js +29 -0
- package/dist/lib/session/prompt.d.ts +23 -7
- package/dist/lib/session/prompt.js +46 -8
- package/dist/lib/session/remote/peer-stream.d.ts +24 -2
- package/dist/lib/session/remote/peer-stream.js +33 -6
- package/dist/lib/session/remote/remote-list.d.ts +20 -0
- package/dist/lib/session/remote/remote-list.js +22 -6
- package/dist/lib/session/remote/watch.d.ts +12 -0
- package/dist/lib/session/remote/watch.js +9 -0
- package/dist/lib/session/remote-preview-cache.d.ts +29 -0
- package/dist/lib/session/remote-preview-cache.js +373 -0
- package/dist/lib/session/sync/backend.d.ts +3 -3
- package/dist/lib/session/sync/backend.js +3 -3
- package/dist/lib/session/sync/provision.d.ts +1 -1
- package/dist/lib/session/sync/provision.js +2 -2
- package/dist/lib/session/tail.d.ts +50 -0
- package/dist/lib/session/tail.js +219 -0
- package/dist/lib/sessions-client.js +0 -3
- package/dist/lib/setup-tool-install.d.ts +3 -0
- package/dist/lib/setup-tool-install.js +27 -0
- package/dist/lib/setup-tool-status.d.ts +22 -0
- package/dist/lib/setup-tool-status.js +220 -0
- package/dist/lib/share-runtime.d.ts +11 -0
- package/dist/lib/share-runtime.js +63 -0
- package/dist/lib/signin-badge.d.ts +19 -4
- package/dist/lib/signin-badge.js +29 -11
- package/dist/lib/smart-launch.d.ts +1 -5
- package/dist/lib/smart-launch.js +3 -11
- package/dist/lib/ssh-exec.d.ts +44 -0
- package/dist/lib/ssh-exec.js +119 -0
- package/dist/lib/startup/command-registry.d.ts +6 -4
- package/dist/lib/startup/command-registry.js +10 -7
- package/dist/lib/state.js +2 -2
- package/dist/lib/storage/selection.d.ts +2 -2
- package/dist/lib/storage/selection.js +2 -2
- package/dist/lib/sync-umbrella.d.ts +5 -0
- package/dist/lib/sync-umbrella.js +18 -10
- package/dist/lib/term-driver.d.ts +24 -0
- package/dist/lib/term-driver.js +36 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/index.js +1 -1
- package/dist/lib/terminal/inject.d.ts +38 -0
- package/dist/lib/terminal/inject.js +55 -9
- package/dist/lib/terminal/transport.d.ts +15 -5
- package/dist/lib/terminal/transport.js +61 -11
- package/dist/lib/traces/backend.d.ts +1 -2
- package/dist/lib/traces/backend.js +1 -2
- package/dist/lib/traces/provision.d.ts +1 -1
- package/dist/lib/traces/provision.js +2 -2
- package/dist/lib/types.d.ts +8 -6
- package/package.json +2 -3
- package/dist/commands/artifacts-setup.d.ts +0 -53
- package/dist/commands/artifacts-setup.js +0 -161
- package/dist/commands/artifacts.d.ts +0 -18
- package/dist/commands/artifacts.js +0 -58
- package/dist/commands/attach.d.ts +0 -12
- package/dist/commands/attach.js +0 -86
- package/dist/commands/auth-mint.d.ts +0 -12
- package/dist/commands/auth-mint.js +0 -108
- package/dist/commands/reconnect.d.ts +0 -46
- package/dist/commands/reconnect.js +0 -115
- package/dist/commands/share.d.ts +0 -293
- package/dist/commands/share.js +0 -1424
- package/dist/lib/fleet/remote-login.d.ts +0 -170
- package/dist/lib/fleet/remote-login.js +0 -568
- package/dist/lib/share/analytics.d.ts +0 -13
- package/dist/lib/share/analytics.js +0 -45
- package/dist/lib/share/backend.d.ts +0 -120
- package/dist/lib/share/backend.js +0 -176
- package/dist/lib/share/capture.d.ts +0 -31
- package/dist/lib/share/capture.js +0 -174
- package/dist/lib/share/config.d.ts +0 -72
- package/dist/lib/share/config.js +0 -211
- package/dist/lib/share/delete.d.ts +0 -123
- package/dist/lib/share/delete.js +0 -173
- package/dist/lib/share/html.d.ts +0 -20
- package/dist/lib/share/html.js +0 -88
- package/dist/lib/share/http-error.d.ts +0 -53
- package/dist/lib/share/http-error.js +0 -65
- package/dist/lib/share/og.d.ts +0 -26
- package/dist/lib/share/og.js +0 -84
- package/dist/lib/share/provision.d.ts +0 -127
- package/dist/lib/share/provision.js +0 -285
- package/dist/lib/share/publish.d.ts +0 -379
- package/dist/lib/share/publish.js +0 -818
- package/dist/lib/share/worker-template.d.ts +0 -27
- package/dist/lib/share/worker-template.js +0 -2424
- package/dist/lib/storage/index.d.ts +0 -14
- package/dist/lib/storage/index.js +0 -14
- package/dist/lib/storage/visibility.d.ts +0 -82
- package/dist/lib/storage/visibility.js +0 -99
package/dist/lib/signin-badge.js
CHANGED
|
@@ -11,13 +11,15 @@
|
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { addWorkerRefusal } from './accounts/add.js';
|
|
13
13
|
import { AGENTS } from './agents.js';
|
|
14
|
+
import { HARNESS_AUTH } from './harness-auth-capabilities.js';
|
|
14
15
|
import { CONFIG_ENV_ISOLATED_AGENTS } from './installations/shims.js';
|
|
15
16
|
/**
|
|
16
17
|
* The exact command that logs a given agent in — for warn banners and nudges.
|
|
17
18
|
* Driven off the registry `cliCommand` with the per-agent subcommand overrides
|
|
18
|
-
* (verified against the real CLIs): codex/grok
|
|
19
|
-
*
|
|
20
|
-
* remaining agents (kimi, gemini, …) start
|
|
19
|
+
* (verified against the real CLIs): codex/grok/opencode run the finite login
|
|
20
|
+
* subcommand `HARNESS_AUTH` wires (`loginSubcommand`), claude logs in from
|
|
21
|
+
* inside its TUI via `/login`, and the remaining agents (kimi, gemini, …) start
|
|
22
|
+
* their device/oauth flow on launch.
|
|
21
23
|
*/
|
|
22
24
|
export function loginHint(agentId) {
|
|
23
25
|
const cli = AGENTS[agentId]?.cliCommand ?? agentId;
|
|
@@ -26,9 +28,8 @@ export function loginHint(agentId) {
|
|
|
26
28
|
return `${cli}, then /login`;
|
|
27
29
|
case 'codex':
|
|
28
30
|
case 'grok':
|
|
29
|
-
return `${cli} login`;
|
|
30
31
|
case 'opencode':
|
|
31
|
-
return `${cli}
|
|
32
|
+
return `${cli} ${loginSubcommand(agentId)}`;
|
|
32
33
|
// Warp Agent CLI has no `login` subcommand: running `warp` opens a browser
|
|
33
34
|
// sign-in on launch (or set WARP_API_KEY / pass --api-key), so the default
|
|
34
35
|
// bare-`warp` hint is correct.
|
|
@@ -36,10 +37,28 @@ export function loginHint(agentId) {
|
|
|
36
37
|
return cli;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Harnesses that log back in through a finite native subcommand runnable via
|
|
42
|
+
* `agents run <agent>@<version> -- <args>`. Claude logs in from inside its TUI
|
|
43
|
+
* (`/login`); cursor and the rest start their device/oauth flow on launch.
|
|
44
|
+
*/
|
|
45
|
+
export const SUBCOMMAND_LOGIN_AGENTS = ['codex', 'grok', 'opencode'];
|
|
46
|
+
/**
|
|
47
|
+
* The finite native login subcommand (`login`, `login --device-auth`,
|
|
48
|
+
* `auth login`) for a {@link SUBCOMMAND_LOGIN_AGENTS} harness, read from the one
|
|
49
|
+
* `HARNESS_AUTH` row so every surface that spells it — the hint, the per-version
|
|
50
|
+
* fix, `agents doctor` — agrees. Null for every other harness.
|
|
51
|
+
*/
|
|
52
|
+
export function loginSubcommand(agent) {
|
|
53
|
+
if (!SUBCOMMAND_LOGIN_AGENTS.includes(agent))
|
|
54
|
+
return null;
|
|
55
|
+
return HARNESS_AUTH[agent].login.join(' ');
|
|
56
|
+
}
|
|
39
57
|
/**
|
|
40
58
|
* Exact action shown beside a non-live account. Every emitted command exists
|
|
41
59
|
* today — never a planned surface and never a hidden verb:
|
|
42
|
-
* - Per-device harnesses repair
|
|
60
|
+
* - Per-device harnesses (kimi/antigravity) repair on the box itself: run the
|
|
61
|
+
* harness there (`loginHint`) and complete its native login.
|
|
43
62
|
* - Named accounts re-auth through `agents accounts login <harness>#<name>`.
|
|
44
63
|
* A known account with no slot on a headed device is onboarded with
|
|
45
64
|
* `agents accounts add <harness> <name>`. A worker never runs an
|
|
@@ -55,7 +74,7 @@ export function fixFor(input) {
|
|
|
55
74
|
if (verdict === 'live' || verdict === 'rate_limited' || verdict === 'unverified' || verdict === 'ready')
|
|
56
75
|
return null;
|
|
57
76
|
if (input.provisioning === 'per-device' || verdict === 'per-device') {
|
|
58
|
-
return
|
|
77
|
+
return loginHint(agent);
|
|
59
78
|
}
|
|
60
79
|
if (input.name) {
|
|
61
80
|
const worker = addWorkerRefusal(agent, input.name);
|
|
@@ -70,10 +89,9 @@ export function fixFor(input) {
|
|
|
70
89
|
return loginHint(agent);
|
|
71
90
|
if (agent === 'claude')
|
|
72
91
|
return `agents run ${agent}@${version}, then /login`;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return `agents run ${agent}@${version} -- auth login`;
|
|
92
|
+
const sub = loginSubcommand(agent);
|
|
93
|
+
if (sub)
|
|
94
|
+
return `agents run ${agent}@${version} -- ${sub}`;
|
|
77
95
|
return `agents run ${agent}@${version}`;
|
|
78
96
|
}
|
|
79
97
|
/**
|
|
@@ -117,16 +117,12 @@ type DeviceAutoHostOptions = {
|
|
|
117
117
|
device?: string;
|
|
118
118
|
on?: string;
|
|
119
119
|
computer?: string;
|
|
120
|
-
/** @deprecated Hidden alias for `--device auto`. */
|
|
121
|
-
smart?: boolean;
|
|
122
120
|
balanced?: boolean;
|
|
123
121
|
strategy?: string;
|
|
124
122
|
};
|
|
125
123
|
export type DeviceAutoApplyResult = {
|
|
126
124
|
/** True when automatic placement ran successfully. */
|
|
127
125
|
attempted: boolean;
|
|
128
|
-
/** True when deprecated `--smart` was seen. */
|
|
129
|
-
deprecationSmart: boolean;
|
|
130
126
|
/** Present when affinity resolved without error. */
|
|
131
127
|
banner?: {
|
|
132
128
|
hostLabel: string;
|
|
@@ -135,7 +131,7 @@ export type DeviceAutoApplyResult = {
|
|
|
135
131
|
};
|
|
136
132
|
};
|
|
137
133
|
/**
|
|
138
|
-
* Apply `--device auto`
|
|
134
|
+
* Apply `--device auto` onto run options.
|
|
139
135
|
* Mutates `options` in place. Placement failures propagate without rewriting
|
|
140
136
|
* `auto`, so callers fail loud instead of silently launching locally.
|
|
141
137
|
*/
|
package/dist/lib/smart-launch.js
CHANGED
|
@@ -176,7 +176,7 @@ export async function resolveDeviceAuto(agent, opts = {}) {
|
|
|
176
176
|
if (eligiblePool.length === 0) {
|
|
177
177
|
throw new Error(formatNoHealthyDeviceError(pool, signals, agent));
|
|
178
178
|
}
|
|
179
|
-
// `agents devices
|
|
179
|
+
// `agents devices config <name> auto-launch.preferred on` boosts a device in the ranking — resolved
|
|
180
180
|
// over the same pool so a fleet default reaches a doc-less box.
|
|
181
181
|
const preferred = opts.preferred ?? autoLaunchPreferredSet(pool, { roster: pool });
|
|
182
182
|
const picked = pickBestDevice(eligiblePool, [], { signals, agentLabel: agent, preferred });
|
|
@@ -260,21 +260,14 @@ export function isDeviceAuto(value) {
|
|
|
260
260
|
}
|
|
261
261
|
const HOST_SLOTS = ['host', 'device', 'on', 'computer'];
|
|
262
262
|
/**
|
|
263
|
-
* Apply `--device auto`
|
|
263
|
+
* Apply `--device auto` onto run options.
|
|
264
264
|
* Mutates `options` in place. Placement failures propagate without rewriting
|
|
265
265
|
* `auto`, so callers fail loud instead of silently launching locally.
|
|
266
266
|
*/
|
|
267
267
|
export async function applyDeviceAutoToOptions(options, deps = {}) {
|
|
268
|
-
let deprecationSmart = false;
|
|
269
|
-
if (options.smart) {
|
|
270
|
-
const anyHost = HOST_SLOTS.some((k) => typeof options[k] === 'string' && options[k].trim() !== '');
|
|
271
|
-
if (!anyHost)
|
|
272
|
-
options.device = 'auto';
|
|
273
|
-
deprecationSmart = true;
|
|
274
|
-
}
|
|
275
268
|
const hasAuto = HOST_SLOTS.some((k) => isDeviceAuto(options[k]));
|
|
276
269
|
if (!hasAuto) {
|
|
277
|
-
return { attempted: false
|
|
270
|
+
return { attempted: false };
|
|
278
271
|
}
|
|
279
272
|
const accountPickerRequested = deps.accountPickerRequested ?? false;
|
|
280
273
|
const resolve = deps.resolve ?? ((accountPicker) => resolveDeviceAuto(deps.agent, { accountPicker }));
|
|
@@ -296,7 +289,6 @@ export async function applyDeviceAutoToOptions(options, deps = {}) {
|
|
|
296
289
|
const acctNote = accountPickerRequested ? 'accounts=picker' : 'accounts=balanced';
|
|
297
290
|
return {
|
|
298
291
|
attempted: true,
|
|
299
|
-
deprecationSmart,
|
|
300
292
|
banner: { hostLabel, deviceHint, acctNote },
|
|
301
293
|
};
|
|
302
294
|
}
|
package/dist/lib/ssh-exec.d.ts
CHANGED
|
@@ -173,6 +173,50 @@ interface SshExecRawStreamOptions extends SshExecOptions {
|
|
|
173
173
|
* offset-resumed log following where a UTF-8 decode boundary must not shift the
|
|
174
174
|
* byte cursor.
|
|
175
175
|
*/
|
|
176
|
+
/** Grace between SIGTERM and SIGKILL for a stream that will not stop. */
|
|
177
|
+
export declare const SSH_STREAM_KILL_GRACE_MS = 3000;
|
|
178
|
+
/** Bytes of a stream's stderr retained; the rest is dropped, with a note. */
|
|
179
|
+
export declare const SSH_STREAM_MAX_STDERR: number;
|
|
180
|
+
export interface SshStreamResult {
|
|
181
|
+
code: number | null;
|
|
182
|
+
stderr: Buffer;
|
|
183
|
+
timedOut: boolean;
|
|
184
|
+
/** True when the child had to be SIGKILLed after ignoring SIGTERM. */
|
|
185
|
+
killed: boolean;
|
|
186
|
+
/** True when stderr was truncated at {@link SSH_STREAM_MAX_STDERR}. */
|
|
187
|
+
stderrTruncated: boolean;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Stream one ssh command's stdout, with the args and env a CALLER built.
|
|
191
|
+
*
|
|
192
|
+
* This exists because {@link sshExecRawStream} assembles its own ssh args, which
|
|
193
|
+
* means it cannot carry a device's canonical auth — the askpass shim for a
|
|
194
|
+
* password-auth box, `-i`/`IdentitiesOnly` for an explicit identity file, or the
|
|
195
|
+
* managed known-hosts pinning. A caller that has already built those through
|
|
196
|
+
* `buildSshInvocation` needs a way to run them, and duplicating the transport to
|
|
197
|
+
* get it would be the worse answer. So this takes `args`/`env` verbatim and adds
|
|
198
|
+
* only the lifecycle guarantees:
|
|
199
|
+
*
|
|
200
|
+
* - **SIGTERM then SIGKILL.** A timeout or abort that only sends SIGTERM leaks a
|
|
201
|
+
* child that ignores it — ssh does, mid-handshake — and the promise never
|
|
202
|
+
* settles. After {@link SSH_STREAM_KILL_GRACE_MS} the child is SIGKILLed and
|
|
203
|
+
* `killed` says so.
|
|
204
|
+
* - **Bounded stderr.** A peer that writes endlessly to stderr would otherwise
|
|
205
|
+
* grow this buffer without limit; it is capped and `stderrTruncated` says so.
|
|
206
|
+
* - **`onStdout` cannot break the caller's cleanup.** A throw from the consumer
|
|
207
|
+
* is captured and re-thrown by the awaiting caller, so a write failure ends the
|
|
208
|
+
* transfer instead of escaping as an unhandled error inside a stream event.
|
|
209
|
+
*/
|
|
210
|
+
export declare function sshStreamWithArgs(opts: {
|
|
211
|
+
args: string[];
|
|
212
|
+
env?: Record<string, string>;
|
|
213
|
+
onStdout: (chunk: Buffer) => void;
|
|
214
|
+
timeoutMs?: number;
|
|
215
|
+
killGraceMs?: number;
|
|
216
|
+
maxStderrBytes?: number;
|
|
217
|
+
signal?: AbortSignal;
|
|
218
|
+
sshBin?: string;
|
|
219
|
+
}): Promise<SshStreamResult>;
|
|
176
220
|
export declare function sshExecRawStream(target: string, remoteCmd: string, opts: SshExecRawStreamOptions): Promise<Omit<SshExecRawResult, 'stdout'>>;
|
|
177
221
|
/** True if `target` is reachable over ssh (a passwordless `true` succeeds quickly). */
|
|
178
222
|
export declare function sshReachable(target: string, timeoutMs?: number): boolean;
|
package/dist/lib/ssh-exec.js
CHANGED
|
@@ -317,6 +317,125 @@ export function sshExecRaw(target, remoteCmd, opts = {}) {
|
|
|
317
317
|
* offset-resumed log following where a UTF-8 decode boundary must not shift the
|
|
318
318
|
* byte cursor.
|
|
319
319
|
*/
|
|
320
|
+
/** Grace between SIGTERM and SIGKILL for a stream that will not stop. */
|
|
321
|
+
export const SSH_STREAM_KILL_GRACE_MS = 3_000;
|
|
322
|
+
/** Bytes of a stream's stderr retained; the rest is dropped, with a note. */
|
|
323
|
+
export const SSH_STREAM_MAX_STDERR = 8 * 1024;
|
|
324
|
+
/**
|
|
325
|
+
* Stream one ssh command's stdout, with the args and env a CALLER built.
|
|
326
|
+
*
|
|
327
|
+
* This exists because {@link sshExecRawStream} assembles its own ssh args, which
|
|
328
|
+
* means it cannot carry a device's canonical auth — the askpass shim for a
|
|
329
|
+
* password-auth box, `-i`/`IdentitiesOnly` for an explicit identity file, or the
|
|
330
|
+
* managed known-hosts pinning. A caller that has already built those through
|
|
331
|
+
* `buildSshInvocation` needs a way to run them, and duplicating the transport to
|
|
332
|
+
* get it would be the worse answer. So this takes `args`/`env` verbatim and adds
|
|
333
|
+
* only the lifecycle guarantees:
|
|
334
|
+
*
|
|
335
|
+
* - **SIGTERM then SIGKILL.** A timeout or abort that only sends SIGTERM leaks a
|
|
336
|
+
* child that ignores it — ssh does, mid-handshake — and the promise never
|
|
337
|
+
* settles. After {@link SSH_STREAM_KILL_GRACE_MS} the child is SIGKILLed and
|
|
338
|
+
* `killed` says so.
|
|
339
|
+
* - **Bounded stderr.** A peer that writes endlessly to stderr would otherwise
|
|
340
|
+
* grow this buffer without limit; it is capped and `stderrTruncated` says so.
|
|
341
|
+
* - **`onStdout` cannot break the caller's cleanup.** A throw from the consumer
|
|
342
|
+
* is captured and re-thrown by the awaiting caller, so a write failure ends the
|
|
343
|
+
* transfer instead of escaping as an unhandled error inside a stream event.
|
|
344
|
+
*/
|
|
345
|
+
export function sshStreamWithArgs(opts) {
|
|
346
|
+
const maxStderr = opts.maxStderrBytes ?? SSH_STREAM_MAX_STDERR;
|
|
347
|
+
const grace = opts.killGraceMs ?? SSH_STREAM_KILL_GRACE_MS;
|
|
348
|
+
return new Promise((resolve, reject) => {
|
|
349
|
+
const child = spawn(opts.sshBin ?? 'ssh', opts.args, {
|
|
350
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
351
|
+
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
352
|
+
windowsHide: true,
|
|
353
|
+
// Own process group on POSIX so a kill reaches the whole tree. Killing only
|
|
354
|
+
// the ssh pid is not enough: a descendant that inherited stdout keeps the
|
|
355
|
+
// pipe open, `'close'` never fires, and the promise hangs for as long as
|
|
356
|
+
// that grandchild lives — which defeats the timeout entirely.
|
|
357
|
+
detached: process.platform !== 'win32',
|
|
358
|
+
});
|
|
359
|
+
const stderr = [];
|
|
360
|
+
let stderrBytes = 0;
|
|
361
|
+
let stderrTruncated = false;
|
|
362
|
+
let timedOut = false;
|
|
363
|
+
let killed = false;
|
|
364
|
+
let consumerError;
|
|
365
|
+
let settled = false;
|
|
366
|
+
let killTimer;
|
|
367
|
+
/** Signal the whole group where the platform supports it, else just the child. */
|
|
368
|
+
const signal = (sig) => {
|
|
369
|
+
try {
|
|
370
|
+
if (process.platform !== 'win32' && child.pid)
|
|
371
|
+
process.kill(-child.pid, sig);
|
|
372
|
+
else
|
|
373
|
+
child.kill(sig);
|
|
374
|
+
}
|
|
375
|
+
catch { /* already gone */ }
|
|
376
|
+
};
|
|
377
|
+
const escalate = () => {
|
|
378
|
+
signal('SIGTERM');
|
|
379
|
+
// Escalation, not a second polite ask: a child still alive after the grace
|
|
380
|
+
// is one that is not going to honour SIGTERM.
|
|
381
|
+
killTimer = setTimeout(() => { killed = true; signal('SIGKILL'); }, grace);
|
|
382
|
+
killTimer.unref?.();
|
|
383
|
+
};
|
|
384
|
+
const stop = () => escalate();
|
|
385
|
+
const finish = (code) => {
|
|
386
|
+
if (settled)
|
|
387
|
+
return;
|
|
388
|
+
settled = true;
|
|
389
|
+
if (timer)
|
|
390
|
+
clearTimeout(timer);
|
|
391
|
+
if (killTimer)
|
|
392
|
+
clearTimeout(killTimer);
|
|
393
|
+
opts.signal?.removeEventListener('abort', stop);
|
|
394
|
+
if (consumerError !== undefined) {
|
|
395
|
+
reject(consumerError);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
resolve({ code, stderr: Buffer.concat(stderr), timedOut, killed, stderrTruncated });
|
|
399
|
+
};
|
|
400
|
+
const timer = opts.timeoutMs
|
|
401
|
+
? setTimeout(() => { timedOut = true; escalate(); }, opts.timeoutMs)
|
|
402
|
+
: null;
|
|
403
|
+
child.stdout.on('data', (chunk) => {
|
|
404
|
+
if (consumerError !== undefined)
|
|
405
|
+
return;
|
|
406
|
+
try {
|
|
407
|
+
opts.onStdout(chunk);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
// Captured, not thrown: a throw here would surface as an unhandled error
|
|
411
|
+
// on the stream and skip the caller's cleanup entirely.
|
|
412
|
+
consumerError = error;
|
|
413
|
+
escalate();
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
child.stderr.on('data', (chunk) => {
|
|
417
|
+
const room = maxStderr - stderrBytes;
|
|
418
|
+
if (room <= 0) {
|
|
419
|
+
stderrTruncated = true;
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (chunk.length > room) {
|
|
423
|
+
stderrTruncated = true;
|
|
424
|
+
stderr.push(chunk.subarray(0, room));
|
|
425
|
+
stderrBytes = maxStderr;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
stderr.push(chunk);
|
|
429
|
+
stderrBytes += chunk.length;
|
|
430
|
+
});
|
|
431
|
+
child.once('error', () => finish(null));
|
|
432
|
+
child.once('close', finish);
|
|
433
|
+
if (opts.signal?.aborted)
|
|
434
|
+
escalate();
|
|
435
|
+
else
|
|
436
|
+
opts.signal?.addEventListener('abort', stop, { once: true });
|
|
437
|
+
});
|
|
438
|
+
}
|
|
320
439
|
export function sshExecRawStream(target, remoteCmd, opts) {
|
|
321
440
|
assertValidSshTarget(target);
|
|
322
441
|
const mux = opts.multiplex === false ? [] : controlOpts();
|
|
@@ -17,15 +17,17 @@ export declare const KNOWN_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
|
17
17
|
* name is gone, the spellchecker finds a neighbour, and the CLI runs something
|
|
18
18
|
* the user never asked for instead of saying the command is gone.
|
|
19
19
|
*
|
|
20
|
-
* `set` moved under `agents models`/`agents config` (RUSH-2579); `share
|
|
21
|
-
*
|
|
20
|
+
* `set` moved under `agents models`/`agents config` (RUSH-2579); `share`,
|
|
21
|
+
* `unshare`, and the whole `artifacts` group moved OUT to the standalone
|
|
22
|
+
* `artifacts` CLI (`@phnx-labs/artifacts-cli`, PHNX-3992) — artifact sharing is
|
|
23
|
+
* no longer an agents-cli surface at all. login/logout/budget/bench/mine/
|
|
22
24
|
* cost/output/profiles/snapshot/cp/resume/roster moved under nested homes
|
|
23
25
|
* (cli-surface-consolidate). `timeline` was removed as a duplicated surface —
|
|
24
26
|
* use `agents feed --filter updates` (RUSH-2692). `status` moved under
|
|
25
27
|
* `agents sync status` (RUSH-2864). `tickets` was removed — use `linear`
|
|
26
28
|
* (linear-cli) (RUSH-2932). `alias` moved under `agents setup alias` (RUSH-2965).
|
|
27
|
-
* `inbox` was a pure alias of `agents feed` (RUSH-2984). `
|
|
28
|
-
* `agents
|
|
29
|
+
* `inbox` was a pure alias of `agents feed` (RUSH-2984). `audit` nested under
|
|
30
|
+
* `agents events audit`.
|
|
29
31
|
* `trends` was removed with the insights recipe collapse — the one counter
|
|
30
32
|
* surface is `agents insights mix` (PHNX-3391). `serve` (the
|
|
31
33
|
* read-only local web companion + `--control` anchor) was removed with the
|
|
@@ -2,14 +2,14 @@ const LOADED_COMMAND_NAMES = [
|
|
|
2
2
|
'accounts', 'auth', 'view', 'inspect', 'feedback', 'commands', 'hooks', 'skills', 'rules', 'memory',
|
|
3
3
|
'permissions', 'mcp', 'clis', 'subagents', 'plugins', 'workflows', 'add', 'use',
|
|
4
4
|
'remove', 'rm', 'purge', 'update', 'prune', 'import', 'registry', 'search', 'install', 'packages',
|
|
5
|
-
'routines', 'monitors', 'projects', 'run', '_callback', 'open', '
|
|
5
|
+
'routines', 'monitors', 'projects', 'run', '_callback', 'open', 'fork', 'config',
|
|
6
6
|
'models', 'modes', 'trash', 'restore', 'doctor',
|
|
7
7
|
'route', 'routes', 'harness', 'harnesses', 'secrets', 'menubar', 'sync',
|
|
8
8
|
'refresh-rules', 'factory', 'insights', 'trace', 'reminders',
|
|
9
9
|
'tmux', 'watchdog', 'browser', 'computer', 'logs', 'events',
|
|
10
10
|
'ssh', 'devices', 'fleet', 'repos', 'repo', 'setup', 'uninstall', 'upgrade', 'sessions',
|
|
11
|
-
'teams', 'cloud', 'message', 'send', '
|
|
12
|
-
'mailboxes', 'mailbox', '
|
|
11
|
+
'teams', 'cloud', 'message', 'send', 'feed',
|
|
12
|
+
'mailboxes', 'mailbox', 'webhooks',
|
|
13
13
|
'humans', 'daemon', 'traces',
|
|
14
14
|
];
|
|
15
15
|
const INLINE_COMMAND_NAMES = [
|
|
@@ -37,15 +37,17 @@ export const KNOWN_TOP_LEVEL_COMMANDS = new Set([
|
|
|
37
37
|
* name is gone, the spellchecker finds a neighbour, and the CLI runs something
|
|
38
38
|
* the user never asked for instead of saying the command is gone.
|
|
39
39
|
*
|
|
40
|
-
* `set` moved under `agents models`/`agents config` (RUSH-2579); `share
|
|
41
|
-
*
|
|
40
|
+
* `set` moved under `agents models`/`agents config` (RUSH-2579); `share`,
|
|
41
|
+
* `unshare`, and the whole `artifacts` group moved OUT to the standalone
|
|
42
|
+
* `artifacts` CLI (`@phnx-labs/artifacts-cli`, PHNX-3992) — artifact sharing is
|
|
43
|
+
* no longer an agents-cli surface at all. login/logout/budget/bench/mine/
|
|
42
44
|
* cost/output/profiles/snapshot/cp/resume/roster moved under nested homes
|
|
43
45
|
* (cli-surface-consolidate). `timeline` was removed as a duplicated surface —
|
|
44
46
|
* use `agents feed --filter updates` (RUSH-2692). `status` moved under
|
|
45
47
|
* `agents sync status` (RUSH-2864). `tickets` was removed — use `linear`
|
|
46
48
|
* (linear-cli) (RUSH-2932). `alias` moved under `agents setup alias` (RUSH-2965).
|
|
47
|
-
* `inbox` was a pure alias of `agents feed` (RUSH-2984). `
|
|
48
|
-
* `agents
|
|
49
|
+
* `inbox` was a pure alias of `agents feed` (RUSH-2984). `audit` nested under
|
|
50
|
+
* `agents events audit`.
|
|
49
51
|
* `trends` was removed with the insights recipe collapse — the one counter
|
|
50
52
|
* surface is `agents insights mix` (PHNX-3391). `serve` (the
|
|
51
53
|
* read-only local web companion + `--control` anchor) was removed with the
|
|
@@ -81,6 +83,7 @@ export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
|
|
|
81
83
|
'roster',
|
|
82
84
|
'set',
|
|
83
85
|
'share',
|
|
86
|
+
'artifacts',
|
|
84
87
|
'timeline',
|
|
85
88
|
'status',
|
|
86
89
|
'tickets',
|
package/dist/lib/state.js
CHANGED
|
@@ -215,8 +215,8 @@ export function getOptionalUserAgentsDir() {
|
|
|
215
215
|
* Origin `owner/repo` slug (lowercased, `.git` stripped) of a git checkout, or
|
|
216
216
|
* null when `dir` isn't a git repo / has no origin. Extracts the slug from any
|
|
217
217
|
* remote URL form: `git@host:owner/repo.git`, `https://host/owner/repo.git`,
|
|
218
|
-
* `ssh://git@host/owner/repo`. Sync (
|
|
219
|
-
* can be used from the synchronous getProjectAgentsDir walk.
|
|
218
|
+
* `ssh://git@host/owner/repo`. Sync (uses `execFileSync`, not the async git
|
|
219
|
+
* helpers) so it can be used from the synchronous getProjectAgentsDir walk.
|
|
220
220
|
*/
|
|
221
221
|
function gitOriginSlug(dir) {
|
|
222
222
|
let url;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* covers — those differ (share uses an email-handle namespace with public reads
|
|
14
14
|
* and covers; a traces/sessions adapter uses the userId and differs again). Each
|
|
15
15
|
* surface keeps its own DISCRIMINATED, typed adapter that reads this decision and
|
|
16
|
-
* returns its own `kind`-tagged backend. See `lib/
|
|
17
|
-
*
|
|
16
|
+
* returns its own `kind`-tagged backend. See `lib/traces/backend.ts` for a
|
|
17
|
+
* concrete adapter.
|
|
18
18
|
*/
|
|
19
19
|
import { type PhoenixSession } from '../identity/client.js';
|
|
20
20
|
/** The two legitimate principals any managed-capable surface can resolve to. */
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* covers — those differ (share uses an email-handle namespace with public reads
|
|
14
14
|
* and covers; a traces/sessions adapter uses the userId and differs again). Each
|
|
15
15
|
* surface keeps its own DISCRIMINATED, typed adapter that reads this decision and
|
|
16
|
-
* returns its own `kind`-tagged backend. See `lib/
|
|
17
|
-
*
|
|
16
|
+
* returns its own `kind`-tagged backend. See `lib/traces/backend.ts` for a
|
|
17
|
+
* concrete adapter.
|
|
18
18
|
*/
|
|
19
19
|
import { readSession } from '../identity/client.js';
|
|
20
20
|
/**
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* arrives with `agents secrets vault unlock`, #366/#367)
|
|
16
16
|
* reconcile-> refresh({ skipPrompts }) — re-materialize resources into homes
|
|
17
17
|
*/
|
|
18
|
+
import type { ResourceSelection } from './installations/versions.js';
|
|
18
19
|
/** The umbrella flags off `agents sync`. */
|
|
19
20
|
export interface UmbrellaFlags {
|
|
20
21
|
repos?: boolean;
|
|
@@ -87,6 +88,10 @@ interface RunUmbrellaArgs {
|
|
|
87
88
|
* `agents sync --json` / fleet fan-out leave stdout as a single JSON object.
|
|
88
89
|
*/
|
|
89
90
|
quiet?: boolean;
|
|
91
|
+
/** Optional resource-only reconcile across installed versions. */
|
|
92
|
+
selection?: ResourceSelection;
|
|
93
|
+
/** Explicit consent for selected plugins that add executable surfaces. */
|
|
94
|
+
allowExecSurfaces?: boolean;
|
|
90
95
|
}
|
|
91
96
|
/**
|
|
92
97
|
* Execute the planned stages in order: repos -> secrets -> reconcile.
|
|
@@ -53,7 +53,7 @@ export function planUmbrellaStages(f) {
|
|
|
53
53
|
* reconcile — `agents sync` should make as much current as it can in one pass.
|
|
54
54
|
*/
|
|
55
55
|
export async function runUmbrellaSync(args) {
|
|
56
|
-
const { flags, log, yes, passphrase, quiet = false } = args;
|
|
56
|
+
const { flags, log, yes, passphrase, quiet = false, selection, allowExecSurfaces = false } = args;
|
|
57
57
|
const plan = planUmbrellaStages(flags);
|
|
58
58
|
const result = { plan, reconciled: false, declined: [], reconciledVersions: [] };
|
|
59
59
|
if (plan.fetchRepos) {
|
|
@@ -131,7 +131,13 @@ export async function runUmbrellaSync(args) {
|
|
|
131
131
|
}
|
|
132
132
|
if (plan.reconcile) {
|
|
133
133
|
const { refresh } = await import('./refresh.js');
|
|
134
|
-
const refreshed = await refresh({
|
|
134
|
+
const refreshed = await refresh({
|
|
135
|
+
skipPrompts: yes,
|
|
136
|
+
quiet,
|
|
137
|
+
skipClis: selection !== undefined,
|
|
138
|
+
selection,
|
|
139
|
+
allowExecSurfaces,
|
|
140
|
+
});
|
|
135
141
|
result.reconciled = true;
|
|
136
142
|
result.declined = refreshed.declined;
|
|
137
143
|
result.reconciledVersions = refreshed.reconciled;
|
|
@@ -140,14 +146,16 @@ export async function runUmbrellaSync(args) {
|
|
|
140
146
|
// rather than silently adding them (refresh mode). Soft: a machine without
|
|
141
147
|
// tailscale is a clean no-op, never a sync failure. First-run population is
|
|
142
148
|
// `agents setup` / manual `agents devices sync` (bootstrap).
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
if (!selection) {
|
|
150
|
+
const { runDeviceSync } = await import('./devices/sync.js');
|
|
151
|
+
const { reconcilePendingSentinels } = await import('./devices/pending.js');
|
|
152
|
+
const dev = await runDeviceSync({ soft: true, mode: 'refresh' });
|
|
153
|
+
if (dev.ok)
|
|
154
|
+
await reconcilePendingSentinels(dev.pending);
|
|
155
|
+
result.devices = { synced: dev.synced, pending: dev.pending.length, skipped: !dev.ok };
|
|
156
|
+
if (dev.ok) {
|
|
157
|
+
log(`devices: ${dev.synced} refreshed${dev.pending.length ? `, ${dev.pending.length} new pending` : ''}`);
|
|
158
|
+
}
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
161
|
return result;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The subset of the `term` CLI a screen-scraping drive loop needs — faked in tests. */
|
|
2
|
+
export interface TermDriver {
|
|
3
|
+
start(opts?: {
|
|
4
|
+
rows?: number;
|
|
5
|
+
cols?: number;
|
|
6
|
+
}): Promise<string>;
|
|
7
|
+
exec(id: string, command: string): Promise<void>;
|
|
8
|
+
write(id: string, input: string): Promise<void>;
|
|
9
|
+
screen(id: string): Promise<{
|
|
10
|
+
screen: string;
|
|
11
|
+
exited: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
stop(id: string): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/** Real driver over the standalone `term` CLI (`./term-client.js`). */
|
|
16
|
+
export declare function defaultTermDriver(): TermDriver;
|
|
17
|
+
export interface DriveOptions {
|
|
18
|
+
/** Wait after launching before steering / scraping (default 4000ms). */
|
|
19
|
+
initialDelayMs?: number;
|
|
20
|
+
/** Poll cadence for the scrape loop (default 1000ms). */
|
|
21
|
+
pollMs?: number;
|
|
22
|
+
/** Overall scrape deadline (default 90000ms). */
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable terminal driver over the standalone `term` CLI.
|
|
3
|
+
*
|
|
4
|
+
* A small seam that lets a flow drive a PTY session — start, exec, write,
|
|
5
|
+
* screen-scrape, stop — through the real `term` CLI in production and a fake in
|
|
6
|
+
* tests. The setup-token mint (`lib/auth-mint.ts`) is the sole consumer today.
|
|
7
|
+
*/
|
|
8
|
+
import { termStart, termExec, termWrite, termScreen, termStop } from './term-client.js';
|
|
9
|
+
/** Real driver over the standalone `term` CLI (`./term-client.js`). */
|
|
10
|
+
export function defaultTermDriver() {
|
|
11
|
+
const expectOk = (res, what) => {
|
|
12
|
+
if (!res.ok)
|
|
13
|
+
throw new Error(`term ${what} failed: ${res.error ?? 'unknown'}`);
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
async start(opts) {
|
|
17
|
+
const res = await termStart({ rows: opts?.rows ?? 40, cols: opts?.cols ?? 120 });
|
|
18
|
+
expectOk(res, 'start');
|
|
19
|
+
return res.id;
|
|
20
|
+
},
|
|
21
|
+
async exec(id, command) {
|
|
22
|
+
expectOk(await termExec(id, command), 'exec');
|
|
23
|
+
},
|
|
24
|
+
async write(id, input) {
|
|
25
|
+
expectOk(await termWrite(id, input), 'write');
|
|
26
|
+
},
|
|
27
|
+
async screen(id) {
|
|
28
|
+
const res = await termScreen(id);
|
|
29
|
+
expectOk(res, 'screen');
|
|
30
|
+
return { screen: res.screen ?? '', exited: Boolean(res.exited) };
|
|
31
|
+
},
|
|
32
|
+
async stop(id) {
|
|
33
|
+
await termStop(id).catch(() => undefined);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -14,7 +14,7 @@ export { makeVscodiumAgentBackend, spawnUri, EDITOR_VARIANTS, type EditorVariant
|
|
|
14
14
|
export { planLayouts, type Packing } from './policy.js';
|
|
15
15
|
export { specForRequest, buildRequests, openSurface, openSurfaces, type OpenOptions, type OpenManyOptions, type BuildRequestsOptions, type SurfaceItem, } from './engine.js';
|
|
16
16
|
export { runLocal, runRemote, runSpec, remoteCommand, type HostResolver, type RunResult } from './transport.js';
|
|
17
|
-
export { injectIntoTerminal, tmuxSendKeysArgv, tmuxInjectSpecs, itermInjectScript, ghosttyInjectScript, appleScriptInjectSpec, vscodiumInjectUri, vscodiumInjectSpec, type InjectTarget, type InjectBackend, type InjectOptions, type InjectResult, } from './inject.js';
|
|
17
|
+
export { backendCarriesPaste, injectIntoTerminal, tmuxSendKeysArgv, tmuxInjectSpecs, itermInjectScript, ghosttyInjectScript, appleScriptInjectSpec, vscodiumInjectUri, vscodiumInjectSpec, type InjectTarget, type InjectBackend, type InjectOptions, type InjectResult, } from './inject.js';
|
|
18
18
|
export { resolveInjectTarget, resolveInjectTargetForSession, type InjectResolution, type InjectRail, type ResolveOptions, } from './resolve.js';
|
|
19
19
|
export { iLoginShell } from './shell.js';
|
|
20
20
|
export { shellQuote } from './quote.js';
|
|
@@ -5,7 +5,7 @@ export { makeVscodiumAgentBackend, spawnUri, EDITOR_VARIANTS } from './backends/
|
|
|
5
5
|
export { planLayouts } from './policy.js';
|
|
6
6
|
export { specForRequest, buildRequests, openSurface, openSurfaces, } from './engine.js';
|
|
7
7
|
export { runLocal, runRemote, runSpec, remoteCommand } from './transport.js';
|
|
8
|
-
export { injectIntoTerminal, tmuxSendKeysArgv, tmuxInjectSpecs, itermInjectScript, ghosttyInjectScript, appleScriptInjectSpec, vscodiumInjectUri, vscodiumInjectSpec, } from './inject.js';
|
|
8
|
+
export { backendCarriesPaste, injectIntoTerminal, tmuxSendKeysArgv, tmuxInjectSpecs, itermInjectScript, ghosttyInjectScript, appleScriptInjectSpec, vscodiumInjectUri, vscodiumInjectSpec, } from './inject.js';
|
|
9
9
|
export { resolveInjectTarget, resolveInjectTargetForSession, } from './resolve.js';
|
|
10
10
|
export { iLoginShell } from './shell.js';
|
|
11
11
|
export { shellQuote } from './quote.js';
|
|
@@ -88,6 +88,21 @@ export interface InjectOptions {
|
|
|
88
88
|
ctx?: EngineContext;
|
|
89
89
|
/** Don't execute — return the spec(s) that WOULD run. Lets the macOS paths be asserted on Linux. */
|
|
90
90
|
dryRun?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Hard bound for the WHOLE injection, in ms. Each spec is run under the
|
|
93
|
+
* remaining budget with process-group cancellation, and a spec is never
|
|
94
|
+
* STARTED once the budget is gone — an operator-facing caller must be able to
|
|
95
|
+
* return a verdict on time without leaving a launcher running behind it
|
|
96
|
+
* (PHNX-3999). Omitted keeps the historical unbounded behaviour.
|
|
97
|
+
*/
|
|
98
|
+
deadlineMs?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Frame the text in bracketed-paste markers so a TUI inserts it verbatim
|
|
101
|
+
* instead of reading each embedded newline as a submit. Required for a
|
|
102
|
+
* multiline free-text answer. Only the tmux rail can carry it (see
|
|
103
|
+
* {@link backendCarriesPaste}); every other backend refuses with an error.
|
|
104
|
+
*/
|
|
105
|
+
paste?: boolean;
|
|
91
106
|
}
|
|
92
107
|
export interface InjectResult {
|
|
93
108
|
ok: boolean;
|
|
@@ -106,10 +121,33 @@ export interface InjectResult {
|
|
|
106
121
|
confirmed: boolean;
|
|
107
122
|
/** Discrete writes delivered: 2 for the Ink-safe text+Enter split, 1 when combined or enter=false. */
|
|
108
123
|
writes: number;
|
|
124
|
+
/**
|
|
125
|
+
* How many specs were STARTED. This -- not `writes` -- is what says whether
|
|
126
|
+
* anything may have reached the terminal: a spec that failed or timed out may
|
|
127
|
+
* still have written bytes before dying, so only `started === 0` proves
|
|
128
|
+
* nothing landed and the answer is cleanly retryable (PHNX-3999).
|
|
129
|
+
*/
|
|
130
|
+
started: number;
|
|
109
131
|
/** For the tmux / AppleScript backends (or any dryRun), the spec(s) that ran / would run. */
|
|
110
132
|
specs?: LaunchSpec[];
|
|
111
133
|
error?: string;
|
|
112
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Bracketed-paste framing (DEC mode 2004) — the pty contract for "insert this
|
|
137
|
+
* verbatim, do not submit". A readline/Ink composer that sees the start marker
|
|
138
|
+
* buffers every byte up to the end marker, so an embedded newline lands as a
|
|
139
|
+
* literal newline in the draft instead of submitting the partial line.
|
|
140
|
+
*/
|
|
141
|
+
export declare const BRACKETED_PASTE_START = "\u001B[200~";
|
|
142
|
+
export declare const BRACKETED_PASTE_END = "\u001B[201~";
|
|
143
|
+
/**
|
|
144
|
+
* Only tmux can carry the framing: `send-keys -l` writes the marker bytes
|
|
145
|
+
* straight to the pty. ghostty simulates keystrokes, vscodium hands a JSON
|
|
146
|
+
* payload to the extension, and iTerm's AppleScript `write text` cannot encode
|
|
147
|
+
* a raw ESC byte — so a paste request on those is refused rather than
|
|
148
|
+
* half-delivered as one submit per line.
|
|
149
|
+
*/
|
|
150
|
+
export declare function backendCarriesPaste(backend: InjectBackend): boolean;
|
|
113
151
|
/**
|
|
114
152
|
* argv for `tmux send-keys` targeting a pane by id. Starts with `tmux` (like the
|
|
115
153
|
* engine's `tmuxTabArgv`) so the same transport runs it locally or over SSH. The
|