@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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent mailbox — a file-spool that lets a message reach an already-running
|
|
3
|
+
* agent mid-flight. One box per logical agent, keyed by a unique-per-launch id
|
|
4
|
+
* (session UUID / teams agentId / loop runId), so boxes are disjoint and a
|
|
5
|
+
* message can never reach the wrong agent (see docs / plan velvety-conjuring-popcorn).
|
|
6
|
+
*
|
|
7
|
+
* Layout: <root>/<mailboxId>/{inbox,processing,consumed}/<msgId>.json
|
|
8
|
+
* inbox/ pending, written by `agents message`
|
|
9
|
+
* processing/ claimed by a drain (claim-first = crash-safe)
|
|
10
|
+
* consumed/ archived after delivery (and dropped mismatches)
|
|
11
|
+
*
|
|
12
|
+
* A box has a SINGLE consumer — the owning agent, whose tool calls (and thus
|
|
13
|
+
* hook-driven drains) are sequential. Writers may be concurrent; each enqueue
|
|
14
|
+
* is atomic (temp-write + rename), so a drain never observes a partial file.
|
|
15
|
+
* Delivery is at-least-once: an interrupted drain leaves the message in
|
|
16
|
+
* processing/, and the next drain recovers it. Consumers dedup by `msgId`.
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'fs';
|
|
19
|
+
import * as path from 'path';
|
|
20
|
+
import { randomUUID } from 'crypto';
|
|
21
|
+
import { getMailboxRootDir } from './state.js';
|
|
22
|
+
/**
|
|
23
|
+
* A mailboxId must be a single, separator-free path segment. Real ids (session
|
|
24
|
+
* UUID / teams agentId / loop runId) already satisfy this; rejecting anything
|
|
25
|
+
* else fails loud instead of silently misrouting — a message whose id contained
|
|
26
|
+
* a `/` would nest under a different dir than the `to` stamp it is matched
|
|
27
|
+
* against, and would be dropped with no error. Also blocks `.`/`..` traversal
|
|
28
|
+
* once `agents message` starts accepting external target ids.
|
|
29
|
+
*/
|
|
30
|
+
export function isValidMailboxId(mailboxId) {
|
|
31
|
+
return /^[A-Za-z0-9._-]+$/.test(mailboxId) && mailboxId !== '.' && mailboxId !== '..';
|
|
32
|
+
}
|
|
33
|
+
export function assertValidMailboxId(mailboxId) {
|
|
34
|
+
if (!isValidMailboxId(mailboxId)) {
|
|
35
|
+
throw new Error(`Invalid mailboxId ${JSON.stringify(mailboxId)}: must be a single path segment ` +
|
|
36
|
+
`matching [A-Za-z0-9._-] (no separators, not '.'/'..').`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Absolute path to a box. `root` override is for tests. */
|
|
40
|
+
export function mailboxDir(mailboxId, root = getMailboxRootDir()) {
|
|
41
|
+
assertValidMailboxId(mailboxId);
|
|
42
|
+
return path.join(root, mailboxId);
|
|
43
|
+
}
|
|
44
|
+
function inboxDir(boxDir) { return path.join(boxDir, 'inbox'); }
|
|
45
|
+
function processingDir(boxDir) { return path.join(boxDir, 'processing'); }
|
|
46
|
+
function consumedDir(boxDir) { return path.join(boxDir, 'consumed'); }
|
|
47
|
+
/** Create the three sub-buckets. Idempotent. */
|
|
48
|
+
function ensureDirs(boxDir) {
|
|
49
|
+
fs.mkdirSync(inboxDir(boxDir), { recursive: true });
|
|
50
|
+
fs.mkdirSync(processingDir(boxDir), { recursive: true });
|
|
51
|
+
fs.mkdirSync(consumedDir(boxDir), { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
let seq = 0;
|
|
54
|
+
/**
|
|
55
|
+
* `<epochMs>-<seq>-<rand>` — sorts by filename in FIFO order. The per-process
|
|
56
|
+
* monotonic `seq` breaks ties within the same millisecond (so a single writer's
|
|
57
|
+
* order is preserved); `rand` keeps it unique across processes/hosts.
|
|
58
|
+
*/
|
|
59
|
+
function newMsgId() {
|
|
60
|
+
const s = String(seq++).padStart(6, '0');
|
|
61
|
+
return `${Date.now()}-${s}-${randomUUID().slice(0, 8)}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Enqueue a message into `boxDir` atomically. Returns the msgId. The `to` field
|
|
65
|
+
* is stamped so a drain can refuse a message that lands in the wrong box.
|
|
66
|
+
*/
|
|
67
|
+
export function enqueue(boxDir, msg) {
|
|
68
|
+
assertValidMailboxId(msg.to);
|
|
69
|
+
ensureDirs(boxDir);
|
|
70
|
+
const msgId = newMsgId();
|
|
71
|
+
const record = {
|
|
72
|
+
msgId,
|
|
73
|
+
to: msg.to,
|
|
74
|
+
from: msg.from,
|
|
75
|
+
ts: new Date().toISOString(),
|
|
76
|
+
text: msg.text,
|
|
77
|
+
};
|
|
78
|
+
const target = path.join(inboxDir(boxDir), `${msgId}.json`);
|
|
79
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
80
|
+
fs.writeFileSync(tmp, JSON.stringify(record, null, 2), 'utf-8');
|
|
81
|
+
fs.renameSync(tmp, target); // atomic on a single filesystem
|
|
82
|
+
return msgId;
|
|
83
|
+
}
|
|
84
|
+
/** Parse a message file. Returns null on missing/corrupt/invalid-shape. */
|
|
85
|
+
function readMessage(file) {
|
|
86
|
+
let raw;
|
|
87
|
+
try {
|
|
88
|
+
raw = fs.readFileSync(file, 'utf-8');
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(raw);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const m = parsed;
|
|
101
|
+
if (typeof m?.msgId !== 'string' || typeof m?.to !== 'string' || typeof m?.text !== 'string') {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return { msgId: m.msgId, to: m.to, from: m.from, ts: m.ts ?? '', text: m.text };
|
|
105
|
+
}
|
|
106
|
+
function jsonFiles(dir) {
|
|
107
|
+
let names;
|
|
108
|
+
try {
|
|
109
|
+
names = fs.readdirSync(dir);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
return names.filter((n) => n.endsWith('.json')).sort();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Consume a file already sitting in `processing/`: read, verify it is addressed
|
|
118
|
+
* to this box, then archive to `consumed/`. Returns the message iff valid AND
|
|
119
|
+
* addressed here; a mismatched/corrupt file is archived (dropped) so it never
|
|
120
|
+
* loops. Returns null when the file vanished (a racing drain took it).
|
|
121
|
+
*/
|
|
122
|
+
function consumeClaimed(boxDir, name, expectedTo) {
|
|
123
|
+
const src = path.join(processingDir(boxDir), name);
|
|
124
|
+
const dest = path.join(consumedDir(boxDir), name);
|
|
125
|
+
const msg = readMessage(src);
|
|
126
|
+
try {
|
|
127
|
+
fs.renameSync(src, dest);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null; // already archived/claimed elsewhere
|
|
131
|
+
}
|
|
132
|
+
if (!msg || msg.to !== expectedTo)
|
|
133
|
+
return null; // dropped (corrupt or wrong box)
|
|
134
|
+
return msg;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Drain the box: return every pending message addressed to it, in FIFO order,
|
|
138
|
+
* removing them from the queue. Claim-first (inbox → processing → consumed) so
|
|
139
|
+
* an interrupted drain is recovered on the next call (at-least-once). Corrupt
|
|
140
|
+
* files and messages addressed to a different box are dropped, not returned.
|
|
141
|
+
*
|
|
142
|
+
* `boxId` defaults to the box's directory name — the id it was created under.
|
|
143
|
+
*/
|
|
144
|
+
export function drain(boxDir, boxId = path.basename(boxDir)) {
|
|
145
|
+
ensureDirs(boxDir);
|
|
146
|
+
const out = [];
|
|
147
|
+
// 1. Recover orphans left in processing/ by a prior interrupted drain.
|
|
148
|
+
for (const name of jsonFiles(processingDir(boxDir))) {
|
|
149
|
+
const msg = consumeClaimed(boxDir, name, boxId);
|
|
150
|
+
if (msg)
|
|
151
|
+
out.push(msg);
|
|
152
|
+
}
|
|
153
|
+
// 2. Claim and consume pending inbox messages.
|
|
154
|
+
for (const name of jsonFiles(inboxDir(boxDir))) {
|
|
155
|
+
const from = path.join(inboxDir(boxDir), name);
|
|
156
|
+
const to = path.join(processingDir(boxDir), name);
|
|
157
|
+
try {
|
|
158
|
+
fs.renameSync(from, to); // atomic claim
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
continue; // vanished — a racing drain took it
|
|
162
|
+
}
|
|
163
|
+
const msg = consumeClaimed(boxDir, name, boxId);
|
|
164
|
+
if (msg)
|
|
165
|
+
out.push(msg);
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
/** Read pending messages (inbox + in-flight) without consuming them. FIFO. */
|
|
170
|
+
export function peek(boxDir, boxId = path.basename(boxDir)) {
|
|
171
|
+
const out = [];
|
|
172
|
+
for (const dir of [processingDir(boxDir), inboxDir(boxDir)]) {
|
|
173
|
+
for (const name of jsonFiles(dir)) {
|
|
174
|
+
const msg = readMessage(path.join(dir, name));
|
|
175
|
+
if (msg && msg.to === boxId)
|
|
176
|
+
out.push(msg);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
/** Delete pending (not-yet-claimed) inbox messages. Returns the count removed. */
|
|
182
|
+
export function clear(boxDir) {
|
|
183
|
+
let n = 0;
|
|
184
|
+
for (const name of jsonFiles(inboxDir(boxDir))) {
|
|
185
|
+
try {
|
|
186
|
+
fs.unlinkSync(path.join(inboxDir(boxDir), name));
|
|
187
|
+
n++;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// already gone — ignore
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return n;
|
|
194
|
+
}
|
package/dist/lib/mcp.d.ts
CHANGED
|
@@ -39,6 +39,11 @@ export interface McpTargetOperationResult {
|
|
|
39
39
|
* Parse an MCP server config from a YAML file.
|
|
40
40
|
*/
|
|
41
41
|
export declare function parseMcpServerConfig(filePath: string): McpYamlConfig | null;
|
|
42
|
+
/**
|
|
43
|
+
* Validate an MCP server name. Rejects names that could be misinterpreted as
|
|
44
|
+
* command-line options or that contain characters unsafe for argv/identifier use.
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateMcpServerName(name: string): void;
|
|
42
47
|
/**
|
|
43
48
|
* List all MCP server configs from ~/.agents/mcp/.
|
|
44
49
|
*/
|
package/dist/lib/mcp.js
CHANGED
|
@@ -34,6 +34,18 @@ export function parseMcpServerConfig(filePath) {
|
|
|
34
34
|
}
|
|
35
35
|
return validateMcpYamlConfig(parsed);
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate an MCP server name. Rejects names that could be misinterpreted as
|
|
39
|
+
* command-line options or that contain characters unsafe for argv/identifier use.
|
|
40
|
+
*/
|
|
41
|
+
export function validateMcpServerName(name) {
|
|
42
|
+
if (name.startsWith('-')) {
|
|
43
|
+
throw new Error(`Invalid MCP server name '${name}': names cannot start with '-'`);
|
|
44
|
+
}
|
|
45
|
+
if (/[\s\0-\x1f\x7f]/.test(name)) {
|
|
46
|
+
throw new Error(`Invalid MCP server name '${name}': names cannot contain whitespace or control characters`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
37
49
|
function validateMcpYamlConfig(parsed) {
|
|
38
50
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
39
51
|
return null;
|
|
@@ -41,6 +53,7 @@ function validateMcpYamlConfig(parsed) {
|
|
|
41
53
|
const config = parsed;
|
|
42
54
|
if (typeof config.name !== 'string' || config.name.length === 0)
|
|
43
55
|
return null;
|
|
56
|
+
validateMcpServerName(config.name);
|
|
44
57
|
if (config.transport !== 'stdio' && config.transport !== 'http')
|
|
45
58
|
return null;
|
|
46
59
|
const result = {
|
|
@@ -205,12 +218,12 @@ function installMcpViaClaude(binaryPath, server, versionHome) {
|
|
|
205
218
|
envArgs.push('--env', `${key}=${value}`);
|
|
206
219
|
}
|
|
207
220
|
}
|
|
208
|
-
// claude mcp add --scope user --transport stdio
|
|
221
|
+
// claude mcp add --scope user --transport stdio [--env K=V]... -- <name> <cmd> [args...]
|
|
209
222
|
const args = [
|
|
210
223
|
'mcp', 'add', '--scope', 'user', '--transport', 'stdio',
|
|
211
|
-
server.name,
|
|
212
224
|
...envArgs,
|
|
213
225
|
'--',
|
|
226
|
+
server.name,
|
|
214
227
|
server.config.command,
|
|
215
228
|
...(server.config.args || [])
|
|
216
229
|
];
|
|
@@ -222,8 +235,8 @@ function installMcpViaClaude(binaryPath, server, versionHome) {
|
|
|
222
235
|
});
|
|
223
236
|
}
|
|
224
237
|
else {
|
|
225
|
-
// claude mcp add --scope user --transport http <name> <url>
|
|
226
|
-
execFileSync(binaryPath, ['mcp', 'add', '--scope', 'user', '--transport', 'http', server.name, server.config.url], {
|
|
238
|
+
// claude mcp add --scope user --transport http -- <name> <url>
|
|
239
|
+
execFileSync(binaryPath, ['mcp', 'add', '--scope', 'user', '--transport', 'http', '--', server.name, server.config.url], {
|
|
227
240
|
stdio: 'pipe',
|
|
228
241
|
timeout: 30000,
|
|
229
242
|
env: execEnv,
|
|
@@ -237,10 +250,11 @@ function installMcpViaClaude(binaryPath, server, versionHome) {
|
|
|
237
250
|
*/
|
|
238
251
|
function installMcpViaCodex(binaryPath, server, versionHome) {
|
|
239
252
|
if (server.config.transport === 'stdio') {
|
|
240
|
-
// codex mcp add <name>
|
|
253
|
+
// codex mcp add -- <name> <cmd> [args...]
|
|
241
254
|
const args = [
|
|
242
|
-
'mcp', 'add',
|
|
255
|
+
'mcp', 'add',
|
|
243
256
|
'--',
|
|
257
|
+
server.name,
|
|
244
258
|
server.config.command,
|
|
245
259
|
...(server.config.args || [])
|
|
246
260
|
];
|
|
@@ -272,11 +286,12 @@ export async function registerMcpCommandToTargets(targets, name, commandSpec, sc
|
|
|
272
286
|
}
|
|
273
287
|
function registerMcpCommand(agentId, name, commandSpec, scope, transport, options = {}) {
|
|
274
288
|
try {
|
|
289
|
+
validateMcpServerName(name);
|
|
275
290
|
const bin = options.binary || AGENTS[agentId].cliCommand;
|
|
276
291
|
const commandArgs = [commandSpec.command, ...commandSpec.args];
|
|
277
292
|
const args = agentId === 'claude'
|
|
278
|
-
? ['mcp', 'add', '--transport', transport, '--scope', scope,
|
|
279
|
-
: ['mcp', 'add',
|
|
293
|
+
? ['mcp', 'add', '--transport', transport, '--scope', scope, '--', name, ...commandArgs]
|
|
294
|
+
: ['mcp', 'add', '--', name, ...commandArgs];
|
|
280
295
|
const env = options.home ? { ...process.env, HOME: options.home } : process.env;
|
|
281
296
|
execFileSync(bin, args, { stdio: 'pipe', timeout: 30000, env, shell: needsWindowsShell(bin) });
|
|
282
297
|
return { success: true };
|
|
@@ -508,6 +523,7 @@ export function installMcpServers(agentId, version, versionHome, mcpNames, optio
|
|
|
508
523
|
* Write an MCP server config to ~/.agents/mcp/.
|
|
509
524
|
*/
|
|
510
525
|
export function writeMcpServerConfig(config) {
|
|
526
|
+
validateMcpServerName(config.name);
|
|
511
527
|
const mcpDir = getUserMcpDir();
|
|
512
528
|
fs.mkdirSync(mcpDir, { recursive: true });
|
|
513
529
|
const fileName = `${config.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}.yaml`;
|
package/dist/lib/migrate.d.ts
CHANGED
|
@@ -25,6 +25,25 @@
|
|
|
25
25
|
* LEGACY_SYSTEM_DIR" without duplicating data.
|
|
26
26
|
*/
|
|
27
27
|
export declare function foldLegacySystemRepo(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Migrate a legacy single-repo agents.yaml into ~/.agents/agents.yaml.
|
|
30
|
+
*
|
|
31
|
+
* The post-fold system dir (~/.agents/.system) is a pull-only mirror of the
|
|
32
|
+
* npm-shipped defaults. Its agents.yaml is a TRACKED file that readMeta() reads
|
|
33
|
+
* in place as the defaults base (see SYSTEM_META_FILE in state.ts). Deleting or
|
|
34
|
+
* moving a tracked file out of the mirror dirties its working tree, which
|
|
35
|
+
* permanently wedges `agents setup`, `agents sync`, and background auto-pull
|
|
36
|
+
* ("Working tree has uncommitted changes.") because the mirror sync refuses a
|
|
37
|
+
* dirty tree. So when agents.yaml is tracked there, treat the mirror as strictly
|
|
38
|
+
* read-only and leave the file alone.
|
|
39
|
+
*
|
|
40
|
+
* Only an UNTRACKED agents.yaml is residue from the pre-split single-repo layout
|
|
41
|
+
* (or a legacy ~/.agents-system fold) and is safe to move/drop.
|
|
42
|
+
*
|
|
43
|
+
* Params default to the real on-disk locations; they are injectable so tests can
|
|
44
|
+
* drive a fixture tree without touching the user's ~/.agents.
|
|
45
|
+
*/
|
|
46
|
+
export declare function migrateAgentsYaml(systemDir?: string, userDir?: string): void;
|
|
28
47
|
/**
|
|
29
48
|
* Repair self-referential agent binary symlinks.
|
|
30
49
|
*
|
package/dist/lib/migrate.js
CHANGED
|
@@ -8,6 +8,9 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import * as os from 'os';
|
|
10
10
|
import * as yaml from 'yaml';
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import { atomicWriteFileSync } from './fs-atomic.js';
|
|
13
|
+
import { machineId } from './machine-id.js';
|
|
11
14
|
import { AGENTS, agentConfigDirName, findInPath } from './agents.js';
|
|
12
15
|
import { createLink } from './platform/index.js';
|
|
13
16
|
const HOME = process.env.HOME ?? os.homedir();
|
|
@@ -79,21 +82,46 @@ export function foldLegacySystemRepo() {
|
|
|
79
82
|
}
|
|
80
83
|
catch { /* best-effort */ }
|
|
81
84
|
}
|
|
85
|
+
/** True when `relPath` is a file tracked by a git repo rooted at `repoDir`. */
|
|
86
|
+
function isTrackedInGitRepo(repoDir, relPath) {
|
|
87
|
+
try {
|
|
88
|
+
execSync(`git ls-files --error-unmatch -- ${relPath}`, { cwd: repoDir, stdio: 'ignore' });
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Not a git repo, or the file is untracked — either way, not tracked.
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
82
96
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
97
|
+
* Migrate a legacy single-repo agents.yaml into ~/.agents/agents.yaml.
|
|
98
|
+
*
|
|
99
|
+
* The post-fold system dir (~/.agents/.system) is a pull-only mirror of the
|
|
100
|
+
* npm-shipped defaults. Its agents.yaml is a TRACKED file that readMeta() reads
|
|
101
|
+
* in place as the defaults base (see SYSTEM_META_FILE in state.ts). Deleting or
|
|
102
|
+
* moving a tracked file out of the mirror dirties its working tree, which
|
|
103
|
+
* permanently wedges `agents setup`, `agents sync`, and background auto-pull
|
|
104
|
+
* ("Working tree has uncommitted changes.") because the mirror sync refuses a
|
|
105
|
+
* dirty tree. So when agents.yaml is tracked there, treat the mirror as strictly
|
|
106
|
+
* read-only and leave the file alone.
|
|
107
|
+
*
|
|
108
|
+
* Only an UNTRACKED agents.yaml is residue from the pre-split single-repo layout
|
|
109
|
+
* (or a legacy ~/.agents-system fold) and is safe to move/drop.
|
|
110
|
+
*
|
|
111
|
+
* Params default to the real on-disk locations; they are injectable so tests can
|
|
112
|
+
* drive a fixture tree without touching the user's ~/.agents.
|
|
87
113
|
*/
|
|
88
|
-
function migrateAgentsYaml() {
|
|
89
|
-
const src = path.join(
|
|
90
|
-
const dest = path.join(
|
|
114
|
+
export function migrateAgentsYaml(systemDir = SYSTEM_DIR, userDir = USER_DIR) {
|
|
115
|
+
const src = path.join(systemDir, 'agents.yaml');
|
|
116
|
+
const dest = path.join(userDir, 'agents.yaml');
|
|
91
117
|
if (!fs.existsSync(src))
|
|
92
118
|
return;
|
|
119
|
+
// Tracked in the system mirror → npm-shipped defaults; never mutate. readMeta()
|
|
120
|
+
// already surfaces it in place, so no user-dir copy is needed either.
|
|
121
|
+
if (isTrackedInGitRepo(systemDir, 'agents.yaml'))
|
|
122
|
+
return;
|
|
93
123
|
if (fs.existsSync(dest)) {
|
|
94
|
-
// User copy is authoritative — drop the stale system leftover.
|
|
95
|
-
// repo (npm-shipped) does not track agents.yaml; any copy here is residue
|
|
96
|
-
// from the pre-split layout.
|
|
124
|
+
// User copy is authoritative — drop the untracked stale system leftover.
|
|
97
125
|
try {
|
|
98
126
|
fs.unlinkSync(src);
|
|
99
127
|
}
|
|
@@ -101,7 +129,7 @@ function migrateAgentsYaml() {
|
|
|
101
129
|
return;
|
|
102
130
|
}
|
|
103
131
|
try {
|
|
104
|
-
fs.mkdirSync(
|
|
132
|
+
fs.mkdirSync(userDir, { recursive: true, mode: 0o700 });
|
|
105
133
|
fs.renameSync(src, dest);
|
|
106
134
|
console.error('Migrated agents.yaml to ~/.agents/');
|
|
107
135
|
}
|
|
@@ -656,22 +684,31 @@ function migratePermissionSetsToPresets() {
|
|
|
656
684
|
* switching is owned by `agents use`, not the migrator.
|
|
657
685
|
*/
|
|
658
686
|
function repairAgentConfigSymlinks() {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
catch {
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
const agentsBlock = yaml.match(/^agents:\s*\n((?: [^\n]*\n)+)/m);
|
|
667
|
-
if (!agentsBlock)
|
|
668
|
-
return;
|
|
687
|
+
// Version pins live in the per-device file post-split (~/.agents/devices/<machine>/
|
|
688
|
+
// agents.yaml); central agents.yaml may still carry them mid-transition. Read
|
|
689
|
+
// device first (authoritative), then central, first-seen-agent wins.
|
|
669
690
|
const defaults = [];
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
691
|
+
const collectPins = (file) => {
|
|
692
|
+
let text;
|
|
693
|
+
try {
|
|
694
|
+
text = fs.readFileSync(file, 'utf-8');
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const block = text.match(/^agents:\s*\n((?: [^\n]*\n)+)/m);
|
|
700
|
+
if (!block)
|
|
701
|
+
return;
|
|
702
|
+
for (const line of block[1].split('\n')) {
|
|
703
|
+
const m = line.match(/^\s+([a-z][a-z0-9_-]*):\s*([^\s#]+)/);
|
|
704
|
+
if (m && !defaults.some((d) => d.agent === m[1]))
|
|
705
|
+
defaults.push({ agent: m[1], version: m[2] });
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
collectPins(path.join(USER_DIR, 'devices', machineId(), 'agents.yaml'));
|
|
709
|
+
collectPins(path.join(USER_DIR, 'agents.yaml'));
|
|
710
|
+
if (defaults.length === 0)
|
|
711
|
+
return;
|
|
675
712
|
let repaired = 0;
|
|
676
713
|
for (const { agent, version } of defaults) {
|
|
677
714
|
const configDirName = agent in AGENTS ? agentConfigDirName(agent) : `.${agent}`;
|
|
@@ -1625,6 +1662,73 @@ function migrateVersionResourcesToPatterns() {
|
|
|
1625
1662
|
console.error('Migrated agents.yaml versions: entries to pattern format');
|
|
1626
1663
|
}
|
|
1627
1664
|
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Split the machine-local fields out of the committed central agents.yaml so it
|
|
1667
|
+
* becomes portable and syncs cleanly (no more skip-worktree band-aid):
|
|
1668
|
+
* agents: -> ~/.agents/devices/<machineId>/agents.yaml (committed, per-device)
|
|
1669
|
+
* versions: -> ~/.agents/.history/version-resources.json (gitignored, machine-local)
|
|
1670
|
+
* Then clear any externally-set skip-worktree bit. Idempotent: no-op once central
|
|
1671
|
+
* carries neither field. Operates on raw YAML (never through state.ts).
|
|
1672
|
+
*/
|
|
1673
|
+
function migrateSplitDeviceLocalMeta() {
|
|
1674
|
+
const metaFile = path.join(USER_DIR, 'agents.yaml');
|
|
1675
|
+
if (!fs.existsSync(metaFile))
|
|
1676
|
+
return;
|
|
1677
|
+
let meta;
|
|
1678
|
+
try {
|
|
1679
|
+
meta = yaml.parse(fs.readFileSync(metaFile, 'utf-8')) || {};
|
|
1680
|
+
}
|
|
1681
|
+
catch {
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const agents = meta.agents;
|
|
1685
|
+
const versions = meta.versions;
|
|
1686
|
+
const hasLocal = (!!agents && Object.keys(agents).length > 0) || (!!versions && Object.keys(versions).length > 0);
|
|
1687
|
+
const HEADER = '# agents-cli metadata\n# Auto-generated - do not edit manually\n# https://github.com/phnx-labs/agents-cli\n\n';
|
|
1688
|
+
// Only rewrite central when it actually carries machine-local fields — a
|
|
1689
|
+
// machine whose agents.yaml is already portable-only is left untouched.
|
|
1690
|
+
if (hasLocal) {
|
|
1691
|
+
// agents: -> per-device file (merge, existing device entries win).
|
|
1692
|
+
if (agents && Object.keys(agents).length > 0) {
|
|
1693
|
+
const devicePath = path.join(USER_DIR, 'devices', machineId(), 'agents.yaml');
|
|
1694
|
+
let existing = {};
|
|
1695
|
+
try {
|
|
1696
|
+
const dm = yaml.parse(fs.readFileSync(devicePath, 'utf-8'));
|
|
1697
|
+
if (dm?.agents)
|
|
1698
|
+
existing = dm.agents;
|
|
1699
|
+
}
|
|
1700
|
+
catch { /* absent */ }
|
|
1701
|
+
fs.mkdirSync(path.dirname(devicePath), { recursive: true });
|
|
1702
|
+
atomicWriteFileSync(devicePath, HEADER + yaml.stringify({ agents: { ...agents, ...existing } }));
|
|
1703
|
+
}
|
|
1704
|
+
// versions: -> machine-local history JSON (merge, existing wins).
|
|
1705
|
+
if (versions && Object.keys(versions).length > 0) {
|
|
1706
|
+
const vrPath = path.join(USER_DIR, '.history', 'version-resources.json');
|
|
1707
|
+
let existing = {};
|
|
1708
|
+
try {
|
|
1709
|
+
existing = JSON.parse(fs.readFileSync(vrPath, 'utf-8')) || {};
|
|
1710
|
+
}
|
|
1711
|
+
catch { /* absent */ }
|
|
1712
|
+
fs.mkdirSync(path.dirname(vrPath), { recursive: true });
|
|
1713
|
+
atomicWriteFileSync(vrPath, JSON.stringify({ ...versions, ...existing }, null, 2) + '\n');
|
|
1714
|
+
}
|
|
1715
|
+
// Strip machine-local fields from central and rewrite (portable only) — after
|
|
1716
|
+
// the device/history writes above, so a crash never loses data.
|
|
1717
|
+
delete meta.agents;
|
|
1718
|
+
delete meta.versions;
|
|
1719
|
+
atomicWriteFileSync(metaFile, HEADER + yaml.stringify(meta));
|
|
1720
|
+
}
|
|
1721
|
+
// Always clear any skip-worktree bit (idempotent, best-effort) so agents.yaml
|
|
1722
|
+
// syncs cleanly on every machine — even one that had nothing to split. Runs
|
|
1723
|
+
// after any rewrite so the tracked content already matches the split shape.
|
|
1724
|
+
try {
|
|
1725
|
+
execSync('git update-index --no-skip-worktree agents.yaml', { cwd: USER_DIR, stdio: 'ignore' });
|
|
1726
|
+
}
|
|
1727
|
+
catch { /* not a git repo / bit not set */ }
|
|
1728
|
+
if (hasLocal) {
|
|
1729
|
+
console.error('Split agents.yaml: agents: -> devices/, versions: -> .history/version-resources.json');
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1628
1732
|
/**
|
|
1629
1733
|
* Rename the legacy `extras-extras/` plugin-marketplace dir to `agents-extras/`
|
|
1630
1734
|
* inside every installed agent version-home, and rewrite cross-references in
|
|
@@ -1805,6 +1909,10 @@ export async function runMigration() {
|
|
|
1805
1909
|
foldUserHooksYamlIntoAgentsYaml();
|
|
1806
1910
|
foldBrowserProfilesIntoAgentsYaml();
|
|
1807
1911
|
migrateVersionResourcesToPatterns();
|
|
1912
|
+
// Split machine-local fields (agents:/versions:) out of the committed central
|
|
1913
|
+
// agents.yaml. After migrateVersionResourcesToPatterns so versions: is already
|
|
1914
|
+
// in pattern form when it moves to the history file.
|
|
1915
|
+
migrateSplitDeviceLocalMeta();
|
|
1808
1916
|
// Bucket moves: collapse runtime state into ~/.agents/.history and ~/.agents/.cache.
|
|
1809
1917
|
migrateRuntimeToHistory();
|
|
1810
1918
|
migrateRuntimeToCache();
|
package/dist/lib/overdue.js
CHANGED
|
@@ -44,6 +44,9 @@ export function detectOverdueJobs(now = new Date()) {
|
|
|
44
44
|
for (const job of listJobs()) {
|
|
45
45
|
if (!job.enabled || job.runOnce)
|
|
46
46
|
continue;
|
|
47
|
+
// Trigger-only jobs (no cron schedule) never have an expected fire time.
|
|
48
|
+
if (!job.schedule)
|
|
49
|
+
continue;
|
|
47
50
|
let expected = null;
|
|
48
51
|
try {
|
|
49
52
|
const cronOptions = { paused: true };
|
|
@@ -2,6 +2,10 @@ import type { AgentId, PermissionSet, InstalledPermission, ClaudePermissions, Op
|
|
|
2
2
|
/** Filename used for Codex Starlark deny-rules generated from permission groups. */
|
|
3
3
|
export declare const CODEX_RULES_FILENAME = "agents-deny.rules";
|
|
4
4
|
export type ParsedRules = PermissionSet;
|
|
5
|
+
export declare const COMPUTER_PERMISSION_RULE_PREFIX = "Computer";
|
|
6
|
+
export declare const COMPUTER_APP_GATED_VERBS: readonly ["screenshot", "describe", "get-text", "launch", "raise", "click", "right-click", "type", "type-text", "key", "drag", "scroll", "ax-action", "focus", "wait"];
|
|
7
|
+
export declare const COMPUTER_INPUT_GATED_VERBS: readonly ["raise", "click", "right-click", "type", "type-text", "key", "drag", "scroll", "ax-action", "focus"];
|
|
8
|
+
export declare function formatComputerPermissionGrantHint(bundleId?: string): string;
|
|
5
9
|
export declare function containsBroadGrants(rules: ParsedRules): {
|
|
6
10
|
broad: string[];
|
|
7
11
|
reason: string;
|
package/dist/lib/permissions.js
CHANGED
|
@@ -25,6 +25,41 @@ const HOME = os.homedir();
|
|
|
25
25
|
// the writer in `applyPermissionsToVersion` handles the format dispatch.)
|
|
26
26
|
/** Filename used for Codex Starlark deny-rules generated from permission groups. */
|
|
27
27
|
export const CODEX_RULES_FILENAME = 'agents-deny.rules';
|
|
28
|
+
export const COMPUTER_PERMISSION_RULE_PREFIX = 'Computer';
|
|
29
|
+
export const COMPUTER_APP_GATED_VERBS = [
|
|
30
|
+
'screenshot',
|
|
31
|
+
'describe',
|
|
32
|
+
'get-text',
|
|
33
|
+
'launch',
|
|
34
|
+
'raise',
|
|
35
|
+
'click',
|
|
36
|
+
'right-click',
|
|
37
|
+
'type',
|
|
38
|
+
'type-text',
|
|
39
|
+
'key',
|
|
40
|
+
'drag',
|
|
41
|
+
'scroll',
|
|
42
|
+
'ax-action',
|
|
43
|
+
'focus',
|
|
44
|
+
'wait',
|
|
45
|
+
];
|
|
46
|
+
export const COMPUTER_INPUT_GATED_VERBS = [
|
|
47
|
+
'raise',
|
|
48
|
+
'click',
|
|
49
|
+
'right-click',
|
|
50
|
+
'type',
|
|
51
|
+
'type-text',
|
|
52
|
+
'key',
|
|
53
|
+
'drag',
|
|
54
|
+
'scroll',
|
|
55
|
+
'ax-action',
|
|
56
|
+
'focus',
|
|
57
|
+
];
|
|
58
|
+
export function formatComputerPermissionGrantHint(bundleId) {
|
|
59
|
+
const target = bundleId && bundleId.length > 0 ? bundleId : '<bundle-id>';
|
|
60
|
+
return `add Computer(${target}) to a permissions group, then \`agents computer reload\`\n` +
|
|
61
|
+
`app-targeted computer verbs are gated by Computer(<bundle-id>): ${COMPUTER_APP_GATED_VERBS.join(', ')}`;
|
|
62
|
+
}
|
|
28
63
|
export function containsBroadGrants(rules) {
|
|
29
64
|
const broad = [];
|
|
30
65
|
const reasons = new Set();
|
package/dist/lib/picker.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
/** Configuration for the interactive picker prompt. */
|
|
9
9
|
export interface PickerConfig<T> {
|
|
10
10
|
message: string;
|
|
11
|
+
/** Optional dim hint line rendered directly under the header (above the rows). */
|
|
12
|
+
subtitle?: string;
|
|
11
13
|
items: T[];
|
|
12
14
|
filter: (query: string) => T[];
|
|
13
15
|
labelFor: (item: T, query: string) => string;
|
|
@@ -22,7 +24,31 @@ export interface PickerConfig<T> {
|
|
|
22
24
|
export interface PickedItem<T> {
|
|
23
25
|
item: T;
|
|
24
26
|
}
|
|
27
|
+
/** Configuration for the multi-select picker prompt. */
|
|
28
|
+
export interface MultiPickerConfig<T> {
|
|
29
|
+
message: string;
|
|
30
|
+
items: T[];
|
|
31
|
+
filter: (query: string) => T[];
|
|
32
|
+
labelFor: (item: T, query: string) => string;
|
|
33
|
+
/** Stable identity for an item — drives the selected set. */
|
|
34
|
+
keyFor: (item: T) => string;
|
|
35
|
+
buildPreview?: (item: T) => string;
|
|
36
|
+
pageSize?: number;
|
|
37
|
+
initialSearch?: string;
|
|
38
|
+
emptyMessage?: string;
|
|
39
|
+
enterHint?: string;
|
|
40
|
+
}
|
|
25
41
|
/** Clip a picker preview so the full prompt can fit in the terminal viewport. */
|
|
26
42
|
export declare function limitPreviewHeight(preview: string, maxRows: number, width: number): string;
|
|
27
43
|
/** Show an interactive fuzzy-filter picker and return the selected item, or null on cancel. */
|
|
28
44
|
export declare function itemPicker<T>(config: PickerConfig<T>): Promise<PickedItem<T> | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Multi-select variant of {@link itemPicker}. Same searchable, paginated list
|
|
47
|
+
* and preview pane, but `space` toggles a checkbox on the active row instead of
|
|
48
|
+
* the preview (preview moves to `tab`), and `enter` confirms every checked row.
|
|
49
|
+
*
|
|
50
|
+
* Returns the selected items (in the config's `items` order) or `null` on
|
|
51
|
+
* cancel. Pressing `enter` with nothing checked confirms just the highlighted
|
|
52
|
+
* row, so a quick single-pick still works.
|
|
53
|
+
*/
|
|
54
|
+
export declare function multiItemPicker<T>(config: MultiPickerConfig<T>): Promise<T[] | null>;
|