agentradar-cli 0.3.5 → 0.3.7

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/dist/index.js CHANGED
@@ -1,44 +1,63 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
+ import { homedir } from "node:os";
3
4
  import { installVendorSkill } from "./skill-install.js";
4
5
  import { startChannel } from "./channel.js";
5
6
  import { runRestartSelftest, startVendorRunner } from "./runner.js";
6
- import { cancelJoin, CODEX_GUARD_REFUSAL, ensureCodexGuard, joinOrganisation, leaveOrganisation, setupVendorAgent } from "./setup.js";
7
- import { callAwpTool, callToolOverCli, createTaskOverCli, INBOX_SEARCH_USAGE, METRIC_REPORT_USAGE, PROPOSE_HUMAN_USAGE, inboxSearchOverCli, LISTING_DESCRIBE_USAGE, listServiceToolsOverCli, parseCallArgs, PERMISSION_VERB_TOOLS, runServiceToolOverCli, TASK_VERB_TOOLS } from "./tool-run.js";
7
+ import { cancelJoin, CODEX_GUARD_REFUSAL, dropStaleMcp, ensureCodexGuard, joinOrganisation, leaveOrganisation, setupVendorAgent } from "./setup.js";
8
+ import { callAwpTool, callToolOverCli, EXIT, parseCallArgs } from "./tool-run.js";
8
9
  import { installSyncTimer, syncAgentDir } from "./sync.js";
9
10
  import { importVaultOverCli } from "./vault-import.js";
10
11
  import { cloneVaultOverCli } from "./vault-clone.js";
11
12
  import { secretsOverCli } from "./secrets.js";
13
+ import { adminOverCli } from "./admin.js";
12
14
  // `run` is the one prefix every agent-facing command shares, so one harness "always allow agentradar run"
13
15
  // covers a whole session: `run <link>` (or `run join <link>`) joins, `run <runner-id> <tool>` calls a tool.
14
16
  export const runArgs = (raw) => raw[0] !== "run" ? raw
15
17
  : raw[1] === "join" ? raw.slice(1)
16
18
  : /^https?:\/\//.test(raw[1] ?? "") ? ["join", ...raw.slice(1)]
17
19
  : ["call", ...raw.slice(1)];
