@humwick/runner 0.0.0-stage → 0.130.0

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.
Files changed (293) hide show
  1. package/LICENSE +167 -0
  2. package/agent-bin/agent-relay-browser +10 -0
  3. package/agent-bin/humwick +10 -0
  4. package/hooks/fff-search-redirect/fff-search-redirect.cjs +161 -0
  5. package/hooks/fff-search-redirect/hooks.json +13 -0
  6. package/hooks/rg-r-guard/hooks.json +18 -0
  7. package/hooks/rg-r-guard/rg-r-guard.cjs +144 -0
  8. package/hooks/web-tools-redirect/hooks.json +13 -0
  9. package/hooks/web-tools-redirect/web-tools-redirect.cjs +84 -0
  10. package/node_modules/@humwick/offload/LICENSE +167 -0
  11. package/node_modules/@humwick/offload/README.md +57 -0
  12. package/node_modules/@humwick/offload/dist/async-runner.d.ts +18 -0
  13. package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +1 -0
  14. package/node_modules/@humwick/offload/dist/async-runner.js +158 -0
  15. package/node_modules/@humwick/offload/dist/async-runner.js.map +1 -0
  16. package/node_modules/@humwick/offload/dist/envelope.d.ts +25 -0
  17. package/node_modules/@humwick/offload/dist/envelope.d.ts.map +1 -0
  18. package/node_modules/@humwick/offload/dist/envelope.js +80 -0
  19. package/node_modules/@humwick/offload/dist/envelope.js.map +1 -0
  20. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +10 -0
  21. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +1 -0
  22. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +59 -0
  23. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +1 -0
  24. package/node_modules/@humwick/offload/dist/index.d.ts +5 -0
  25. package/node_modules/@humwick/offload/dist/index.d.ts.map +1 -0
  26. package/node_modules/@humwick/offload/dist/index.js +5 -0
  27. package/node_modules/@humwick/offload/dist/index.js.map +1 -0
  28. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +62 -0
  29. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +1 -0
  30. package/node_modules/@humwick/offload/dist/subprocess-runner.js +295 -0
  31. package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +1 -0
  32. package/node_modules/@humwick/offload/dist/types.d.ts +111 -0
  33. package/node_modules/@humwick/offload/dist/types.d.ts.map +1 -0
  34. package/node_modules/@humwick/offload/dist/types.js +176 -0
  35. package/node_modules/@humwick/offload/dist/types.js.map +1 -0
  36. package/node_modules/@humwick/offload/dist/worker-pool.d.ts +23 -0
  37. package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +1 -0
  38. package/node_modules/@humwick/offload/dist/worker-pool.js +435 -0
  39. package/node_modules/@humwick/offload/dist/worker-pool.js.map +1 -0
  40. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +2 -0
  41. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +1 -0
  42. package/node_modules/@humwick/offload/dist/worker-runtime.js +27 -0
  43. package/node_modules/@humwick/offload/dist/worker-runtime.js.map +1 -0
  44. package/node_modules/@humwick/offload/dist/worker.d.ts +5 -0
  45. package/node_modules/@humwick/offload/dist/worker.d.ts.map +1 -0
  46. package/node_modules/@humwick/offload/dist/worker.js +1 -0
  47. package/node_modules/@humwick/offload/dist/worker.js.map +1 -0
  48. package/node_modules/@humwick/offload/package.json +46 -0
  49. package/node_modules/@humwick/offload/src/async-runner.ts +170 -0
  50. package/node_modules/@humwick/offload/src/envelope.ts +92 -0
  51. package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +49 -0
  52. package/node_modules/@humwick/offload/src/index.ts +4 -0
  53. package/node_modules/@humwick/offload/src/subprocess-runner.ts +334 -0
  54. package/node_modules/@humwick/offload/src/types.ts +235 -0
  55. package/node_modules/@humwick/offload/src/worker-pool.ts +463 -0
  56. package/node_modules/@humwick/offload/src/worker-runtime.ts +32 -0
  57. package/node_modules/@humwick/offload/src/worker.ts +5 -0
  58. package/package.json +47 -4
  59. package/plugins/browser/skills/playwright-cli/SKILL.md +53 -0
  60. package/plugins/claude/.claude-plugin/plugin.json +8 -0
  61. package/plugins/claude/claude-statusline-probe.cjs +144 -0
  62. package/plugins/claude/hooks/gate-run-guard.provisioned.mjs +914 -0
  63. package/plugins/claude/hooks/gate-run-guard.sh +9 -0
  64. package/plugins/claude/hooks/gate-run-guard.ts +237 -0
  65. package/plugins/claude/hooks/hooks.json +158 -0
  66. package/plugins/claude/hooks/kill-guard.provisioned.mjs +7027 -0
  67. package/plugins/claude/hooks/kill-guard.sh +341 -0
  68. package/plugins/claude/hooks/kill-guard.ts +337 -0
  69. package/plugins/claude/hooks/permission-request.sh +22 -0
  70. package/plugins/claude/hooks/post-compact.sh +10 -0
  71. package/plugins/claude/hooks/post-tool-use.sh +8 -0
  72. package/plugins/claude/hooks/pre-compact.sh +12 -0
  73. package/plugins/claude/hooks/read-only-bash-guard.sh +587 -0
  74. package/plugins/claude/hooks/relay-status.sh +373 -0
  75. package/plugins/claude/hooks/session-end.sh +26 -0
  76. package/plugins/claude/hooks/session-start.sh +31 -0
  77. package/plugins/claude/hooks/shell-trust.ts +28 -0
  78. package/plugins/claude/hooks/stop-failure.sh +47 -0
  79. package/plugins/claude/hooks/stop.sh +48 -0
  80. package/plugins/claude/hooks/subagent-guard.cjs +26 -0
  81. package/plugins/claude/hooks/subagent-skill-policy.cjs +160 -0
  82. package/plugins/claude/hooks/subagent-start.sh +13 -0
  83. package/plugins/claude/hooks/subagent-stop.sh +19 -0
  84. package/plugins/claude/hooks/user-prompt-submit.sh +18 -0
  85. package/plugins/claude/hooks/wait-loop-guard.ts +120 -0
  86. package/plugins/claude/monitors/monitors.json +7 -0
  87. package/plugins/claude/monitors/relay-monitor.provisioned.mjs +786 -0
  88. package/plugins/claude/monitors/relay-monitor.ts +58 -0
  89. package/plugins/claude/skills/disconnect/SKILL.md +16 -0
  90. package/plugins/claude/skills/label/SKILL.md +23 -0
  91. package/plugins/claude/skills/message/SKILL.md +40 -0
  92. package/plugins/claude/skills/pair/SKILL.md +30 -0
  93. package/plugins/claude/skills/react/SKILL.md +18 -0
  94. package/plugins/claude/skills/read-message/SKILL.md +24 -0
  95. package/plugins/claude/skills/reply/SKILL.md +44 -0
  96. package/plugins/claude/skills/send-claimable/SKILL.md +39 -0
  97. package/plugins/claude/skills/slice-first/SKILL.md +32 -0
  98. package/plugins/claude/skills/slice-first/references/plan-recipe.md +46 -0
  99. package/plugins/claude/skills/status/SKILL.md +16 -0
  100. package/plugins/claude/skills/tags/SKILL.md +25 -0
  101. package/plugins/claude/skills/task/SKILL.md +28 -0
  102. package/plugins/claude-output-style/.claude-plugin/plugin.json +5 -0
  103. package/plugins/claude-output-style/output-styles/agent-relay.md +5 -0
  104. package/plugins/codex/skills/disconnect/SKILL.md +15 -0
  105. package/plugins/codex/skills/guide/SKILL.md +15 -0
  106. package/plugins/codex/skills/label/SKILL.md +22 -0
  107. package/plugins/codex/skills/message/SKILL.md +40 -0
  108. package/plugins/codex/skills/pair/SKILL.md +29 -0
  109. package/plugins/codex/skills/react/SKILL.md +17 -0
  110. package/plugins/codex/skills/read-message/SKILL.md +23 -0
  111. package/plugins/codex/skills/reply/SKILL.md +44 -0
  112. package/plugins/codex/skills/send-claimable/SKILL.md +39 -0
  113. package/plugins/codex/skills/slice-first/SKILL.md +32 -0
  114. package/plugins/codex/skills/slice-first/references/plan-recipe.md +46 -0
  115. package/plugins/codex/skills/status/SKILL.md +15 -0
  116. package/plugins/codex/skills/tags/SKILL.md +24 -0
  117. package/plugins/codex/skills/task/SKILL.md +28 -0
  118. package/src/adapter.ts +1061 -0
  119. package/src/adapters/__fixtures__/codex-app-server.ts +47 -0
  120. package/src/adapters/claude-api.ts +243 -0
  121. package/src/adapters/claude-context-command.ts +16 -0
  122. package/src/adapters/claude-credentials.ts +22 -0
  123. package/src/adapters/claude-delivery-lanes.ts +28 -0
  124. package/src/adapters/claude-delivery.ts +166 -0
  125. package/src/adapters/claude-effort.ts +94 -0
  126. package/src/adapters/claude-exit.ts +290 -0
  127. package/src/adapters/claude-headless-mirror.ts +88 -0
  128. package/src/adapters/claude-headless-refusal.ts +51 -0
  129. package/src/adapters/claude-headless.ts +617 -0
  130. package/src/adapters/claude-monitor-delivery-plan.ts +317 -0
  131. package/src/adapters/claude-permission.ts +243 -0
  132. package/src/adapters/claude-prompt-gates.ts +269 -0
  133. package/src/adapters/claude-prompt-hook.ts +145 -0
  134. package/src/adapters/claude-session-capture.ts +293 -0
  135. package/src/adapters/claude-session-probe.ts +414 -0
  136. package/src/adapters/claude-shutdown.ts +190 -0
  137. package/src/adapters/claude-status-detectors.ts +426 -0
  138. package/src/adapters/claude-subagents.ts +93 -0
  139. package/src/adapters/claude-submit-confirmation.ts +287 -0
  140. package/src/adapters/claude-tmux.ts +548 -0
  141. package/src/adapters/claude-token-usage-capture.ts +253 -0
  142. package/src/adapters/claude-tool-policy.ts +165 -0
  143. package/src/adapters/claude-transcript-tail.ts +193 -0
  144. package/src/adapters/claude-transcript.ts +812 -0
  145. package/src/adapters/claude.ts +1097 -0
  146. package/src/adapters/codex-agent-message-capture.ts +189 -0
  147. package/src/adapters/codex-app-server.ts +70 -0
  148. package/src/adapters/codex-client.ts +362 -0
  149. package/src/adapters/codex-effective-config.ts +87 -0
  150. package/src/adapters/codex-launch-config.ts +153 -0
  151. package/src/adapters/codex-rollout-context.ts +272 -0
  152. package/src/adapters/codex-skill-discovery.ts +18 -0
  153. package/src/adapters/codex-token-usage-capture.ts +163 -0
  154. package/src/adapters/codex-tool-output.ts +53 -0
  155. package/src/adapters/codex-turn-outcome.ts +27 -0
  156. package/src/adapters/codex-version.ts +64 -0
  157. package/src/adapters/codex.ts +1442 -0
  158. package/src/adapters/incremental-file.ts +110 -0
  159. package/src/adapters/opencode-api.ts +71 -0
  160. package/src/adapters/opencode-context.ts +277 -0
  161. package/src/adapters/opencode-model-registry.ts +126 -0
  162. package/src/adapters/opencode-provider.ts +169 -0
  163. package/src/adapters/opencode-server-retry.ts +50 -0
  164. package/src/adapters/opencode-soft-steer.ts +302 -0
  165. package/src/adapters/opencode-spawn.ts +145 -0
  166. package/src/adapters/opencode-subscribe.ts +62 -0
  167. package/src/adapters/opencode-token-usage-capture.ts +187 -0
  168. package/src/adapters/opencode-tool-events.ts +302 -0
  169. package/src/adapters/opencode-tools-config.ts +17 -0
  170. package/src/adapters/opencode.ts +2264 -0
  171. package/src/adapters/project-shell.ts +80 -0
  172. package/src/adapters/provider-launch-config.ts +21 -0
  173. package/src/adapters/provider-quota-harvest.ts +127 -0
  174. package/src/adapters/service-access.ts +153 -0
  175. package/src/adapters/tmux-offload.ts +207 -0
  176. package/src/adapters/tool-summary.ts +214 -0
  177. package/src/agent-redact.ts +75 -0
  178. package/src/agent-runner/commands/dispatch.ts +185 -0
  179. package/src/agent-runner/commands/lifecycle.ts +84 -0
  180. package/src/agent-runner/commands/prompt.ts +284 -0
  181. package/src/agent-runner/credentials.ts +208 -0
  182. package/src/agent-runner/http-liveness.ts +85 -0
  183. package/src/agent-runner/lifecycle-milestones.ts +207 -0
  184. package/src/agent-runner/lifecycle-phase.ts +80 -0
  185. package/src/agent-runner/registration.ts +163 -0
  186. package/src/agent-runner/runner-context.ts +158 -0
  187. package/src/agent-runner/session-mirror.ts +278 -0
  188. package/src/agent-runner/task-claims.ts +116 -0
  189. package/src/agent-runner/token-usage-coverage.ts +54 -0
  190. package/src/agent-runner/turn-report.ts +269 -0
  191. package/src/api-budget-accounting.ts +91 -0
  192. package/src/area-memory.ts +255 -0
  193. package/src/artifact-path-materialize.ts +52 -0
  194. package/src/async-guard.ts +35 -0
  195. package/src/attachment-cache-sweep.ts +31 -0
  196. package/src/attachment-cache.ts +195 -0
  197. package/src/background-script-reconcile.ts +106 -0
  198. package/src/bounded-log.ts +59 -0
  199. package/src/bounded-spawn-sync.ts +18 -0
  200. package/src/busy-reconciler.ts +282 -0
  201. package/src/claim-tracker.ts +140 -0
  202. package/src/claude-settings.ts +167 -0
  203. package/src/claude-workspace-trust.ts +289 -0
  204. package/src/config.ts +547 -0
  205. package/src/context-boundary.ts +101 -0
  206. package/src/context-command.ts +151 -0
  207. package/src/context-probe-state.ts +6 -0
  208. package/src/continuation-archive.ts +179 -0
  209. package/src/control-server.ts +730 -0
  210. package/src/delivery-policy.ts +9 -0
  211. package/src/delivery-result.ts +24 -0
  212. package/src/effort-command.ts +125 -0
  213. package/src/empty-turn.ts +816 -0
  214. package/src/free-port.ts +25 -0
  215. package/src/fs-offload.ts +26 -0
  216. package/src/gate-run-config.ts +35 -0
  217. package/src/gate-run-lease-probe.ts +38 -0
  218. package/src/host-asset-plan.ts +450 -0
  219. package/src/host-claude-config-worker.ts +34 -0
  220. package/src/host-sources.ts +40 -0
  221. package/src/inbox-carrier.ts +7 -0
  222. package/src/index.ts +386 -0
  223. package/src/launch-assembly.ts +955 -0
  224. package/src/liveness.ts +105 -0
  225. package/src/logger.ts +108 -0
  226. package/src/mcp-outbox.ts +204 -0
  227. package/src/mcp-preflight.ts +86 -0
  228. package/src/mcp-source-selection.ts +54 -0
  229. package/src/mcp-tool-deferral.ts +35 -0
  230. package/src/mcp-vault-env.ts +25 -0
  231. package/src/message-body-config/config.ts +16 -0
  232. package/src/message-body-config.ts +1 -0
  233. package/src/mid-turn-delivery.ts +43 -0
  234. package/src/native-memory-lever.ts +329 -0
  235. package/src/native-self-resume.ts +244 -0
  236. package/src/outbox.ts +453 -0
  237. package/src/park-post.ts +17 -0
  238. package/src/pending-permission-store.ts +148 -0
  239. package/src/process-meta.ts +9 -0
  240. package/src/profile-home.ts +419 -0
  241. package/src/profile-projection.ts +411 -0
  242. package/src/project-hooks.ts +65 -0
  243. package/src/project-instructions.ts +119 -0
  244. package/src/provider-double.ts +59 -0
  245. package/src/provider-hold.ts +228 -0
  246. package/src/provider-home-marker.ts +3 -0
  247. package/src/provider-home-tombstone.ts +4 -0
  248. package/src/provider-input-queue.ts +134 -0
  249. package/src/provider-integration.ts +93 -0
  250. package/src/provider-processes.ts +17 -0
  251. package/src/provider-submit-failure.ts +15 -0
  252. package/src/providers.ts +35 -0
  253. package/src/provisioning.ts +703 -0
  254. package/src/question-decision.ts +18 -0
  255. package/src/quota.ts +22 -0
  256. package/src/rate-limit.ts +351 -0
  257. package/src/refused-prompt-store.ts +36 -0
  258. package/src/relay-injection-events.ts +161 -0
  259. package/src/relay-instructions.ts +107 -0
  260. package/src/relay-mcp-proxy.long-turn-e2e.ts +183 -0
  261. package/src/relay-mcp-proxy.ts +704 -0
  262. package/src/relay-mcp.ts +583 -0
  263. package/src/reply-obligation-cache.ts +211 -0
  264. package/src/response-capture-report.ts +111 -0
  265. package/src/response-routing.ts +6 -0
  266. package/src/runner-core.ts +2955 -0
  267. package/src/runner-exit.ts +12 -0
  268. package/src/runner-helpers.ts +582 -0
  269. package/src/runner-insights.ts +105 -0
  270. package/src/runner-mcp-proxy.ts +46 -0
  271. package/src/runner-spawn-config.ts +68 -0
  272. package/src/runner-stale-scratch-sweep.ts +21 -0
  273. package/src/runner.ts +9 -0
  274. package/src/runtime-token-file.ts +45 -0
  275. package/src/runtime-token-remint.ts +56 -0
  276. package/src/service-access.ts +88 -0
  277. package/src/session-destroy.ts +27 -0
  278. package/src/session-insights.ts +118 -0
  279. package/src/session-scratch.ts +296 -0
  280. package/src/spawn-input.ts +147 -0
  281. package/src/startup-config-fetch.ts +48 -0
  282. package/src/subreaper.ts +113 -0
  283. package/src/template-resolver.ts +29 -0
  284. package/src/test-run-dedup.ts +435 -0
  285. package/src/transcript-context.ts +99 -0
  286. package/src/transport-errors.ts +48 -0
  287. package/src/turn-opener.ts +55 -0
  288. package/src/turn-reconcile-event.ts +88 -0
  289. package/src/vault-proxy-hop.ts +99 -0
  290. package/src/version.ts +44 -0
  291. package/src/web-tool-call-proxy.ts +128 -0
  292. package/src/web-tools.ts +59 -0
  293. package/README.md +0 -3
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ source "${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/hooks/relay-status.sh"
4
+ relay_install_hook_guard permission-request
5
+
6
+ port="${AGENT_RELAY_RUNNER_PORT:-}"
7
+ if [[ -z "$port" ]]; then
8
+ exit 0
9
+ fi
10
+
11
+ payload="$(cat)"
12
+ response="$(
13
+ printf '%s' "$payload" | curl -fsS \
14
+ --max-time 900 \
15
+ -H 'Content-Type: application/json' \
16
+ --data-binary @- \
17
+ "http://127.0.0.1:${port}/permissions/request" 2>/dev/null || true
18
+ )"
19
+
20
+ if [[ -n "$response" ]]; then
21
+ printf '%s\n' "$response"
22
+ fi
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ source "${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/hooks/relay-status.sh"
4
+ relay_install_hook_guard post-compact
5
+
6
+ # #3398 — the trigger (manual for our /compact, auto for Claude's own) tells the runner which input settled.
7
+ trigger="$(relay_json_string_field trigger "$(cat || true)")"
8
+ metadata=""
9
+ [ -n "$trigger" ] && metadata="{\"compactTrigger\":\"$(relay_json_escape "$trigger")\"}"
10
+ relay_post_status idle provider-turn "" "" "" "" "" compacted "$metadata"
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ source "${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/hooks/relay-status.sh"
4
+ relay_install_hook_guard post-tool-use
5
+ [ -n "${AGENT_RELAY_RUNNER_PORT:-}" ] || exit 0
6
+ # #3646: stdout belongs to this tool result, before Claude can make its next call.
7
+ context="$(relay_post_json "$AGENT_RELAY_RUNNER_PORT" /tool-use 4 || true)"
8
+ relay_emit_additional_context PostToolUse "$context"
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ source "${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/hooks/relay-status.sh"
4
+ relay_install_hook_guard pre-compact
5
+
6
+ payload="$(cat || true)"
7
+ transcript_path="$(relay_json_string_field transcript_path "$payload")"
8
+
9
+ relay_post_timeline_status busy provider-turn "" compacting
10
+ # Pre-destroy seam (#183): compaction is about to discard context — capture this segment's
11
+ # Insights (#184) from the full transcript first. The most common boundary in practice.
12
+ relay_post_session_boundary "$transcript_path" compact
@@ -0,0 +1,587 @@
1
+ #!/usr/bin/env bash
2
+ # BEST-EFFORT GUARDRAIL / defense-in-depth only. This hook denies common
3
+ # mutating Bash patterns for read-only Claude sessions, but it is not a sandbox.
4
+ # Real boundaries are Relay token scope, unavailable write tools, and future
5
+ # OS-level enforcement.
6
+ set -euo pipefail
7
+ HOOK_DIR="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/hooks"
8
+ source "${HOOK_DIR}/relay-status.sh"
9
+ relay_install_hook_guard read-only-bash-guard
10
+ # Shared fleet-fratricide kill-pattern matcher (#753): trim_shell_text,
11
+ # split_segments, shell_words and fratricide_deny_reason live here.
12
+ source "${HOOK_DIR}/kill-guard.sh"
13
+
14
+ payload="$(cat)"
15
+
16
+ emit_deny() {
17
+ local reason="${1:-Read-only Claude sessions may only run inspection commands.}"
18
+ printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s"}}\n' \
19
+ "$(relay_json_escape "$reason")"
20
+ }
21
+
22
+ shopt -s extglob
23
+
24
+ # Extract the Bash command once. jq is strongly preferred; both the fleet-kill
25
+ # guard (all modes) and the read-only allow-list (read-only mode) consume it.
26
+ jq_available=0
27
+ tool_name=""
28
+ command_text=""
29
+ if command -v jq >/dev/null 2>&1; then
30
+ jq_available=1
31
+ tool_name="$(printf '%s' "$payload" | jq -r '.tool_name // empty' 2>/dev/null || true)"
32
+ command_text="$(printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null || true)"
33
+ fi
34
+
35
+ # --- Fleet-fratricide guard: ACTIVE IN ALL APPROVAL MODES (#753) ---
36
+ # A broad pkill/killall/kill can SIGTERM every sibling agent's runner host-wide,
37
+ # so this block runs before (and independent of) the read-only allow-list.
38
+ if (( jq_available )); then
39
+ if [ "${tool_name,,}" = "bash" ] && [ -n "$command_text" ]; then
40
+ if fratricide_reason="$(fratricide_deny_reason "$command_text")"; then
41
+ emit_deny "$fratricide_reason"
42
+ exit 0
43
+ fi
44
+ fi
45
+ else
46
+ # No jq: best-effort raw scan so the fleet guard still bites the obvious cases.
47
+ if fratricide_reason="$(fratricide_raw_reason "$payload")"; then
48
+ emit_deny "$fratricide_reason"
49
+ exit 0
50
+ fi
51
+ fi
52
+
53
+ # --- Read-only allow-list: only the read-only approval mode below this point ---
54
+ case "${AGENT_RELAY_APPROVAL:-}" in
55
+ open|guarded)
56
+ exit 0
57
+ ;;
58
+ read-only)
59
+ ;;
60
+ *)
61
+ # Unknown approval values fail closed so typos such as "readonly" or
62
+ # casing drift do not silently disable the read-only boundary.
63
+ ;;
64
+ esac
65
+
66
+ if (( ! jq_available )); then
67
+ emit_deny "Read-only Bash guard could not inspect the command because jq is unavailable."
68
+ exit 0
69
+ fi
70
+
71
+ if [ "${tool_name,,}" != "bash" ]; then
72
+ exit 0
73
+ fi
74
+
75
+ if [ -z "$command_text" ]; then
76
+ emit_deny "Read-only Bash guard denied an empty Bash command."
77
+ exit 0
78
+ fi
79
+
80
+ DENY_REASON=""
81
+
82
+ deny_segment() {
83
+ DENY_REASON="$1"
84
+ return 1
85
+ }
86
+
87
+ hard_deny_reason() {
88
+ local text="$1"
89
+ case "$text" in
90
+ *'$('*|*'`'*)
91
+ printf '%s' "Read-only Bash guard denied command substitution."
92
+ return 0
93
+ ;;
94
+ *'<('*|*'>('*)
95
+ printf '%s' "Read-only Bash guard denied process substitution."
96
+ return 0
97
+ ;;
98
+ esac
99
+
100
+ local i len ch next quote="" escaped=0
101
+ len=${#text}
102
+ for ((i = 0; i < len; i++)); do
103
+ ch="${text:i:1}"
104
+ if (( escaped )); then
105
+ escaped=0
106
+ continue
107
+ fi
108
+ if [ -n "$quote" ]; then
109
+ if [ "$quote" = '"' ] && [ "$ch" = "\\" ]; then
110
+ escaped=1
111
+ continue
112
+ fi
113
+ if [ "$ch" = "$quote" ]; then
114
+ quote=""
115
+ fi
116
+ continue
117
+ fi
118
+
119
+ case "$ch" in
120
+ "'"|'"')
121
+ quote="$ch"
122
+ ;;
123
+ "\\")
124
+ escaped=1
125
+ ;;
126
+ ">")
127
+ printf '%s' "Read-only Bash guard denied shell output redirection."
128
+ return 0
129
+ ;;
130
+ "<")
131
+ next="${text:i+1:1}"
132
+ if [ "$next" = ">" ]; then
133
+ printf '%s' "Read-only Bash guard denied shell output redirection."
134
+ return 0
135
+ fi
136
+ ;;
137
+ esac
138
+ done
139
+
140
+ return 1
141
+ }
142
+
143
+ validate_git_branch() {
144
+ local i="$1" arg
145
+ while (( i < ${#WORDS[@]} )); do
146
+ arg="${WORDS[i]}"
147
+ case "$arg" in
148
+ --list|--all|--remotes|--show-current|-a|-r|-v|-vv)
149
+ ;;
150
+ *)
151
+ deny_segment "Read-only Bash guard denied a git branch mutation or unsupported branch option."
152
+ return 1
153
+ ;;
154
+ esac
155
+ ((i += 1))
156
+ done
157
+ return 0
158
+ }
159
+
160
+ validate_git_read_args() {
161
+ local i="$1" arg
162
+ while (( i < ${#WORDS[@]} )); do
163
+ arg="${WORDS[i]}"
164
+ if [ "$arg" = "--" ]; then
165
+ return 0
166
+ fi
167
+ case "$arg" in
168
+ --output|--output=*)
169
+ deny_segment "Read-only Bash guard denied git output-to-file options."
170
+ return 1
171
+ ;;
172
+ esac
173
+ ((i += 1))
174
+ done
175
+ return 0
176
+ }
177
+
178
+ validate_git() {
179
+ local i="$1" arg sub
180
+ while (( i < ${#WORDS[@]} )); do
181
+ arg="${WORDS[i]}"
182
+ case "$arg" in
183
+ -C)
184
+ ((i += 1))
185
+ if (( i >= ${#WORDS[@]} )); then
186
+ deny_segment "Read-only Bash guard denied git -C without a path."
187
+ return 1
188
+ fi
189
+ ((i += 1))
190
+ ;;
191
+ --no-pager)
192
+ ((i += 1))
193
+ ;;
194
+ -c|--config-env|--config-env=*)
195
+ deny_segment "Read-only Bash guard denied git configuration or alias injection."
196
+ return 1
197
+ ;;
198
+ --)
199
+ ((i += 1))
200
+ break
201
+ ;;
202
+ -*)
203
+ deny_segment "Read-only Bash guard denied an unsupported git global option."
204
+ return 1
205
+ ;;
206
+ *)
207
+ break
208
+ ;;
209
+ esac
210
+ done
211
+
212
+ if (( i >= ${#WORDS[@]} )); then
213
+ deny_segment "Read-only Bash guard denied git without an explicit read-only subcommand."
214
+ return 1
215
+ fi
216
+
217
+ sub="${WORDS[i],,}"
218
+ ((i += 1))
219
+ case "$sub" in
220
+ diff|log|status|show|blame|grep|rev-parse|describe)
221
+ validate_git_read_args "$i"
222
+ return $?
223
+ ;;
224
+ branch)
225
+ validate_git_branch "$i"
226
+ return $?
227
+ ;;
228
+ *)
229
+ deny_segment "Read-only Bash guard denied a git subcommand that is not in the read-only allow-list."
230
+ return 1
231
+ ;;
232
+ esac
233
+ }
234
+
235
+ find_arg_takes_operand() {
236
+ local arg="$1"
237
+ case "$arg" in
238
+ -name|-iname|-path|-ipath|-wholename|-iwholename|-regex|-iregex|-type|-xtype|-user|-group|-newer|-samefile|-size|-perm|-mtime|-mmin|-atime|-amin|-ctime|-cmin|-maxdepth|-mindepth|-printf|-printf0)
239
+ return 0
240
+ ;;
241
+ -newer??)
242
+ return 0
243
+ ;;
244
+ *)
245
+ return 1
246
+ ;;
247
+ esac
248
+ }
249
+
250
+ validate_find() {
251
+ local i="$1" arg skip_next=0
252
+ while (( i < ${#WORDS[@]} )); do
253
+ arg="${WORDS[i]}"
254
+ if (( skip_next )); then
255
+ skip_next=0
256
+ ((i += 1))
257
+ continue
258
+ fi
259
+ case "$arg" in
260
+ -exec|-execdir|-ok|-okdir|-delete|-fls|-fprint|-fprintf|-fprint0)
261
+ deny_segment "Read-only Bash guard denied find mutating action predicates."
262
+ return 1
263
+ ;;
264
+ esac
265
+ if find_arg_takes_operand "$arg"; then
266
+ skip_next=1
267
+ fi
268
+ ((i += 1))
269
+ done
270
+ return 0
271
+ }
272
+
273
+ validate_fd() {
274
+ local i="$1" arg
275
+ while (( i < ${#WORDS[@]} )); do
276
+ arg="${WORDS[i]}"
277
+ if [ "$arg" = "--" ]; then
278
+ return 0
279
+ fi
280
+ case "$arg" in
281
+ -x|-X|--exec|--exec-batch)
282
+ deny_segment "Read-only Bash guard denied fd exec action options."
283
+ return 1
284
+ ;;
285
+ esac
286
+ ((i += 1))
287
+ done
288
+ return 0
289
+ }
290
+
291
+ validate_rg() {
292
+ local i="$1" arg
293
+ while (( i < ${#WORDS[@]} )); do
294
+ arg="${WORDS[i]}"
295
+ if [ "$arg" = "--" ]; then
296
+ return 0
297
+ fi
298
+ case "$arg" in
299
+ --pre|--pre=*)
300
+ deny_segment "Read-only Bash guard denied rg preprocessor execution options."
301
+ return 1
302
+ ;;
303
+ esac
304
+ ((i += 1))
305
+ done
306
+ return 0
307
+ }
308
+
309
+ validate_sort() {
310
+ local i="$1" arg
311
+ while (( i < ${#WORDS[@]} )); do
312
+ arg="${WORDS[i]}"
313
+ if [ "$arg" = "--" ]; then
314
+ return 0
315
+ fi
316
+ case "$arg" in
317
+ -o|-o?*|--output|--output=*)
318
+ deny_segment "Read-only Bash guard denied sort output-to-file options."
319
+ return 1
320
+ ;;
321
+ esac
322
+ ((i += 1))
323
+ done
324
+ return 0
325
+ }
326
+
327
+ uniq_arg_takes_operand() {
328
+ local arg="$1"
329
+ case "$arg" in
330
+ -f|-s|-w|--skip-fields|--skip-chars|--check-chars)
331
+ return 0
332
+ ;;
333
+ *)
334
+ return 1
335
+ ;;
336
+ esac
337
+ }
338
+
339
+ validate_uniq() {
340
+ local i="$1" arg operands=0 after_options=0 skip_next=0
341
+ while (( i < ${#WORDS[@]} )); do
342
+ arg="${WORDS[i]}"
343
+ if (( skip_next )); then
344
+ skip_next=0
345
+ ((i += 1))
346
+ continue
347
+ fi
348
+ if (( ! after_options )) && [ "$arg" = "--" ]; then
349
+ after_options=1
350
+ ((i += 1))
351
+ continue
352
+ fi
353
+ if (( ! after_options )) && [ "$arg" != "-" ] && [[ "$arg" == -* ]]; then
354
+ if uniq_arg_takes_operand "$arg"; then
355
+ skip_next=1
356
+ fi
357
+ ((i += 1))
358
+ continue
359
+ fi
360
+
361
+ ((operands += 1))
362
+ if (( operands > 1 )); then
363
+ deny_segment "Read-only Bash guard denied uniq output-file operands."
364
+ return 1
365
+ fi
366
+ ((i += 1))
367
+ done
368
+ return 0
369
+ }
370
+
371
+ validate_yq() {
372
+ local i="$1" arg
373
+ while (( i < ${#WORDS[@]} )); do
374
+ arg="${WORDS[i]}"
375
+ if [ "$arg" = "--" ]; then
376
+ return 0
377
+ fi
378
+ case "$arg" in
379
+ -i|-i?*|--inplace|--inplace=*)
380
+ deny_segment "Read-only Bash guard denied yq in-place edit options."
381
+ return 1
382
+ ;;
383
+ esac
384
+ ((i += 1))
385
+ done
386
+ return 0
387
+ }
388
+
389
+ validate_bun() {
390
+ local i="$1" sub next
391
+ if (( i >= ${#WORDS[@]} )); then
392
+ deny_segment "Read-only Bash guard denied bun without an explicit test/typecheck command."
393
+ return 1
394
+ fi
395
+ sub="${WORDS[i],,}"
396
+ case "$sub" in
397
+ test)
398
+ return 0
399
+ ;;
400
+ run)
401
+ next="${WORDS[i+1]:-}"
402
+ if [ "$next" = "typecheck" ]; then
403
+ return 0
404
+ fi
405
+ deny_segment "Read-only Bash guard only allows bun run typecheck in read-only sessions."
406
+ return 1
407
+ ;;
408
+ *)
409
+ deny_segment "Read-only Bash guard denied a bun command outside the test/typecheck allow-list."
410
+ return 1
411
+ ;;
412
+ esac
413
+ }
414
+
415
+ validate_agent_relay() {
416
+ local i="$1" sub next
417
+ if (( i >= ${#WORDS[@]} )); then
418
+ deny_segment "Read-only Bash guard denied agent-relay without an allowed read/message subcommand."
419
+ return 1
420
+ fi
421
+ sub="${WORDS[i]}"
422
+ next="${WORDS[i+1]:-}"
423
+ case "$sub" in
424
+ /reply|/react|/message|/status|/read-message|/tags|/whoami|/guide)
425
+ return 0
426
+ ;;
427
+ get-message|status|whoami|guide|tags)
428
+ return 0
429
+ ;;
430
+ workspace)
431
+ if [ "$next" = "status" ]; then
432
+ return 0
433
+ fi
434
+ deny_segment "Read-only Bash guard denied a mutating agent-relay workspace command."
435
+ return 1
436
+ ;;
437
+ *)
438
+ deny_segment "Read-only Bash guard denied an agent-relay command outside the read/message allow-list."
439
+ return 1
440
+ ;;
441
+ esac
442
+ }
443
+
444
+ validate_leading_assignment() {
445
+ local assignment="$1" name value
446
+ name="${assignment%%=*}"
447
+ value="${assignment#*=}"
448
+
449
+ case "$name" in
450
+ LANG|LANGUAGE|LC_*)
451
+ if [[ "$value" =~ ^[A-Za-z0-9_.:@+-]+$ ]]; then
452
+ return 0
453
+ fi
454
+ ;;
455
+ TZ)
456
+ if [[ "$value" =~ ^[A-Za-z0-9_+:-]+(/[A-Za-z0-9_+:-]+)*$ ]]; then
457
+ return 0
458
+ fi
459
+ ;;
460
+ PAGER|GIT_PAGER)
461
+ if [ "$value" = "cat" ]; then
462
+ return 0
463
+ fi
464
+ ;;
465
+ esac
466
+
467
+ deny_segment "Read-only Bash guard denied unsafe leading environment assignment."
468
+ return 1
469
+ }
470
+
471
+ validate_segment() {
472
+ local raw="$1" segment i=0 saw_assignment=0 command_name base
473
+ segment="$(trim_shell_text "$raw")"
474
+ if [ -z "$segment" ]; then
475
+ return 0
476
+ fi
477
+ if ! shell_words "$segment"; then
478
+ deny_segment "Read-only Bash guard denied shell syntax it could not parse."
479
+ return 1
480
+ fi
481
+ if (( ${#WORDS[@]} == 0 )); then
482
+ return 0
483
+ fi
484
+
485
+ while (( i < ${#WORDS[@]} )) && [[ "${WORDS[i]}" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; do
486
+ if ! validate_leading_assignment "${WORDS[i]}"; then
487
+ return 1
488
+ fi
489
+ saw_assignment=1
490
+ ((i += 1))
491
+ done
492
+
493
+ if (( i >= ${#WORDS[@]} )); then
494
+ deny_segment "Read-only Bash guard denied an assignment-only shell segment."
495
+ return 1
496
+ fi
497
+
498
+ command_name="${WORDS[i]}"
499
+ if [[ "$command_name" == \$* ]]; then
500
+ deny_segment "Read-only Bash guard denied an indirect command name."
501
+ return 1
502
+ fi
503
+
504
+ base="${command_name##*/}"
505
+ base="${base,,}"
506
+ if [ -z "$base" ]; then
507
+ deny_segment "Read-only Bash guard denied an empty command name."
508
+ return 1
509
+ fi
510
+
511
+ case "$base" in
512
+ bash|sh|zsh|dash|env|python*|node|ruby|perl|php|awk|eval|exec|source|.)
513
+ deny_segment "Read-only Bash guard denied a nested shell, interpreter, or shell re-entry command."
514
+ return 1
515
+ ;;
516
+ esac
517
+
518
+ if (( saw_assignment )) && [[ "$command_name" == *'$'* ]]; then
519
+ deny_segment "Read-only Bash guard denied assignment-based command indirection."
520
+ return 1
521
+ fi
522
+
523
+ case "$base" in
524
+ git)
525
+ validate_git "$((i + 1))"
526
+ return $?
527
+ ;;
528
+ find)
529
+ validate_find "$((i + 1))"
530
+ return $?
531
+ ;;
532
+ fd)
533
+ validate_fd "$((i + 1))"
534
+ return $?
535
+ ;;
536
+ rg)
537
+ validate_rg "$((i + 1))"
538
+ return $?
539
+ ;;
540
+ sort)
541
+ validate_sort "$((i + 1))"
542
+ return $?
543
+ ;;
544
+ uniq)
545
+ validate_uniq "$((i + 1))"
546
+ return $?
547
+ ;;
548
+ yq)
549
+ validate_yq "$((i + 1))"
550
+ return $?
551
+ ;;
552
+ grep|cat|ls|head|tail|wc|pwd|jq|nl|cut)
553
+ return 0
554
+ ;;
555
+ bun)
556
+ validate_bun "$((i + 1))"
557
+ return $?
558
+ ;;
559
+ agent-relay)
560
+ validate_agent_relay "$((i + 1))"
561
+ return $?
562
+ ;;
563
+ *)
564
+ deny_segment "Read-only Bash guard denied a command outside the read-only allow-list."
565
+ return 1
566
+ ;;
567
+ esac
568
+ }
569
+
570
+ if reason="$(hard_deny_reason "$command_text")"; then
571
+ emit_deny "$reason"
572
+ exit 0
573
+ fi
574
+
575
+ if ! split_segments "$command_text"; then
576
+ emit_deny "Read-only Bash guard denied shell syntax it could not parse."
577
+ exit 0
578
+ fi
579
+
580
+ for segment in "${SEGMENTS[@]}"; do
581
+ if ! validate_segment "$segment"; then
582
+ emit_deny "${DENY_REASON:-Read-only Claude sessions may only run explicit inspection commands.}"
583
+ exit 0
584
+ fi
585
+ done
586
+
587
+ exit 0