@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.
- package/CHANGELOG.md +113 -0
- package/LICENSE +185 -21
- package/README.md +13 -5
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/computer.js +81 -0
- package/dist/commands/cost.js +17 -12
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +54 -97
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +283 -12
- package/dist/commands/hosts.js +37 -29
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +62 -16
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +196 -91
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +221 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +143 -1
- package/dist/commands/sessions.js +628 -80
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/view.d.ts +3 -0
- package/dist/commands/view.js +19 -8
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +82 -32
- package/dist/lib/agents.js +30 -9
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.d.ts +8 -0
- package/dist/lib/browser/drivers/ssh.js +77 -16
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +37 -12
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +183 -23
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +92 -0
- package/dist/lib/daemon.js +222 -18
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +10 -3
- package/dist/lib/events.js +101 -24
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +100 -12
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +135 -10
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +45 -13
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +74 -0
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +51 -7
- package/dist/lib/hosts/ready.js +98 -25
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +22 -0
- package/dist/lib/hosts/tasks.js +29 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +26 -0
- package/dist/lib/picker.js +127 -0
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +61 -10
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +61 -0
- package/dist/lib/session/active.js +196 -17
- package/dist/lib/session/discover.d.ts +37 -0
- package/dist/lib/session/discover.js +204 -27
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +286 -2
- package/dist/lib/session/pid-registry.d.ts +22 -0
- package/dist/lib/session/pid-registry.js +104 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +153 -0
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +213 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +18 -8
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +1 -7
- package/dist/lib/session/sync/config.js +4 -11
- package/dist/lib/session/types.d.ts +18 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +44 -6
- package/dist/lib/shims.js +143 -57
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +47 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +62 -63
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +8 -0
- package/dist/lib/startup/command-registry.js +17 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +17 -9
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/registry.js +25 -9
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +19 -0
- package/dist/lib/terminal/backends/index.js +31 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +17 -0
- package/dist/lib/terminal/index.js +8 -0
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/types.d.ts +7 -0
- package/dist/lib/versions.d.ts +30 -3
- package/dist/lib/versions.js +129 -18
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- 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
|
|
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 = '
|
|
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
|
}
|
package/dist/lib/agents.js
CHANGED
|
@@ -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:
|
|
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
|
|
496
|
-
// in ~/.factory/droids/). Config isolation rides the ~/.factory
|
|
497
|
-
// switch (no FACTORY_HOME env var exists). Headless:
|
|
498
|
-
// with --auto low|medium|high, -o stream-json,
|
|
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:
|
|
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:
|
|
541
|
+
hooks: true,
|
|
521
542
|
mcp: true,
|
|
522
543
|
allowlist: false,
|
|
523
544
|
skills: false,
|
|
524
545
|
commands: true,
|
|
525
|
-
plugins:
|
|
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;
|