20
+ const USAGE = [
21
+ "Usage: agentradar <noun> [<sub-noun>] <verb> [--<flag> <value> | --<flag>-file <path> ...] [--dry-run] [--json] [--runner <id>]",
22
+ " Every agent command comes from the server. agentradar --help lists the groups (as $AR_CLI_RUNNER_ID),",
23
+ " agentradar <group> --help a group's commands, agentradar <command> --help its flags.",
24
+ " Examples:",
25
+ " agentradar inbox next",
26
+ " agentradar channels reply --thread-id <id> --text \"On it\"",
27
+ " agentradar tasks create --title \"Draft the brief\" --dry-run",
28
+ " The runner is $AR_CLI_RUNNER_ID, or --runner <id>. --dry-run prints the exact call and sends nothing.",
29
+ " Exit codes: 0 done; 1 refused or unreachable (the reason is on stderr); 2 the command was wrong.",
30
+ "Local commands:",
31
+ " agentradar join <join-link> [--name <n>] [--model <m>] [--trust] [--attach-tmux <pane> | --attach-current] [--session <id>] [--no-service | --service]",
32
+ " agentradar join cancel <join-link|request-id>",
33
+ " agentradar setup [--trust] [--harness claude|codex]",
34
+ " agentradar leave <runner-id>",
35
+ " agentradar channel <runner-id>",
36
+ " agentradar selftest <runner-id> [--status|--restart]",
37
+ " agentradar runner start <runner-id>",
38
+ " agentradar call <runner-id> <tool | command> [--<flag> <value> ...] [--args '<json>']",
39
+ " agentradar vault import <runner-id> <dir> [--vault <vault-id>] [--source <name>] [--write]",
40
+ " agentradar vault clone <runner-id> <vault-id> [dir]",
41
+ " agentradar secrets pull|renew|env <runner-id> | agentradar secrets exec <runner-id> -- <command>",
42
+ " agentradar secrets upload|rotate <runner-id> --env-file <path> | --from-env <NAME,...> | --prompt <NAME> [--replace]",
43
+ " agentradar skill install | agentradar sync [dir] | agentradar sync install [dir] [--interval <s>]",
44
+ " agentradar admin broadcast --dry-run|--send --target all --text <text> --cta <next step>",
45
+ " agentradar upgrade | agentradar --version",
46
+ ].join("\n");
18
47
  async function main() {
19
48
  const args = runArgs(process.argv.slice(2));
20
49
  if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
21
- console.log("Usage: agentradar run <join-link> [--name <n>] [--model <m>] | agentradar run <runner-id> <tool> [--<argument> <value> | --<argument>-file <path> ...] | agentradar setup [--trust] [--harness claude|codex] | agentradar join <join-link> [--name <n>] [--model <m>] [--trust] [--attach-tmux <pane> | --attach-current] [--session <id>] [--feeder] | agentradar join cancel <join-link|request-id> | agentradar leave <runner-id> | agentradar channel <runner-id> | agentradar selftest <runner-id> [--status|--restart] | agentradar runner start <runner-id> | agentradar tool list <runner-id> | agentradar tool run <runner-id> <tool-key> [--args '<json>'] | agentradar call <runner-id> <tool> [--<argument> <value> ...] [--args '<json>'] | agentradar task create|repeat|make-recurring <runner-id> --args '<json>' | agentradar inbox search <runner-id> [--query <text>] [--<filter> <value> ...] [--json] | agentradar listing describe <runner-id> --capabilities <text> | agentradar permissions mine|roles|set|request|grant-tool <runner-id> [--<argument> <value> ...] | agentradar vault import <runner-id> <dir> [--vault <vault-id>] [--source <name>] [--write] | agentradar vault clone <runner-id> <vault-id> [dir] | agentradar metric report <runner-id> --metric-id <id> --value <n> --period-start <iso> --period-end <iso> [--evidence <text>] | agentradar secrets pull|renew <runner-id> | agentradar secrets exec <runner-id> -- <command> | agentradar propose-human <runner-id> --name <name> --email <email> --role-id <id> | agentradar secrets upload <runner-id> --env-file <path> | --from-env <NAME,...> | --prompt <NAME> [--replace] | agentradar skill install | agentradar sync [dir] | agentradar sync install [dir] [--interval <s>]");
22
- console.log([
23
- "agentradar run <runner-id> <noun> <verb> [--<argument> <value> ...] [--dry-run] [--json]",
24
- " Every agent tool, grouped in AgentRadar terms: inbox, thread, message, task, metric, vault, ...",
25
- " agentradar run <runner-id> --help the command groups",
26
- " agentradar run <runner-id> thread --help a group's verbs",
27
- " agentradar run <runner-id> thread reply --help one command's arguments",
28
- " Examples:",
29
- " agentradar run r1 inbox next",
30
- " agentradar run r1 thread reply --thread-id <id> --text \"On it\"",
31
- " agentradar run r1 inbox ack --message-ids <id> --message-ids <id>",
32
- " agentradar run r1 task create --title \"Draft the brief\" --dry-run",
33
- " --dry-run prints the exact call and sends nothing. Output is JSON. Exit codes: 0 done; 1 refused or",
34
- " unreachable (reason on stderr); 2 the command was wrong.",
35
- "agentradar upgrade install the latest agentradar-cli",
36
- ].join("\n"));
50
+ console.log(USAGE);
51
+ // The agent's own groups, from its server, when this shell is a runner's.
52
+ const runner = process.env.AR_CLI_RUNNER_ID;
53
+ if (runner)
54
+ process.exitCode = await callToolOverCli(runner, "", undefined, {}, [["help", ""]]);
37
55
  return;
38
56
  }
39
57
  if (args.length === 1 && args[0] === "upgrade") {
40
58
  const { selfUpdate, runningVersion } = await import("./update.js");
41
59
  const installed = await selfUpdate();
60
+ await dropStaleMcp(process.cwd(), homedir());
42
61
  console.log(installed ? `agentradar upgraded to ${installed}` : `agentradar ${await runningVersion()} is the latest`);
43
62
  return;
44
63
  }
