@phnx-labs/agents-cli 1.20.34 → 1.20.36

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 +113 -0
  2. package/LICENSE +185 -21
  3. package/README.md +13 -5
  4. package/dist/commands/audit.d.ts +14 -0
  5. package/dist/commands/audit.js +68 -0
  6. package/dist/commands/browser.js +82 -8
  7. package/dist/commands/check.d.ts +15 -0
  8. package/dist/commands/check.js +84 -0
  9. package/dist/commands/cloud.js +143 -3
  10. package/dist/commands/computer-actions.d.ts +15 -0
  11. package/dist/commands/computer-actions.js +120 -22
  12. package/dist/commands/computer.js +81 -0
  13. package/dist/commands/cost.js +17 -12
  14. package/dist/commands/daemon.js +4 -1
  15. package/dist/commands/doctor.d.ts +1 -0
  16. package/dist/commands/doctor.js +54 -97
  17. package/dist/commands/events.d.ts +16 -0
  18. package/dist/commands/events.js +173 -0
  19. package/dist/commands/exec.d.ts +19 -0
  20. package/dist/commands/exec.js +283 -12
  21. package/dist/commands/hosts.js +37 -29
  22. package/dist/commands/inspect.d.ts +2 -0
  23. package/dist/commands/inspect.js +62 -16
  24. package/dist/commands/lock.d.ts +12 -0
  25. package/dist/commands/lock.js +70 -0
  26. package/dist/commands/logs.d.ts +17 -0
  27. package/dist/commands/logs.js +139 -0
  28. package/dist/commands/message.d.ts +15 -0
  29. package/dist/commands/message.js +56 -0
  30. package/dist/commands/models.d.ts +3 -0
  31. package/dist/commands/models.js +27 -8
  32. package/dist/commands/permissions.js +9 -2
  33. package/dist/commands/repo.d.ts +34 -0
  34. package/dist/commands/repo.js +243 -65
  35. package/dist/commands/resource-view.d.ts +20 -0
  36. package/dist/commands/resource-view.js +90 -28
  37. package/dist/commands/routines.js +101 -5
  38. package/dist/commands/secrets-migrate.js +106 -57
  39. package/dist/commands/secrets.d.ts +31 -18
  40. package/dist/commands/secrets.js +196 -91
  41. package/dist/commands/serve.d.ts +10 -0
  42. package/dist/commands/serve.js +37 -0
  43. package/dist/commands/sessions-inject.d.ts +14 -0
  44. package/dist/commands/sessions-inject.js +111 -0
  45. package/dist/commands/sessions-picker.d.ts +2 -0
  46. package/dist/commands/sessions-picker.js +24 -3
  47. package/dist/commands/sessions-resume.d.ts +2 -0
  48. package/dist/commands/sessions-resume.js +221 -0
  49. package/dist/commands/sessions-tail.d.ts +10 -0
  50. package/dist/commands/sessions-tail.js +11 -0
  51. package/dist/commands/sessions.d.ts +143 -1
  52. package/dist/commands/sessions.js +628 -80
  53. package/dist/commands/setup.js +4 -2
  54. package/dist/commands/sync.d.ts +3 -1
  55. package/dist/commands/sync.js +156 -4
  56. package/dist/commands/teams.js +217 -0
  57. package/dist/commands/versions.js +2 -4
  58. package/dist/commands/view.d.ts +3 -0
  59. package/dist/commands/view.js +19 -8
  60. package/dist/commands/wallet.d.ts +6 -0
  61. package/dist/commands/wallet.js +22 -5
  62. package/dist/commands/watchdog.d.ts +18 -0
  63. package/dist/commands/watchdog.js +238 -0
  64. package/dist/index.js +82 -32
  65. package/dist/lib/agents.js +30 -9
  66. package/dist/lib/audit/log.d.ts +92 -0
  67. package/dist/lib/audit/log.js +177 -0
  68. package/dist/lib/auto-pull.js +2 -1
  69. package/dist/lib/browser/cdp.js +5 -1
  70. package/dist/lib/browser/chrome.d.ts +10 -0
  71. package/dist/lib/browser/chrome.js +18 -7
  72. package/dist/lib/browser/drivers/ssh.d.ts +8 -0
  73. package/dist/lib/browser/drivers/ssh.js +77 -16
  74. package/dist/lib/browser/har.d.ts +84 -0
  75. package/dist/lib/browser/har.js +77 -0
  76. package/dist/lib/browser/ipc.d.ts +3 -0
  77. package/dist/lib/browser/ipc.js +37 -12
  78. package/dist/lib/browser/profiles.d.ts +1 -1
  79. package/dist/lib/browser/profiles.js +8 -10
  80. package/dist/lib/browser/refs.d.ts +65 -0
  81. package/dist/lib/browser/refs.js +73 -1
  82. package/dist/lib/browser/runtime-state.js +1 -0
  83. package/dist/lib/browser/service.d.ts +38 -2
  84. package/dist/lib/browser/service.js +183 -23
  85. package/dist/lib/browser/types.d.ts +14 -1
  86. package/dist/lib/budget/live-cloud.d.ts +42 -0
  87. package/dist/lib/budget/live-cloud.js +79 -0
  88. package/dist/lib/budget/live-team.d.ts +31 -0
  89. package/dist/lib/budget/live-team.js +115 -0
  90. package/dist/lib/cloud/codex.js +4 -0
  91. package/dist/lib/cloud/rush.d.ts +12 -1
  92. package/dist/lib/cloud/rush.js +13 -3
  93. package/dist/lib/cloud/types.d.ts +9 -0
  94. package/dist/lib/computer/dispatch.d.ts +8 -0
  95. package/dist/lib/computer/dispatch.js +125 -0
  96. package/dist/lib/computer/loop.d.ts +62 -0
  97. package/dist/lib/computer/loop.js +98 -0
  98. package/dist/lib/computer/model.d.ts +44 -0
  99. package/dist/lib/computer/model.js +157 -0
  100. package/dist/lib/concurrency.d.ts +19 -0
  101. package/dist/lib/concurrency.js +33 -0
  102. package/dist/lib/daemon.d.ts +92 -0
  103. package/dist/lib/daemon.js +222 -18
  104. package/dist/lib/devices/connect.d.ts +3 -2
  105. package/dist/lib/devices/connect.js +5 -3
  106. package/dist/lib/devices/registry.d.ts +7 -0
  107. package/dist/lib/devices/registry.js +24 -0
  108. package/dist/lib/devices/tailscale.js +1 -1
  109. package/dist/lib/drift.d.ts +52 -0
  110. package/dist/lib/drift.js +112 -0
  111. package/dist/lib/events.d.ts +10 -3
  112. package/dist/lib/events.js +101 -24
  113. package/dist/lib/exec.d.ts +17 -0
  114. package/dist/lib/exec.js +100 -12
  115. package/dist/lib/git.d.ts +27 -0
  116. package/dist/lib/git.js +56 -1
  117. package/dist/lib/hooks/cache.d.ts +6 -0
  118. package/dist/lib/hooks/cache.js +54 -12
  119. package/dist/lib/hooks.d.ts +27 -0
  120. package/dist/lib/hooks.js +135 -10
  121. package/dist/lib/hosts/dispatch.d.ts +15 -0
  122. package/dist/lib/hosts/dispatch.js +45 -13
  123. package/dist/lib/hosts/logs.d.ts +16 -0
  124. package/dist/lib/hosts/logs.js +74 -0
  125. package/dist/lib/hosts/option.js +1 -1
  126. package/dist/lib/hosts/passthrough.js +3 -1
  127. package/dist/lib/hosts/progress.d.ts +66 -0
  128. package/dist/lib/hosts/progress.js +125 -17
  129. package/dist/lib/hosts/ready.d.ts +51 -7
  130. package/dist/lib/hosts/ready.js +98 -25
  131. package/dist/lib/hosts/reconcile.d.ts +53 -0
  132. package/dist/lib/hosts/reconcile.js +81 -0
  133. package/dist/lib/hosts/registry.d.ts +19 -2
  134. package/dist/lib/hosts/registry.js +58 -2
  135. package/dist/lib/hosts/remote-cmd.d.ts +62 -1
  136. package/dist/lib/hosts/remote-cmd.js +70 -1
  137. package/dist/lib/hosts/remote-os.d.ts +17 -0
  138. package/dist/lib/hosts/remote-os.js +30 -0
  139. package/dist/lib/hosts/session-index.d.ts +34 -0
  140. package/dist/lib/hosts/session-index.js +56 -0
  141. package/dist/lib/hosts/tasks.d.ts +22 -0
  142. package/dist/lib/hosts/tasks.js +29 -0
  143. package/dist/lib/lock.d.ts +93 -0
  144. package/dist/lib/lock.js +207 -0
  145. package/dist/lib/loop.js +16 -1
  146. package/dist/lib/machine-id.d.ts +21 -0
  147. package/dist/lib/machine-id.js +26 -0
  148. package/dist/lib/mailbox-target.d.ts +36 -0
  149. package/dist/lib/mailbox-target.js +45 -0
  150. package/dist/lib/mailbox.d.ts +47 -0
  151. package/dist/lib/mailbox.js +194 -0
  152. package/dist/lib/mcp.d.ts +5 -0
  153. package/dist/lib/mcp.js +24 -8
  154. package/dist/lib/migrate.d.ts +19 -0
  155. package/dist/lib/migrate.js +134 -26
  156. package/dist/lib/overdue.js +3 -0
  157. package/dist/lib/permissions.d.ts +4 -0
  158. package/dist/lib/permissions.js +35 -0
  159. package/dist/lib/picker.d.ts +26 -0
  160. package/dist/lib/picker.js +127 -0
  161. package/dist/lib/platform/exec.d.ts +46 -0
  162. package/dist/lib/platform/exec.js +74 -0
  163. package/dist/lib/platform/process.d.ts +31 -0
  164. package/dist/lib/platform/process.js +34 -1
  165. package/dist/lib/platform/winpath.js +2 -0
  166. package/dist/lib/plugin-marketplace.d.ts +30 -0
  167. package/dist/lib/plugin-marketplace.js +215 -2
  168. package/dist/lib/plugins.d.ts +5 -0
  169. package/dist/lib/plugins.js +61 -10
  170. package/dist/lib/profiles.d.ts +25 -0
  171. package/dist/lib/profiles.js +22 -6
  172. package/dist/lib/pty-client.js +2 -1
  173. package/dist/lib/rotate.d.ts +61 -0
  174. package/dist/lib/rotate.js +52 -0
  175. package/dist/lib/routines.d.ts +40 -2
  176. package/dist/lib/routines.js +66 -8
  177. package/dist/lib/runner.d.ts +11 -2
  178. package/dist/lib/runner.js +49 -7
  179. package/dist/lib/scheduler.js +6 -1
  180. package/dist/lib/secrets/bundles.d.ts +60 -4
  181. package/dist/lib/secrets/bundles.js +131 -12
  182. package/dist/lib/secrets/filestore.d.ts +3 -0
  183. package/dist/lib/secrets/filestore.js +42 -16
  184. package/dist/lib/secrets/index.d.ts +43 -2
  185. package/dist/lib/secrets/index.js +102 -3
  186. package/dist/lib/secrets/mcp.d.ts +93 -0
  187. package/dist/lib/secrets/mcp.js +205 -0
  188. package/dist/lib/secrets/remote.js +12 -5
  189. package/dist/lib/secrets/sync.js +83 -4
  190. package/dist/lib/secrets/windows.js +14 -3
  191. package/dist/lib/serve/data.d.ts +81 -0
  192. package/dist/lib/serve/data.js +91 -0
  193. package/dist/lib/serve/page.d.ts +7 -0
  194. package/dist/lib/serve/page.js +140 -0
  195. package/dist/lib/serve/server.d.ts +46 -0
  196. package/dist/lib/serve/server.js +115 -0
  197. package/dist/lib/session/active.d.ts +61 -0
  198. package/dist/lib/session/active.js +196 -17
  199. package/dist/lib/session/discover.d.ts +37 -0
  200. package/dist/lib/session/discover.js +204 -27
  201. package/dist/lib/session/inject.d.ts +18 -0
  202. package/dist/lib/session/inject.js +21 -0
  203. package/dist/lib/session/parse.d.ts +6 -0
  204. package/dist/lib/session/parse.js +286 -2
  205. package/dist/lib/session/pid-registry.d.ts +22 -0
  206. package/dist/lib/session/pid-registry.js +104 -0
  207. package/dist/lib/session/provenance.d.ts +14 -2
  208. package/dist/lib/session/provenance.js +39 -8
  209. package/dist/lib/session/remote-active.d.ts +26 -0
  210. package/dist/lib/session/remote-active.js +153 -0
  211. package/dist/lib/session/remote-list.d.ts +51 -0
  212. package/dist/lib/session/remote-list.js +213 -0
  213. package/dist/lib/session/remote.d.ts +7 -1
  214. package/dist/lib/session/remote.js +18 -8
  215. package/dist/lib/session/render.js +1 -1
  216. package/dist/lib/session/sync/config.d.ts +1 -7
  217. package/dist/lib/session/sync/config.js +4 -11
  218. package/dist/lib/session/types.d.ts +18 -1
  219. package/dist/lib/session/types.js +1 -1
  220. package/dist/lib/shims.d.ts +44 -6
  221. package/dist/lib/shims.js +143 -57
  222. package/dist/lib/ssh-exec.d.ts +26 -3
  223. package/dist/lib/ssh-exec.js +47 -3
  224. package/dist/lib/ssh-tunnel.d.ts +24 -5
  225. package/dist/lib/ssh-tunnel.js +62 -63
  226. package/dist/lib/staleness/writers/hooks.js +1 -1
  227. package/dist/lib/startup/command-registry.d.ts +8 -0
  228. package/dist/lib/startup/command-registry.js +17 -1
  229. package/dist/lib/state.d.ts +13 -0
  230. package/dist/lib/state.js +103 -9
  231. package/dist/lib/sync-umbrella.d.ts +14 -7
  232. package/dist/lib/sync-umbrella.js +17 -9
  233. package/dist/lib/teams/forEach.d.ts +110 -0
  234. package/dist/lib/teams/forEach.js +186 -0
  235. package/dist/lib/teams/index.d.ts +1 -0
  236. package/dist/lib/teams/index.js +1 -0
  237. package/dist/lib/teams/pr-watch.d.ts +226 -0
  238. package/dist/lib/teams/pr-watch.js +371 -0
  239. package/dist/lib/teams/registry.js +25 -9
  240. package/dist/lib/teams/supervisor.d.ts +14 -1
  241. package/dist/lib/teams/supervisor.js +19 -0
  242. package/dist/lib/teams/worktree.d.ts +9 -0
  243. package/dist/lib/teams/worktree.js +32 -0
  244. package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
  245. package/dist/lib/terminal/backends/ghostty.js +69 -0
  246. package/dist/lib/terminal/backends/index.d.ts +19 -0
  247. package/dist/lib/terminal/backends/index.js +31 -0
  248. package/dist/lib/terminal/backends/iterm.d.ts +6 -0
  249. package/dist/lib/terminal/backends/iterm.js +62 -0
  250. package/dist/lib/terminal/backends/tmux.d.ts +14 -0
  251. package/dist/lib/terminal/backends/tmux.js +23 -0
  252. package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
  253. package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
  254. package/dist/lib/terminal/engine.d.ts +39 -0
  255. package/dist/lib/terminal/engine.js +54 -0
  256. package/dist/lib/terminal/index.d.ts +17 -0
  257. package/dist/lib/terminal/index.js +8 -0
  258. package/dist/lib/terminal/inject.d.ts +204 -0
  259. package/dist/lib/terminal/inject.js +247 -0
  260. package/dist/lib/terminal/policy.d.ts +11 -0
  261. package/dist/lib/terminal/policy.js +11 -0
  262. package/dist/lib/terminal/quote.d.ts +11 -0
  263. package/dist/lib/terminal/quote.js +13 -0
  264. package/dist/lib/terminal/resolve.d.ts +64 -0
  265. package/dist/lib/terminal/resolve.js +90 -0
  266. package/dist/lib/terminal/shell.d.ts +6 -0
  267. package/dist/lib/terminal/shell.js +23 -0
  268. package/dist/lib/terminal/transport.d.ts +15 -0
  269. package/dist/lib/terminal/transport.js +40 -0
  270. package/dist/lib/terminal/types.d.ts +59 -0
  271. package/dist/lib/terminal/types.js +13 -0
  272. package/dist/lib/triggers/webhook.d.ts +85 -0
  273. package/dist/lib/triggers/webhook.js +141 -0
  274. package/dist/lib/types.d.ts +7 -0
  275. package/dist/lib/versions.d.ts +30 -3
  276. package/dist/lib/versions.js +129 -18
  277. package/dist/lib/watchdog/index.d.ts +3 -0
  278. package/dist/lib/watchdog/index.js +5 -0
  279. package/dist/lib/watchdog/read.d.ts +35 -0
  280. package/dist/lib/watchdog/read.js +149 -0
  281. package/dist/lib/watchdog/runner.d.ts +127 -0
  282. package/dist/lib/watchdog/runner.js +322 -0
  283. package/dist/lib/watchdog/watchdog.d.ts +40 -0
  284. package/dist/lib/watchdog/watchdog.js +166 -0
  285. package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
  286. package/dist/lib/watchdog/watchdogTail.js +154 -0
  287. package/dist/lib/whats-new.d.ts +9 -0
  288. package/dist/lib/whats-new.js +35 -0
  289. package/dist/lib/workflows.d.ts +166 -0
  290. package/dist/lib/workflows.js +193 -0
  291. package/package.json +5 -4
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `agents watchdog` — the watchdog CONSUMER (RUSH-1415).
3
+ *
4
+ * Runs the tick loop that ties the merged pieces together: list active sessions,
5
+ * classify stalls, read the tail, decide (deterministic promise-without-toolcall
6
+ * by default), run the resolver safety gate, and inject "Continue." into the EXACT
7
+ * split — all without the Swift menu-bar. See src/lib/watchdog/runner.ts.
8
+ *
9
+ * agents watchdog one tick, dry — prints what it WOULD nudge/skip and why
10
+ * agents watchdog --nudge one tick, actually injects (explicit opt-in)
11
+ * agents watchdog --watch daemon loop (poll every --interval)
12
+ * agents watchdog --json machine-readable tick output (for the menu-bar)
13
+ * agents watchdog enable|disable flip the global auto-nudge sentinel (default OFF)
14
+ * agents watchdog policy <id> <p> per-session policy: off | keep | handsoff
15
+ */
16
+ import type { Command } from 'commander';
17
+ /** Register the `agents watchdog` command tree. */
18
+ export declare function registerWatchdogCommand(program: Command): void;
@@ -0,0 +1,238 @@
1
+ /**
2
+ * `agents watchdog` — the watchdog CONSUMER (RUSH-1415).
3
+ *
4
+ * Runs the tick loop that ties the merged pieces together: list active sessions,
5
+ * classify stalls, read the tail, decide (deterministic promise-without-toolcall
6
+ * by default), run the resolver safety gate, and inject "Continue." into the EXACT
7
+ * split — all without the Swift menu-bar. See src/lib/watchdog/runner.ts.
8
+ *
9
+ * agents watchdog one tick, dry — prints what it WOULD nudge/skip and why
10
+ * agents watchdog --nudge one tick, actually injects (explicit opt-in)
11
+ * agents watchdog --watch daemon loop (poll every --interval)
12
+ * agents watchdog --json machine-readable tick output (for the menu-bar)
13
+ * agents watchdog enable|disable flip the global auto-nudge sentinel (default OFF)
14
+ * agents watchdog policy <id> <p> per-session policy: off | keep | handsoff
15
+ */
16
+ import chalk from 'chalk';
17
+ import * as fs from 'fs';
18
+ import * as path from 'path';
19
+ import { setHelpSections } from '../lib/help.js';
20
+ import { parseDuration } from '../lib/hooks/cache.js';
21
+ import { getRuntimeStateDir } from '../lib/state.js';
22
+ import { runWatchdogTick, writePolicySentinel, DEFAULT_THRESHOLDS, } from '../lib/watchdog/runner.js';
23
+ /** Default state dir the runner and these subcommands share. */
24
+ function stateDir() {
25
+ return path.join(getRuntimeStateDir(), 'watchdog');
26
+ }
27
+ /** Global auto-nudge sentinel — present = enabled. Default OFF (opt-in). */
28
+ function enabledSentinelPath() {
29
+ return path.join(stateDir(), 'enabled');
30
+ }
31
+ function isGloballyEnabled() {
32
+ return fs.existsSync(enabledSentinelPath());
33
+ }
34
+ function setGloballyEnabled(on) {
35
+ const p = enabledSentinelPath();
36
+ if (on) {
37
+ fs.mkdirSync(path.dirname(p), { recursive: true });
38
+ fs.writeFileSync(p, 'enabled\n');
39
+ }
40
+ else {
41
+ try {
42
+ fs.rmSync(p);
43
+ }
44
+ catch { /* already off */ }
45
+ }
46
+ }
47
+ /** Parse a duration flag ("60s", "5m", "1h") to ms, or fall back to `fallbackMs`. */
48
+ function durationMsOr(raw, fallbackMs) {
49
+ if (raw === undefined)
50
+ return fallbackMs;
51
+ const secs = parseDuration(raw);
52
+ return secs === null ? fallbackMs : secs * 1000;
53
+ }
54
+ function humanMs(ms) {
55
+ if (ms >= 3600_000)
56
+ return `${Math.round(ms / 3600_000)}h`;
57
+ if (ms >= 60_000)
58
+ return `${Math.round(ms / 60_000)}m`;
59
+ return `${Math.round(ms / 1000)}s`;
60
+ }
61
+ function colorForOutcome(o) {
62
+ if (o.injected)
63
+ return chalk.green;
64
+ if (o.addressable === false)
65
+ return chalk.yellow;
66
+ if (o.stall === 'stalled' && o.decision === 'nudge')
67
+ return chalk.cyan;
68
+ return chalk.dim;
69
+ }
70
+ /** Render one tick's outcomes as a human status block. */
71
+ function printTick(result, willInject) {
72
+ const { counts } = result;
73
+ const mode = willInject ? chalk.green('nudge') : chalk.dim('dry');
74
+ console.log(`${chalk.bold('watchdog')} ${mode} ` +
75
+ `${counts.total} live · ${counts.stalled} stalled · ` +
76
+ `${chalk.green(String(counts.nudged))} nudged · ` +
77
+ `${chalk.yellow(String(counts.unaddressable))} un-addressable`);
78
+ for (const o of result.outcomes) {
79
+ const tag = o.injected ? 'NUDGED'
80
+ : o.addressable === false ? 'FLAGGED'
81
+ : o.decision === 'nudge' ? 'WOULD-NUDGE'
82
+ : 'skip';
83
+ const c = colorForOutcome(o);
84
+ const who = o.label || o.sessionId?.slice(0, 8) || o.kind;
85
+ const where = o.host ? chalk.dim(`[${o.host}]`) : '';
86
+ const rail = o.rail ? chalk.dim(`→${o.rail}`) : '';
87
+ console.log(` ${c(tag.padEnd(11))} ${chalk.bold(who)} ${where} ${rail}`);
88
+ console.log(` ${chalk.dim(o.reason)}`);
89
+ }
90
+ }
91
+ async function sleep(ms) {
92
+ return new Promise((resolve) => setTimeout(resolve, ms));
93
+ }
94
+ /** Register the `agents watchdog` command tree. */
95
+ export function registerWatchdogCommand(program) {
96
+ const cmd = program
97
+ .command('watchdog')
98
+ .description('Auto-nudge stalled agent terminals: detect stalls, resolve the exact split, inject "Continue." — no menu-bar needed.')
99
+ .option('--nudge', 'Actually inject (default is a dry run that only reports what it would do)')
100
+ .option('--watch', 'Daemon loop: run a tick every --interval until interrupted')
101
+ .option('--interval <dur>', 'Poll interval in --watch mode (e.g. 30s, 1m)', '30s')
102
+ .option('--stall <dur>', 'Idle time before a session counts as stalled', humanMs(DEFAULT_THRESHOLDS.stallMs))
103
+ .option('--cooldown <dur>', 'Minimum time between nudges to the same session', humanMs(DEFAULT_THRESHOLDS.cooldownMs))
104
+ .option('--dormant <dur>', 'Idle time after which a session is left alone (dormant)', humanMs(DEFAULT_THRESHOLDS.dormantMs))
105
+ .option('--text <text>', 'Nudge text delivered into the terminal', 'Continue.')
106
+ .option('--smart', 'Use the LLM decider (agents run) instead of the deterministic path (non-reproducible)')
107
+ .option('--smart-agent <agent>', 'Agent the --smart decider runs as', 'claude')
108
+ .option('--allow-ghostty-focus', 'Permit the coarse, focus-stealing Ghostty path (off by default)')
109
+ .option('--json', 'Emit the tick result as JSON (for the menu-bar / scripts)')
110
+ .action(async (opts) => {
111
+ const thresholds = {
112
+ stallMs: durationMsOr(opts.stall, DEFAULT_THRESHOLDS.stallMs),
113
+ cooldownMs: durationMsOr(opts.cooldown, DEFAULT_THRESHOLDS.cooldownMs),
114
+ dormantMs: durationMsOr(opts.dormant, DEFAULT_THRESHOLDS.dormantMs),
115
+ };
116
+ // Injection gate: --nudge is the explicit per-run opt-in; the global sentinel
117
+ // enables the automatic daemon. Default OFF: bare `agents watchdog` is dry.
118
+ // Re-read the sentinel every tick so a running `--watch` daemon reflects a
119
+ // later `agents watchdog enable`/`disable` (or the Swift menu-bar toggle)
120
+ // from another shell without a restart.
121
+ const computeWillInject = () => opts.nudge === true || (opts.watch === true && isGloballyEnabled());
122
+ const tickOnce = async (willInject) => runWatchdogTick({
123
+ nudge: willInject,
124
+ nudgeText: opts.text,
125
+ smart: opts.smart === true,
126
+ smartAgent: opts.smartAgent,
127
+ thresholds,
128
+ allowGhosttyFocus: opts.allowGhosttyFocus === true,
129
+ stateDir: stateDir(),
130
+ });
131
+ if (!opts.watch) {
132
+ const willInject = computeWillInject();
133
+ const result = await tickOnce(willInject);
134
+ if (opts.json)
135
+ console.log(JSON.stringify(result, null, 2));
136
+ else
137
+ printTick(result, willInject);
138
+ return;
139
+ }
140
+ // Daemon loop.
141
+ const intervalMs = durationMsOr(opts.interval, 30_000);
142
+ if (!computeWillInject() && !opts.json) {
143
+ console.log(chalk.yellow(`watchdog --watch is DETECT-ONLY (global auto-nudge is ${isGloballyEnabled() ? 'on' : 'off'}). ` +
144
+ `Pass --nudge or run 'agents watchdog enable' to inject.`));
145
+ }
146
+ // eslint-disable-next-line no-constant-condition
147
+ while (true) {
148
+ // Re-evaluated each tick: picks up enable/disable flips mid-run.
149
+ const willInject = computeWillInject();
150
+ const result = await tickOnce(willInject);
151
+ if (opts.json)
152
+ console.log(JSON.stringify(result));
153
+ else
154
+ printTick(result, willInject);
155
+ await sleep(intervalMs);
156
+ }
157
+ });
158
+ setHelpSections(cmd, {
159
+ examples: `
160
+ # One tick, dry — see what it WOULD nudge and why (safe, no injection)
161
+ agents watchdog
162
+
163
+ # One tick, actually inject "Continue." into stalled+addressable splits
164
+ agents watchdog --nudge
165
+
166
+ # Watch loop every 30s, tighter stall threshold
167
+ agents watchdog --watch --interval 30s --stall 60s --cooldown 5m
168
+
169
+ # Machine-readable for the menu-bar
170
+ agents watchdog --json
171
+
172
+ # Turn on the global auto-nudge (so --watch injects without --nudge)
173
+ agents watchdog enable
174
+
175
+ # Leave one session detected-but-untouched
176
+ agents watchdog policy <sessionId> handsoff
177
+ `,
178
+ notes: `
179
+ Decision path (default, deterministic): a session is nudged only when its
180
+ transcript tail shows it ANNOUNCED an action but no tool call followed
181
+ (promise-without-toolcall) AND it is not waiting on the user. Completions and
182
+ open questions are skipped.
183
+
184
+ Safety gate: a nudge is delivered ONLY when the resolver can name the EXACT
185
+ split the agent lives in (tmux / iTerm / IDE terminal). Un-addressable stalls
186
+ (e.g. Ghostty with no tmux) are flagged for the menu-bar and SKIPPED — never
187
+ a guessed or frontmost target.
188
+
189
+ Policy: global auto-nudge defaults OFF (opt-in via 'enable' or --nudge).
190
+ Per-session: off (ignore), keep (default), handsoff (detect + flag, never inject).
191
+
192
+ State (tray-readable): ${path.join('~/.agents/.cache/state/watchdog', '{nudges,flags,last-tick}.json')}
193
+ `,
194
+ });
195
+ // --- global enable/disable/status -----------------------------------------
196
+ cmd.command('enable')
197
+ .description('Turn ON global auto-nudge (so `agents watchdog --watch` injects without --nudge).')
198
+ .action(() => {
199
+ setGloballyEnabled(true);
200
+ console.log(chalk.green('watchdog: global auto-nudge ENABLED'));
201
+ });
202
+ cmd.command('disable')
203
+ .description('Turn OFF global auto-nudge (back to detect-only unless --nudge is passed).')
204
+ .action(() => {
205
+ setGloballyEnabled(false);
206
+ console.log(chalk.yellow('watchdog: global auto-nudge DISABLED'));
207
+ });
208
+ cmd.command('status')
209
+ .description('Show whether global auto-nudge is on and where state is written.')
210
+ .option('--json', 'Emit status as JSON (for the menu-bar / scripts)')
211
+ .action((_opts, command) => {
212
+ // The parent `watchdog` command also declares --json and greedily parses it
213
+ // before dispatching here, so `watchdog status --json` lands the flag on the
214
+ // parent, not this subcommand. optsWithGlobals() merges both levels, so we
215
+ // read it correctly regardless of which command commander bound it to.
216
+ const json = command.optsWithGlobals().json === true;
217
+ const on = isGloballyEnabled();
218
+ if (json) {
219
+ console.log(JSON.stringify({ enabled: on, stateDir: stateDir() }));
220
+ return;
221
+ }
222
+ console.log(`global auto-nudge: ${on ? chalk.green('ON') : chalk.dim('off')}`);
223
+ console.log(`state dir: ${chalk.dim(stateDir())}`);
224
+ });
225
+ // --- per-session policy ----------------------------------------------------
226
+ cmd.command('policy <sessionId> <policy>')
227
+ .description('Set per-session policy: off (ignore) | keep (default) | handsoff (detect + flag, never inject).')
228
+ .action((sessionId, policy) => {
229
+ const p = policy.toLowerCase();
230
+ if (p !== 'off' && p !== 'keep' && p !== 'handsoff') {
231
+ console.error(chalk.red(`invalid policy '${policy}'. Use: off | keep | handsoff`));
232
+ process.exitCode = 1;
233
+ return;
234
+ }
235
+ writePolicySentinel(stateDir(), sessionId, p);
236
+ console.log(`watchdog: session ${chalk.bold(sessionId.slice(0, 8))} policy = ${chalk.cyan(p)}`);
237
+ });
238
+ }
package/dist/index.js CHANGED
@@ -51,9 +51,11 @@ if (IS_DEV_BUILD) {
51
51
  // module on each invocation (which loaded the whole ~50-module tree before the
52
52
  // first byte of output), the registry maps a command name to a thunk that
53
53
  // imports only what that command needs. See src/lib/startup/command-registry.ts.
54
- import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadStatus, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadBrowser, loadComputer, loadHosts, loadSsh, loadPull, loadPush, loadRepo, loadSetup, } from './lib/startup/command-registry.js';
54
+ import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadCheck, loadStatus, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadSsh, loadPull, loadPush, loadRepo, loadSetup, } from './lib/startup/command-registry.js';
55
55
  import { applyGlobalHelpConventions } from './lib/help.js';
