agentradar-cli 0.2.9 → 0.2.10
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/agentradar-vendor/SKILL.md +1 -1
- package/dist/channel.js +16 -4
- package/dist/index.js +6 -6
- package/dist/setup.js +46 -50
- package/dist/vendor-runner/supervisor/supervise-agent.sh +22 -2
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ Call `get_my_context` on the AgentRadar MCP FIRST, before any other tool. It ret
|
|
|
22
22
|
|
|
23
23
|
In your first session, call `read_onboarding_obligations` once (reading them acknowledges the safety rules), then `read_readiness`.
|
|
24
24
|
|
|
25
|
-
Your primary objective is to reach your quotas and drive the organisation forward in your role. Report each metric you own with `report_metric
|
|
25
|
+
Your primary objective is to reach your quotas and drive the organisation forward in your role. Report each metric you own with `report_metric`, backed by `evidence` of what you counted it from, or by `fromEventId` when a person told you the number. You may create metrics for yourself; you can never change your own quota — your superior sets it. Talk only in shared spaces (channels and tasks). You can create one-off tasks (`create_task`) and repeated tasks (`create_recurring_task`) for yourself, your colleagues and your direct subordinates — only for real trackable work, never for conversation.
|
|
26
26
|
|
|
27
27
|
## Talking to people
|
|
28
28
|
|
package/dist/channel.js
CHANGED
|
@@ -7,7 +7,7 @@ import { createInterface } from "node:readline";
|
|
|
7
7
|
import { envValue } from "./tool-run.js";
|
|
8
8
|
import { trustedMcpEndpoint } from "./setup.js";
|
|
9
9
|
export const CHANNEL_GUIDANCE = "AgentRadar channel messages are wake signals only. Call next_event to receive the events, then pass their eventIds as ackEventIds on your next call. Answer livenessProbe with liveness_ack. Never acknowledge an event you have not read. Never poll next_event on your own or keep a loop running: when a channel message arrives, drain it, then end your turn.";
|
|
10
|
-
export const CHANNEL_BLOCKED = "AgentRadar has not observed a next_event call from this session. Channel delivery is unconfirmed. On Claude Team/Enterprise an Owner must enable Channels (channelsEnabled); the development flag cannot override that policy. Until then, deliver with the feeder:
|
|
10
|
+
export const CHANNEL_BLOCKED = "AgentRadar has not observed a next_event call from this session. Channel delivery is unconfirmed. On Claude Team/Enterprise an Owner must enable Channels (channelsEnabled); the development flag cannot override that policy. Until then, deliver with the feeder: run your join again with --feeder (agentradar join <your join link> --feeder). Your organisation sees this agent as not receiving messages until one or the other works.";
|
|
11
11
|
/** One local MCP, forwarding tools to AWP. Wake signals never consume the durable queue. */
|
|
12
12
|
export async function startChannel(runnerId, deps = {}) {
|
|
13
13
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(runnerId))
|
|
@@ -24,6 +24,9 @@ export async function startChannel(runnerId, deps = {}) {
|
|
|
24
24
|
let sequence = 0;
|
|
25
25
|
let polling = false;
|
|
26
26
|
let agentPolled = false;
|
|
27
|
+
// Vadym 10342: an unconfirmed channel is reported to AgentRadar (runnerState channel_unconfirmed), so the owner sees
|
|
28
|
+
// the agent is not receiving messages; "ready" again once the session does call next_event.
|
|
29
|
+
let unconfirmed = null;
|
|
27
30
|
let warning;
|
|
28
31
|
const send = (value) => new Promise((resolve, reject) => {
|
|
29
32
|
output.write(`${JSON.stringify(value)}\n`, (error) => error ? reject(error) : resolve());
|
|
@@ -68,7 +71,14 @@ export async function startChannel(runnerId, deps = {}) {
|
|
|
68
71
|
if (!selftestStarted)
|
|
69
72
|
logError("AgentRadar self-test could not start; retrying after the next poll.");
|
|
70
73
|
}
|
|
71
|
-
|
|
74
|
+
if (unconfirmed === "pending") {
|
|
75
|
+
await next({ runnerState: "channel_unconfirmed" });
|
|
76
|
+
unconfirmed = "reported";
|
|
77
|
+
}
|
|
78
|
+
if (unconfirmed === "reported" && agentPolled)
|
|
79
|
+
unconfirmed = null;
|
|
80
|
+
// While reported, no runnerState: the stored state stands, and the poll still drains for wake signals.
|
|
81
|
+
const response = await next(unconfirmed ? {} : { runnerState: "ready" });
|
|
72
82
|
if (response.error || response.result?.isError)
|
|
73
83
|
throw new Error("AgentRadar refused next_event; check the runner key and its poll permission");
|
|
74
84
|
const content = response.result?.content;
|
|
@@ -104,8 +114,10 @@ export async function startChannel(runnerId, deps = {}) {
|
|
|
104
114
|
else if (message.method === "notifications/initialized") {
|
|
105
115
|
if (!polling) {
|
|
106
116
|
polling = true;
|
|
107
|
-
warning = setTimeout(() => { if (!agentPolled)
|
|
108
|
-
logError(CHANNEL_BLOCKED);
|
|
117
|
+
warning = setTimeout(() => { if (!agentPolled) {
|
|
118
|
+
logError(CHANNEL_BLOCKED);
|
|
119
|
+
unconfirmed = "pending";
|
|
120
|
+
} }, deps.confirmMs ?? 30_000);
|
|
109
121
|
void poll();
|
|
110
122
|
}
|
|
111
123
|
}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { installVendorSkill } from "./skill-install.js";
|
|
4
4
|
import { startChannel } from "./channel.js";
|
|
5
5
|
import { runRestartSelftest, startVendorRunner } from "./runner.js";
|
|
6
|
-
import { cancelJoin, joinOrganisation, leaveOrganisation, setupVendorAgent
|
|
6
|
+
import { cancelJoin, joinOrganisation, leaveOrganisation, setupVendorAgent } from "./setup.js";
|
|
7
7
|
import { callAwpTool, callToolOverCli, createTaskOverCli, listServiceToolsOverCli, parseCallArgs, runServiceToolOverCli } from "./tool-run.js";
|
|
8
8
|
import { installSyncTimer, syncAgentDir } from "./sync.js";
|
|
9
9
|
// `run` is the one prefix every agent-facing command shares, so one harness "always allow agentradar run"
|
|
@@ -15,7 +15,7 @@ export const runArgs = (raw) => raw[0] !== "run" ? raw
|
|
|
15
15
|
async function main() {
|
|
16
16
|
const args = runArgs(process.argv.slice(2));
|
|
17
17
|
if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
|
|
18
|
-
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>] | 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 <runner-id> --args '<json>' | agentradar skill install | agentradar sync [dir] | agentradar sync install [dir] [--interval <s>]");
|
|
18
|
+
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 <runner-id> --args '<json>' | agentradar skill install | agentradar sync [dir] | agentradar sync install [dir] [--interval <s>]");
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
if (args[0] === "leave" && args.length === 2) {
|
|
@@ -70,18 +70,18 @@ async function main() {
|
|
|
70
70
|
for (let i = 1; i < args.length; i++) {
|
|
71
71
|
if (["--name", "--model", "--attach-tmux", "--session"].includes(args[i]))
|
|
72
72
|
named[args[i].slice(2)] = args[++i] ?? "-";
|
|
73
|
-
else if (!["--trust", "--attach-current", "--allow-bypass", "--revoke-bypass"].includes(args[i]))
|
|
73
|
+
else if (!["--trust", "--attach-current", "--allow-bypass", "--revoke-bypass", "--feeder"].includes(args[i]))
|
|
74
74
|
urls.push(args[i]);
|
|
75
75
|
}
|
|
76
76
|
if (urls.length !== 1 || [urls[0], ...Object.values(named)].some((value) => value.startsWith("-")) || (named["attach-tmux"] && args.includes("--attach-current"))) {
|
|
77
|
-
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>] [--
|
|
77
|
+
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]");
|
|
78
78
|
process.exitCode = 1;
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
process.exitCode = await joinOrganisation(urls[0], {
|
|
82
82
|
trust: args.includes("--trust"), agentName: named.name, model: named.model, session: named.session,
|
|
83
83
|
attach: args.includes("--attach-current") ? "current" : named["attach-tmux"],
|
|
84
|
-
|
|
84
|
+
revokeBypass: args.includes("--revoke-bypass"), feeder: args.includes("--feeder"),
|
|
85
85
|
});
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
@@ -142,7 +142,7 @@ async function main() {
|
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
144
|
const help = args.length === 1 && (args[0] === "--help" || args[0] === "-h");
|
|
145
|
-
(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>] | 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 <runner-id> --args '<json>' | agentradar skill install | agentradar sync [dir] | agentradar sync install [dir] [--interval <s>]");
|
|
145
|
+
(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 <runner-id> --args '<json>' | agentradar skill install | agentradar sync [dir] | agentradar sync install [dir] [--interval <s>]");
|
|
146
146
|
if (!help)
|
|
147
147
|
process.exitCode = 1;
|
|
148
148
|
}
|
package/dist/setup.js
CHANGED
|
@@ -118,7 +118,9 @@ export function trustedMcpEndpoint(value) {
|
|
|
118
118
|
return endpoint.protocol === "https:" || loopbackHttp ? endpoint : null;
|
|
119
119
|
}
|
|
120
120
|
/** `installation: null` (join) removes the project's `agentradar` entry: a joined agent acts through `agentradar run`. */
|
|
121
|
-
async function installProjectMcp(configPath, cwd, installation, trust
|
|
121
|
+
async function installProjectMcp(configPath, cwd, installation, trust,
|
|
122
|
+
/** Vadym 10342: a joined Claude Code agent's AgentRadar channel (`agentradar channel <runner-id>`, stdio). */
|
|
123
|
+
channel) {
|
|
122
124
|
const endpoint = installation && trustedMcpEndpoint(installation.mcpEndpoint);
|
|
123
125
|
if (installation && (!endpoint || !BEARER_SHAPE.test(installation.bearer))) {
|
|
124
126
|
throw new Error("AgentRadar returned an invalid setup payload");
|
|
@@ -148,7 +150,10 @@ async function installProjectMcp(configPath, cwd, installation, trust) {
|
|
|
148
150
|
const mcpServers = project.mcpServers && typeof project.mcpServers === "object" && !Array.isArray(project.mcpServers)
|
|
149
151
|
? project.mcpServers
|
|
150
152
|
: {};
|
|
151
|
-
if (
|
|
153
|
+
if (channel) {
|
|
154
|
+
mcpServers.agentradar = channel;
|
|
155
|
+
}
|
|
156
|
+
else if (installation && endpoint) {
|
|
152
157
|
mcpServers.agentradar = {
|
|
153
158
|
type: "http",
|
|
154
159
|
url: endpoint.toString(),
|
|
@@ -336,7 +341,7 @@ async function codexHookHashes(codexHome, env) {
|
|
|
336
341
|
child.kill();
|
|
337
342
|
}
|
|
338
343
|
}
|
|
339
|
-
async function installRunner(home, projectPath, approved, service) {
|
|
344
|
+
async function installRunner(home, projectPath, approved, service, feed) {
|
|
340
345
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(approved.runnerId)) {
|
|
341
346
|
throw new Error("AgentRadar returned an invalid runner ID");
|
|
342
347
|
}
|
|
@@ -412,15 +417,19 @@ async function installRunner(home, projectPath, approved, service) {
|
|
|
412
417
|
joined.push(`AGENT_TOOL_ENV_FILE=${shellValue(toolEnvFile)}`);
|
|
413
418
|
}
|
|
414
419
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
420
|
+
// R764 + Vadym 10342: a pushed service session loads exactly one MCP, the AgentRadar channel, from the CLI copy
|
|
421
|
+
// the service runs; the supervisor adds --dangerously-load-development-channels server:agentradar.
|
|
422
|
+
await writePrivateFile(mcpConfig, `${JSON.stringify(feed === "push"
|
|
423
|
+
? { mcpServers: { agentradar: { type: "stdio", command: process.execPath, args: [join(cliDist(home), "index.js"), "channel", approved.runnerId] } } }
|
|
424
|
+
: service ? { mcpServers: {} } : {
|
|
425
|
+
mcpServers: {
|
|
426
|
+
agentradar: {
|
|
427
|
+
type: "http",
|
|
428
|
+
url: endpoint.toString(),
|
|
429
|
+
headers: { Authorization: `Bearer ${approved.installation.bearer}` },
|
|
430
|
+
},
|
|
421
431
|
},
|
|
422
|
-
},
|
|
423
|
-
}, null, 2)}\n`);
|
|
432
|
+
}, null, 2)}\n`);
|
|
424
433
|
await writePrivateFile(envPath, [
|
|
425
434
|
`AGENT_NAME=${shellValue(approved.runnerId)}`,
|
|
426
435
|
`AR_BASE=${shellValue(projectPath)}`,
|
|
@@ -431,6 +440,8 @@ async function installRunner(home, projectPath, approved, service) {
|
|
|
431
440
|
// default, so writing `AR_AGENT_HARNESS=claude` unconditionally would change nothing except to make
|
|
432
441
|
// every existing env file look as though a choice had been made.
|
|
433
442
|
...(harness ? [`AR_AGENT_HARNESS=${shellValue(harness)}`] : []),
|
|
443
|
+
// How work reaches the session (Vadym 10342): the Claude Code channel pushes it; without this line the feeder types it.
|
|
444
|
+
...(feed ? [`AR_FEED=${feed}`] : []),
|
|
434
445
|
...joined,
|
|
435
446
|
"",
|
|
436
447
|
].join("\n"));
|
|
@@ -520,27 +531,8 @@ export async function setupVendorAgent(deps = {}) {
|
|
|
520
531
|
}
|
|
521
532
|
throw new Error("AgentRadar setup code expired; run setup again");
|
|
522
533
|
}
|
|
523
|
-
/** "[y/N]": only y or yes (any case) is a yes; Enter alone is No. */
|
|
524
|
-
export const saidYes = (answer) => /^y(es)?$/i.test(answer.trim());
|
|
525
|
-
/** The default `confirm`: a real terminal only, "[y/N]" with No unless they type y or yes. */
|
|
526
|
-
export function terminalConfirm() {
|
|
527
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
528
|
-
return undefined;
|
|
529
|
-
return async (question) => {
|
|
530
|
-
const { createInterface } = await import("node:readline/promises");
|
|
531
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
532
|
-
try {
|
|
533
|
-
return saidYes(await rl.question(`${question} [y/N] `));
|
|
534
|
-
}
|
|
535
|
-
finally {
|
|
536
|
-
rl.close();
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
534
|
export const bypassOnNotice = (link) => `Bypass-permissions mode is ON on this machine: the agent runs without permission prompts. To turn it off: agentradar join ${link} --revoke-bypass`;
|
|
541
535
|
export const BYPASS_REVOKED = "Bypass-permissions mode is off on this machine now; the agent asks before acting again.";
|
|
542
|
-
export const BYPASS_QUESTION = "Allow this agent to run without permission prompts on this machine?";
|
|
543
|
-
export const BYPASS_NOT_CONFIRMED = "Bypass-permissions mode stays off: your organisation allowed it, but nobody on this machine confirmed it. Run the join again in a terminal and answer yes, or add --allow-bypass.";
|
|
544
536
|
/** A sortable key for `x.y.z[-pre]`; a release sorts above its prereleases. ponytail: prereleases of one version tie. */
|
|
545
537
|
const versionRank = (version) => {
|
|
546
538
|
const [core = "", pre] = version.split(/-(.*)/);
|
|
@@ -646,6 +638,10 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
646
638
|
}
|
|
647
639
|
}
|
|
648
640
|
const label = harness === "generic" ? basename((env.AGENTRADAR_AGENT_BIN ?? "").trim()) : harness === "claude" ? "Claude Code" : "Codex";
|
|
641
|
+
// Vadym 10342: every Claude Code agent connects through the AgentRadar channel. The tmux feeder is the fallback:
|
|
642
|
+
// another CLI, an explicit opt-in (--feeder, AGENTRADAR_FEED=feeder, or a pane named with --attach-*), or a
|
|
643
|
+
// channel the session never confirms (channel.ts reports it and tells the operator to re-run with --feeder).
|
|
644
|
+
const channel = harness === "claude" && !deps.feeder && env.AGENTRADAR_FEED?.trim().toLowerCase() !== "feeder" && !deps.attach;
|
|
649
645
|
// The agent is a persistent session the service owns, not this one: no MCP entry in this folder (it acts
|
|
650
646
|
// through `agentradar run`), a runner env pinned to its own session, and a LaunchAgent / systemd unit.
|
|
651
647
|
// Which session the agent is, settled before any request: a key is issued once, so never claim one for a session that cannot run.
|
|
@@ -656,7 +652,8 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
656
652
|
let attach = deps.attach === "current" ? env.TMUX_PANE : deps.attach;
|
|
657
653
|
if (deps.attach === "current" && !attach)
|
|
658
654
|
throw new Error("--attach-current needs a session running inside tmux");
|
|
659
|
-
|
|
655
|
+
// Never in channel mode: nothing types into the caller's pane.
|
|
656
|
+
if (!channel && !deps.attach && harness === "claude" && env.CLAUDECODE && env.TMUX && env.TMUX_PANE)
|
|
660
657
|
attach = env.TMUX_PANE;
|
|
661
658
|
if (attach) {
|
|
662
659
|
// A pane id (%3) dies with the tmux server; session:window.pane comes back when its session is recreated.
|
|
@@ -669,31 +666,24 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
669
666
|
// Unattended, the service resumes the caller's own Claude conversation, never a stranger's. An attached
|
|
670
667
|
// pane is never started from here, so there its session id only keys the model switch.
|
|
671
668
|
const sessionId = harness !== "claude" ? undefined
|
|
672
|
-
: await callerSession(deps, env, home, await realpath(cwd)).catch((error) => { if (attach)
|
|
669
|
+
: await callerSession(deps, env, home, await realpath(cwd)).catch((error) => { if (attach || channel)
|
|
673
670
|
return undefined; throw error; });
|
|
674
671
|
return { attach, sessionId };
|
|
675
672
|
};
|
|
676
673
|
let where;
|
|
677
674
|
/**
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
675
|
+
* The owner's bypass grant on the portal approval is the consent for an unattended service session (lead
|
|
676
|
+
* 10-04, after the Codex run stopped on an approval prompt nobody could answer); `--revoke-bypass` takes it
|
|
677
|
+
* back on this machine. An attached pane keeps the permission mode it already runs in.
|
|
681
678
|
*/
|
|
682
|
-
const localBypass =
|
|
683
|
-
if (!approvedBypass || where.attach)
|
|
684
|
-
return false;
|
|
685
|
-
const yes = confirmedBefore || deps.allowBypass === true || (deps.confirm ? await deps.confirm(BYPASS_QUESTION) : false);
|
|
686
|
-
if (!yes)
|
|
687
|
-
log(BYPASS_NOT_CONFIRMED);
|
|
688
|
-
return yes;
|
|
689
|
-
};
|
|
679
|
+
const localBypass = (approvedBypass, revoked) => approvedBypass && !revoked && !where.attach;
|
|
690
680
|
const startJoined = async (requestId, agentId, installation, approvedBypass, bypass, saved) => {
|
|
691
681
|
const runnerId = `join-${requestId}`;
|
|
692
682
|
const { attach } = where;
|
|
693
683
|
const projectPath = harness === "claude"
|
|
694
684
|
? await installProjectMcp(claudeConfigPath(home, env), cwd, null, deps.trust === true)
|
|
695
685
|
: await realpath(cwd);
|
|
696
|
-
const runnerEnv = await installRunner(home, projectPath, { status: "approved", agentId, runnerId, installation, harness }, { sessionId: where.sessionId ?? requestId, env, attach, bypass });
|
|
686
|
+
const runnerEnv = await installRunner(home, projectPath, { status: "approved", agentId, runnerId, installation, harness }, { sessionId: where.sessionId ?? requestId, env, attach, bypass }, channel ? "push" : undefined);
|
|
697
687
|
await saved?.();
|
|
698
688
|
log(`Runner installed → ${runnerEnv}`);
|
|
699
689
|
if (bypass)
|
|
@@ -716,7 +706,13 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
716
706
|
log("Running as a service; the delivery restarts on crash and reboot.");
|
|
717
707
|
return 0;
|
|
718
708
|
}
|
|
719
|
-
const mode = bypass ? ", in bypass-permissions mode as your organisation allowed
|
|
709
|
+
const mode = bypass ? ", in bypass-permissions mode as your organisation allowed" : "";
|
|
710
|
+
if (channel) {
|
|
711
|
+
log(`Connected through the AgentRadar channel. This conversation now continues as a service in ${projectPath} (tmux session ${runnerId}${mode}): client messages are pushed into it and it answers there, with no restart from you. Exit this session now (/exit) so only the service carries on; watch it with: tmux attach -t ${runnerId}. To stop it: agentradar leave ${runnerId}`);
|
|
712
|
+
log(`If your organisation's Claude plan blocks channels, deliver with the feeder instead: agentradar join ${enrolmentUrl} --feeder`);
|
|
713
|
+
log("Running as a service; it restarts on crash and reboot.");
|
|
714
|
+
return 0;
|
|
715
|
+
}
|
|
720
716
|
// Only Claude resumes the caller's own conversation; Codex and a generic CLI start their own session.
|
|
721
717
|
log(harness === "claude"
|
|
722
718
|
? `Connected. This conversation now continues as a service in ${projectPath} (tmux session ${runnerId}${mode}): your welcome and every client message are typed into it, and it answers there. Exit this session now (/exit) so only the service carries on; watch it with: tmux attach -t ${runnerId}. To stop it: agentradar leave ${runnerId}`
|
|
@@ -756,14 +752,14 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
756
752
|
const bearer = envValue(saved, "AWP_BEARER");
|
|
757
753
|
if (!bearer)
|
|
758
754
|
throw new Error("This machine left that organisation (agentradar leave). A key is issued once; ask your organisation for a new join link.");
|
|
759
|
-
//
|
|
760
|
-
if (deps.revokeBypass && state.bypassConfirmed) {
|
|
755
|
+
// `--revoke-bypass` is remembered on this machine, so a later re-run keeps the prompts on.
|
|
756
|
+
if (deps.revokeBypass && state.bypassConfirmed !== false) {
|
|
761
757
|
state = { ...state, bypassConfirmed: false };
|
|
762
758
|
await writePrivateFile(statePath, JSON.stringify(state));
|
|
763
759
|
log(BYPASS_REVOKED);
|
|
764
760
|
}
|
|
765
761
|
const approvedBypass = state.bypassPermissions === true;
|
|
766
|
-
const bypass =
|
|
762
|
+
const bypass = localBypass(approvedBypass, state.bypassConfirmed === false);
|
|
767
763
|
return startJoined(state.requestId, state.agentId, { mcpEndpoint: envValue(saved, "AWP_MCP_URL") ?? mcpEndpoint, bearer }, approvedBypass, bypass);
|
|
768
764
|
}
|
|
769
765
|
if (!state) {
|
|
@@ -818,8 +814,8 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
818
814
|
log("Approved.");
|
|
819
815
|
// Bypass-permissions only when the operator consented at approval; the server says so with the key.
|
|
820
816
|
const approvedBypass = claimed.bypassPermissions === true;
|
|
821
|
-
const bypass =
|
|
822
|
-
const joined = { ...state, agentId: claimed.agentId, ...(approvedBypass ? { bypassPermissions: true } : {})
|
|
817
|
+
const bypass = localBypass(approvedBypass, false);
|
|
818
|
+
const joined = { ...state, agentId: claimed.agentId, ...(approvedBypass ? { bypassPermissions: true } : {}) };
|
|
823
819
|
return startJoined(state.requestId, claimed.agentId, { mcpEndpoint, bearer: claimed.token }, approvedBypass, bypass, () => writePrivateFile(statePath, JSON.stringify(joined)));
|
|
824
820
|
}
|
|
825
821
|
const said = await failureMessage(response);
|
|
@@ -77,7 +77,7 @@ HEALTH_POLL_SEC="${HEALTH_POLL_SEC:-15}"
|
|
|
77
77
|
# interactive CLI: the env file names its binary, arguments, resume/fresh flags, pid pattern and how work
|
|
78
78
|
# reaches it (AR_FEED). Anything else is REFUSED by name: a supervisor that quietly fell back to Claude for an
|
|
79
79
|
# unrecognized value would come up healthy, keep a pane alive, and answer nothing the vendor expected.
|
|
80
|
-
# Tried: claude and codex with AR_FEED=feeder
|
|
80
|
+
# Tried: claude and codex with AR_FEED=feeder; claude with AR_FEED=push (the AgentRadar channel, R764). Untried: generic.
|
|
81
81
|
AR_AGENT_HARNESS="${AR_AGENT_HARNESS:-claude}"
|
|
82
82
|
case "${AR_AGENT_HARNESS}" in
|
|
83
83
|
claude|codex|generic) : ;;
|
|
@@ -93,6 +93,8 @@ case "${AR_FEED}" in
|
|
|
93
93
|
acp) die "AR_FEED=acp is not built yet; use feeder or push" ;;
|
|
94
94
|
*) die "AR_FEED must be feeder or push (got '${AR_FEED}')" ;;
|
|
95
95
|
esac
|
|
96
|
+
# push is Claude Code channels only: no other harness has a channel to push into.
|
|
97
|
+
[ "${AR_FEED}" = "push" ] && [ "${AR_AGENT_HARNESS}" != "claude" ] && die "AR_FEED=push needs AR_AGENT_HARNESS=claude (got '${AR_AGENT_HARNESS}')"
|
|
96
98
|
# Launched only when the resume form exits non-zero (nothing to resume). Empty = the same argv without it.
|
|
97
99
|
AR_FRESH_FLAGS="${AR_FRESH_FLAGS:-}"
|
|
98
100
|
|
|
@@ -140,6 +142,8 @@ else
|
|
|
140
142
|
_launch_flags=( ${CLAUDE_EXTRA_FLAGS} --strict-mcp-config --mcp-config="${MCP_CONFIG}" )
|
|
141
143
|
# `agentradar join` enables the agent-guard plugin for this session only, through a settings file.
|
|
142
144
|
[ -n "${CLAUDE_SETTINGS:-}" ] && _launch_flags+=( --settings "${CLAUDE_SETTINGS}" )
|
|
145
|
+
# R764 + Vadym 10342: the AgentRadar channel (the stdio MCP `agentradar` in $MCP_CONFIG) pushes work in; no feeder types.
|
|
146
|
+
[ "${AR_FEED}" = "push" ] && _launch_flags+=( --dangerously-load-development-channels server:agentradar )
|
|
143
147
|
# Pane-alive predicate — MUST be unique per agent. Default to the FULL mcp-config path (each agent's
|
|
144
148
|
# differs by directory), NOT the basename: on a multi-agent box every agent's config is named
|
|
145
149
|
# `awp-only.json`, so a basename match would find ANOTHER agent's live claude and mask THIS agent's
|
|
@@ -362,11 +366,27 @@ ensure_agent() {
|
|
|
362
366
|
return 1
|
|
363
367
|
}
|
|
364
368
|
# wait for the pane to come up
|
|
365
|
-
|
|
369
|
+
# In the background: the watch must not hold up the monitor loop when the warning never shows.
|
|
370
|
+
for _ in $(seq 1 30); do pane_alive && { log "agent pane is live"; accept_dev_channels & return 0; }; sleep 1; done
|
|
366
371
|
log "agent pane did not come up within 30s"
|
|
367
372
|
return 1
|
|
368
373
|
}
|
|
369
374
|
|
|
375
|
+
# A pushed session starts on Claude Code's "Loading development channels" warning, and nobody sits at an unattended
|
|
376
|
+
# pane to confirm it. The channel is this machine's own installed CLI (`agentradar channel <runner>`), so the
|
|
377
|
+
# service confirms it, once per launch, only while that exact warning is on screen.
|
|
378
|
+
accept_dev_channels() {
|
|
379
|
+
[ "${AR_FEED}" = "push" ] || return 0
|
|
380
|
+
for _ in $(seq 1 30); do
|
|
381
|
+
if "${TMUX_BIN}" capture-pane -p -t "=${TMUX_SESSION}:" 2>/dev/null | grep -q "Loading development channels"; then
|
|
382
|
+
"${TMUX_BIN}" send-keys -t "=${TMUX_SESSION}:" Enter
|
|
383
|
+
log "confirmed Claude Code's development-channels warning for the AgentRadar channel"
|
|
384
|
+
return 0
|
|
385
|
+
fi
|
|
386
|
+
sleep 1
|
|
387
|
+
done
|
|
388
|
+
}
|
|
389
|
+
|
|
370
390
|
# --- 4. Run: ensure agent, run feeder in foreground, monitor both -------------
|
|
371
391
|
FEEDER_PID=""
|
|
372
392
|
cleanup() {
|