agentradar-cli 0.3.11 → 0.3.13
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/agent-guard/scripts/guard.mjs +2 -2
- package/dist/agent-guard/skills/agentradar-discipline/SKILL.md +2 -1
- package/dist/channel.js +3 -3
- package/dist/runner.js +2 -2
- package/dist/secrets.js +2 -2
- package/dist/setup.js +4 -3
- package/dist/tool-run.js +6 -2
- package/dist/update.js +15 -0
- package/dist/vendor-runner/session-frame.mjs +33 -4
- package/package.json +1 -1
|
@@ -38,7 +38,7 @@ const call = (tool) => `agentradar call ${runner} ${tool}`;
|
|
|
38
38
|
|
|
39
39
|
const RULES = [
|
|
40
40
|
"Answer every message in the channel it came from, and a human first: someone is waiting.",
|
|
41
|
-
`Acknowledge what you handled: ${call("
|
|
41
|
+
`Acknowledge what you handled: ${call("inbox_ack")} --input '{"messageIds":[...]}' for messages, ${call("acknowledge_task")} --issueId <id> --nextUp '<your next action>' --nextUpAt <ISO time> for a task you take on.`,
|
|
42
42
|
"Keep your main session free: any long job (a script, a report, a rebuild, many API calls) goes to a teammate or sub-agent, or a background job, even when it looks quick. A human's \"how to work\" order holds for every later job.",
|
|
43
43
|
"Talk is not a task: reply conversationally, and create a task only for trackable, billable work.",
|
|
44
44
|
"Write so a 13-year-old would understand: short, plain words, one next step, no ids or tool names unless asked. Say it once.",
|
|
@@ -138,7 +138,7 @@ function stopReason() {
|
|
|
138
138
|
return [
|
|
139
139
|
`AgentRadar: you still owe ${messages.length + tasks.length + asks.length + unread.length + late.length} item(s). Do not go idle yet.`,
|
|
140
140
|
...(messages.length ? [
|
|
141
|
-
`Messages received but not acknowledged: ${messages.join(", ")}. Each reached you earlier in this session as an event carrying that eventId (${call("read_history")} --conversationId <its conversationId> shows the thread). Reply to each in the channel it came from (a human first), then ${call("
|
|
141
|
+
`Messages received but not acknowledged: ${messages.join(", ")}. Each reached you earlier in this session as an event carrying that eventId (${call("read_history")} --conversationId <its conversationId> shows the thread). Reply to each in the channel it came from (a human first), then ${call("inbox_ack")} --input '${JSON.stringify({ messageIds: messages })}'.`,
|
|
142
142
|
] : []),
|
|
143
143
|
...tasks.map((t) => `Task assigned to you, not acknowledged: ${name(t)}. Read it first with ${call("get_task")} --issueId ${t.issueId} and do what its description asks (its title alone is not the work). Take it on or say why not, then ${call("acknowledge_task")} --issueId ${t.issueId} --nextUp '<your next action>' --nextUpAt <ISO time>.`),
|
|
144
144
|
...unread.map((t) => `Comments on your task ${name(t)} you have not read: ${(t.commentIds ?? []).join(", ")}. Read them with ${call("get_task")} --issueId ${t.issueId} (its unreadComments), then answer each one that needs it with ${call("comment_on_task")} --issueId ${t.issueId} --replyToCommentId <its id> --body '<your answer>'.`),
|
|
@@ -13,7 +13,7 @@ Every tool named in this skill runs as `agentradar run $AR_CLI_RUNNER_ID <tool>`
|
|
|
13
13
|
|
|
14
14
|
1. Run `agentradar call $AR_CLI_RUNNER_ID agent_obligations`.
|
|
15
15
|
2. `owed` must be 0:
|
|
16
|
-
- each id in `unacknowledgedMessages` is a message you received and have not handled. Reply in the channel it came from, then `agentradar call $AR_CLI_RUNNER_ID
|
|
16
|
+
- each id in `unacknowledgedMessages` is a message you received and have not handled. Reply in the channel it came from, then `agentradar call $AR_CLI_RUNNER_ID inbox_ack --input '{"messageIds":["<id>"]}'`;
|
|
17
17
|
- each entry in `unacknowledgedTasks` is a task assigned to you. Read it, take it on or say why not, then `agentradar call $AR_CLI_RUNNER_ID acknowledge_task --issueId <issueId> --nextUp '<your next action>' --nextUpAt <ISO time>` (your next action on it, and when);
|
|
18
18
|
- each entry in `lateNextUps` is a task whose next up is past its time. Do it, then `agentradar call $AR_CLI_RUNNER_ID comment_on_task --issueId <issueId> --body '<what you did>' --nextUp '<your next action>' --nextUpAt <ISO time>`.
|
|
19
19
|
3. Each entry in `inProgressTasks` is either still moving, or its thread says who it is waiting on. `overdueTasks` (past the due date), `blockedTooLong` (blocked over 24 h) and `notStarted` (To do 24 h after you acknowledged it) are not owed, but say in each task's comments what happens next.
|
|
@@ -32,6 +32,7 @@ In a hosted workspace, every teammate you start keeps its own process in the wor
|
|
|
32
32
|
|
|
33
33
|
- Answer every message in the channel it came from. A human who wrote is waiting for you; answer them first.
|
|
34
34
|
- Keep your main session free for messages. Any long job (a script, a report, a rebuild, many API calls) goes to a teammate or sub-agent (in Claude Code: the Agent tool, with a `name`) or to a background job, even when it looks quick; first reply in the thread with one short line saying what you are doing, so the sender is not left waiting. When a human tells you how to work ("delegate that"), it holds for every later job, not just this one. Never say a rule is saved unless you wrote it with `remember`.
|
|
35
|
+
- When your answer needs more than one tool call (or one that runs long), acknowledge first: on Telegram or Slack react to the message (preferred, no extra text); elsewhere reply in the thread with one short line saying what you are doing. Then do the work and answer in the thread.
|
|
35
36
|
- Talk is not a task. Reply conversationally by default; create a task only for trackable, billable work.
|
|
36
37
|
- Write to humans so a 13-year-old would understand: a few short lines in plain words, ending with one next step (not a list of ideas), no ids, paths, tool names or internal terms unless asked. Say money in dollars, never cents; give every link with what it opens. Say a thing once. Follow what they told you for the rest of the chat, and never offer to contact anyone else unless they ask.
|
|
37
38
|
- Never ask for, print or pass on a credential. AgentRadar holds the keys.
|
package/dist/channel.js
CHANGED
|
@@ -7,7 +7,7 @@ import { homedir } from "node:os";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
9
9
|
import { awpEndpoint, envValue } from "./tool-run.js";
|
|
10
|
-
import { noteCliUpdate } from "./update.js";
|
|
10
|
+
import { noteCliUpdate, refusedLine } from "./update.js";
|
|
11
11
|
import { trustedMcpEndpoint } from "./setup.js";
|
|
12
12
|
import { syncJoinedSkills } from "./skill-install.js";
|
|
13
13
|
export const CHANNEL_GUIDANCE = "AgentRadar channel messages carry your new events in full, in order, one per line, each with its own next step: act on each line as it says. Run `agentradar run $AR_CLI_RUNNER_ID inbox next` only when a channel message tells you to. Answer a liveness check with `agentradar run $AR_CLI_RUNNER_ID liveness_ack --nonce <nonce>`. Never acknowledge an event you have not read. Never poll on your own or keep a loop running: when a channel message arrives, drain it, then end your turn. Only you reply or act as this agent: a sub-agent or teammate returns its result to you, and you send it.";
|
|
@@ -143,12 +143,12 @@ export async function startChannel(runnerId, deps = {}) {
|
|
|
143
143
|
await noteCliUpdate(response.headers, logError);
|
|
144
144
|
if (!response.ok) {
|
|
145
145
|
const said = await response.json().then((body) => body.error?.message).catch(() => null);
|
|
146
|
-
throw new Error(said ??
|
|
146
|
+
throw new Error(said ?? refusedLine("the channel call", response.status));
|
|
147
147
|
}
|
|
148
148
|
const text = await response.text();
|
|
149
149
|
const json = text.trim().startsWith("{") ? text : text.split("\n").find((line) => line.startsWith("data:"))?.slice(5);
|
|
150
150
|
if (!json)
|
|
151
|
-
throw new Error("
|
|
151
|
+
throw new Error("AgentRadar answered the channel call with no JSON-RPC body. Nothing was done. Now: run `agentradar upgrade`, then restart the channel.");
|
|
152
152
|
return JSON.parse(json);
|
|
153
153
|
};
|
|
154
154
|
const next = (args = {}) => remote("tools/call", { name: "next_event", arguments: { ...args, sessionId } });
|
package/dist/runner.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { claimPendingJoin, removeAgentService, shellValue, trustedMcpEndpoint, writePrivateFile } from "./setup.js";
|
|
8
8
|
import { callAwpTool, envValue } from "./tool-run.js";
|
|
9
|
-
import { cliHeader, installedVersion, noteCliUpdate, revertCli, runningVersion, selfUpdate } from "./update.js";
|
|
9
|
+
import { cliHeader, installedVersion, noteCliUpdate, refusedLine, 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_…`).
|
|
@@ -332,7 +332,7 @@ export async function startVendorRunner(runnerId, home = homedir(), deps = {}) {
|
|
|
332
332
|
const claim = async () => {
|
|
333
333
|
const response = await post("agent-hires/bearers/claim");
|
|
334
334
|
if (!response.ok)
|
|
335
|
-
throw new Error(
|
|
335
|
+
throw new Error(await response.json().then((b) => typeof b.error === "string" ? b.error : null).catch(() => null) ?? refusedLine("the hire claim", response.status));
|
|
336
336
|
const { hires: claimed } = await response.json();
|
|
337
337
|
const written = [];
|
|
338
338
|
for (const hire of claimed) {
|
package/dist/secrets.js
CHANGED
|
@@ -15,7 +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
|
+
import { cliHeader, noteCliUpdate, refusedLine } from "./update.js";
|
|
19
19
|
export const SECRETS_FILE = join(".agentradar", "secrets.enc");
|
|
20
20
|
export const SECRETS_USAGE = "Usage: agentradar secrets pull|renew|env <runner-id> | agentradar secrets exec <runner-id> -- <command> [args...]"
|
|
21
21
|
+ " | agentradar secrets upload <runner-id> --env-file <path> | --from-env <NAME,...> | --prompt <NAME> [--only <NAME,...>] [--replace --approval-details '<json>'] [--token <token>]"
|
|
@@ -40,7 +40,7 @@ async function post(runnerId, path, body, deps) {
|
|
|
40
40
|
await noteCliUpdate(res.headers, deps.logError);
|
|
41
41
|
const json = await res.json().catch(() => ({}));
|
|
42
42
|
if (!res.ok)
|
|
43
|
-
throw new Error(json.error ??
|
|
43
|
+
throw new Error(json.error ?? refusedLine("the secrets request", res.status));
|
|
44
44
|
return json;
|
|
45
45
|
}
|
|
46
46
|
/**
|
package/dist/setup.js
CHANGED
|
@@ -924,9 +924,10 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
924
924
|
};
|
|
925
925
|
const restartInPlace = (runnerId) => {
|
|
926
926
|
const config = join(home, ".agentradar", "config", runnerId);
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
927
|
+
const sh = (v) => /^[\w@%+=:,./-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`;
|
|
928
|
+
return `restart your agent ONCE with this one command, which already keeps your conversation (--continue); if you normally launch claude with other flags, add them:
|
|
929
|
+
env AR_CLI_RUNNER_ID=${sh(runnerId)} claude --continue --mcp-config ${sh(join(config, "awp-only.json"))} --dangerously-load-development-channels server:agentradar --settings ${sh(join(config, "agentradar-settings.json"))}
|
|
930
|
+
Accept the one-time development-channel prompt when it appears. To stop: agentradar leave ${sh(runnerId)}, then launch without those flags.`;
|
|
930
931
|
};
|
|
931
932
|
const installService = deps.installService ?? ((id) => installAgentService(id, home, env));
|
|
932
933
|
const connected = (runnerId, projectPath, approvedBypass, bypass) => {
|
package/dist/tool-run.js
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdir, readFile, realpath, stat, utimes, writeFile } from "node:fs/prom
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { hostIsTeammate } from "./channel.js";
|
|
5
|
-
import { cliHeader, noteCliUpdate, withCliUpdate } from "./update.js";
|
|
5
|
+
import { cliHeader, noteCliUpdate, refusedLine, withCliUpdate } from "./update.js";
|
|
6
6
|
const UNAUTHORIZED = "AgentRadar refused the tool call (401): this agent's key is no longer valid on this server (it was revoked, or the hire it was issued for has ended). Nothing was done. Stop calling tools with this key and tell the human who runs you.";
|
|
7
7
|
/**
|
|
8
8
|
* One `KEY='value'` line as `setup.ts` wrote it, single-quoted with `'` escaped as `'\''`.
|
|
@@ -77,6 +77,10 @@ export async function callToolOverCli(runnerId, command, argumentsJson, deps = {
|
|
|
77
77
|
if (tool !== "next_event") {
|
|
78
78
|
// Any call after a channel wake proves the agent took it in: touch the file the channel watches before acking the batch.
|
|
79
79
|
const code = await callAwpTool(runnerId, tool, parameters, deps, shown);
|
|
80
|
+
// HOSTED-COMPACTION-LOOP: a platform hook (AR_HOOK) is not the agent. A refused auto-compaction's PreCompact report
|
|
81
|
+
// counted as taking the wake in, so the channel acked it unread and re-pushed it every 30 s with no backoff, forever.
|
|
82
|
+
if ((deps.env ?? process.env).AR_HOOK === "1")
|
|
83
|
+
return code;
|
|
80
84
|
const now = new Date();
|
|
81
85
|
await utimes(delivered, now, now).catch(() => mkdir(dirname(delivered), { recursive: true })
|
|
82
86
|
.then(() => writeFile(delivered, "[]", { mode: 0o600, flag: "wx" }))).catch(() => undefined);
|
|
@@ -653,7 +657,7 @@ async function postAwp(url, bearer, method, params, deps) {
|
|
|
653
657
|
if (!response.ok) {
|
|
654
658
|
// 426 says how to upgrade; 401 says why the key was refused and how to re-join.
|
|
655
659
|
const said = await response.json().then((body) => body.error?.message).catch(() => null);
|
|
656
|
-
logError(said ?? (response.status === 401 ? UNAUTHORIZED :
|
|
660
|
+
logError(said ?? (response.status === 401 ? UNAUTHORIZED : refusedLine("the tool call", response.status)));
|
|
657
661
|
return null;
|
|
658
662
|
}
|
|
659
663
|
const body = await response.json();
|
package/dist/update.js
CHANGED
|
@@ -21,6 +21,21 @@ export const runningVersion = async () => JSON.parse(await readFile(join(PACKAGE
|
|
|
21
21
|
* and as `x-agentradar-surface` so it can count which client a call came from (MCPR-1).
|
|
22
22
|
*/
|
|
23
23
|
export const cliHeader = async (surface = "cli") => ({ "x-agentradar-cli": await runningVersion().catch(() => ""), "x-agentradar-surface": surface });
|
|
24
|
+
/**
|
|
25
|
+
* CLI-VERSION-GATE (Vadym 12835): the server names its newest CLI on every reply (`x-agentradar-cli-latest`). When this one
|
|
26
|
+
* is behind, the command prints ONE warning line on stderr and its JSON output carries `cliUpdate` (`withCliUpdate`).
|
|
27
|
+
* A CLI below the server's minimum is refused with 426 and the same command, so an agent can always fix itself.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Agent's-eye (Vadym 13105): a bare status says nothing. A refusal the server gave no sentence for names what was refused,
|
|
31
|
+
* says nothing was done, and the one step to take. A 401 means the key is no good here; a 429 or 5xx means the server is busy or
|
|
32
|
+
* down (an upgrade would not help: wait and retry); any other status may be an old CLI.
|
|
33
|
+
*/
|
|
34
|
+
export const refusedLine = (what, status) => status === 401
|
|
35
|
+
? `AgentRadar refused ${what} (401): this agent's key is not valid on this server. Nothing was done. Now: tell the human who runs you, then run \`npx agentradar run <join-link>\` with the new join link they give you.`
|
|
36
|
+
: status === 429 || status >= 500
|
|
37
|
+
? `AgentRadar is unavailable for ${what} (${status}). Nothing was done. Now: wait a minute and run the same command again; if it still fails, tell the human who runs you.`
|
|
38
|
+
: `AgentRadar refused ${what} (${status}) and gave no reason. Nothing was done. Now: run \`agentradar upgrade\`, then run the same command again; if it is refused again, tell the human who runs you.`;
|
|
24
39
|
let behind = null;
|
|
25
40
|
export const forgetCliUpdate = () => { behind = null; };
|
|
26
41
|
export async function noteCliUpdate(headers, logError = console.error) {
|
|
@@ -57,12 +57,15 @@ const NO_TEXT = (run, source, turn, evId) => {
|
|
|
57
57
|
/**
|
|
58
58
|
* FRAME-CATCHUP-STRUCTURED: the catch-up step, built only from next_event's structured `catchUp` (catch-up.ts CatchUp) and never from
|
|
59
59
|
* guidance text, where a name or title could forge a "Now: catch up" line (qa-sec MED #2679). Only counts, a normalized time, this
|
|
60
|
-
* wake's own conversation id
|
|
60
|
+
* wake's own conversation id, a base64url cursor and, per file, its plain-character name and type and a plain id reach the text,
|
|
61
|
+
* so nothing in it can carry a command. Mirrors catchUpLine.
|
|
61
62
|
*/
|
|
62
63
|
function catchUpStep(c, conversationId, run) {
|
|
63
64
|
const count = (v) => Number.isSafeInteger(v) && v >= 0 ? v : null;
|
|
64
65
|
const total = count(c?.newMessages), nonMembers = count(c?.nonMembers) ?? 0, edited = count(c?.edited) ?? 0;
|
|
65
|
-
|
|
66
|
+
const slack = slackThreadStep(c?.slackThread, total ?? 0, c?.more === true, run);
|
|
67
|
+
if ((!total && !slack) || /["`$\\]/.test(conversationId)) return null;
|
|
68
|
+
if (!total) return `Now: read this thread first, then act on it.${slack}`;
|
|
66
69
|
const seen = c.lastAckAt && !Number.isNaN(Date.parse(c.lastAckAt)) ? new Date(c.lastAckAt).toISOString().slice(0, 16).replace("T", " ") : null;
|
|
67
70
|
const after = typeof c.readHistoryAfter === "string" && /^[A-Za-z0-9_-]+$/.test(c.readHistoryAfter) ? c.readHistoryAfter : null;
|
|
68
71
|
const n = `${total}${c.more === true ? "+" : ""}`, one = total === 1 && c.more !== true;
|
|
@@ -73,7 +76,33 @@ function catchUpStep(c, conversationId, run) {
|
|
|
73
76
|
? ` ${edited === 1 ? "One of them is a message" : `${edited} of them are messages`} you had already seen that the sender EDITED or deleted since: `
|
|
74
77
|
+ `an edited line is marked "(EDITED <time> ...)" and a deleted one is no longer listed. Answer from the new text, never from the version you read before.`
|
|
75
78
|
: "";
|
|
76
|
-
return `Now: catch up with the thread first (${what}${strangers}) with ${how}, then act on it.${edits}`;
|
|
79
|
+
return `Now: catch up with the thread first (${what}${strangers}) with ${how}, then act on it.${edits}${catchUpFiles(c.attachments, run)}${slack}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** SLACK-THREAD-BACKFILL: catch-up.ts slackThreadText, byte for byte; the ids are re-checked here so only Slack-shaped ids reach the text. */
|
|
83
|
+
function slackThreadStep(t, held, more, run) {
|
|
84
|
+
if (!t || !/^[CG][A-Z0-9]{2,20}$/.test(String(t.channel)) || !/^\d{10}\.\d{6}$/.test(String(t.threadTs))) return "";
|
|
85
|
+
const has = held ? `${held}${more ? "+" : ""} earlier message${held === 1 && !more ? "" : "s"} of it so far` : "none of its earlier messages yet";
|
|
86
|
+
return ` This Slack thread began before you were tagged in it, and your Inbox holds ${has}.`
|
|
87
|
+
+ ` Read the whole thread from Slack now: \`${run} slack_history --args '{"channel":"${t.channel}","threadTs":"${t.threadTs}"}'\`;`
|
|
88
|
+
+ ` for more context, read the channel around it with the same command without "threadTs".`
|
|
89
|
+
+ ` Slack gives 15 messages and about one read a minute: pass the "nextCursor" it returns as "cursor" for the next 15, and if it answers "ratelimited", wait a minute and run it again.`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** ATTACHMENT-MARKER: the files in the catch-up, as catch-up.ts catchUpFilesText writes them (kept byte for byte by catch-up-cta-realpg). */
|
|
93
|
+
function catchUpFiles(files, run) {
|
|
94
|
+
if (!Array.isArray(files) || !files.length) return "";
|
|
95
|
+
const plainId = (v) => typeof v === "string" && /^[\w-]{1,256}$/.test(v);
|
|
96
|
+
const sizeText = (n) => typeof n !== "number" || !Number.isFinite(n) || n <= 0 ? "" : n < 1024 ? `${n} B` : n < 1048576 ? `${(n / 1024).toFixed(1)} KB` : `${(n / 1048576).toFixed(1)} MB`;
|
|
97
|
+
const list = files.slice(0, 5).map((f, i) => {
|
|
98
|
+
const name = String(f?.name ?? "").replace(/[^\p{L}\p{N} ._'-]/gu, "").slice(0, 60).trim();
|
|
99
|
+
const kind = String(f?.kind ?? "").split(";")[0].replace(/[^\w/+.-]/g, "").slice(0, 60) || "file";
|
|
100
|
+
const cmd = plainId(f?.eventId) && plainId(f?.id) ? ` - download: \`${run} download_attachment --args '{"eventId":"${f.eventId}","attachmentId":"${f.id}"}'\`` : "";
|
|
101
|
+
return `[Attachment ${i + 1}: ${kind}${name ? ` "${name}"` : ""}${sizeText(f?.size) ? ` ${sizeText(f.size)}` : ""}${plainId(f?.id) ? `, id ${f.id}` : ""}]${cmd}`;
|
|
102
|
+
}).join(" ");
|
|
103
|
+
const more = files.length > 5 ? ` and ${files.length - 5} more (\`${run} read_history\` lists each with its command)` : "";
|
|
104
|
+
return ` ${files.length === 1 ? "One of them carries a file" : `They carry ${files.length} files`}: ${list}${more}. `
|
|
105
|
+
+ `MUST download a file before you answer anything about it (a photo or document someone "posted above" is one of these); a message with no caption is not an empty message.`;
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
const NO_REPLY_SOURCES = new Set(["moderator", "approval", "agentradar", undefined, null, ""]);
|
|
@@ -235,7 +264,7 @@ export function restartLine(notice, cliRunnerId, now = Date.now()) {
|
|
|
235
264
|
return `Your workspace ${when}. ${RESTART_REASONS[notice.reason]} Anything you had running was stopped and is not running now: background jobs, loops and open shells. Files in your home folder are still there; /tmp and the rest of the system disk are gone, so keep scripts, notes and results in your home folder. Before you do anything else: 1. Check what finished: look at the files and logs your job wrote, and the results of the calls it made. 2. Restart only the items that did not finish, with \`agentradar run ${cliRunnerId || "$AR_CLI_RUNNER_ID"}\`, passing each one the same --idempotency-key it had before. An item whose key already ran returns its earlier result and is not sent twice. 3. When you tell anyone the job stopped, give them the reason above, not only "a restart". Then handle the message below.`;
|
|
236
265
|
}
|
|
237
266
|
|
|
238
|
-
const ACK_FIRST_CTA = "
|
|
267
|
+
const ACK_FIRST_CTA = "More than one tool call (or a long one)? Acknowledge first: on Telegram or Slack react to this message (preferred); elsewhere reply with one short line saying what you are doing. Then answer here; either replaces the platform's \"Still working on it…\".";
|
|
239
268
|
|
|
240
269
|
const RESTART_REASONS = {
|
|
241
270
|
idle: "It stopped after the workspace was idle. Running turns, tools and background jobs keep it awake, even when they are silent; there is no runtime cap.",
|