agentradar-cli 0.3.17 → 0.3.20
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/LICENSE +21 -0
- package/dist/agent-guard/hooks/hooks.json +1 -1
- package/dist/agent-guard/scripts/compaction-report.mjs +24 -6
- package/dist/agent-guard/scripts/guard.mjs +54 -2
- package/dist/agent-guard/skills/agentradar-discipline/SKILL.md +1 -0
- package/dist/asset-get.js +1 -1
- package/dist/channel.js +4 -4
- package/dist/github.js +8 -5
- package/dist/index.js +10 -14
- package/dist/runner.js +4 -3
- package/dist/secrets.js +29 -8
- package/dist/setup.js +1 -1
- package/dist/tool-run.js +42 -13
- package/dist/vendor-runner/feeder.mjs +58 -12
- package/dist/vendor-runner/pane-live.sh +0 -0
- package/dist/vendor-runner/session-frame.mjs +58 -29
- package/dist/vendor-runner/supervisor/install-agent.sh +0 -0
- package/dist/vendor-runner/supervisor/supervise-agent.sh +84 -26
- package/package.json +8 -8
|
@@ -77,7 +77,7 @@ BROWSER_READY_MAX_SEC="${BROWSER_READY_MAX_SEC:-20}" # #150: bounded wait for th
|
|
|
77
77
|
BROWSER_READY_SETTLE_SEC="${BROWSER_READY_SETTLE_SEC:-3}" # #150: settle for extension/native-host after cold launch
|
|
78
78
|
HEALTH_POLL_SEC="${HEALTH_POLL_SEC:-15}"
|
|
79
79
|
AR_API400_LOOP_MAX="${AR_API400_LOOP_MAX:-3}" # same `API Error: 400`/413 line this many times in the pane = poisoned session
|
|
80
|
-
AR_API400_COOLDOWN_SEC="${AR_API400_COOLDOWN_SEC:-300}" # at most one
|
|
80
|
+
AR_API400_COOLDOWN_SEC="${AR_API400_COOLDOWN_SEC:-300}" # at most one refused-turn heal (/compact) per this window
|
|
81
81
|
|
|
82
82
|
# --- 2a. R004 HARNESS SELECTION — one documented variable picks the CLI ---------
|
|
83
83
|
# The Bible names two agent harnesses: Claude Code and Codex. `claude` is the default, so every existing
|
|
@@ -144,7 +144,7 @@ else
|
|
|
144
144
|
# `agentradar join` pins `--resume <id>` / `--session-id <id>` so the service never resumes the vendor's
|
|
145
145
|
# own conversation in the same folder; an env file that sets nothing keeps `--continue`.
|
|
146
146
|
AR_RESUME_FLAGS="${AR_RESUME_FLAGS:---continue}"
|
|
147
|
-
# A heal
|
|
147
|
+
# A fresh-session heal of an older build wrote its new session id next to the env file; a restart honors it, so it never resumes the poisoned id.
|
|
148
148
|
# Only while newer than the env file: a re-join rewrites the env file and its pin wins again.
|
|
149
149
|
_SID_FILE="${ENV_FILE}.session-id"
|
|
150
150
|
if [ -f "${_SID_FILE}" ] && [ "${_SID_FILE}" -nt "${ENV_FILE}" ] && IFS= read -r _saved_sid < "${_SID_FILE}" \
|
|
@@ -211,7 +211,7 @@ if [ -n "${AGENT_TOOL_ENV_FILE}" ]; then
|
|
|
211
211
|
# identity). Reject at parse time (visible warning) AND — belt-and-suspenders — the launch lists
|
|
212
212
|
# the reserved allowlist AFTER TOOL_ENV_ARGS so `env` last-wins keeps reserved vars authoritative.
|
|
213
213
|
case "${_tline%%=*}" in
|
|
214
|
-
HOME|PATH|USER|LOGNAME|SHELL|TERM|COLORTERM|LANG|LC_ALL|LC_CTYPE|TMPDIR|NODE_OPTIONS|CLAUDE_CONFIG_DIR|CLAUDE_CODE_OAUTH_TOKEN|CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC|CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF|CODEX_HOME|AR_CLI_RUNNER_ID|AR_AGENTRADAR_BIN|AR_DISCIPLINE_EVERY|CLAUDE_CODE_PLUGIN_CACHE_DIR|AR_GUARD_MARKER|AR_RESUME_FLAGS|AR_FRESH_FLAGS|AR_OAUTH_TOKEN_FILE|AR_TOOL_ENV_FILE|ANTHROPIC_API_KEY|AWP_BEARER|AWP_BEARER_FILE|AWP_MCP_URL|TMUX_SESSION|AGENT_PID_PATTERN)
|
|
214
|
+
HOME|PATH|USER|LOGNAME|SHELL|TERM|COLORTERM|LANG|LC_ALL|LC_CTYPE|TMPDIR|NODE_OPTIONS|CLAUDE_CONFIG_DIR|CLAUDE_CODE_OAUTH_TOKEN|CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC|CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF|CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION|CODEX_HOME|AR_CLI_RUNNER_ID|AR_AGENTRADAR_BIN|AR_DISCIPLINE_EVERY|CLAUDE_CODE_PLUGIN_CACHE_DIR|AR_GUARD_MARKER|AR_RESUME_FLAGS|AR_FRESH_FLAGS|AR_OAUTH_TOKEN_FILE|AR_TOOL_ENV_FILE|ANTHROPIC_API_KEY|AWP_BEARER|AWP_BEARER_FILE|AWP_MCP_URL|TMUX_SESSION|AGENT_PID_PATTERN)
|
|
215
215
|
log "WARN ignoring reserved key '${_tline%%=*}' in ${AGENT_TOOL_ENV_FILE} (cannot override supervisor/agent env)"; continue ;;
|
|
216
216
|
esac
|
|
217
217
|
TOOL_ENV_ARGS+=("${_tline}")
|
|
@@ -347,12 +347,73 @@ ensure_browser() {
|
|
|
347
347
|
# A session grown past the gateway's request cap gets 413 on every turn and on every auto compaction, so it can
|
|
348
348
|
# never shrink (COMPACTION-LOOP-GUARD, Levels 11-03: 6 MB hosted OCX cap); Claude Code 2.1.295 prints that turn as
|
|
349
349
|
# `Request too large (max 32MB). ...` (no `API Error: 413`, and a failed compaction prints nothing).
|
|
350
|
+
# A context filled with images refuses every turn with `⎿ Context limit reached · /compact or /clear to continue`, and
|
|
351
|
+
# /compact with `⎿ Compaction failed · attached media exceeds size limits` (HOSTED-MEDIA-COMPACT-LOOP, Cupcake 10-31..11-02:
|
|
352
|
+
# 126 refused turns; hosted keeps auto compaction on, so the first line means it already failed). Both are matched as whole
|
|
353
|
+
# CLI lines ([^ ]+ for the glyphs, which a non-UTF-8 tmux may print as _), never inside tool output; one media line is enough.
|
|
350
354
|
# True when one identical such line (digits normalised: the message index moves; trailing padding cut) fills the
|
|
351
355
|
# pane scrollback AR_API400_LOOP_MAX times. ponytail: only the last 200 lines are read, so a very slow loop could slip by.
|
|
352
356
|
api_error_loop() {
|
|
357
|
+
# Only lines after the last `⎿ Compacted` count: a loop that /compact already healed stays on screen (compact_in_place).
|
|
353
358
|
"${TMUX_BIN}" capture-pane -p -J -t "=${TMUX_SESSION}:" -S -200 2>/dev/null \
|
|
354
|
-
|
|
|
355
|
-
|
|
|
359
|
+
| awk '/^ *[^ ]+ +Compacted( |$)/{b=""; next} {b=b $0 "\n"} END{printf "%s", b}' \
|
|
360
|
+
| grep -oE 'API Error: 4(00|13) .*|Request too large .*|^ *[^ ]+ +(Context limit reached [^ ]+ /compact or /clear to continue|Compaction failed [^ ]+ attached media exceeds size limits) *$' \
|
|
361
|
+
| sed -E 's/[0-9]+/N/g; s/[[:space:]]+$//' | sort | uniq -c | sort -rn \
|
|
362
|
+
| awk -v m="${AR_API400_LOOP_MAX}" '$1>=m || /attached media/{ok=1} END{exit !ok}'
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
# CONTEXT-RESET-IN-PLACE (Vadym 19475 + the lead 19:5x: "NEVER start a fresh session"): a refused-turn loop is healed by
|
|
366
|
+
# typing /compact into the same session with the hosted pre-stop sender, retried once. When that cannot heal it, it sets
|
|
367
|
+
# _park_reason and returns 1 (park_agent): harness (codex or generic: the sender reads only Claude's transcript), media (the
|
|
368
|
+
# media line: Claude's own /compact already failed on the images), reloop (the loop is back within 30 min of a compaction),
|
|
369
|
+
# compact_failed (no compact_boundary twice: busy or stuck pane, compaction error or timeout, at most ~5 min each).
|
|
370
|
+
compact_in_place() {
|
|
371
|
+
local run="${HOME}/.agentradar/run" try
|
|
372
|
+
_park_reason=""
|
|
373
|
+
[ "${AR_AGENT_HARNESS}" = "claude" ] || { _park_reason=harness; return 1; }
|
|
374
|
+
[ $(( $(date +%s) - ${_LAST_COMPACT_HEAL:-0} )) -ge 1800 ] || { _park_reason=reloop; return 1; }
|
|
375
|
+
"${TMUX_BIN}" capture-pane -p -J -t "=${TMUX_SESSION}:" -S -200 2>/dev/null \
|
|
376
|
+
| grep -qE '^ *[^ ]+ +Compaction failed [^ ]+ attached media exceeds size limits *$' && { _park_reason=media; return 1; }
|
|
377
|
+
for try in 1 2; do
|
|
378
|
+
# uid -1: talk to the supervisor's own tmux server (the sender's uid 1000 switch is for a root bridge). minTokens 0: a
|
|
379
|
+
# small poisoned history is compacted too, it is the only way to keep the session.
|
|
380
|
+
AR_COMPACT_JS="$(cd "$(dirname "$0")/.." && pwd)/pre-stop-compact.mjs" TMUX_BIN="${TMUX_BIN}" TMUX_SESSION="${TMUX_SESSION}" \
|
|
381
|
+
node --input-type=module -e '
|
|
382
|
+
import { spawn, spawnSync } from "node:child_process"; import { join } from "node:path";
|
|
383
|
+
const { compactBeforeStop, paneTail, tmux, sessionProjectsDir } = await import(process.env.AR_COMPACT_JS);
|
|
384
|
+
const bin = process.env.TMUX_BIN, session = `=${process.env.TMUX_SESSION}:`;
|
|
385
|
+
const dir = process.env.CLAUDE_CONFIG_DIR ? join(process.env.CLAUDE_CONFIG_DIR, "projects") : sessionProjectsDir();
|
|
386
|
+
const r = await compactBeforeStop({ dir, session, workloadBusy: () => false, report: () => {}, minTokens: 0,
|
|
387
|
+
run: (argv) => tmux(argv, { uid: -1, spawnFn: (_c, a, o) => spawn(bin, a, o) }),
|
|
388
|
+
capture: () => paneTail(session, { uid: -1, run: (_c, a, o) => spawnSync(bin, a, o) }) });
|
|
389
|
+
process.exit(r.compacted ? 0 : 1);' && break
|
|
390
|
+
[ "${try}" = 2 ] && { _park_reason=compact_failed; return 1; }
|
|
391
|
+
log "/compact did not heal it — typing it once more"
|
|
392
|
+
done
|
|
393
|
+
_LAST_COMPACT_HEAL=$(date +%s)
|
|
394
|
+
mkdir -p "${run}" && printf '{"reason":"context_compacted","at":"%s"}' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "${run}/restart-notice.json" \
|
|
395
|
+
|| log "WARN could not leave the compaction handoff in ${run}"
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
# A loop compact_in_place could not heal parks the agent; it is never replaced by a fresh session. Its session id and
|
|
399
|
+
# transcript stay; only its process stops (kill-session), so the refused turn is not sent again and nothing is typed into
|
|
400
|
+
# it: the feeder stops polling a pane that is not live and new events wait on the server. No heal runs while _PARKED is set,
|
|
401
|
+
# so the next start (an operator restart of the unit) resumes the same conversation. A hosted runner reports the park
|
|
402
|
+
# (report_compaction context_parked): the instance admins are emailed, never the owner (19089). A vendor box has no
|
|
403
|
+
# hosted session env, so there only this log says it.
|
|
404
|
+
park_agent() {
|
|
405
|
+
log "PARKED (${_park_reason}): the refused-turn loop did not heal in place. No fresh session: stopping the agent's process; its conversation is kept. Screen:"
|
|
406
|
+
"${TMUX_BIN}" capture-pane -p -t "=${TMUX_SESSION}:" 2>/dev/null | tail -20
|
|
407
|
+
_PARKED=1
|
|
408
|
+
"${TMUX_BIN}" kill-session -t "=${TMUX_SESSION}" 2>/dev/null || true
|
|
409
|
+
if AR_COMPACT_JS="$(cd "$(dirname "$0")/.." && pwd)/pre-stop-compact.mjs" REASON="${_park_reason}" node --input-type=module -e '
|
|
410
|
+
const { reportToServer } = await import(process.env.AR_COMPACT_JS);
|
|
411
|
+
const dir = process.env.AR_SESSION_DIR;
|
|
412
|
+
process.exit(reportToServer({ phase: "context_parked", reason: process.env.REASON }, dir ? { dir } : {}) ? 0 : 1);'; then
|
|
413
|
+
log "parked: the operators were alerted"
|
|
414
|
+
else
|
|
415
|
+
log "WARN parked, but the operators could not be alerted (no hosted session env here, or the report failed)"
|
|
416
|
+
fi
|
|
356
417
|
}
|
|
357
418
|
|
|
358
419
|
# --- 3. Ensure the agent's tmux+claude session (idempotent) -------------------
|
|
@@ -380,18 +441,6 @@ ensure_agent() {
|
|
|
380
441
|
die "not starting Codex: ${_guard_out:-the AgentRadar guard check gave no answer}"
|
|
381
442
|
fi
|
|
382
443
|
_resume_now="${AR_RESUME_FLAGS}"
|
|
383
|
-
if [ "${_FORCE_FRESH:-0}" = 1 ]; then # poisoned history: do not resume it
|
|
384
|
-
_resume_now="${AR_FRESH_FLAGS}"
|
|
385
|
-
# `agentradar join` writes AR_FRESH_FLAGS as the same `--resume <id>` as AR_RESUME_FLAGS, so a pinned session
|
|
386
|
-
# (either flag names an id) is healed onto a NEW id, generated now; later restarts resume that one, never the poisoned id.
|
|
387
|
-
case "${AR_AGENT_HARNESS}:${AR_RESUME_FLAGS} ${AR_FRESH_FLAGS}" in
|
|
388
|
-
claude:*--resume\ *|claude:*--session-id\ *)
|
|
389
|
-
_new_sid="$(node -e 'process.stdout.write(require("crypto").randomUUID())')" || die "could not generate a fresh session id"
|
|
390
|
-
_resume_now="--session-id ${_new_sid}"; AR_RESUME_FLAGS="--resume ${_new_sid}"; AR_FRESH_FLAGS="--session-id ${_new_sid}"
|
|
391
|
-
printf '%s\n' "${_new_sid}" > "${_SID_FILE}.tmp" && mv "${_SID_FILE}.tmp" "${_SID_FILE}" || log "WARN could not save the new session id to ${_SID_FILE}" ;;
|
|
392
|
-
esac
|
|
393
|
-
_FORCE_FRESH=0
|
|
394
|
-
fi
|
|
395
444
|
ensure_browser # #150: bring up the browser first for --chrome agents (idempotent, no-op otherwise)
|
|
396
445
|
if [ -n "${PRE_LAUNCH_HOOK}" ] && [ -x "${PRE_LAUNCH_HOOK}" ]; then
|
|
397
446
|
log "running pre-launch hook: ${PRE_LAUNCH_HOOK}"
|
|
@@ -407,7 +456,8 @@ ensure_agent() {
|
|
|
407
456
|
# The agent's own tool creds (from AGENT_TOOL_ENV_FILE) are NOT inlined here anymore (#166): they were staged to a 0600 file in 2b and are passed only as the PATH
|
|
408
457
|
# AR_TOOL_ENV_FILE, which the wrapper reads+exports before exec — so no tool VALUE appears on the env -i
|
|
409
458
|
# argv / tmux pane_start_command. Reserved keys were already rejected at tool-file parse time (2b).
|
|
410
|
-
|
|
459
|
+
# TEAMMATE-HANG-CULPRIT-1105A: tmux comes from apt unpinned in the hosted image, so its version goes in the log.
|
|
460
|
+
log "starting agent (${AR_AGENT_HARNESS}) in tmux '${TMUX_SESSION}' ($("${TMUX_BIN}" -V 2>/dev/null || echo "tmux version unknown")) (tool-env file: ${_TOOL_ENV_FILE:-none})"
|
|
411
461
|
# _tool_env uses the ${arr[@]+"${arr[@]}"} form so an EMPTY array is safe under `set -u` (bash 3.2).
|
|
412
462
|
# First-launch handling (#153/H4 durable-feeder): `claude --continue` EXITS 1 when the cwd has NO prior
|
|
413
463
|
# conversation (for example, a fresh engagement folder) → the pane dies → the supervisor
|
|
@@ -446,6 +496,8 @@ ensure_agent() {
|
|
|
446
496
|
# visible once there is colour to look at. LC_ALL stays empty on purpose: an empty LC_ALL does not
|
|
447
497
|
# override, so LC_CTYPE governs and a vendor can still set LANG per agent.
|
|
448
498
|
#
|
|
499
|
+
# CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false (LEAK-1-CACHE-REBUILD-19165): Claude Code's next-prompt suggestion is a
|
|
500
|
+
# background model call after a turn (~30 output tokens, seconds after it); session-host/loop.sh already turns it off; reserved so a tool env file cannot switch it back on (SUGGEST-ENV-RESERVED-4908).
|
|
449
501
|
# Channels: CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF=1 keeps the tengu_harbor flag readable from the
|
|
450
502
|
# disk cache even if telemetry is off, and it and CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC are reserved so a
|
|
451
503
|
# tool file cannot turn channels off. See docs/claude-code-channels-gates.md.
|
|
@@ -471,9 +523,9 @@ ensure_agent() {
|
|
|
471
523
|
LANG="${LANG:-en_US.UTF-8}" LC_ALL="${LC_ALL:-}" LC_CTYPE="${AR_AGENT_LC_CTYPE:-en_US.UTF-8}" \
|
|
472
524
|
TMPDIR="${TMPDIR:-/tmp}" NODE_OPTIONS="${NODE_OPTIONS:-}" \
|
|
473
525
|
CODEX_HOME="${CODEX_HOME:-}" AR_RESUME_FLAGS="${_resume_now}" AR_FRESH_FLAGS="${AR_FRESH_FLAGS}" \
|
|
474
|
-
DISABLE_AUTOUPDATER=1 CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF=1 \
|
|
526
|
+
DISABLE_AUTOUPDATER=1 CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF=1 CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false \
|
|
475
527
|
${_claude_cfg[@]+"${_claude_cfg[@]}"} ${_tool_env[@]+"${_tool_env[@]}"} \
|
|
476
|
-
/bin/sh -c 'if [ -n "${AR_TOOL_ENV_FILE:-}" ]; then while IFS= read -r _l || [ -n "$_l" ]; do case "$_l" in ""|\#*) continue ;; esac; _k=${_l%%=*}; _v=${_l#*=}; case "$_k" in HOME|PATH|USER|LOGNAME|SHELL|TERM|COLORTERM|LANG|LC_ALL|LC_CTYPE|TMPDIR|NODE_OPTIONS|CLAUDE_CONFIG_DIR|CLAUDE_CODE_OAUTH_TOKEN|CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC|CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF|CODEX_HOME|AR_CLI_RUNNER_ID|AR_AGENTRADAR_BIN|AR_DISCIPLINE_EVERY|CLAUDE_CODE_PLUGIN_CACHE_DIR|AR_GUARD_MARKER|AR_RESUME_FLAGS|AR_FRESH_FLAGS|AR_OAUTH_TOKEN_FILE|AR_TOOL_ENV_FILE|ANTHROPIC_API_KEY|AWP_BEARER|AWP_BEARER_FILE|AWP_MCP_URL|TMUX_SESSION|AGENT_PID_PATTERN) continue ;; esac; export "$_k=$_v"; done < "$AR_TOOL_ENV_FILE"; unset AR_TOOL_ENV_FILE _l _k _v; fi; if [ -n "${AR_CLI_RUNNER_ID:-}" ] && [ -n "${AR_AGENTRADAR_BIN:-}" ] && [ -f .agentradar/secrets.enc ]; then _e=$("$AR_AGENTRADAR_BIN" secrets env "$AR_CLI_RUNNER_ID") && eval "$_e"; unset _e; fi; if [ -n "${AR_CLI_RUNNER_ID:-}" ] && [ -n "${AR_AGENTRADAR_BIN:-}" ]; then _g="$HOME/.agentradar/run/github/$AR_CLI_RUNNER_ID"; _n=${GIT_CONFIG_COUNT:-0}; case "$_n" in ""|*[!0-9]*) _n=0 ;; esac; _n=${_n#"${_n%%[!0]*}"}; _n=${_n:-0}; export PATH="$_g/bin:$PATH" "GIT_CONFIG_KEY_$_n=include.path" "GIT_CONFIG_VALUE_$_n=$_g/gitconfig" GIT_CONFIG_COUNT=$((_n + 1)); "$AR_AGENTRADAR_BIN" github setup "$AR_CLI_RUNNER_ID" >/dev/null 2>&1 & unset _g _n; fi; RF="${AR_RESUME_FLAGS:-}"; FF="${AR_FRESH_FLAGS:-}"; unset AR_RESUME_FLAGS AR_FRESH_FLAGS; CB="$1"; shift; S=$(date +%s); "$CB" $RF "$@" || [ $(( $(date +%s) - S )) -ge 30 ] || exec "$CB" $FF "$@"' sh "${AGENT_BIN}" "${_launch_flags[@]}" || {
|
|
528
|
+
/bin/sh -c 'if [ -n "${AR_TOOL_ENV_FILE:-}" ]; then while IFS= read -r _l || [ -n "$_l" ]; do case "$_l" in ""|\#*) continue ;; esac; _k=${_l%%=*}; _v=${_l#*=}; case "$_k" in HOME|PATH|USER|LOGNAME|SHELL|TERM|COLORTERM|LANG|LC_ALL|LC_CTYPE|TMPDIR|NODE_OPTIONS|CLAUDE_CONFIG_DIR|CLAUDE_CODE_OAUTH_TOKEN|CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC|CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF|CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION|CODEX_HOME|AR_CLI_RUNNER_ID|AR_AGENTRADAR_BIN|AR_DISCIPLINE_EVERY|CLAUDE_CODE_PLUGIN_CACHE_DIR|AR_GUARD_MARKER|AR_RESUME_FLAGS|AR_FRESH_FLAGS|AR_OAUTH_TOKEN_FILE|AR_TOOL_ENV_FILE|ANTHROPIC_API_KEY|AWP_BEARER|AWP_BEARER_FILE|AWP_MCP_URL|TMUX_SESSION|AGENT_PID_PATTERN) continue ;; esac; export "$_k=$_v"; done < "$AR_TOOL_ENV_FILE"; unset AR_TOOL_ENV_FILE _l _k _v; fi; if [ -n "${AR_CLI_RUNNER_ID:-}" ] && [ -n "${AR_AGENTRADAR_BIN:-}" ] && [ -f .agentradar/secrets.enc ]; then _e=$("$AR_AGENTRADAR_BIN" secrets env "$AR_CLI_RUNNER_ID") && eval "$_e"; unset _e; fi; if [ -n "${AR_CLI_RUNNER_ID:-}" ] && [ -n "${AR_AGENTRADAR_BIN:-}" ]; then _g="$HOME/.agentradar/run/github/$AR_CLI_RUNNER_ID"; _n=${GIT_CONFIG_COUNT:-0}; case "$_n" in ""|*[!0-9]*) _n=0 ;; esac; _n=${_n#"${_n%%[!0]*}"}; _n=${_n:-0}; export PATH="$_g/bin:$PATH" "GIT_CONFIG_KEY_$_n=include.path" "GIT_CONFIG_VALUE_$_n=$_g/gitconfig" GIT_CONFIG_COUNT=$((_n + 1)); "$AR_AGENTRADAR_BIN" github setup "$AR_CLI_RUNNER_ID" >/dev/null 2>&1 & unset _g _n; fi; RF="${AR_RESUME_FLAGS:-}"; FF="${AR_FRESH_FLAGS:-}"; unset AR_RESUME_FLAGS AR_FRESH_FLAGS; CB="$1"; shift; S=$(date +%s); "$CB" $RF "$@" || [ $(( $(date +%s) - S )) -ge 30 ] || exec "$CB" $FF "$@"' sh "${AGENT_BIN}" "${_launch_flags[@]}" || {
|
|
477
529
|
log "tmux could not start agent session '${TMUX_SESSION}'"
|
|
478
530
|
return 1
|
|
479
531
|
}
|
|
@@ -543,14 +595,14 @@ if [ "${AR_FEED}" = "feeder" ]; then
|
|
|
543
595
|
# AR_TOOL_ENV_FILE: the login probe must see the same gateway env the pane runs with.
|
|
544
596
|
AWP_MCP_URL="${AWP_MCP_URL}" AWP_BEARER_FILE="${AWP_BEARER_FILE}" TMUX_SESSION="${TMUX_SESSION}" \
|
|
545
597
|
AGENT_PID_PATTERN="${AGENT_PID_PATTERN}" AR_AGENT_HARNESS="${AR_AGENT_HARNESS}" AGENT_BIN="${AGENT_BIN}" \
|
|
546
|
-
AR_TOOL_ENV_FILE="${_TOOL_ENV_FILE}" AR_GUARD_MARKER="${AR_GUARD_MARKER}" \
|
|
598
|
+
AR_TOOL_ENV_FILE="${_TOOL_ENV_FILE}" AR_GUARD_MARKER="${AR_GUARD_MARKER}" AR_REARM_PROMPT="${AR_REARM_PROMPT:-}" \
|
|
547
599
|
node "${FEEDER_JS}" &
|
|
548
600
|
FEEDER_PID=$!
|
|
549
601
|
log "feeder pid ${FEEDER_PID}"
|
|
550
602
|
else
|
|
551
603
|
log "no feeder (AR_FEED=${AR_FEED}): the session receives its own work; the feeder only reports the pane"
|
|
552
604
|
# Best-effort like the bridge: its exit never restarts the unit. Same 0600 bearer FILE, never the raw bearer.
|
|
553
|
-
AWP_MCP_URL="${AWP_MCP_URL}" AWP_BEARER_FILE="${AWP_BEARER_FILE}" TMUX_SESSION="${TMUX_SESSION}" AR_FEED=push \
|
|
605
|
+
AWP_MCP_URL="${AWP_MCP_URL}" AWP_BEARER_FILE="${AWP_BEARER_FILE}" TMUX_SESSION="${TMUX_SESSION}" AR_FEED=push AGENT_PID_PATTERN="${AGENT_PID_PATTERN}" AR_REARM_PROMPT="${AR_REARM_PROMPT:-}" \
|
|
554
606
|
node "${FEEDER_JS}" &
|
|
555
607
|
PANE_REPORT_PID=$!
|
|
556
608
|
fi
|
|
@@ -598,13 +650,19 @@ while true; do
|
|
|
598
650
|
exit 1
|
|
599
651
|
fi
|
|
600
652
|
_heal=0
|
|
601
|
-
if
|
|
653
|
+
if [ "${_PARKED:-0}" = 1 ]; then
|
|
654
|
+
: # parked (park_agent): no heal and no fresh session until an operator restarts the unit
|
|
655
|
+
elif ! pane_alive; then
|
|
602
656
|
log "agent pane died — recovering it in place"
|
|
603
657
|
_heal=1
|
|
604
658
|
elif [ "${AR_ATTACH:-}" != "1" ] && [ $(( $(date +%s) - ${_LAST_HEAL:-0} )) -ge "${AR_API400_COOLDOWN_SEC}" ] && api_error_loop; then
|
|
605
|
-
log "agent pane is looping on the same API 400 —
|
|
606
|
-
|
|
607
|
-
|
|
659
|
+
log "agent pane is looping on the same refused turn (API 400/413 or a full context) — typing /compact into it first"
|
|
660
|
+
if compact_in_place; then
|
|
661
|
+
log "compacted in place: the agent keeps its conversation"
|
|
662
|
+
_LAST_HEAL=$(date +%s)
|
|
663
|
+
else
|
|
664
|
+
park_agent
|
|
665
|
+
fi
|
|
608
666
|
fi
|
|
609
667
|
if [ "${_heal}" = 1 ]; then
|
|
610
668
|
if ensure_agent; then
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentradar-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.20",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=20"
|
|
6
6
|
},
|
|
@@ -30,19 +30,19 @@
|
|
|
30
30
|
"README.md"
|
|
31
31
|
],
|
|
32
32
|
"license": "MIT",
|
|
33
|
+
"reservedEnv": "^(HOME|PATH|USER|LOGNAME|SHELL|TERM|COLORTERM|LANG|LC_[A-Z]+|TMPDIR|NODE_(OPTIONS|PATH|EXTRA_CA_CERTS|TLS_REJECT_UNAUTHORIZED)|CLAUDE_[A-Z_]+|CODEX_[A-Z_]+|AR_[A-Z_]+|ANTHROPIC_[A-Z_]+|OPENAI_BASE_URL|AWP_[A-Z_]+|PAPERCLIP_[A-Z_]+|AGENTRADAR_[A-Z_]+|TMUX_SESSION|AGENT_PID_PATTERN|DISABLE_AUTOUPDATER|[A-Z0-9]+_PROXY|SSL_CERT_(FILE|DIR)|(REQUESTS|CURL)_CA_BUNDLE|GIT_[A-Z0-9_]+|SSH_(ASKPASS|ASKPASS_REQUIRE|AUTH_SOCK|SK_PROVIDER)|PYTHON[A-Z_]*|PERL5(OPT|LIB|DB)|PERLLIB|RUBY(OPT|LIB)|JAVA_TOOL_OPTIONS|_JAVA_OPTIONS|JDK_JAVA_OPTIONS|NPM_CONFIG_[A-Z0-9_]+|PIP_[A-Z0-9_]+|UV_(?![A-Z0-9_]*_(USERNAME|PASSWORD|TOKEN)$)[A-Z0-9_]+|YARN_(?![A-Z0-9_]*_AUTH_(TOKEN|IDENT)$)[A-Z0-9_]+|BUN_CONFIG_(?!TOKEN$)[A-Z0-9_]+|GO(PROXY|SUMDB|NOSUMDB|NOSUMCHECK|PRIVATE|NOPROXY|INSECURE|FLAGS|TOOLCHAIN|ENV|ROOT|TOOLDIR)|CARGO_(?![A-Z0-9_]*TOKEN$)[A-Z0-9_]+|RUSTC(_WRAPPER|_WORKSPACE_WRAPPER)?|RUST(DOC)?FLAGS|RUSTDOC|CC|CXX|RUSTUP_[A-Z0-9_]+|EDITOR|VISUAL|PAGER|PROMPT_COMMAND|ZDOTDIR|(LD|DYLD)_[A-Z_]+|BASH_ENV|ENV)$",
|
|
33
34
|
"publishConfig": {
|
|
34
35
|
"access": "public"
|
|
35
36
|
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.20.5",
|
|
39
|
+
"typescript": "^5.7.3",
|
|
40
|
+
"vitest": "^4.1.8"
|
|
41
|
+
},
|
|
36
42
|
"scripts": {
|
|
37
43
|
"build": "tsc && mkdir -p dist/agentradar-vendor dist/vendor-runner/supervisor && cp ../../skills/agentradar-vendor/SKILL.md dist/agentradar-vendor/SKILL.md && cp ../../templates/vendor-runner/feeder.mjs ../../templates/vendor-runner/pane-live.sh ../../templates/vendor-runner/session-frame.mjs ../../templates/vendor-runner/skill-zip.mjs ../../templates/vendor-runner/terminal-bridge.mjs dist/vendor-runner/ && cp ../../templates/vendor-runner/supervisor/supervise-agent.sh ../../templates/vendor-runner/supervisor/install-agent.sh ../../templates/vendor-runner/supervisor/com.agentradar.agent.plist.template ../../templates/vendor-runner/supervisor/agentradar-agent@.service.template dist/vendor-runner/supervisor/ && rm -rf dist/agent-guard && cp -R ../../templates/agent-guard dist/agent-guard && rm dist/agent-guard/scripts/*.test.mjs && chmod +x dist/index.js dist/vendor-runner/pane-live.sh dist/vendor-runner/supervisor/supervise-agent.sh dist/vendor-runner/supervisor/install-agent.sh",
|
|
38
44
|
"clean": "rm -rf dist",
|
|
39
|
-
"prepack": "pnpm run build",
|
|
40
45
|
"typecheck": "tsc --noEmit",
|
|
41
46
|
"test": "vitest run"
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"@types/node": "^22.20.5",
|
|
45
|
-
"typescript": "^5.7.3",
|
|
46
|
-
"vitest": "^4.1.8"
|
|
47
47
|
}
|
|
48
|
-
}
|
|
48
|
+
}
|