@phnx-labs/agents-cli 1.22.83 → 1.22.85

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.
Files changed (291) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +80 -53
  3. package/dist/bootstrap.d.ts +7 -6
  4. package/dist/bootstrap.js +20 -27
  5. package/dist/cli/command-registry.js +5 -1
  6. package/dist/commands/accounts.d.ts +6 -13
  7. package/dist/commands/accounts.js +330 -143
  8. package/dist/commands/apply.js +1 -1
  9. package/dist/commands/auth-mint.d.ts +7 -3
  10. package/dist/commands/auth-mint.js +21 -9
  11. package/dist/commands/auth.js +2 -3
  12. package/dist/commands/browser.js +10 -10
  13. package/dist/commands/daemon.d.ts +7 -3
  14. package/dist/commands/daemon.js +40 -25
  15. package/dist/commands/doctor.js +28 -2
  16. package/dist/commands/exec.js +87 -39
  17. package/dist/commands/fleet-capture.js +21 -2
  18. package/dist/commands/harness-wizard.js +2 -2
  19. package/dist/commands/lease.js +7 -12
  20. package/dist/commands/profiles.d.ts +2 -2
  21. package/dist/commands/profiles.js +20 -13
  22. package/dist/commands/repo.js +3 -3
  23. package/dist/commands/run-account-picker.d.ts +3 -3
  24. package/dist/commands/run-account-picker.js +29 -30
  25. package/dist/commands/secrets-passthrough.d.ts +20 -0
  26. package/dist/commands/secrets-passthrough.js +43 -0
  27. package/dist/commands/sessions-trace.d.ts +11 -0
  28. package/dist/commands/sessions-trace.js +71 -0
  29. package/dist/commands/setup-accounts.d.ts +3 -2
  30. package/dist/commands/setup-accounts.js +4 -4
  31. package/dist/commands/setup-secrets.d.ts +16 -21
  32. package/dist/commands/setup-secrets.js +43 -224
  33. package/dist/commands/ssh.js +2 -2
  34. package/dist/commands/sync.js +1 -1
  35. package/dist/commands/update.d.ts +10 -0
  36. package/dist/commands/update.js +43 -40
  37. package/dist/commands/versions.d.ts +11 -11
  38. package/dist/commands/versions.js +131 -83
  39. package/dist/commands/view.d.ts +1 -9
  40. package/dist/commands/view.js +41 -55
  41. package/dist/commands/webhook.js +5 -5
  42. package/dist/commands/workflows.js +2 -1
  43. package/dist/index.d.ts +12 -12
  44. package/dist/index.js +13 -42
  45. package/dist/lib/account-capabilities.d.ts +1 -1
  46. package/dist/lib/account-capabilities.js +1 -1
  47. package/dist/lib/account-catalog.d.ts +121 -6
  48. package/dist/lib/account-catalog.js +421 -7
  49. package/dist/lib/account-registry.d.ts +8 -30
  50. package/dist/lib/account-registry.js +36 -79
  51. package/dist/lib/account-schema.d.ts +1 -1
  52. package/dist/lib/account-schema.js +1 -1
  53. package/dist/lib/accounting/account-pool-collect.js +16 -4
  54. package/dist/lib/accounting/rotate.d.ts +20 -7
  55. package/dist/lib/accounting/rotate.js +89 -13
  56. package/dist/lib/accounting/usage.js +11 -16
  57. package/dist/lib/accounts/add.d.ts +138 -0
  58. package/dist/lib/accounts/add.js +651 -0
  59. package/dist/lib/accounts/migrate.d.ts +117 -0
  60. package/dist/lib/accounts/migrate.js +536 -0
  61. package/dist/lib/accounts/slots.d.ts +13 -0
  62. package/dist/lib/accounts/slots.js +100 -0
  63. package/dist/lib/agent-spec/agents.d.ts +16 -0
  64. package/dist/lib/agent-spec/agents.js +32 -4
  65. package/dist/lib/app-bundle-install.js +5 -4
  66. package/dist/lib/auth-health.d.ts +3 -0
  67. package/dist/lib/auth-health.js +11 -0
  68. package/dist/lib/auth-mint.d.ts +52 -16
  69. package/dist/lib/auth-mint.js +117 -39
  70. package/dist/lib/browser/chrome.d.ts +1 -1
  71. package/dist/lib/browser/chrome.js +5 -5
  72. package/dist/lib/byok-usage.js +3 -3
  73. package/dist/lib/claude-account-token.d.ts +47 -3
  74. package/dist/lib/claude-account-token.js +172 -50
  75. package/dist/lib/cloud/antigravity.js +4 -4
  76. package/dist/lib/cloud/cursor.js +4 -4
  77. package/dist/lib/crabbox/cli.d.ts +1 -1
  78. package/dist/lib/crabbox/cli.js +6 -6
  79. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  80. package/dist/lib/crabbox/runtimes.js +5 -5
  81. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  82. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  83. package/dist/lib/daemon/auth-sync-service.js +28 -1
  84. package/dist/lib/daemon/daemon.d.ts +6 -16
  85. package/dist/lib/daemon/daemon.js +52 -97
  86. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  87. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  88. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  89. package/dist/lib/daemon/harness-update-service.js +1 -1
  90. package/dist/lib/daemon/runner.d.ts +15 -3
  91. package/dist/lib/daemon/runner.js +49 -24
  92. package/dist/lib/daemon-health.d.ts +1 -2
  93. package/dist/lib/daemon-health.js +7 -6
  94. package/dist/lib/daemon-services.d.ts +1 -1
  95. package/dist/lib/daemon-services.js +1 -11
  96. package/dist/lib/daemon-ticks.js +23 -2
  97. package/dist/lib/daemon-webhooks.d.ts +8 -7
  98. package/dist/lib/daemon-webhooks.js +10 -9
  99. package/dist/lib/device-config.js +1 -1
  100. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  101. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  102. package/dist/lib/devices/harness-inventory.js +126 -4
  103. package/dist/lib/doctor-diff.js +2 -1
  104. package/dist/lib/exec-account-home.d.ts +38 -0
  105. package/dist/lib/exec-account-home.js +164 -0
  106. package/dist/lib/exec.d.ts +30 -0
  107. package/dist/lib/exec.js +101 -31
  108. package/dist/lib/feed/activity-stream.d.ts +60 -0
  109. package/dist/lib/feed/activity-stream.js +271 -0
  110. package/dist/lib/feed/activity.d.ts +7 -0
  111. package/dist/lib/feed/activity.js +10 -4
  112. package/dist/lib/feed/watch.d.ts +9 -3
  113. package/dist/lib/feed/watch.js +72 -36
  114. package/dist/lib/fleet/apply.d.ts +2 -2
  115. package/dist/lib/fleet/apply.js +4 -4
  116. package/dist/lib/fleet/auth-sync.js +4 -3
  117. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  118. package/dist/lib/fleet-shared-state.d.ts +6 -0
  119. package/dist/lib/harness/adapter.d.ts +7 -7
  120. package/dist/lib/harness/adapter.js +10 -6
  121. package/dist/lib/harness/adapters/grok.js +8 -3
  122. package/dist/lib/harness/adapters/muse.js +1 -1
  123. package/dist/lib/harness/adapters/opencode.js +12 -3
  124. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  125. package/dist/lib/harness-auth-capabilities.js +52 -0
  126. package/dist/lib/helper-versions.d.ts +6 -4
  127. package/dist/lib/helper-versions.js +5 -4
  128. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  129. package/dist/lib/hosts/credential-transport.js +37 -0
  130. package/dist/lib/hosts/dispatch.d.ts +7 -0
  131. package/dist/lib/hosts/dispatch.js +13 -6
  132. package/dist/lib/identity/client.d.ts +3 -3
  133. package/dist/lib/identity/client.js +3 -3
  134. package/dist/lib/installations/index.d.ts +1 -1
  135. package/dist/lib/installations/index.js +1 -1
  136. package/dist/lib/installations/migrate.js +9 -0
  137. package/dist/lib/installations/resolve.js +1 -1
  138. package/dist/lib/installations/shims.d.ts +15 -0
  139. package/dist/lib/installations/shims.js +69 -0
  140. package/dist/lib/installations/store.d.ts +43 -0
  141. package/dist/lib/installations/store.js +75 -7
  142. package/dist/lib/installations/versions.js +1 -1
  143. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  144. package/dist/lib/menubar/install-menubar.js +3 -3
  145. package/dist/lib/native-accounts.d.ts +35 -0
  146. package/dist/lib/native-accounts.js +41 -0
  147. package/dist/lib/net-close.d.ts +11 -0
  148. package/dist/lib/net-close.js +25 -0
  149. package/dist/lib/openclaw-keychain.js +27 -1
  150. package/dist/lib/profiles.js +22 -9
  151. package/dist/lib/project-resources.js +1 -1
  152. package/dist/lib/reserved-stores.d.ts +53 -0
  153. package/dist/lib/reserved-stores.js +120 -0
  154. package/dist/lib/secrets-client.d.ts +191 -0
  155. package/dist/lib/secrets-client.js +710 -0
  156. package/dist/lib/secrets-policy.d.ts +222 -0
  157. package/dist/lib/secrets-policy.js +483 -0
  158. package/dist/lib/secrets-types.d.ts +188 -0
  159. package/dist/lib/secrets-types.js +24 -0
  160. package/dist/lib/service-manifest.js +3 -4
  161. package/dist/lib/session/active.d.ts +38 -4
  162. package/dist/lib/session/active.js +36 -4
  163. package/dist/lib/session/bash-command.js +10 -0
  164. package/dist/lib/session/db.d.ts +65 -2
  165. package/dist/lib/session/db.js +178 -18
  166. package/dist/lib/session/mirror.d.ts +5 -0
  167. package/dist/lib/session/mirror.js +166 -0
  168. package/dist/lib/session/parse.d.ts +49 -0
  169. package/dist/lib/session/parse.js +324 -30
  170. package/dist/lib/session/prompt.d.ts +93 -2
  171. package/dist/lib/session/prompt.js +266 -15
  172. package/dist/lib/session/remote/peer-stream.d.ts +47 -0
  173. package/dist/lib/session/remote/peer-stream.js +142 -0
  174. package/dist/lib/session/remote/watch.d.ts +1 -1
  175. package/dist/lib/session/remote/watch.js +41 -42
  176. package/dist/lib/session/session-cache.d.ts +9 -0
  177. package/dist/lib/session/session-cache.js +40 -2
  178. package/dist/lib/session/sync/config.js +2 -2
  179. package/dist/lib/session/timeline-pass.d.ts +129 -0
  180. package/dist/lib/session/timeline-pass.js +323 -0
  181. package/dist/lib/session/timeline.d.ts +182 -0
  182. package/dist/lib/session/timeline.js +636 -0
  183. package/dist/lib/session/types.d.ts +155 -1
  184. package/dist/lib/sha256-asset.d.ts +11 -16
  185. package/dist/lib/sha256-asset.js +11 -16
  186. package/dist/lib/share/config.js +14 -5
  187. package/dist/lib/signin-badge.d.ts +26 -0
  188. package/dist/lib/signin-badge.js +42 -0
  189. package/dist/lib/staleness/detectors/workflows.js +3 -105
  190. package/dist/lib/staleness/writers/workflows.js +2 -1
  191. package/dist/lib/state.d.ts +7 -4
  192. package/dist/lib/state.js +18 -7
  193. package/dist/lib/summarizer/pass.d.ts +2 -0
  194. package/dist/lib/summarizer/pass.js +14 -1
  195. package/dist/lib/summarizer/summarize.d.ts +7 -0
  196. package/dist/lib/summarizer/summarize.js +3 -0
  197. package/dist/lib/sync-umbrella.js +2 -2
  198. package/dist/lib/teams/agents.js +1 -1
  199. package/dist/lib/types.d.ts +74 -33
  200. package/dist/lib/view-types.d.ts +6 -3
  201. package/dist/lib/workflows-registry.d.ts +71 -0
  202. package/dist/lib/workflows-registry.js +280 -0
  203. package/dist/lib/workflows.d.ts +21 -21
  204. package/dist/lib/workflows.js +12 -327
  205. package/package.json +2 -3
  206. package/scripts/postinstall.js +4 -29
  207. package/dist/commands/secrets-import.d.ts +0 -18
  208. package/dist/commands/secrets-import.js +0 -74
  209. package/dist/commands/secrets-migrate.d.ts +0 -25
  210. package/dist/commands/secrets-migrate.js +0 -334
  211. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  212. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  213. package/dist/commands/secrets-sync.d.ts +0 -11
  214. package/dist/commands/secrets-sync.js +0 -153
  215. package/dist/commands/secrets-vault.d.ts +0 -10
  216. package/dist/commands/secrets-vault.js +0 -130
  217. package/dist/commands/secrets.d.ts +0 -212
  218. package/dist/commands/secrets.js +0 -3030
  219. package/dist/lib/accounts/connect.d.ts +0 -170
  220. package/dist/lib/accounts/connect.js +0 -383
  221. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  222. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  223. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  224. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  225. package/dist/lib/secrets/agent.d.ts +0 -358
  226. package/dist/lib/secrets/agent.js +0 -1291
  227. package/dist/lib/secrets/audit.d.ts +0 -46
  228. package/dist/lib/secrets/audit.js +0 -101
  229. package/dist/lib/secrets/bundles.d.ts +0 -290
  230. package/dist/lib/secrets/bundles.js +0 -1547
  231. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  232. package/dist/lib/secrets/download-keychain.js +0 -70
  233. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  234. package/dist/lib/secrets/drivers/rush.js +0 -90
  235. package/dist/lib/secrets/fallback.d.ts +0 -48
  236. package/dist/lib/secrets/fallback.js +0 -48
  237. package/dist/lib/secrets/filestore.d.ts +0 -222
  238. package/dist/lib/secrets/filestore.js +0 -1099
  239. package/dist/lib/secrets/headless.d.ts +0 -43
  240. package/dist/lib/secrets/headless.js +0 -56
  241. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  242. package/dist/lib/secrets/icloud-import.js +0 -206
  243. package/dist/lib/secrets/index.d.ts +0 -451
  244. package/dist/lib/secrets/index.js +0 -1568
  245. package/dist/lib/secrets/install-helper.d.ts +0 -72
  246. package/dist/lib/secrets/install-helper.js +0 -245
  247. package/dist/lib/secrets/lease.d.ts +0 -25
  248. package/dist/lib/secrets/lease.js +0 -44
  249. package/dist/lib/secrets/linux.d.ts +0 -77
  250. package/dist/lib/secrets/linux.js +0 -393
  251. package/dist/lib/secrets/list-filter.d.ts +0 -109
  252. package/dist/lib/secrets/list-filter.js +0 -261
  253. package/dist/lib/secrets/mcp.d.ts +0 -93
  254. package/dist/lib/secrets/mcp.js +0 -211
  255. package/dist/lib/secrets/profiles.d.ts +0 -10
  256. package/dist/lib/secrets/profiles.js +0 -13
  257. package/dist/lib/secrets/push.d.ts +0 -133
  258. package/dist/lib/secrets/push.js +0 -273
  259. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  260. package/dist/lib/secrets/rc-hygiene.js +0 -143
  261. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  262. package/dist/lib/secrets/read-backoff.js +0 -64
  263. package/dist/lib/secrets/reaper.d.ts +0 -90
  264. package/dist/lib/secrets/reaper.js +0 -243
  265. package/dist/lib/secrets/remote.d.ts +0 -152
  266. package/dist/lib/secrets/remote.js +0 -344
  267. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  268. package/dist/lib/secrets/reserved-sync.js +0 -147
  269. package/dist/lib/secrets/scope.d.ts +0 -26
  270. package/dist/lib/secrets/scope.js +0 -29
  271. package/dist/lib/secrets/session-store.d.ts +0 -107
  272. package/dist/lib/secrets/session-store.js +0 -342
  273. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  274. package/dist/lib/secrets/sync-backend.js +0 -13
  275. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  276. package/dist/lib/secrets/sync-commands.js +0 -21
  277. package/dist/lib/secrets/sync.d.ts +0 -48
  278. package/dist/lib/secrets/sync.js +0 -237
  279. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  280. package/dist/lib/secrets/unlock-hints.js +0 -36
  281. package/dist/lib/secrets/usage-db.d.ts +0 -46
  282. package/dist/lib/secrets/usage-db.js +0 -96
  283. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  284. package/dist/lib/secrets/vault-age-helper.js +0 -34
  285. package/dist/lib/secrets/vault.d.ts +0 -49
  286. package/dist/lib/secrets/vault.js +0 -399
  287. package/dist/lib/secrets/windows.d.ts +0 -81
  288. package/dist/lib/secrets/windows.js +0 -556
  289. package/scripts/install-helper.js +0 -97
  290. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  291. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -1,64 +0,0 @@