@@ -67,7 +86,7 @@ async function main() {
67
86
  await startChannel(args[1]);
68
87
  return;
69
88
  }
70
- if (args[0] === "selftest" && args.length === 3 && args[2] === "--restart") {
89
+ if (args[0] === "selftest" && args[1] !== "run" && args.length === 3 && args[2] === "--restart") {
71
90
  // A native-channel session resumes in this terminal; print the result after it closes, not over its screen.
72
91
  const report = [];
73
92
  process.once("exit", () => { if (report.length)
@@ -75,7 +94,7 @@ async function main() {
75
94
  await runRestartSelftest(args[1], { log: (text) => report.push(text) });
76
95
  return;
77
96
  }
78
- if (args[0] === "selftest" && (args.length === 2 || (args.length === 3 && args[2] === "--status"))) {
97
+ if (args[0] === "selftest" && args[1] !== "run" && (args.length === 2 || (args.length === 3 && args[2] === "--status"))) {
79
98
  process.exitCode = await callAwpTool(args[1], "run_selftest", { statusOnly: args[2] === "--status" }, {});
80
99
  return;
81
100
  }
@@ -107,18 +126,18 @@ async function main() {
107
126
  for (let i = 1; i < args.length; i++) {
108
127
  if (["--name", "--model", "--attach-tmux", "--session"].includes(args[i]))
109
128
  named[args[i].slice(2)] = args[++i] ?? "-";
110
- else if (!["--trust", "--attach-current", "--allow-bypass", "--revoke-bypass", "--feeder"].includes(args[i]))
129
+ else if (!["--trust", "--attach-current", "--allow-bypass", "--revoke-bypass", "--feeder", "--service", "--no-service"].includes(args[i]))
111
130
  urls.push(args[i]);
112
131
  }
113
- if (urls.length !== 1 || [urls[0], ...Object.values(named)].some((value) => value.startsWith("-")) || (named["attach-tmux"] && args.includes("--attach-current"))) {
114
- console.error("Usage: agentradar join <join-link> [--name <your name>] [--model <your model id>] [--trust] [--attach-tmux <session[:window.pane]> | --attach-current] [--session <claude session id>] [--revoke-bypass] [--feeder]");
132
+ if (urls.length !== 1 || [urls[0], ...Object.values(named)].some((value) => value.startsWith("-")) || (named["attach-tmux"] && args.includes("--attach-current")) || (args.includes("--service") && args.includes("--no-service"))) {
133
+ console.error("Usage: agentradar join <join-link> [--name <your name>] [--model <your model id>] [--trust] [--attach-tmux <session[:window.pane]> | --attach-current (Codex and other CLIs)] [--session <claude session id>] [--revoke-bypass] [--no-service | --service]");
115
134
  process.exitCode = 1;
116
135
  return;
117
136
  }
118
137
  process.exitCode = await joinOrganisation(urls[0], {
119
138
  trust: args.includes("--trust"), agentName: named.name, model: named.model, session: named.session,
120
139
  attach: args.includes("--attach-current") ? "current" : named["attach-tmux"],
121
- revokeBypass: args.includes("--revoke-bypass"), feeder: args.includes("--feeder"),
140
+ revokeBypass: args.includes("--revoke-bypass"), feeder: args.includes("--feeder"), service: args.includes("--service"),
122
141
  });
123
142
  return;
124
143
  }
@@ -131,21 +150,16 @@ async function main() {
131
150
  process.exitCode = await startVendorRunner(args[2]);
132
151
  return;
133
152
  }
134
- if (args.length === 3 && args[0] === "tool" && args[1] === "list") {
135
- process.exitCode = await listServiceToolsOverCli(args[2]);
136
- return;
137
- }
138
- if ((args[0] === "tool" && args[1] === "run") || args[0] === "call") {
139
- const call = args[0] === "call";
140
- const parsed = parseCallArgs(args.slice(call ? 1 : 2));
141
- if (!parsed || (!call && parsed.flags.length)) {
142
- console.error(call ? "Usage: agentradar run <runner-id> <tool> [--<argument> <value> ...] [--args '<json>'] | agentradar run <runner-id> [<tool>] --help" : "Usage: agentradar tool run <runner-id> <tool-key> [--args '<json>']");
143
- process.exitCode = 1;
153
+ if (args[0] === "call") {
154
+ // `call <runner-id> <tool | command>`: the runner named in place, for a shell that has no AR_CLI_RUNNER_ID.
155
+ const parsed = parseCallArgs(args.slice(1));
156
+ const [runnerId, ...words] = parsed?.words ?? [];
157
+ if (!parsed || !runnerId || words.length > 3 || (!words.length && !parsed.flags.some(([flag]) => flag === "help"))) {
158
+ console.error("Usage: agentradar call <runner-id> <tool | command> [--<flag> <value> ...] [--args '<json>'] | agentradar call <runner-id> [<command>] --help");
159
+ process.exitCode = EXIT.usage;
144
160
  return;
145
161
  }
146
- process.exitCode = call
147
- ? await callToolOverCli(parsed.runnerId, parsed.tool, parsed.json, {}, parsed.flags)
148
- : await runServiceToolOverCli(parsed.runnerId, parsed.tool, parsed.json);
162
+ process.exitCode = await callToolOverCli(runnerId, words.join(" "), parsed.json, {}, parsed.flags);
149
163
  return;
150
164
  }
151
165
  if (args[0] === "vault" && args[1] === "import") {
@@ -168,71 +182,13 @@ async function main() {
168
182
  process.exitCode = await cloneVaultOverCli(args[2], args[3], args[4]);
169
183
  return;
170
184
  }
171
- if (args[0] === "inbox" && args[1] === "search") {
172
- const parsed = parseCallArgs([...args.slice(2).filter((arg) => arg !== "--json"), "inbox_search"]);
173
- if (!parsed || parsed.tool !== "inbox_search") {
174
- console.error(INBOX_SEARCH_USAGE);
175
- process.exitCode = 1;
176
- return;
177
- }
178
- process.exitCode = await inboxSearchOverCli(parsed.runnerId, parsed.flags, args.includes("--json"));
179
- return;
180
- }
181
- if (args[0] === "metric" && args[1] === "report") {
182
- // report_metric over the same endpoint and key: an agent's own metric, a subordinate's, or a person's below it.
183
- const parsed = parseCallArgs([...args.slice(2), "report_metric"]);
184
- if (!parsed || !parsed.flags.some(([flag]) => flag === "metric-id")) {
185
- console.error(METRIC_REPORT_USAGE);
186
- process.exitCode = 1;
187
- return;
188
- }
189
- process.exitCode = await callToolOverCli(parsed.runnerId, parsed.tool, undefined, {}, parsed.flags);
190
- return;
191
- }
192
- if (args[0] === "propose-human") {
193
- // R013: the Chief invites a human it found into a vacant role, the MCP `propose_human` over the same endpoint and key.
194
- const parsed = parseCallArgs([...args.slice(1), "propose_human"]);
195
- if (!parsed || !["name", "email", "role-id"].every((k) => parsed.flags.some(([flag]) => flag === k))) {
196
- console.error(PROPOSE_HUMAN_USAGE);
197
- process.exitCode = 1;
198
- return;
199
- }
200
- process.exitCode = await callToolOverCli(parsed.runnerId, parsed.tool, undefined, {}, parsed.flags);
201
- return;
202
- }
203
- if (args[0] === "listing" && args[1] === "describe") {
204
- // UAT U1: the agent's public catalog description, the MCP `set_listing_description` over the same endpoint and key.
205
- const parsed = parseCallArgs([...args.slice(2), "set_listing_description"]);
206
- if (!parsed || !parsed.flags.some(([flag]) => flag === "capabilities")) {
207
- console.error(LISTING_DESCRIBE_USAGE);
208
- process.exitCode = 1;
209
- return;
210
- }
211
- process.exitCode = await callToolOverCli(parsed.runnerId, parsed.tool, undefined, {}, parsed.flags);
212
- return;
213
- }
214
- if (args[0] === "permissions" && args[1] && Object.hasOwn(PERMISSION_VERB_TOOLS, args[1])) {
215
- const tool = PERMISSION_VERB_TOOLS[args[1]];
216
- const parsed = parseCallArgs([...args.slice(2), tool]);
217
- if (!parsed || parsed.tool !== tool) {
218
- console.error("Usage: agentradar permissions mine|roles|set|request|grant-tool <runner-id> [--<argument> <value> ...]");
219
- process.exitCode = 1;
220
- return;
221
- }
222
- process.exitCode = await callToolOverCli(parsed.runnerId, tool, parsed.json, {}, parsed.flags);
223
- return;
224
- }
225
- if (args[0] === "secrets") {
185
+ // The local secrets verbs take the runner in place; `secrets list|upload|grant|revoke|rotate --<flag>` are the server's.
186
+ if (args[0] === "secrets" && ["pull", "renew", "env", "exec", "upload", "rotate"].includes(args[1] ?? "") && args[2] && !args[2].startsWith("-")) {
226
187
  process.exitCode = await secretsOverCli(args.slice(1));
227
188
  return;
228
189
  }
229
- if (args[0] === "task" && args[1] && Object.hasOwn(TASK_VERB_TOOLS, args[1])) {
230
- if (args.length !== 5 || args[3] !== "--args") {
231
- console.error("Usage: agentradar task create|repeat|make-recurring <runner-id> --args '<json>'");
232
- process.exitCode = 1;
233
- return;
234
- }
235
- process.exitCode = await createTaskOverCli(args[2], args[1], args[4]);
190
+ if (args[0] === "admin") {
191
+ process.exitCode = await adminOverCli(args.slice(1));
236
192
  return;
237
193
  }
238
194
  if (args.length === 2 && args[0] === "skill" && args[1] === "install") {
@@ -253,13 +209,21 @@ async function main() {
253
209
  return;
254
210
  }
255
211
  if (args[0] === "sync" && args.length <= 2) {
212
+ await dropStaleMcp(args[1] ?? process.cwd(), homedir());
256
213
  process.exitCode = await syncAgentDir(args[1]);
257
214
  return;
258
215
  }
259
- const help = args.length === 1 && (args[0] === "--help" || args[0] === "-h");
260
- (help ? console.log : console.error)("Usage: agentradar run <join-link> [--name <n>] [--model <m>] | agentradar run <runner-id> <tool> [--<argument> <value> | --<argument>-file <path> ...] | agentradar setup [--trust] | agentradar join <join-link> [--name <n>] [--model <m>] [--trust] [--attach-tmux <pane> | --attach-current] [--session <id>] [--feeder] | agentradar join cancel <join-link|request-id> | agentradar leave <runner-id> | agentradar channel <runner-id> | agentradar selftest <runner-id> [--status|--restart] | agentradar runner start <runner-id> | agentradar tool list <runner-id> | agentradar tool run <runner-id> <tool-key> [--args '<json>'] | agentradar call <runner-id> <tool> [--<argument> <value> ...] [--args '<json>'] | agentradar task create|repeat|make-recurring <runner-id> --args '<json>' | agentradar inbox search <runner-id> [--query <text>] [--<filter> <value> ...] [--json] | agentradar listing describe <runner-id> --capabilities <text> | agentradar permissions mine|roles|set|request|grant-tool <runner-id> [--<argument> <value> ...] | agentradar metric report <runner-id> --metric-id <id> --value <n> --period-start <iso> --period-end <iso> [--evidence <text>] | agentradar secrets pull|renew <runner-id> | agentradar secrets exec <runner-id> -- <command> | agentradar propose-human <runner-id> --name <name> --email <email> --role-id <id> | agentradar secrets upload <runner-id> --env-file <path> | --from-env <NAME,...> | --prompt <NAME> [--replace] | agentradar skill install | agentradar sync [dir] | agentradar sync install [dir] [--interval <s>]");
261
- if (!help)
262
- process.exitCode = 1;
216
+ // #1871: every other command is the server's, `<noun> [<sub-noun>] <verb>`, run as this shell's runner.
217
+ const parsed = parseCallArgs(args);
218
+ const runner = parsed?.flags.find(([flag]) => flag === "runner")?.[1] ?? process.env.AR_CLI_RUNNER_ID;
219
+ if (!parsed || !runner || parsed.words.length > 3 || (!parsed.words.length && !parsed.flags.some(([flag]) => flag === "help"))) {
220
+ console.error(USAGE);
221
+ if (parsed && !runner)
222
+ console.error("Agent commands run as a runner: set AR_CLI_RUNNER_ID or pass --runner <id>.");
223
+ process.exitCode = EXIT.usage;
224
+ return;
225
+ }
226
+ process.exitCode = await callToolOverCli(runner, parsed.words.join(" "), parsed.json, {}, parsed.flags.filter(([flag]) => flag !== "runner"));
263
227
  }
264
228
  /**
265
229
  * 🔴 `error.message` ALONE IS TWO WORDS ON THE FAILURE A VENDOR IS MOST LIKELY TO HIT. Node's `fetch`
package/dist/runner.js CHANGED
@@ -6,14 +6,14 @@ import { fileURLToPath } from "node:url";
6
6
  import { promisify } from "node:util";
7
7
  import { shellValue, trustedMcpEndpoint, writePrivateFile } from "./setup.js";
8
8
  import { callAwpTool, envValue } from "./tool-run.js";
9
- import { installedVersion, revertCli, runningVersion, selfUpdate } from "./update.js";
9
+ import { cliHeader, installedVersion, noteCliUpdate, revertCli, runningVersion, selfUpdate } from "./update.js";
10
10
  const RUNNER_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
11
11
  const ENGAGEMENT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
12
12
  // Per-hire bearers are ordinary agent keys (`pcp_…`); the runner's own is an AWP key (`ar_…`).
13
13
  const HIRE_BEARER = /^(ar|pcp)_[A-Za-z0-9_-]+$/;
14
14
  // What a hire's env file sets for itself; the catalog agent's values for these never reach it.
15
15
  // A hire always runs Claude, whose MCP config it owns; a Codex home would be the catalog agent's.
16
- const HIRE_OWN_KEYS = /^(export\s+)?(AGENT_NAME|AR_BASE|AWP_MCP_URL|AWP_BEARER|AWP_BEARER_FILE|MCP_CONFIG|TMUX_SESSION|AGENT_PID_PATTERN|AR_AGENT_HARNESS|CODEX_HOME)=/;
16
+ const HIRE_OWN_KEYS = /^(export\s+)?(AGENT_NAME|AR_CLI_RUNNER_ID|CLAUDE_CONFIG_DIR|AR_BASE|AWP_MCP_URL|AWP_BEARER|AWP_BEARER_FILE|MCP_CONFIG|TMUX_SESSION|AGENT_PID_PATTERN|AR_AGENT_HARNESS|CODEX_HOME)=/;
17
17
  const CODEX_HARNESS = /^(export\s+)?AR_AGENT_HARNESS=\s*["']?codex\b/m;
18
18
  // While a pane shows this, its agent is mid-turn (the feeder's in-progress hint, same for Claude and Codex).
19
19
  const MID_TURN = /esc to interrupt|\besc to cancel\b/i;
@@ -285,12 +285,16 @@ export async function startVendorRunner(runnerId, home = homedir(), deps = {}) {
285
285
  const killSession = (name) => spawnChild("bash", [
286
286
  "-c", 'PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"; exec "${TMUX_BIN:-tmux}" kill-session -t "$1"', "tmux", `=_${name}`,
287
287
  ]).once("error", () => { });
288
- const post = (path, body, token = bearer) => (deps.fetch ?? fetch)(new URL(path, mcpUrl), {
289
- method: "POST",
290
- headers: { authorization: `Bearer ${token}`, accept: "application/json", "content-type": "application/json", "cache-control": "no-store" },
291
- body: JSON.stringify(body ?? {}),
292
- signal: AbortSignal.timeout(30_000),
293
- });
288
+ const post = async (path, body, token = bearer) => {
289
+ const response = await (deps.fetch ?? fetch)(new URL(path, mcpUrl), {
290
+ method: "POST",
291
+ headers: { ...(await cliHeader("feeder")), authorization: `Bearer ${token}`, accept: "application/json", "content-type": "application/json", "cache-control": "no-store" },
292
+ body: JSON.stringify(body ?? {}),
293
+ signal: AbortSignal.timeout(30_000),
294
+ });
295
+ await noteCliUpdate(response.headers);
296
+ return response;
297
+ };
294
298
  const launchAll = async () => {
295
299
  for (const file of await readdir(agentDir)) {
296
300
  const engagementId = file.startsWith(prefix) && file.endsWith(".env") ? file.slice(prefix.length, -".env".length) : "";
@@ -332,9 +336,9 @@ export async function startVendorRunner(runnerId, home = homedir(), deps = {}) {
332
336
  const configDir = join(home, ".agentradar", "config", name);
333
337
  await mkdir(configDir, { recursive: true, mode: 0o700 });
334
338
  const mcpConfig = join(configDir, "awp-only.json");
335
- await writePrivateFile(mcpConfig, `${JSON.stringify({
336
- mcpServers: { agentradar: { type: "http", url: mcpUrl.toString(), headers: { Authorization: `Bearer ${hire.bearer}` } } },
337
- }, null, 2)}\n`);
339
+ // Vadym 12915: no MCP entry for AgentRadar. The hire acts with `agentradar run $AR_CLI_RUNNER_ID <tool>`,
340
+ // which reads this hire's own env file; --strict-mcp-config still confines the pane to this empty list.
341
+ await writePrivateFile(mcpConfig, `${JSON.stringify({ mcpServers: {} }, null, 2)}\n`);
338
342
  // Every setting the vendor put in the catalog agent's env (tool env, harness flags) is copied, as
339
343
  // read when the runner started, except the ones this hire sets for itself — the shared bearer among
340
344
  // them. Copied rather than sourced: sourcing would put the shared bearer in the hire's supervisor.
@@ -343,6 +347,7 @@ export async function startVendorRunner(runnerId, home = homedir(), deps = {}) {
343
347
  // ponytail: line by line, so a multi-line quoted value is copied only in part; parse it when one appears.
344
348
  const own = [
345
349
  `AGENT_NAME=${shellValue(name)}`,
350
+ `AR_CLI_RUNNER_ID=${shellValue(name)}`,
346
351
  `AR_BASE=${shellValue(folderOf(hire.engagementId))}`,
347
352
  `AWP_MCP_URL=${shellValue(mcpUrl.toString())}`,
348
353
  `AWP_BEARER=${shellValue(hire.bearer)}`,
package/dist/secrets.js CHANGED
@@ -15,6 +15,7 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
15
15
  import { homedir } from "node:os";
16
16
  import { join } from "node:path";
17
17
  import { awpRequest, envValue } from "./tool-run.js";
18
+ import { cliHeader, noteCliUpdate } from "./update.js";
18
19
  export const SECRETS_FILE = join(".agentradar", "secrets.enc");
19
20
  export const SECRETS_USAGE = "Usage: agentradar secrets pull|renew|env <runner-id> | agentradar secrets exec <runner-id> -- <command> [args...]"
20
21
  + " | agentradar secrets upload <runner-id> --env-file <path> | --from-env <NAME,...> | --prompt <NAME> [--only <NAME,...>] [--replace --approval-details '<json>'] [--token <token>]"
@@ -34,8 +35,9 @@ async function credentials(runnerId, deps) {
34
35
  async function post(runnerId, path, body, deps) {
35
36
  const { origin, bearer } = await credentials(runnerId, deps);
36
37
  const res = await (deps.fetch ?? fetch)(`${origin}/api/agent/${path}`, {
37
- method: "POST", headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" }, body: JSON.stringify(body ?? {}),
38
+ method: "POST", headers: { ...(await cliHeader()), authorization: `Bearer ${bearer}`, "content-type": "application/json" }, body: JSON.stringify(body ?? {}),
38
39
  });
40
+ await noteCliUpdate(res.headers, deps.logError);
39
41
  const json = await res.json().catch(() => ({}));
40
42
  if (!res.ok)
41
43
  throw new Error(json.error ?? `AgentRadar answered ${res.status}`);