56
+ import { renderWhatsNew } from './lib/whats-new.js';
56
57
  import { IS_WINDOWS } from './lib/platform/index.js';
58
+ import { emit, redactArgs } from './lib/events.js';
57
59
  // Transparent shim delegate: the generated Windows `.cmd` shims invoke
58
60
  // `agents __shim <agent>[@version] <raw args>`. Intercept here, before commander
59
61
  // parses anything, so the agent's own flags (`--help`, `--version`, etc.) pass
@@ -75,6 +77,56 @@ program
75
77
  .version(VERSION)
76
78
  .helpOption('-h, --help', 'Show help')
77
79
  .addHelpCommand(false);
80
+ // ─── Audit backbone ────────────────────────────────────────────────────────────
81
+ // One choke point logs every `agents <module> <cmd>` invocation to the structured
82
+ // event log — so team create/disband, agent run, secrets access, and everything
83
+ // else is captured generically (with SSH/remote-user attribution added in emit()),
84
+ // no per-command wiring. `agents events` reads it back. Attached to the root
85
+ // program, so it's inherited by every subcommand regardless of lazy registration.
86
+ /** Command path from the acting command up to (but excluding) the `agents` root. */
87
+ function auditCommandPath(cmd) {
88
+ const parts = [];
89
+ let c = cmd;
90
+ while (c && c.name() && c.name() !== 'agents') {
91
+ parts.unshift(c.name());
92
+ c = c.parent;
93
+ }
94
+ return parts;
95
+ }
96
+ const auditStarts = new WeakMap();
97
+ program.hook('preAction', (_thisCommand, actionCommand) => {
98
+ try {
99
+ const parts = auditCommandPath(actionCommand);
100
+ if (parts.length === 0)
101
+ return;
102
+ auditStarts.set(actionCommand, Date.now());
103
+ emit('command.start', {
104
+ module: parts[0],
105
+ command: parts.join(' '),
106
+ args: redactArgs(actionCommand.args),
107
+ cwd: process.cwd(),
108
+ });
109
+ }
110
+ catch {
111
+ // Audit logging must never break command dispatch.
112
+ }
113
+ });
114
+ program.hook('postAction', (_thisCommand, actionCommand) => {
115
+ try {
116
+ const parts = auditCommandPath(actionCommand);
117
+ if (parts.length === 0)
118
+ return;
119
+ const started = auditStarts.get(actionCommand);
120
+ emit('command.end', {
121
+ module: parts[0],
122
+ command: parts.join(' '),
123
+ ...(started !== undefined ? { durationMs: Date.now() - started } : {}),
124
+ });
125
+ }
126
+ catch {
127
+ // Best-effort completion record; the start line is the durable audit fact.
128
+ }
129
+ });
78
130
  // Custom help for the main program only
