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.
@@ -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 fresh-session heal per this window
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 (below) wrote its new session id next to the env file; a restart honors it, so it never resumes the poisoned id.
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
- | grep -oE 'API Error: 4(00|13) .*|Request too large .*' | sed -E 's/[0-9]+/N/g; s/[[:space:]]+$//' | sort | uniq -c | sort -rn \
355
- | awk -v m="${AR_API400_LOOP_MAX}" 'NR==1{ok=($1>=m)} END{exit !ok}'
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
- log "starting agent (${AR_AGENT_HARNESS}) in tmux '${TMUX_SESSION}' (tool-env file: ${_TOOL_ENV_FILE:-none})"
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 ! pane_alive; then
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 — replacing it with a FRESH session"
606
- _LAST_HEAL=$(date +%s); _FORCE_FRESH=1; _heal=1
607
- "${TMUX_BIN}" kill-session -t "=${TMUX_SESSION}" 2>/dev/null || true
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.17",
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
+ }