1
- /**
2
- * Negative memo for failed/cancelled macOS keychain reads (the "back-off").
3
- *
4
- * A cancelled Touch ID sheet is the user saying "not now" — but a polling
5
- * caller (AGI EXT host's `agents view` loop, a watch script)
6
- * retries the same read a few seconds later and pops the sheet again, forever.
7
- * The headless guard (index.ts `assertRawKeychainReadAllowed`) covers the
8
- * no-TTY case; this memo covers a context that CAN prompt but just had its
9
- * prompt cancelled or fail: the next read of the same item within the TTL
10
- * throws the back-off error instead of re-prompting. Any successful read (or
11
- * write) of the item clears the memo.
12
- *
13
- * Stored as regenerable state under `~/.agents/.cache/keychain-read-backoff/`,
14
- * one file per item (filename is a hash of the item name; the file carries no
15
- * secret material — a name and a deadline only). All operations are
16
- * best-effort: a lost memo costs at most one extra prompt, never a read.
17
- */
18
- import { createHash } from 'node:crypto';
19
- import * as fs from 'fs';
20
- import * as os from 'os';
21
- import * as path from 'path';
22
- /** How long a failed/cancelled read suppresses retries of the same item. */
23
- export const KEYCHAIN_READ_BACKOFF_TTL_MS = 5 * 60 * 1000;
24
- let dirOverride = null;
25
- /** Test seam: point the memo at a temp dir so tests never touch the real cache. */
26
- export function setKeychainReadBackoffDirForTest(dir) {
27
- dirOverride = dir;
28
- }
29
- function backoffDir() {
30
- return dirOverride ?? path.join(os.homedir(), '.agents', '.cache', 'keychain-read-backoff');
31
- }
32
- function backoffFile(key) {
33
- const digest = createHash('sha256').update(key).digest('hex').slice(0, 24);
34
- return path.join(backoffDir(), `${digest}.json`);
35
- }
36
- /** True while `key` is inside the back-off window opened by a failed/cancelled read. */
37
- export function isKeychainReadBackedOff(key, now = Date.now()) {
38
- try {
39
- const parsed = JSON.parse(fs.readFileSync(backoffFile(key), 'utf8'));
40
- return typeof parsed.until === 'number' && parsed.until > now;
41
- }
42
- catch {
43
- return false; // absent or malformed memo → no back-off
44
- }
45
- }
46
- /** Open (or refresh) the back-off window for `key` after a failed/cancelled read. */
47
- export function noteKeychainReadFailure(key, now = Date.now()) {
48
- try {
49
- fs.mkdirSync(backoffDir(), { recursive: true, mode: 0o700 });
50
- fs.writeFileSync(backoffFile(key), JSON.stringify({ item: key, until: now + KEYCHAIN_READ_BACKOFF_TTL_MS }), { mode: 0o600 });
51
- }
52
- catch {
53
- /* best-effort — the cache dir is regenerable; a lost memo costs one prompt */
54
- }
55
- }
56
- /** Clear the memo: a successful read or write of the item resets the back-off. */
57
- export function clearKeychainReadBackoff(key) {
58
- try {
59
- fs.rmSync(backoffFile(key), { force: true });
60
- }
61
- catch {
62
- /* best-effort */
63
- }
64
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * Reaper for orphaned / wedged keychain-helper processes.
3
- *
4
- * coreauthd / LocalAuthentication hangs can leave helper (and parent `agents`)
5
- * processes pile up. The pure planner ({@link planKeychainReap}) is unit-testable
6
- * without a real `ps`; the driver ({@link reapOrphanedKeychainProcesses}) shells
7
- * `ps` once per tick and kills through {@link killTree}.
8
- */
9
- /** Grace before a helper whose parent exited is considered an orphan. */
10
- export declare const ORPHAN_GRACE_SEC = 30;
11
- /** Grace before a helper with a live parent is considered stuck. */
12
- export declare const STUCK_GRACE_SEC = 90;
13
- /** Snapshot of one process row from `ps`, fed into the pure planner. */
14
- export interface KeychainProcessSnapshot {
15
- pid: number;
16
- ppid: number;
17
- /** Elapsed seconds since the process started. */
18
- elapsedSec: number;
19
- /** Stable start-time fingerprint from {@link captureProcessStartTime}; null fails closed. */
20
- startTime: string | null;
21
- /** True for a reap-eligible helper invocation (short-lived keychain verb). */
22
- isHelper: boolean;
23
- /**
24
- * True for the deliberately long-lived `watch-lock` watcher
25
- * (auto-lock-on-sleep). Mutually exclusive with {@link isHelper}; killing it
26
- * silently disables auto-lock-on-sleep, so it is reaped only once the owning
27
- * daemon is provably dead (RUSH-2419).
28
- */
29
- isWatchLock?: boolean;
30
- }
31
- /** Tracked state for a stuck parent across two-sweep debounce. */
32
- export interface StuckParentCandidate {
33
- pid: number;
34
- startTime: string | null;
35
- helperPid: number;
36
- helperStartTime: string | null;
37
- firstSeenAt: number;
38
- stage: 'watch' | 'escalate';
39
- }
40
- /** Result of one planning pass. */
41
- export interface ReapPlan {
42
- /** PIDs to kill this tick. */
43
- kill: number[];
44
- /** Candidates to carry forward to the next sweep. */
45
- nextCandidates: Map<number, StuckParentCandidate>;
46
- }
47
- /**
48
- * Pure predicate: decide which processes to kill given a `ps`-like snapshot.
49
- *
50
- * Three conservative classes:
51
- * 1. Orphaned helper: PPID == 1, helper path, alive longer than grace.
52
- * 2. Stuck `agents` parent: helper child alive longer than stuck grace,
53
- * recorded on first sight, child killed on second sweep, parent on third.
54
- * 3. Orphaned `watch-lock`: long-lived watcher whose owning daemon is gone.
55
- *
56
- * Fails closed when start time can't be captured.
57
- */
58
- export declare function planKeychainReap(snapshots: KeychainProcessSnapshot[], now: number, prevCandidates: ReadonlyMap<number, StuckParentCandidate>): ReapPlan;
59
- /**
60
- * Parse macOS `ps -o etime=` elapsed time into whole seconds.
61
- *
62
- * BSD `etime` renders as `[[dd-]hh:]mm:ss`. `etimes` (raw seconds) is a GNU
63
- * extension that macOS `ps` rejects, so darwin uses `etime`.
64
- */
65
- export declare function parseEtimeToSeconds(raw: string): number | null;
66
- /**
67
- * Whether a `ps` command line is a reap-eligible helper invocation: the installed
68
- * helper binary running a short-lived keychain verb. Returns false for the
69
- * long-lived `watch-lock` watcher, which must never be mistaken for a stuck read.
70
- */
71
- export declare function isReapableHelperCommand(command: string, helperPath: string): boolean;
72
- /**
73
- * Whether a `ps` command line is the deliberately long-lived `watch-lock`
74
- * watcher. Inverse of {@link isReapableHelperCommand} for the watch-lock verb.
75
- */
76
- export declare function isWatchLockHelperCommand(command: string, helperPath: string): boolean;
77
- /** Test seam: reset the persisted candidate state. */
78
- export declare function resetKeychainReaperCandidatesForTest(): void;
79
- /**
80
- * Impure driver: snapshot all processes once with `ps`, plan the reap, then
81
- * execute kills through {@link killTree}.
82
- *
83
- * Path-matches the full helper path so an unrelated binary named "Agents CLI" is
84
- * never targeted. Returns on non-darwin without shelling anything.
85
- */
86
- export declare function reapOrphanedKeychainProcesses(): Promise<{
87
- reaped: number;
88
- details: string[];
89
- plan: ReapPlan;
90
- }>;
@@ -1,243 +0,0 @@
1
- /**
2
- * Reaper for orphaned / wedged keychain-helper processes.
3
- *
4
- * coreauthd / LocalAuthentication hangs can leave helper (and parent `agents`)
5
- * processes pile up. The pure planner ({@link planKeychainReap}) is unit-testable
6
- * without a real `ps`; the driver ({@link reapOrphanedKeychainProcesses}) shells
7
- * `ps` once per tick and kills through {@link killTree}.
8
- */
9
- import { killTree, captureProcessStartTime } from '../platform/process.js';
10
- import { execFileBounded } from '../exec-bounded.js';
11
- /** Deadline for the whole-process-table `ps` on the keychain-reap tick. */
12
- const KEYCHAIN_PS_TIMEOUT_MS = 5_000;
13
- import { getKeychainHelperPath } from './install-helper.js';
14
- /** Grace before a helper whose parent exited is considered an orphan. */
15
- export const ORPHAN_GRACE_SEC = 30;
16
- /** Grace before a helper with a live parent is considered stuck. */
17
- export const STUCK_GRACE_SEC = 90;
18
- /**
19
- * Pure predicate: decide which processes to kill given a `ps`-like snapshot.
20
- *
21
- * Three conservative classes:
22
- * 1. Orphaned helper: PPID == 1, helper path, alive longer than grace.
23
- * 2. Stuck `agents` parent: helper child alive longer than stuck grace,
24
- * recorded on first sight, child killed on second sweep, parent on third.
25
- * 3. Orphaned `watch-lock`: long-lived watcher whose owning daemon is gone.
26
- *
27
- * Fails closed when start time can't be captured.
28
- */
29
- export function planKeychainReap(snapshots, now, prevCandidates) {
30
- const pidMap = new Map(snapshots.map((s) => [s.pid, s]));
31
- const kill = [];
32
- const nextCandidates = new Map();
33
- for (const s of snapshots) {
34
- if (!s.isHelper)
35
- continue;
36
- if (s.ppid === 1) {
37
- // Orphaned helper: fail closed without a start-time fingerprint.
38
- if (s.elapsedSec <= ORPHAN_GRACE_SEC)
39
- continue;
40
- if (s.startTime == null)
41
- continue;
42
- kill.push(s.pid);
43
- continue;
44
- }
45
- // Stuck agents: helper with a live parent wedged longer than the timeout.
46
- if (s.elapsedSec <= STUCK_GRACE_SEC)
47
- continue;
48
- const parent = pidMap.get(s.ppid);
49
- if (!parent)
50
- continue;
51
- if (s.startTime == null || parent.startTime == null)
52
- continue;
53
- const prev = prevCandidates.get(parent.pid);
54
- if (prev &&
55
- prev.helperPid === s.pid &&
56
- prev.helperStartTime === s.startTime &&
57
- prev.startTime === parent.startTime) {
58
- if (prev.stage === 'watch') {
59
- // Second sweep: kill the child first so spawnSync returns; escalate next.
60
- kill.push(s.pid);
61
- nextCandidates.set(parent.pid, { ...prev, stage: 'escalate' });
62
- continue;
63
- }
64
- // Third sweep: child did not free the parent, so the parent is wedged.
65
- kill.push(parent.pid);
66
- continue;
67
- }
68
- // First sight of this (parent, helper) pair — record it and wait.
69
- nextCandidates.set(parent.pid, {
70
- pid: parent.pid,
71
- startTime: parent.startTime,
72
- helperPid: s.pid,
73
- helperStartTime: s.startTime,
74
- firstSeenAt: now,
75
- stage: 'watch',
76
- });
77
- }
78
- // Separate path for orphaned watch-lock watchers: only reap when the owning
79
- // daemon is provably absent from the process table.
80
- for (const s of snapshots) {
81
- if (!s.isWatchLock)
82
- continue;
83
- if (s.startTime == null)
84
- continue;
85
- if (s.elapsedSec <= ORPHAN_GRACE_SEC)
86
- continue;
87
- if (s.ppid !== 1) {
88
- const parent = pidMap.get(s.ppid);
89
- if (parent)
90
- continue;
91
- // Parent missing means the owner is dead (or reparenting race); safe to reap.
92
- }
93
- kill.push(s.pid);
94
- }
95
- return { kill, nextCandidates };
96
- }
97
- /**
98
- * Parse macOS `ps -o etime=` elapsed time into whole seconds.
99
- *
100
- * BSD `etime` renders as `[[dd-]hh:]mm:ss`. `etimes` (raw seconds) is a GNU
101
- * extension that macOS `ps` rejects, so darwin uses `etime`.
102
- */
103
- export function parseEtimeToSeconds(raw) {
104
- const m = raw.match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/);
105
- if (!m)
106
- return null;
107
- const days = m[1] ? parseInt(m[1], 10) : 0;
108
- const hours = m[2] ? parseInt(m[2], 10) : 0;
109
- const mins = parseInt(m[3], 10);
110
- const secs = parseInt(m[4], 10);
111
- if ([days, hours, mins, secs].some(isNaN))
112
- return null;
113
- return ((days * 24 + hours) * 60 + mins) * 60 + secs;
114
- }
115
- /**
116
- * Parse one `ps` output line.
117
- *
118
- * Expected format from `ps -ax -o pid=,ppid=,etime=,command=`:
119
- * "<pid> <ppid> <etime> <command...>"
120
- */
121
- function parsePsLine(line) {
122
- const trimmed = line.trim();
123
- if (!trimmed)
124
- return null;
125
- const m = trimmed.match(/^(\d+)\s+(\d+)\s+(\S+)\s+(.*)$/);
126
- if (!m)
127
- return null;
128
- const pid = parseInt(m[1], 10);
129
- const ppid = parseInt(m[2], 10);
130
- const elapsedSec = parseEtimeToSeconds(m[3]);
131
- const command = m[4];
132
- if (isNaN(pid) || isNaN(ppid) || elapsedSec == null)
133
- return null;
134
- return { pid, ppid, elapsedSec, command };
135
- }
136
- /** The one helper verb that is deliberately long-lived: the auto-lock watcher. */
137
- const HELPER_WATCH_LOCK_VERB = 'watch-lock';
138
- /**
139
- * Whether a `ps` command line is a reap-eligible helper invocation: the installed
140
- * helper binary running a short-lived keychain verb. Returns false for the
141
- * long-lived `watch-lock` watcher, which must never be mistaken for a stuck read.
142
- */
143
- export function isReapableHelperCommand(command, helperPath) {
144
- if (command === helperPath)
145
- return true; // bare exec, no verb — never watch-lock
146
- if (!command.startsWith(`${helperPath} `))
147
- return false; // not our helper
148
- const firstArg = command.slice(helperPath.length + 1).trimStart().split(/\s+/)[0];
149
- return firstArg !== HELPER_WATCH_LOCK_VERB;
150
- }
151
- /**
152
- * Whether a `ps` command line is the deliberately long-lived `watch-lock`
153
- * watcher. Inverse of {@link isReapableHelperCommand} for the watch-lock verb.
154
- */
155
- export function isWatchLockHelperCommand(command, helperPath) {
156
- if (!command.startsWith(`${helperPath} `))
157
- return false;
158
- const firstArg = command.slice(helperPath.length + 1).trimStart().split(/\s+/)[0];
159
- return firstArg === HELPER_WATCH_LOCK_VERB;
160
- }
161
- /** Module-state for the two-sweep stuck-parent debounce. */
162
- let stuckParentCandidates = new Map();
163
- /** Test seam: reset the persisted candidate state. */
164
- export function resetKeychainReaperCandidatesForTest() {
165
- stuckParentCandidates = new Map();
166
- }
167
- /**
168
- * Impure driver: snapshot all processes once with `ps`, plan the reap, then
169
- * execute kills through {@link killTree}.
170
- *
171
- * Path-matches the full helper path so an unrelated binary named "Agents CLI" is
172
- * never targeted. Returns on non-darwin without shelling anything.
173
- */
174
- export async function reapOrphanedKeychainProcesses() {
175
- const details = [];
176
- if (process.platform !== 'darwin') {
177
- return { reaped: 0, details, plan: { kill: [], nextCandidates: new Map() } };
178
- }
179
- let helperPath;
180
- try {
181
- helperPath = getKeychainHelperPath();
182
- }
183
- catch (err) {
184
- return { reaped: 0, details: [`helper path resolution failed: ${err.message}`], plan: { kill: [], nextCandidates: new Map() } };
185
- }
186
- let out;
187
- {
188
- // Async, deadline-bounded: this whole-process-table `ps` runs on the daemon's
189
- // shared event loop every keychain-reap tick, so a synchronous `execFileSync`
190
- // (unbounded) would freeze it (PHNX-3695).
191
- const res = await execFileBounded('ps', ['-ax', '-o', 'pid=,ppid=,etime=,command='], { timeoutMs: KEYCHAIN_PS_TIMEOUT_MS });
192
- if (res.code !== 0) {
193
- return { reaped: 0, details: [`ps failed${res.timedOut ? ' (timed out)' : ''}: ${res.stderr.trim() || `exit ${res.code}`}`], plan: { kill: [], nextCandidates: new Map() } };
194
- }
195
- out = res.stdout;
196
- }
197
- const rows = [];
198
- for (const line of out.split('\n')) {
199
- const parsed = parsePsLine(line);
200
- if (!parsed)
201
- continue;
202
- const { pid, ppid, elapsedSec, command } = parsed;
203
- // Match the full argv so the long-lived `watch-lock` watcher is never
204
- // mistaken for a stuck read. Orphaned watch-locks are tracked separately.
205
- const isHelper = isReapableHelperCommand(command, helperPath);
206
- const isWatchLock = isWatchLockHelperCommand(command, helperPath);
207
- rows.push({ pid, ppid, elapsedSec, isHelper, isWatchLock, startTime: null });
208
- }
209
- const rowByPid = new Map(rows.map((r) => [r.pid, r]));
210
- for (const row of rows) {
211
- if (!row.isHelper && !row.isWatchLock)
212
- continue;
213
- row.startTime = captureProcessStartTime(row.pid);
214
- if (row.isHelper && row.ppid !== 1 && row.elapsedSec > STUCK_GRACE_SEC) {
215
- const parent = rowByPid.get(row.ppid);
216
- if (parent && parent.startTime === null) {
217
- parent.startTime = captureProcessStartTime(row.ppid);
218
- }
219
- }
220
- }
221
- const snapshots = rows.map((r) => ({
222
- pid: r.pid,
223
- ppid: r.ppid,
224
- elapsedSec: r.elapsedSec,
225
- startTime: r.startTime,
226
- isHelper: r.isHelper,
227
- isWatchLock: r.isWatchLock,
228
- }));
229
- const plan = planKeychainReap(snapshots, Date.now(), stuckParentCandidates);
230
- stuckParentCandidates = plan.nextCandidates;
231
- for (const pid of plan.kill) {
232
- if (pid === process.pid)
233
- continue; // never self-terminate the reaper
234
- try {
235
- killTree(pid);
236
- details.push(`killed pid ${pid}`);
237
- }
238
- catch (err) {
239
- details.push(`failed to kill pid ${pid}: ${err.message}`);
240
- }
241
- }
242
- return { reaped: plan.kill.length, details, plan };
243
- }
@@ -1,152 +0,0 @@
1
- /**
2
- * Remote secrets — read and use `agents secrets` bundles that live on another
3
- * host over the same SSH path that `agents secrets export --device` uses.
4
- *
5
- * Browse streams remote stdout verbatim; use resolves a bundle to an env map
6
- * and injects it ephemerally without persisting it locally.
7
- */
8
- import { type SshExecResult } from '../ssh-exec.js';
9
- export declare const REMOTE_TIMEOUT_MS = 30000;
10
- export interface RemoteSecretsRawOptions {
11
- tty?: boolean;
12
- input?: string;
13
- osLookupName?: string;
14
- secret?: boolean;
15
- timeoutMs?: number;
16
- }
17
- /**
18
- * SSH options for secret-carrying transport. Pins the managed host key and
19
- * disables multiplex so no reusable control socket lingers for other invocations.
20
- */
21
- export declare function credentialTransportSshOpts(target: string): {
22
- hostKeyOpts: string[];
23
- multiplex: false;
24
- };
25
- /** Refuse durable credential transfer until the destination SSH key is pinned. */
26
- export declare function assertCredentialTransportHostPinned(target: string, pinned?: boolean): void;
27
- /**
28
- * Trust boundary for a remote-resolved env map. A peer's export output is
29
- * untrusted input that could reshape this process once merged into the env, so
30
- * block dangerous override classes here for every consumer:
31
- * loader/interpreter vars, GIT_*, *_PROXY, and *_BASE_URL.
32
- */
33
- export declare function isDangerousRemoteEnvKey(name: string): boolean;
34
- /**
35
- * Resolve a `--device` value to an ssh target string for the remote-secrets
36
- * path. Delegates to the same resolver `run --device` uses; on a miss, treats
37
- * the value as a raw ssh target and validates it.
38
- */
39
- export declare function resolveHostSshTarget(nameOrAlias: string): Promise<string>;
40
- /**
41
- * Merge `--host` / `--hosts` (and their `--device` / `--devices` aliases) into
42
- * an ordered, de-duplicated list. Empty when none is set.
43
- */
44
- export declare function parseHostsOption(opts: {
45
- host?: string;
46
- hosts?: string;
47
- device?: string;
48
- devices?: string;
49
- }): string[];
50
- /**
51
- * Split a `bundle@host` reference. No `@` → a local bundle. Bundle names can't
52
- * contain `@`, so the FIRST `@` separates bundle from ssh target; the target
53
- * itself may be `user@host` (e.g. `r2.backups@muqsit@box`).
54
- */
55
- export declare function splitBundleRef(ref: string): {
56
- bundle: string;
57
- host?: string;
58
- };
59
- /**
60
- * Run `agents secrets <args>` on a remote host over ssh and return the raw
61
- * result. Used by browse commands; `tty` forces `-tt` so remote passphrase
62
- * prompts can surface.
63
- */
64
- export declare function remoteSecretsRaw(target: string, args: string[], opts?: RemoteSecretsRawOptions): SshExecResult;
65
- /** Async, kill-bounded inverse of {@link remoteSecretsRaw} for daemon callers. */
66
- export declare function remoteSecretsRawAsync(target: string, args: string[], opts?: RemoteSecretsRawOptions): Promise<SshExecResult>;
67
- /**
68
- * Run a remote `agents secrets <args>` foreground with local stdio inherited,
69
- * so the remote sees a real TTY and its passphrase prompt surfaces and reads
70
- * keystrokes. Output streams to the terminal; only the exit code is returned.
71
- */
72
- export declare function remoteSecretsStream(target: string, args: string[], opts?: {
73
- osLookupName?: string;
74
- }): number;
75
- /**
76
- * Resolve a remote bundle to a plaintext env map by driving the remote's
77
- * `agents secrets export <bundle> --plaintext --format json`. Values cross over
78
- * ssh stdout, parsed in memory, never persisted.
79
- *
80
- * The remote unlocks the bundle with its own credentials; this machine does NOT
81
- * forward its passphrase because the remote bundle is encrypted with the
82
- * remote's passphrase.
83
- */
84
- export declare function remoteResolveEnv(target: string, bundle: string, opts?: {
85
- osLookupName?: string;
86
- }): Promise<Record<string, string>>;
87
- /**
88
- * Outcome of a post-push read-back verification.
89
- * - `ok` — the pushed keys materialized readably.
90
- * - `locked-keychain` — read-back gave the specific locked-keychain signal.
91
- * - `error` — a different failure (SSH, timeout, etc.).
92
- */
93
- export type RemoteKeychainWriteVerification = {
94
- ok: true;
95
- } | {
96
- ok: false;
97
- kind: 'locked-keychain';
98
- reason: string;
99
- } | {
100
- ok: false;
101
- kind: 'error';
102
- reason: string;
103
- };
104
- /**
105
- * Decide whether a keychain-backed push to a remote actually persisted its
106
- * secret value items by reading the bundle back the same way a later resolve
107
- * will. Catches the silent failure where headless SSH writes metadata but not
108
- * readable value items to a locked macOS login keychain.
109
- */
110
- export declare function evaluateKeychainWriteVerification(pushedKeys: string[], readBack: {
111
- ok: true;
112
- keys: string[];
113
- } | {
114
- ok: false;
115
- stderr: string;
116
- }): RemoteKeychainWriteVerification;
117
- /**
118
- * Actionable error message for a failed keychain-over-SSH push. Names the cause
119
- * (locked remote login keychain) and steers to the two real fixes.
120
- */
121
- export declare function keychainWriteFailureMessage(host: string, bundle: string, reason: string): string;
122
- /**
123
- * Read a bundle back from a remote over SSH and confirm the pushed keys
124
- * materialized. Drives the marker-gated json transport but keeps only KEY NAMES;
125
- * plaintext values are discarded immediately. Returns a verification verdict.
126
- */
127
- export declare function verifyRemoteKeychainPush(target: string, bundle: string, pushedKeys: string[], opts?: {
128
- osLookupName?: string;
129
- secret?: boolean;
130
- timeoutMs?: number;
131
- }): RemoteKeychainWriteVerification;
132
- /** Async, kill-bounded read-back verification for daemon-originated pushes. */
133
- export declare function verifyRemoteKeychainPushAsync(target: string, bundle: string, pushedKeys: string[], opts?: {
134
- osLookupName?: string;
135
- secret?: boolean;
136
- timeoutMs?: number;
137
- }): Promise<RemoteKeychainWriteVerification>;
138
- /**
139
- * `bash -lc` command + stdin payload for a file-backed remote import.
140
- *
141
- * The file store is passphrase-free: the remote auto-provisions its own machine-
142
- * local key, so reads are headless. AGENTS_SECRETS_PASSPHRASE must NOT be
143
- * forwarded (PHNX-2371): a remote keyed to a secret its daemon does not hold
144
- * reports "Imported N key(s)" then fails every later decrypt.
145
- */
146
- export declare function buildRemoteFileImportCommand(bundle: string, dotenv: string, opts?: {
147
- force?: boolean;
148
- policyNever?: boolean;
149
- }): {
150
- remoteCmd: string;
151
- input: string;
152
- };