@phnx-labs/agents-cli 1.22.52 → 1.22.54
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 +336 -0
- package/README.md +42 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +220 -174
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +26 -17
- package/dist/commands/fleet-capture.js +7 -0
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +4 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +8 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions-inject.js +8 -3
- package/dist/commands/sessions-picker.js +2 -1
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +94 -40
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +52 -3
- package/dist/commands/share.js +262 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +53 -14
- package/dist/commands/status.js +14 -0
- package/dist/commands/sync.js +44 -0
- package/dist/commands/view.d.ts +3 -1
- package/dist/commands/view.js +5 -4
- package/dist/lib/account-registry.d.ts +15 -5
- package/dist/lib/account-registry.js +165 -53
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +37 -1
- package/dist/lib/accounting/usage.js +71 -6
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/answer-router.js +2 -1
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/profiles.d.ts +18 -0
- package/dist/lib/browser/profiles.js +26 -1
- package/dist/lib/browser/registry.d.ts +44 -14
- package/dist/lib/browser/registry.js +141 -45
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/daemon.js +30 -4
- package/dist/lib/daemon/runner.js +10 -2
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +8 -7
- package/dist/lib/devices/config-migration.js +147 -1
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/device-docs.d.ts +35 -0
- package/dist/lib/devices/device-docs.js +163 -0
- package/dist/lib/devices/discovery-policy.d.ts +14 -2
- package/dist/lib/devices/discovery-policy.js +31 -21
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/registry.d.ts +11 -5
- package/dist/lib/devices/registry.js +46 -18
- package/dist/lib/exec.d.ts +88 -30
- package/dist/lib/exec.js +138 -34
- package/dist/lib/feed/feed.d.ts +10 -2
- package/dist/lib/feed/feed.js +35 -2
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/dispatch.d.ts +4 -3
- package/dist/lib/hosts/dispatch.js +12 -8
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/hosts/providers/local.d.ts +9 -3
- package/dist/lib/hosts/providers/local.js +23 -12
- package/dist/lib/hosts/reconnect.d.ts +7 -4
- package/dist/lib/hosts/reconnect.js +29 -25
- package/dist/lib/hosts/registry.js +4 -1
- package/dist/lib/hosts/remote-os.js +3 -1
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.js +8 -1
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.d.ts +10 -1
- package/dist/lib/session/active.js +8 -5
- package/dist/lib/session/actor-sidecar.d.ts +7 -0
- package/dist/lib/session/actor-sidecar.js +2 -0
- package/dist/lib/session/db.d.ts +39 -4
- package/dist/lib/session/db.js +168 -31
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +126 -37
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -0
- package/dist/lib/session/pid-registry.d.ts +7 -0
- package/dist/lib/session/prompt.d.ts +15 -0
- package/dist/lib/session/prompt.js +21 -0
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +25 -0
- package/dist/lib/session/types.js +10 -0
- package/dist/lib/share/publish.d.ts +53 -5
- package/dist/lib/share/publish.js +99 -17
- package/dist/lib/share/worker-template.js +594 -64
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +24 -0
- package/dist/lib/state.js +318 -54
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/terminal/resolve.d.ts +7 -0
- package/dist/lib/terminal/resolve.js +41 -2
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/traces/insights.d.ts +67 -0
- package/dist/lib/traces/insights.js +178 -0
- package/dist/lib/traces/phenotype.d.ts +67 -0
- package/dist/lib/traces/phenotype.js +437 -0
- package/dist/lib/traces/segments.d.ts +133 -0
- package/dist/lib/traces/segments.js +301 -0
- package/dist/lib/traces/sync.d.ts +33 -0
- package/dist/lib/traces/sync.js +11 -2
- package/dist/lib/types.d.ts +47 -1
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +2 -0
- package/dist/lib/watchdog/runner.js +18 -4
- package/package.json +2 -1
|
@@ -13,7 +13,7 @@ export type FindingSeverity = 'critical' | 'warning';
|
|
|
13
13
|
* the JSON consumer group by kind. */
|
|
14
14
|
/** Every finding class. Severity is NOT annotated here — {@link FINDING_SEVERITY}
|
|
15
15
|
* below owns it, and a second copy in these comments is a fourth place to drift. */
|
|
16
|
-
export declare const ALL_FINDING_KINDS: readonly ["logged-out", "logout-unprovable", "missing-hook", "missing-plugin", "unwired-hook", "hook-runtime-broken", "hook-runtime-visibility-unavailable", "cli-missing", "missing-resource", "content-drift", "never-synced", "stale", "repo-behind", "repo-drift", "fleet-resource-gap", "host-cli-missing", "host-cli-invalid", "version-skew", "orphan", "duplicate-hook", "duplicate-hook-drift", "rc-secret-export", "env-secret-export", "exec-policy", "ssh-key-enrollment", "stale-cli", "binary-shadow", "owner-sink-unreachable"];
|
|
16
|
+
export declare const ALL_FINDING_KINDS: readonly ["logged-out", "logout-unprovable", "missing-hook", "missing-plugin", "unwired-hook", "hook-runtime-broken", "hook-runtime-visibility-unavailable", "cli-missing", "missing-resource", "content-drift", "never-synced", "stale", "repo-behind", "repo-drift", "fleet-resource-gap", "host-cli-missing", "host-cli-invalid", "version-skew", "orphan", "duplicate-hook", "duplicate-hook-drift", "rc-secret-export", "env-secret-export", "auth-bundle-wrong-backend", "exec-policy", "ssh-key-enrollment", "stale-cli", "binary-shadow", "owner-sink-unreachable"];
|
|
17
17
|
/**
|
|
18
18
|
* The severity each kind is emitted with - the SINGLE source of truth, read by
|
|
19
19
|
* the builders below and asserted against both prose rubrics by
|
|
@@ -100,6 +100,10 @@ export interface LocalFindingInputs {
|
|
|
100
100
|
* scan reports clean while the leak is still in flight (RUSH-1968). Never the
|
|
101
101
|
* value — only whether it is set. */
|
|
102
102
|
masterPassphraseInEnv?: boolean;
|
|
103
|
+
/** True when the reserved `auth` bundle exists on a non-file backend
|
|
104
|
+
* (SEC-GAP-3): usage/probe ignores the setup-tokens and falls through to
|
|
105
|
+
* Touch ID. Collected by `inspectReservedAuthBundle`. */
|
|
106
|
+
authBundleWrongBackend?: boolean;
|
|
103
107
|
/** The effective PowerShell execution policy and the platform it was read on.
|
|
104
108
|
* Only `win32` yields a finding — the `agents.ps1` launcher is Windows-only. */
|
|
105
109
|
execPolicy?: {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* never-synced · stale · repo-behind · repo-drift · version-skew ·
|
|
27
27
|
* fleet-resource-gap · hook-runtime-visibility-unavailable · orphan · duplicate-hook ·
|
|
28
28
|
* duplicate-hook-drift · host-cli-missing · host-cli-invalid ·
|
|
29
|
-
* rc-secret-export · env-secret-export · exec-policy · stale-cli ·
|
|
29
|
+
* rc-secret-export · env-secret-export · auth-bundle-wrong-backend · exec-policy · stale-cli ·
|
|
30
30
|
* binary-shadow.
|
|
31
31
|
* (RUSH-2162 moved never-synced and duplicate-hook-drift to WARNING: both are
|
|
32
32
|
* stale-sync states one `agents sync` resolves, not "needs you now".)
|
|
@@ -102,6 +102,7 @@ export const ALL_FINDING_KINDS = [
|
|
|
102
102
|
'duplicate-hook-drift', // …with differing content, so a stale copy can disagree
|
|
103
103
|
'rc-secret-export', // credential-shaped export in a shell rc file
|
|
104
104
|
'env-secret-export', // the file-store master key live in THIS process's env
|
|
105
|
+
'auth-bundle-wrong-backend', // reserved `auth` bundle exists but is not file-backed
|
|
105
106
|
'exec-policy', // Windows execution policy blocks agents.ps1
|
|
106
107
|
'ssh-key-enrollment', // Windows OpenSSH public-key path/content/ACL is invalid
|
|
107
108
|
'stale-cli',
|
|
@@ -150,6 +151,7 @@ export const FINDING_SEVERITY = {
|
|
|
150
151
|
'host-cli-invalid': 'warning',
|
|
151
152
|
'rc-secret-export': 'warning',
|
|
152
153
|
'env-secret-export': 'warning',
|
|
154
|
+
'auth-bundle-wrong-backend': 'warning',
|
|
153
155
|
'exec-policy': 'warning',
|
|
154
156
|
'ssh-key-enrollment': 'critical',
|
|
155
157
|
'stale-cli': 'warning',
|
|
@@ -269,6 +271,8 @@ export function remediationFor(finding) {
|
|
|
269
271
|
// that inherited it — an editor, a tmux server, the agents daemon — and
|
|
270
272
|
// each keeps handing it to new children until IT restarts.
|
|
271
273
|
return 'unset at the source, then restart every process that inherited it (shells, editor, tmux, agents daemon)';
|
|
274
|
+
case 'auth-bundle-wrong-backend':
|
|
275
|
+
return 'agents secrets delete auth --yes && agents secrets create auth --backend file';
|
|
272
276
|
case 'exec-policy':
|
|
273
277
|
return 'Set-ExecutionPolicy -Scope CurrentUser RemoteSigned';
|
|
274
278
|
case 'ssh-key-enrollment':
|
|
@@ -519,6 +523,9 @@ export function buildLocalFindings(input) {
|
|
|
519
523
|
const envFinding = envSecretFinding(device, input.masterPassphraseInEnv ?? false);
|
|
520
524
|
if (envFinding)
|
|
521
525
|
out.push(envFinding);
|
|
526
|
+
const authFinding = authBundleWrongBackendFinding(device, input.authBundleWrongBackend ?? false);
|
|
527
|
+
if (authFinding)
|
|
528
|
+
out.push(authFinding);
|
|
522
529
|
// Windows execution policy blocking the generated agents.ps1 launcher.
|
|
523
530
|
const policyFinding = execPolicyFinding(device, input.execPolicy);
|
|
524
531
|
if (policyFinding)
|
|
@@ -688,6 +695,15 @@ function envSecretFinding(device, present) {
|
|
|
688
695
|
+ '(Expected inside a release sign context, which sets it deliberately.)',
|
|
689
696
|
});
|
|
690
697
|
}
|
|
698
|
+
function authBundleWrongBackendFinding(device, present) {
|
|
699
|
+
if (!present)
|
|
700
|
+
return null;
|
|
701
|
+
return finding({
|
|
702
|
+
severity: FINDING_SEVERITY['auth-bundle-wrong-backend'], kind: 'auth-bundle-wrong-backend', device,
|
|
703
|
+
message: "reserved secrets bundle 'auth' exists but is not file-backed — "
|
|
704
|
+
+ 'usage/probe ignores the setup-tokens and falls through to the interactive login',
|
|
705
|
+
});
|
|
706
|
+
}
|
|
691
707
|
function execPolicyFinding(device, execPolicy) {
|
|
692
708
|
if (!execPolicy || execPolicy.platform !== 'win32')
|
|
693
709
|
return null;
|
|
@@ -1038,6 +1054,8 @@ function warningSubject(f) {
|
|
|
1038
1054
|
return 'shell rc';
|
|
1039
1055
|
if (f.kind === 'env-secret-export')
|
|
1040
1056
|
return 'environment';
|
|
1057
|
+
if (f.kind === 'auth-bundle-wrong-backend')
|
|
1058
|
+
return 'auth bundle';
|
|
1041
1059
|
if (f.kind === 'exec-policy')
|
|
1042
1060
|
return 'PowerShell';
|
|
1043
1061
|
if (f.kind === 'fleet-resource-gap')
|
|
@@ -176,10 +176,12 @@ export declare function removeDevice(name: string): Promise<boolean>;
|
|
|
176
176
|
*/
|
|
177
177
|
export type { IgnoredDeviceEntry } from '../fleet/types.js';
|
|
178
178
|
/**
|
|
179
|
-
* The
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
179
|
+
* The EFFECTIVE ignore-list: the union of every box's device doc
|
|
180
|
+
* `fleet.ignored` (deduped by node name, newest `ignoredAt` winning) plus any
|
|
181
|
+
* lingering central-legacy `fleet.ignored` block (drained by the migration).
|
|
182
|
+
* Deterministic and order-independent. A malformed block is a hard error for the
|
|
183
|
+
* same reason the registry is: silently returning [] would let the next write
|
|
184
|
+
* wipe the user's dismissals.
|
|
183
185
|
*/
|
|
184
186
|
export declare function loadIgnoredEntries(meta?: Meta): IgnoredDeviceEntry[];
|
|
185
187
|
/** Load the set of ignored node names. Same corruption contract as
|
|
@@ -195,7 +197,11 @@ export declare function isIgnored(name: string): Promise<boolean>;
|
|
|
195
197
|
* lib/devices/config-migration.ts.
|
|
196
198
|
*/
|
|
197
199
|
export declare function withIgnoredAdded(meta: Meta, names: string[], ignoredAt: string): Meta;
|
|
198
|
-
/** Add a node name to
|
|
200
|
+
/** Add a node name to THIS box's ignore-list (device doc). Idempotent. Returns
|
|
201
|
+
* the resulting cross-box union of dismissed names. Reads only the device docs
|
|
202
|
+
* for the return value — a corrupt central-legacy block surfaces loudly on the
|
|
203
|
+
* effective read path ({@link loadIgnoredEntries}), never blocks a per-box
|
|
204
|
+
* write that does not touch central at all. */
|
|
199
205
|
export declare function addIgnored(name: string): Promise<Set<string>>;
|
|
200
206
|
/** Remove a node name from the ignore-list (un-ignore). Returns false if it was
|
|
201
207
|
* not ignored. */
|
|
@@ -20,6 +20,7 @@ import lockfile from 'proper-lockfile';
|
|
|
20
20
|
import { getDevicesRegistryPath, readMeta, updateMeta } from '../state.js';
|
|
21
21
|
import { atomicWriteJsonSync } from '../fs-atomic.js';
|
|
22
22
|
import { machineId } from '../machine-id.js';
|
|
23
|
+
import { addIgnoredEntry, unionDeviceIgnored } from './device-docs.js';
|
|
23
24
|
/**
|
|
24
25
|
* Whether a fan-out should dial this device, honouring the preference stated on
|
|
25
26
|
* {@link DeviceProfile.reachability}: the live SSH probe wins over the cached
|
|
@@ -287,19 +288,48 @@ export async function removeDevice(name) {
|
|
|
287
288
|
* []. A malformed block is a hard error for the same reason the registry is:
|
|
288
289
|
* silently returning [] would let the next write wipe the user's dismissals.
|
|
289
290
|
*/
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (raw === undefined)
|
|
293
|
-
return [];
|
|
291
|
+
/** Validate a raw ignore-list block, or throw with `where` naming the file. */
|
|
292
|
+
function assertIgnoredShape(raw, where) {
|
|
294
293
|
if (!Array.isArray(raw) ||
|
|
295
294
|
raw.some((e) => !e ||
|
|
296
295
|
typeof e.name !== 'string' ||
|
|
297
296
|
typeof e.ignoredAt !== 'string' ||
|
|
298
297
|
typeof e.ignoredOn !== 'string')) {
|
|
299
|
-
throw new Error(`Device ignore-list corrupted in
|
|
298
|
+
throw new Error(`Device ignore-list corrupted in ${where}: expected a list of { name, ignoredAt, ignoredOn } entries. Inspect and repair it.`);
|
|
300
299
|
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* THIS box's OWN dismissals — the writable slice in `meta.deviceFleet.ignored`
|
|
303
|
+
* (the device doc). `withIgnoredAdded`/`removeIgnored` operate on this so a box
|
|
304
|
+
* only ever edits its own folder (PHNX-3315). Absent => [].
|
|
305
|
+
*/
|
|
306
|
+
function loadOwnIgnoredEntries(meta) {
|
|
307
|
+
const raw = meta.deviceFleet?.ignored;
|
|
308
|
+
if (raw === undefined)
|
|
309
|
+
return [];
|
|
310
|
+
assertIgnoredShape(raw, `devices/<machine>/agents.yaml (fleet.ignored)`);
|
|
301
311
|
return raw;
|
|
302
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* The EFFECTIVE ignore-list: the union of every box's device doc
|
|
315
|
+
* `fleet.ignored` (deduped by node name, newest `ignoredAt` winning) plus any
|
|
316
|
+
* lingering central-legacy `fleet.ignored` block (drained by the migration).
|
|
317
|
+
* Deterministic and order-independent. A malformed block is a hard error for the
|
|
318
|
+
* same reason the registry is: silently returning [] would let the next write
|
|
319
|
+
* wipe the user's dismissals.
|
|
320
|
+
*/
|
|
321
|
+
export function loadIgnoredEntries(meta = readMeta()) {
|
|
322
|
+
const byName = new Map();
|
|
323
|
+
const central = meta.fleet?.ignored;
|
|
324
|
+
if (central !== undefined) {
|
|
325
|
+
assertIgnoredShape(central, `agents.yaml (fleet.ignored)`);
|
|
326
|
+
for (const e of central)
|
|
327
|
+
addIgnoredEntry(byName, e);
|
|
328
|
+
}
|
|
329
|
+
for (const e of unionDeviceIgnored())
|
|
330
|
+
addIgnoredEntry(byName, e);
|
|
331
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
332
|
+
}
|
|
303
333
|
/** Load the set of ignored node names. Same corruption contract as
|
|
304
334
|
* {@link loadIgnoredEntries}. */
|
|
305
335
|
export async function loadIgnored() {
|
|
@@ -317,40 +347,38 @@ export async function isIgnored(name) {
|
|
|
317
347
|
* lib/devices/config-migration.ts.
|
|
318
348
|
*/
|
|
319
349
|
export function withIgnoredAdded(meta, names, ignoredAt) {
|
|
320
|
-
const entries =
|
|
350
|
+
const entries = loadOwnIgnoredEntries(meta); // throws on a corrupted block — never wipe it
|
|
321
351
|
const have = new Set(entries.map((e) => e.name));
|
|
322
352
|
const fresh = names.filter((n) => !have.has(n));
|
|
323
353
|
if (fresh.length === 0)
|
|
324
354
|
return meta;
|
|
325
|
-
const fleet = (meta.fleet ?? { devices: {} });
|
|
326
355
|
const ignored = [
|
|
327
356
|
...entries,
|
|
328
357
|
...fresh.map((name) => ({ name, ignoredAt, ignoredOn: machineId() })),
|
|
329
358
|
].sort((a, b) => a.name.localeCompare(b.name));
|
|
330
|
-
|
|
331
|
-
return { ...meta, fleet: nextFleet };
|
|
359
|
+
return { ...meta, deviceFleet: { ...meta.deviceFleet, ignored } };
|
|
332
360
|
}
|
|
333
|
-
/** Add a node name to
|
|
361
|
+
/** Add a node name to THIS box's ignore-list (device doc). Idempotent. Returns
|
|
362
|
+
* the resulting cross-box union of dismissed names. Reads only the device docs
|
|
363
|
+
* for the return value — a corrupt central-legacy block surfaces loudly on the
|
|
364
|
+
* effective read path ({@link loadIgnoredEntries}), never blocks a per-box
|
|
365
|
+
* write that does not touch central at all. */
|
|
334
366
|
export async function addIgnored(name) {
|
|
335
367
|
assertValidDeviceName(name);
|
|
336
|
-
|
|
337
|
-
return new Set(
|
|
368
|
+
updateMeta((m) => withIgnoredAdded(m, [name], new Date().toISOString()));
|
|
369
|
+
return new Set(unionDeviceIgnored().map((e) => e.name));
|
|
338
370
|
}
|
|
339
371
|
/** Remove a node name from the ignore-list (un-ignore). Returns false if it was
|
|
340
372
|
* not ignored. */
|
|
341
373
|
export async function removeIgnored(name) {
|
|
342
374
|
let removed = false;
|
|
343
375
|
updateMeta((m) => {
|
|
344
|
-
const
|
|
345
|
-
if (!fleet?.ignored)
|
|
346
|
-
return m;
|
|
347
|
-
const entries = loadIgnoredEntries(m);
|
|
376
|
+
const entries = loadOwnIgnoredEntries(m); // only this box's own dismissals are ours to drop
|
|
348
377
|
const next = entries.filter((e) => e.name !== name);
|
|
349
378
|
if (next.length === entries.length)
|
|
350
379
|
return m;
|
|
351
380
|
removed = true;
|
|
352
|
-
|
|
353
|
-
return { ...m, fleet: nextFleet };
|
|
381
|
+
return { ...m, deviceFleet: { ...m.deviceFleet, ignored: next } };
|
|
354
382
|
});
|
|
355
383
|
return removed;
|
|
356
384
|
}
|
package/dist/lib/exec.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentId, Mode } from './types.js';
|
|
2
|
+
import { type UsernsStatus } from './linux-userns.js';
|
|
2
3
|
/**
|
|
3
4
|
* Agent execution modes. Canonical name `skip` (dangerously skip permissions);
|
|
4
5
|
* `full` is accepted as a permanent silent alias via normalizeMode().
|
|
@@ -88,11 +89,46 @@ export interface ModeWarningState {
|
|
|
88
89
|
export declare function defaultModeFor(agent: AgentId): Mode;
|
|
89
90
|
/** Safe mode used when the user did not provide --mode or a configured default. */
|
|
90
91
|
export declare function implicitModeFor(agent: AgentId): ExecMode;
|
|
92
|
+
/**
|
|
93
|
+
* Preflight for Codex's Linux sandbox. Codex ≥0.146 sandboxes `read-only` and
|
|
94
|
+
* `workspace-write` runs with a bundled bubblewrap that needs an unprivileged
|
|
95
|
+
* user namespace; on a box that restricts it (Ubuntu 24.04
|
|
96
|
+
* `apparmor_restrict_unprivileged_userns=1`) bwrap dies with "setting up uid map:
|
|
97
|
+
* Permission denied" and a HEADLESS codex run lands zero tools — no file writes,
|
|
98
|
+
* no shell — while still reporting a completed turn. That silent under-delivery
|
|
99
|
+
* is what breaks `agents teams` codex teammates (always headless + workspace-write)
|
|
100
|
+
* and headless `agents run codex` alike on the fleet. Returns a loud, actionable
|
|
101
|
+
* message to fail the launch with instead of spawning that doomed run; returns
|
|
102
|
+
* null when the run is fine to proceed.
|
|
103
|
+
*
|
|
104
|
+
* Deliberately scoped: only `codex`, only Linux, only a HEADLESS run (an
|
|
105
|
+
* interactive TUI surfaces the bwrap error to the operator itself), and only a
|
|
106
|
+
* SANDBOXED mode — `skip` is codex `--dangerously-bypass-approvals-and-sandbox`,
|
|
107
|
+
* which uses no bwrap and is unaffected. The intended auto=workspace-write config
|
|
108
|
+
* is never weakened here; the run fails loud rather than silently downgrading.
|
|
109
|
+
* PHNX-3285.
|
|
110
|
+
*/
|
|
111
|
+
export declare function codexSandboxPreflight(args: {
|
|
112
|
+
agent: AgentId;
|
|
113
|
+
platform: NodeJS.Platform;
|
|
114
|
+
interactive: boolean;
|
|
115
|
+
mode: Mode;
|
|
116
|
+
userns: UsernsStatus;
|
|
117
|
+
machine: string;
|
|
118
|
+
}): string | null;
|
|
91
119
|
/** Reasoning effort levels passed to agents that support them. 'auto' defers to the agent's default. */
|
|
92
120
|
export type ExecEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
|
|
93
121
|
/** Options for spawning an agent process. Omitting `prompt` launches the CLI interactively. */
|
|
94
122
|
export interface ExecOptions {
|
|
95
123
|
agent: AgentId;
|
|
124
|
+
/**
|
|
125
|
+
* Custom harness / profile name when this run was launched via
|
|
126
|
+
* `agents run <profile>` (e.g. `deepseek`). `agent` stays the HOST CLI
|
|
127
|
+
* that actually executes. Stamped onto `AGENTS_AGENT_NAME`, the pid
|
|
128
|
+
* registry, and the session-actor sidecar so listings can tell the
|
|
129
|
+
* profile apart from a native host run (PHNX-2935).
|
|
130
|
+
*/
|
|
131
|
+
harnessName?: string;
|
|
96
132
|
version?: string;
|
|
97
133
|
/** Version home whose native auth/config is overlaid onto this run's binary. */
|
|
98
134
|
configVersion?: string;
|
|
@@ -177,6 +213,18 @@ export interface ExecOptions {
|
|
|
177
213
|
*/
|
|
178
214
|
raw?: boolean;
|
|
179
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Identity a custom-harness run stamps on env / pid-registry / sidecars.
|
|
218
|
+
* `agent` is the host CLI; `harnessName` is the profile the user launched.
|
|
219
|
+
* Empty/whitespace harness names fall back to the host so a blank stamp
|
|
220
|
+
* never hides a real agent.
|
|
221
|
+
*/
|
|
222
|
+
export declare function stampedAgentName(options: Pick<ExecOptions, 'agent' | 'harnessName'>): string;
|
|
223
|
+
/**
|
|
224
|
+
* Profile name when it differs from the host agent. Undefined for a native
|
|
225
|
+
* run, so pid-registry / sidecar records stay sparse.
|
|
226
|
+
*/
|
|
227
|
+
export declare function customHarnessName(options: Pick<ExecOptions, 'agent' | 'harnessName'>): string | undefined;
|
|
180
228
|
/**
|
|
181
229
|
* Resolve interactive vs headless. Explicit flags are definitive and win over
|
|
182
230
|
* inference: `--interactive` forces interactive, `--headless` forces headless.
|
|
@@ -406,15 +454,18 @@ export interface TmuxWrapContext {
|
|
|
406
454
|
* True when this run was dispatched onto this box over SSH by `--device`
|
|
407
455
|
* (the launcher exports {@link REMOTE_INTERACTIVE_ENV}).
|
|
408
456
|
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
* (
|
|
417
|
-
*
|
|
457
|
+
* Since PHNX-3316 this flag no longer forces the wrap: `tmux.enabled` gates
|
|
458
|
+
* local and remote runs alike, because the operator reading "tmux disabled"
|
|
459
|
+
* expects NO tmux anywhere. A followed remote run left bare is protected by
|
|
460
|
+
* reconnect-and-resume (lib/hosts/reconnect.ts): a dropped link costs the
|
|
461
|
+
* in-flight turn, and the harness session resumes from disk.
|
|
462
|
+
*
|
|
463
|
+
* The flag still matters for one case: a followed remote run whose LAUNCHER
|
|
464
|
+
* has no TTY (CI, a script, another agent driving the CLI — `sshStream`
|
|
465
|
+
* allocates the peer's TTY from `process.stdin.isTTY`, dispatch.ts) gets no
|
|
466
|
+
* TTY on the peer either, so there is nothing to attach to and the detached
|
|
467
|
+
* pane is the run's only interface — it wraps regardless of `configEnabled`
|
|
468
|
+
* (and is refused as `undurable` when tmux is missing).
|
|
418
469
|
*/
|
|
419
470
|
remoteDispatch: boolean;
|
|
420
471
|
/** Whether a tmux binary is on PATH. */
|
|
@@ -423,16 +474,19 @@ export interface TmuxWrapContext {
|
|
|
423
474
|
* True when this process has a real TTY to attach (`stdout.isTTY`).
|
|
424
475
|
* A piped `agents run --interactive` (session-tracker tests, CI) has none:
|
|
425
476
|
* wrapping then treating the failed attach as Ctrl-b d leaked live panes
|
|
426
|
-
* for a week on yosemite-s0 (PHNX-3293).
|
|
427
|
-
* without a TTY —
|
|
477
|
+
* for a week on yosemite-s0 (PHNX-3293). A remote dispatch still wraps
|
|
478
|
+
* without a TTY — a launcher that has none (CI, scripts, another agent)
|
|
479
|
+
* gives the peer nothing to attach to, so the detached pane is the run's
|
|
480
|
+
* only interface.
|
|
428
481
|
*/
|
|
429
482
|
hasTty: boolean;
|
|
430
483
|
}
|
|
431
484
|
/**
|
|
432
|
-
* What to do with an interactive spawn. Three outcomes, not two: a run
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
485
|
+
* What to do with an interactive spawn. Three outcomes, not two: a remote run
|
|
486
|
+
* that WOULD wrap (the operator opted in, or a `--no-follow` run whose only
|
|
487
|
+
* interface is the detached pane) on a box with no tmux is neither "wrap" nor
|
|
488
|
+
* "spawn bare" — it is a launch that should not happen, because a bare remote
|
|
489
|
+
* spawn looks fine right up until the link blinks and the agent dies with it.
|
|
436
490
|
*/
|
|
437
491
|
export type TmuxWrapDecision =
|
|
438
492
|
/** Run the agent in a detached tmux session and attach this TTY. */
|
|
@@ -443,7 +497,7 @@ export type TmuxWrapDecision =
|
|
|
443
497
|
| {
|
|
444
498
|
kind: 'bare';
|
|
445
499
|
}
|
|
446
|
-
/** Remote-dispatched and tmux is missing
|
|
500
|
+
/** Remote-dispatched, wants the wrap, and tmux is missing: refuse, don't pretend. */
|
|
447
501
|
| {
|
|
448
502
|
kind: 'undurable';
|
|
449
503
|
};
|
|
@@ -451,20 +505,24 @@ export type TmuxWrapDecision =
|
|
|
451
505
|
* Decide whether to run an interactive agent INSIDE a detached tmux session on
|
|
452
506
|
* the shared socket (then attach the current TTY) instead of a bare spawn.
|
|
453
507
|
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
508
|
+
* The wrap is opt-in via this device's `tmux.enabled` (`configEnabled`): a
|
|
509
|
+
* unique `%pane` handle so `agents sessions --active` can tell co-located
|
|
510
|
+
* agents apart and `agents focus` re-attaches without forking, plus scrollback
|
|
511
|
+
* and mouse at the operator's keyboard. Off means OFF, for local and remote
|
|
512
|
+
* runs alike (PHNX-3316) — a followed `--device` run left bare is protected by
|
|
513
|
+
* reconnect-and-resume (lib/hosts/reconnect.ts), which rejoins the live pane
|
|
514
|
+
* when one exists and resumes the harness session from disk when it does not.
|
|
515
|
+
* The RUSH-3125 forced remote wrap conflated durability with that preference
|
|
516
|
+
* and surprised every operator who had explicitly left tmux off.
|
|
517
|
+
*
|
|
518
|
+
* One case still wraps regardless: a followed remote run whose launcher has
|
|
519
|
+
* no TTY (CI, scripts, another agent) gives the peer nothing to attach to —
|
|
520
|
+
* the detached pane is the run's only interface, not an ergonomics choice.
|
|
521
|
+
*
|
|
522
|
+
* The per-run opt-outs bind everything: `--raw` / `--no-tmux` /
|
|
523
|
+
* `AGENTS_NO_TMUX=1` are explicit "I want the bare process" requests, and an
|
|
524
|
+
* escape hatch that silently stopped applying over `--device` would be worse
|
|
525
|
+
* than the bare run the user asked for.
|
|
468
526
|
*
|
|
469
527
|
* Pure, so the gate is unit-tested independently of the (side-effecting) spawn.
|
|
470
528
|
*/
|