79
131
  const originalHelpInformation = program.helpInformation.bind(program);
80
132
  program.helpInformation = function () {
@@ -121,6 +173,7 @@ Run and dispatch:
121
173
  teams Coordinate multiple agents on shared work
122
174
  routines Run agents on a cron schedule (scheduler auto-starts)
123
175
  sessions Browse, search, and replay past runs (live-search in TTY; grouped by workspace)
176
+ logs [id] Show a run's log — host-dispatch task or session; -f to follow
124
177
  browser Automate a browser — navigate, click, screenshot, console, network
125
178
  pty Drive interactive terminal programs (REPLs, TUIs) via a persistent PTY session
126
179
 
@@ -130,6 +183,7 @@ Credentials and profiles:
130
183
 
131
184
  Diagnostics:
132
185
  doctor [agent[@version]] Diagnose CLI availability, sync status, and resource divergence
186
+ check CI drift gate: exit non-zero when resources are out of sync
133
187
  usage [agent] Show rate-limit and quota usage per agent
134
188
 
135
189
  Config sync:
@@ -137,6 +191,7 @@ Config sync:
137
191
  pull Clone or pull the system repo at ~/.agents/.system/
138
192
  repo init --path <dir> Scaffold your own editable repo from a template
139
193
  repo add <path|gh:user/repo> Merge an extra repo after the system repo
194
+ lock [--frozen] Write/verify agents.lock (SHA-256 of resolved resources); --frozen fails on drift
140
195
 
141
196
  Beta features:
142
197
  beta Enable preview features (factory, drive, and more)
@@ -175,41 +230,13 @@ async function showWhatsNew(fromVersion, toVersion) {
175
230
  const response = await fetch(`https://unpkg.com/@phnx-labs/agents-cli@${toVersion}/CHANGELOG.md`);
176
231
  if (!response.ok)
177
232
  return;
178
- const changelog = await response.text();
179
- const lines = changelog.split('\n');
180
- const relevantChanges = [];
181
- let inRelevantSection = false;
182
- let currentVersion = '';
183
- for (const line of lines) {
184
- const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
185
- if (versionMatch) {
186
- currentVersion = versionMatch[1];
187
- // Only the range the user actually moved through: (fromVersion, toVersion].
188
- // Bounding the top end matters when upgrading to a specific older
189
- // version, and guards against a changelog that lists unreleased entries.
190
- const inRange = compareVersions(currentVersion, fromVersion) > 0 &&
191
- compareVersions(currentVersion, toVersion) <= 0;
192
- inRelevantSection = inRange;
193
- if (inRelevantSection) {
194
- relevantChanges.push('');
195
- relevantChanges.push(chalk.bold(`v${currentVersion}`));
196
- }
197
- continue;
198
- }
199
- if (inRelevantSection && line.trim()) {
200
- if (line.startsWith('**') && line.endsWith('**')) {
201
- relevantChanges.push(chalk.cyan(line.replace(/\*\*/g, '')));
202
- }
203
- else if (line.startsWith('- ')) {
204
- relevantChanges.push(chalk.gray(` ${line}`));
205
- }
206
- }
207
- }
233
+ const relevantChanges = renderWhatsNew(await response.text(), fromVersion, toVersion);
208
234
  if (relevantChanges.length > 0) {
209
235
  console.log(chalk.bold("\nWhat's new:\n"));
210
236
  for (const line of relevantChanges) {
211
237
  console.log(line);
212
238
  }
239
+ console.log(chalk.gray('\nFull notes: https://github.com/phnx-labs/agents-cli/blob/main/CHANGELOG.md'));
213
240
  console.log();
214
241
  }
215
242
  }
@@ -764,6 +791,7 @@ async function registerAllEagerCommands() {
764
791
  await reg(loadTrash);
765
792
  await reg(loadRestore);
766
793
  await reg(loadDoctor);
794
+ await reg(loadCheck);
767
795
  await reg(loadStatus);
768
796
  registerExecAliasCommand(program);
769
797
  await reg(loadProfiles);
@@ -773,6 +801,7 @@ async function registerAllEagerCommands() {
773
801
  await reg(loadMenubar);
774
802
  await reg(loadBeta);
775
803
  await reg(loadSync);
804
+ await reg(loadLock);
776
805
  await reg(loadRefreshRules);
777
806
  await reg(loadDrive);
778
807
  await reg(loadFactory);
@@ -782,9 +811,13 @@ async function registerAllEagerCommands() {
782
811
  await reg(loadAlias);
783
812
  await reg(loadPty);
784
813
  await reg(loadTmux);
814
+ await reg(loadWatchdog);
785
815
  await reg(loadBrowser);
786
816
  await reg(loadComputer);
787
817
  await reg(loadHosts);
818
+ await reg(loadLogs);
819
+ await reg(loadEvents);
820
+ await reg(loadAudit);
788
821
  await reg(loadSsh);
789
822
  registerJobsCronAliasCommand(program, 'jobs');
790
823
  registerJobsCronAliasCommand(program, 'cron');
@@ -956,7 +989,7 @@ if (process.env.AGENTS_SKIP_MIGRATION !== '1') {
956
989
  // Bumping the suffix re-runs migrations for every user; binary releases that
957
990
  // don't change the schema must NOT re-run (they would destroy user content
958
991
  // when migration steps overlap with user-authored paths). See issue #20.
959
- const sentinelValue = 'v10';
992
+ const sentinelValue = 'v11';
960
993
  let needRun = true;
961
994
  try {
962
995
  if (fs.existsSync(sentinel) && fs.readFileSync(sentinel, 'utf-8').trim() === sentinelValue) {
@@ -989,6 +1022,15 @@ if (process.platform === 'darwin' && process.env.AGENTS_SKIP_MIGRATION !== '1')
989
1022
  }
990
1023
  catch { /* never block CLI startup on the menu bar */ }
991
1024
  }
1025
+ // Bare invocation prints the root help. Commander only auto-displays help on
1026
+ // an empty parse when subcommands are registered, and the lazy-startup path
1027
+ // registers none for a bare call — without this branch, `agents` exits
1028
+ // silently. Runs after first-run setup and migrations so those still fire;
1029
+ // exits 0 to match `agents --help` (and the pre-fix exit code).
1030
+ if (passedArgs.length === 0) {
1031
+ program.outputHelp();
1032
+ process.exit(0);
1033
+ }
992
1034
  try {
993
1035
  await maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequested);
994
1036
  await program.parseAsync();
@@ -1009,6 +1051,14 @@ catch (err) {
1009
1051
  console.error(err.message);
1010
1052
  process.exit(1);
1011
1053
  }
1054
+ // A --host targeting a password-auth device throws this from resolveHost.
1055
+ // It carries an actionable message (switch to key auth / enroll as a host);
1056
+ // handling it here covers every resolveHost caller (run, hosts check/rm,
1057
+ // secrets --host) at the source instead of a catch at each call site.
1058
+ if (err.name === 'DeviceOffloadUnsupportedError') {
1059
+ console.error(err.message);
1060
+ process.exit(1);
1061
+ }
1012
1062
  }
1013
1063
  throw err;
1014
1064
  }
@@ -327,11 +327,16 @@ export const AGENTS = {
327
327
  commandsSubdir: 'commands',
328
328
  skillsDir: path.join(HOME, '.copilot', 'skills'),
329
329
  hooksDir: 'hooks',
330
+ // Copilot reads a plugin's manifest from the plugin ROOT (plugin.json),
331
+ // not `.claude-plugin/plugin.json`. Mirror it there. Verified against the
332
+ // GitHub Copilot CLI (1.0.56): `copilot plugin install` produces an
333
+ // installed plugin dir whose manifest sits at the root.
334
+ pluginManifestDir: '.',
330
335
  instructionsFile: 'AGENTS.md',
331
336
  format: 'markdown',
332
337
  variableSyntax: '$ARGUMENTS',
333
338
  supportsHooks: false,
334
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, modes: ['plan', 'edit', 'auto', 'skip'] },
339
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, modes: ['plan', 'edit', 'auto', 'skip'] },
335
340
  },
336
341
  amp: {
337
342
  id: 'amp',
@@ -491,11 +496,26 @@ export const AGENTS = {
491
496
  // Install: `curl -fsSL https://app.factory.ai/cli | sh` (no npm package).
492
497
  // Binary is NOT in node_modules/.bin — the shim resolves the fixed install
493
498
  // path ~/.local/bin/droid directly (see the droid branch in shims.ts).
494
- // Config: `~/.factory/` (settings.json, mcp.json, droids/, commands/).
495
- // Memory: native AGENTS.md. Subagents = custom droids (top-level .md files
496
- // in ~/.factory/droids/). Config isolation rides the ~/.factory symlink
497
- // switch (no FACTORY_HOME env var exists). Headless: `droid exec "<prompt>"`
498
- // with --auto low|medium|high, -o stream-json, -m <model>, -r <effort>.
499
+ // Config: `~/.factory/` (settings.json, mcp.json, droids/, commands/, hooks/,
500
+ // plugins/). Memory: native AGENTS.md. Subagents = custom droids (top-level
501
+ // .md files in ~/.factory/droids/). Config isolation rides the ~/.factory
502
+ // symlink switch (no FACTORY_HOME env var exists). Headless:
503
+ // `droid exec "<prompt>"` with --auto low|medium|high, -o stream-json,
504
+ // -m <model>, -r <effort>.
505
+ //
506
+ // Hooks: Claude-shaped. settings.json carries a top-level `hooks` object keyed
507
+ // by event (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart,
508
+ // SessionEnd, Stop, SubagentStop, Notification, PreCompact), each an array of
509
+ // `{ matcher?, hooks: [{ type: "command", command, timeout? }] }` matcher
510
+ // groups — verified against the droid binary's zod schema. So the Claude
511
+ // registrar is reused verbatim, just targeting `.factory/settings.json`.
512
+ //
513
+ // Plugins: native `droid plugin` command group + ~/.factory/plugins/ with the
514
+ // same marketplace layout Claude uses (known_marketplaces.json +
515
+ // marketplaces/<name>/plugins/<plugin>/). Droid's plugin manifest dir is
516
+ // `.factory-plugin/` (it also reads `.claude-plugin/` for compatibility) — set
517
+ // pluginManifestDir so syncPluginToVersion mirrors the manifest into it, the
518
+ // same pattern codex uses with `.codex-plugin`.
499
519
  droid: {
500
520
  id: 'droid',
501
521
  name: 'Droid',
@@ -509,20 +529,21 @@ export const AGENTS = {
509
529
  commandsSubdir: 'commands',
510
530
  skillsDir: '', // no skills concept
511
531
  hooksDir: 'hooks',
532
+ pluginManifestDir: '.factory-plugin',
512
533
  instructionsFile: 'AGENTS.md',
513
534
  format: 'markdown',
514
535
  variableSyntax: '$ARGUMENTS',
515
- supportsHooks: false,
536
+ supportsHooks: true,
516
537
  // Factory Droid Computers (cloud VMs) reached via `droid computer ssh` +
517
538
  // remote headless `droid exec`.
518
539
  cloudProvider: 'factory',
519
540
  capabilities: {
520
- hooks: false,
541
+ hooks: true,
521
542
  mcp: true,
522
543
  allowlist: false,
523
544
  skills: false,
524
545
  commands: true,
525
- plugins: false,
546
+ plugins: true,
526
547
  subagents: true,
527
548
  rules: { file: 'AGENTS.md' },
528
549
  workflows: false,
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Cross-agent, tamper-evident audit log of every dispatched run (issue #347).
3
+ *
4
+ * Every run that reaches the single dispatch chokepoint in `agents run`
5
+ * (src/commands/exec.ts) appends ONE record here. Records form a hash chain:
6
+ * each record embeds the previous record's `hash` as `prevHash`, and its own
7
+ * `hash` is `sha256(canonicalJSON(record-without-hash))`. Because `prevHash`
8
+ * is part of the hashed payload, rewriting or reordering any record breaks the
9
+ * chain from that point forward — `verifyAuditChain()` reports the first index
10
+ * that fails to reproduce.
11
+ *
12
+ * Storage is append-only JSONL under the durable-runtime bucket
13
+ * (`~/.agents/.history/audit/log.jsonl`), one record per line. `.history/` is
14
+ * machine-local, gitignored, and NEVER synced by `agents repo push/pull` — so
15
+ * the `repo` field (a git remote url that can embed an access token) never
16
+ * lands in a version-controlled DotAgents repo, and a cross-machine pull can't
17
+ * splice a foreign chain into this one. The write path is deliberately cheap
18
+ * and non-fatal: a logging failure must never crash a run (see
19
+ * `recordDispatchedRun`). Concurrent writers (parallel teams/routines dispatch)
20
+ * are serialized by an advisory lock file so the chain never forks — see
21
+ * `appendAuditRecord`.
22
+ *
23
+ * Governance context: this is the evidence trail an operator needs to answer
24
+ * "which agent, at which version, ran against which repo, and did it succeed?"
25
+ * — the kind of automated-decision logging the EU AI Act (Art. 12) expects of
26
+ * high-risk systems, kept tamper-evident so the log itself can be trusted.
27
+ */
28
+ /** First record's `prevHash` — a fixed anchor so the chain has a root. */
29
+ export declare const GENESIS_HASH = "GENESIS";
30
+ /** A run's outcome, derived from its process exit code. */
31
+ export type AuditOutcome = 'ok' | 'fail';
32
+ /** One immutable audit record. `hash` seals every other field, `prevHash` included. */
33
+ export interface AuditRecord {
34
+ ts: string;
35
+ agent: string;
36
+ version: string;
37
+ repo: string;
38
+ mode: string;
39
+ outcome: AuditOutcome;
40
+ exit: number;
41
+ prevHash: string;
42
+ hash: string;
43
+ }
44
+ /** The caller-supplied fields — the chain fields (`prevHash`/`hash`) are computed here. */
45
+ export type AuditEntry = Omit<AuditRecord, 'prevHash' | 'hash'>;
46
+ /**
47
+ * Absolute path to the append-only audit log. Lives under `.history/` — the
48
+ * durable-but-machine-local runtime bucket that is gitignored and never synced
49
+ * by `agents repo push/pull`. Keeping it here (not under a top-level, tracked
50
+ * `~/.agents/` path) means the token-bearing `repo` field can never leak into a
51
+ * version-controlled DotAgents repo, and no cross-machine pull can fork the chain.
52
+ */
53
+ export declare function getAuditLogPath(): string;
54
+ /**
55
+ * Append one record to the hash chain and return it. Links to the previous
56
+ * record's `hash` (or `GENESIS_HASH` for the first), computes the sealing
57
+ * hash, and writes a single JSONL line. Synchronous so the record is durable
58
+ * before the caller proceeds.
59
+ *
60
+ * Read-last-hash + append run under the canonical advisory file lock
61
+ * (`withFileLock`, backed by proper-lockfile). Without it, two concurrent
62
+ * writers — the norm under parallel teams/routines dispatch — both read the
63
+ * same last hash and both write `prevHash=H`, forking the chain into a false
64
+ * "tampered" verdict. The lock forces a total order so every record links off
65
+ * the genuinely-previous one. `ensureLockTarget` creates the file first because
66
+ * proper-lockfile locks an existing path.
67
+ */
68
+ export declare function appendAuditRecord(entry: AuditEntry, logPath?: string): AuditRecord;
69
+ /**
70
+ * Walk the chain and confirm every record reproduces. A record is valid when
71
+ * (a) its `prevHash` matches the prior record's `hash` (GENESIS for the first)
72
+ * AND (b) recomputing its sealing hash from its own fields reproduces the
73
+ * stored `hash`. Returns the first failing index in `brokenAt`.
74
+ */
75
+ export declare function verifyAuditChain(logPath?: string): {
76
+ ok: boolean;
77
+ brokenAt?: number;
78
+ };
79
+ /** Read the whole chain, oldest-first. Exposed for `agents audit list`. */
80
+ export declare function readAuditLog(logPath?: string): AuditRecord[];
81
+ /**
82
+ * Record ONE dispatched run at the single exec chokepoint. Non-fatal by
83
+ * contract: any failure is caught and warned, never thrown — an audit-log
84
+ * hiccup must not crash a run that already finished.
85
+ */
86
+ export declare function recordDispatchedRun(run: {
87
+ agent: string;
88
+ version: string;
89
+ mode: string;
90
+ cwd: string;
91
+ exitCode: number;
92
+ }): void;