@humwick/runner 0.130.4 → 0.130.6
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 +661 -167
- package/agent-bin/humwick +2 -2
- package/agent-bin/humwick-browser +4 -0
- package/hooks/fff-search-redirect/fff-search-redirect.cjs +1 -1
- package/hooks/web-tools-redirect/web-tools-redirect.cjs +2 -2
- package/package.json +8 -11
- package/plugins/browser/skills/playwright-cli/SKILL.md +8 -6
- package/plugins/claude/.claude-plugin/plugin.json +3 -3
- package/plugins/claude/claude-statusline-probe.cjs +26 -16
- package/plugins/claude/hooks/gate-run-guard.provisioned.mjs +47 -17
- package/plugins/claude/hooks/gate-run-guard.sh +3 -3
- package/plugins/claude/hooks/gate-run-guard.ts +5 -5
- package/plugins/claude/hooks/kill-guard.provisioned.mjs +1 -1
- package/plugins/claude/hooks/kill-guard.sh +4 -4
- package/plugins/claude/hooks/kill-guard.ts +2 -2
- package/plugins/claude/hooks/permission-request.sh +1 -1
- package/plugins/claude/hooks/post-compact.sh +1 -1
- package/plugins/claude/hooks/post-tool-use.sh +3 -3
- package/plugins/claude/hooks/pre-compact.sh +2 -2
- package/plugins/claude/hooks/read-only-bash-guard.sh +4 -4
- package/plugins/claude/hooks/relay-status.sh +32 -32
- package/plugins/claude/hooks/session-end.sh +2 -2
- package/plugins/claude/hooks/session-start.sh +3 -3
- package/plugins/claude/hooks/shell-trust.ts +1 -1
- package/plugins/claude/hooks/stop-failure.sh +2 -2
- package/plugins/claude/hooks/stop.sh +6 -6
- package/plugins/claude/hooks/subagent-skill-policy.cjs +8 -6
- package/plugins/claude/hooks/subagent-stop.sh +3 -3
- package/plugins/claude/hooks/user-prompt-submit.sh +1 -1
- package/plugins/claude/hooks/wait-loop-guard.ts +3 -3
- package/plugins/claude/monitors/monitors.json +2 -2
- package/plugins/claude/monitors/relay-monitor.provisioned.mjs +96 -62
- package/plugins/claude/monitors/relay-monitor.ts +5 -5
- package/plugins/claude/skills/label/SKILL.md +3 -3
- package/plugins/claude/skills/message/SKILL.md +5 -5
- package/plugins/claude/skills/react/SKILL.md +1 -1
- package/plugins/claude/skills/read-message/SKILL.md +4 -4
- package/plugins/claude/skills/reply/SKILL.md +5 -5
- package/plugins/claude/skills/send-claimable/SKILL.md +5 -5
- package/plugins/claude/skills/slice-first/SKILL.md +5 -3
- package/plugins/claude/skills/slice-first/references/plan-recipe.md +30 -29
- package/plugins/claude/skills/status/SKILL.md +3 -3
- package/plugins/claude/skills/tags/SKILL.md +5 -5
- package/plugins/claude/skills/task/SKILL.md +10 -10
- package/plugins/claude-output-style/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-output-style/output-styles/{agent-relay.md → humwick.md} +1 -1
- package/plugins/codex/skills/guide/SKILL.md +3 -3
- package/plugins/codex/skills/label/SKILL.md +3 -3
- package/plugins/codex/skills/message/SKILL.md +5 -5
- package/plugins/codex/skills/react/SKILL.md +1 -1
- package/plugins/codex/skills/read-message/SKILL.md +4 -4
- package/plugins/codex/skills/reply/SKILL.md +5 -5
- package/plugins/codex/skills/send-claimable/SKILL.md +5 -5
- package/plugins/codex/skills/slice-first/SKILL.md +5 -3
- package/plugins/codex/skills/slice-first/references/plan-recipe.md +30 -29
- package/plugins/codex/skills/status/SKILL.md +3 -3
- package/plugins/codex/skills/tags/SKILL.md +5 -5
- package/plugins/codex/skills/task/SKILL.md +11 -10
- package/src/adapter.ts +95 -92
- package/src/adapters/claude-api.ts +8 -11
- package/src/adapters/claude-context-command.ts +1 -1
- package/src/adapters/claude-credentials.ts +7 -3
- package/src/adapters/claude-delivery-lanes.ts +2 -2
- package/src/adapters/claude-delivery.ts +16 -16
- package/src/adapters/claude-effort.ts +2 -2
- package/src/adapters/claude-exit.ts +2 -2
- package/src/adapters/claude-headless-mirror.ts +18 -2
- package/src/adapters/claude-headless-refusal.ts +3 -3
- package/src/adapters/claude-headless.ts +11 -10
- package/src/adapters/claude-launch-exit.ts +1 -1
- package/src/adapters/claude-monitor-delivery-plan.ts +36 -16
- package/src/adapters/claude-permission.ts +5 -5
- package/src/adapters/claude-prompt-gates.ts +9 -9
- package/src/adapters/claude-prompt-hook.ts +2 -2
- package/src/adapters/claude-session-capture.ts +6 -6
- package/src/adapters/claude-session-probe.ts +25 -25
- package/src/adapters/claude-shutdown.ts +11 -11
- package/src/adapters/claude-status-detectors.ts +31 -31
- package/src/adapters/claude-subagents.ts +7 -7
- package/src/adapters/claude-submit-confirmation.ts +6 -6
- package/src/adapters/claude-tmux.ts +43 -43
- package/src/adapters/claude-token-usage-capture.ts +5 -5
- package/src/adapters/claude-tool-policy.ts +7 -7
- package/src/adapters/claude-transcript.ts +24 -24
- package/src/adapters/claude.ts +100 -100
- package/src/adapters/codex-agent-message-capture.ts +26 -63
- package/src/adapters/codex-app-server.ts +1 -1
- package/src/adapters/codex-client.ts +9 -9
- package/src/adapters/codex-effective-config.ts +1 -1
- package/src/adapters/codex-launch-config.ts +5 -5
- package/src/adapters/codex-rollout-context.ts +2 -2
- package/src/adapters/codex-skill-discovery.ts +1 -1
- package/src/adapters/codex-token-usage-capture.ts +16 -16
- package/src/adapters/codex-tool-output.ts +4 -4
- package/src/adapters/codex-version.ts +3 -3
- package/src/adapters/codex.ts +64 -64
- package/src/adapters/opencode-api.ts +1 -1
- package/src/adapters/opencode-context.ts +20 -22
- package/src/adapters/opencode-model-registry.ts +7 -7
- package/src/adapters/opencode-provider.ts +8 -8
- package/src/adapters/opencode-provisioning.ts +30 -0
- package/src/adapters/opencode-server-retry.ts +1 -1
- package/src/adapters/opencode-soft-steer.ts +5 -5
- package/src/adapters/opencode-spawn.ts +18 -14
- package/src/adapters/opencode-token-usage-capture.ts +12 -12
- package/src/adapters/opencode-tool-events.ts +15 -15
- package/src/adapters/opencode.ts +232 -232
- package/src/adapters/project-shell.ts +11 -11
- package/src/adapters/service-access.ts +6 -6
- package/src/adapters/tmux-offload.ts +9 -9
- package/src/adapters/tool-summary.ts +5 -5
- package/src/agent-redact.ts +1 -1
- package/src/agent-runner/commands/dispatch.ts +10 -10
- package/src/agent-runner/commands/lifecycle.ts +2 -2
- package/src/agent-runner/commands/prompt.ts +19 -19
- package/src/agent-runner/credentials.ts +5 -5
- package/src/agent-runner/inbox.ts +1 -1
- package/src/agent-runner/lifecycle-milestones.ts +3 -3
- package/src/agent-runner/lifecycle-phase.ts +2 -2
- package/src/agent-runner/liveness-exchange.ts +4 -10
- package/src/agent-runner/registration.ts +8 -8
- package/src/agent-runner/runner-context.ts +9 -14
- package/src/agent-runner/session-mirror.ts +12 -12
- package/src/agent-runner/task-claims.ts +2 -2
- package/src/agent-runner/token-usage-coverage.ts +1 -1
- package/src/agent-runner/turn-report.ts +12 -12
- package/src/api-budget-accounting.ts +3 -3
- package/src/area-memory.ts +6 -6
- package/src/artifact-path-materialize.ts +1 -1
- package/src/async-guard.ts +5 -5
- package/src/attachment-cache.ts +17 -22
- package/src/background-script-reconcile.ts +6 -6
- package/src/bounded-log.ts +1 -1
- package/src/busy-reconciler.ts +22 -22
- package/src/claim-tracker.ts +3 -3
- package/src/claude-settings.ts +8 -8
- package/src/claude-system-prompt.ts +1 -1
- package/src/claude-workspace-trust.ts +1 -1
- package/src/config.ts +62 -61
- package/src/context-boundary.ts +5 -5
- package/src/context-command.ts +3 -3
- package/src/control-server.ts +32 -32
- package/src/delivery-result.ts +2 -2
- package/src/effort-command.ts +2 -2
- package/src/empty-turn.ts +22 -14
- package/src/free-port.ts +1 -1
- package/src/gate-run-config.ts +3 -3
- package/src/host-asset-plan.ts +24 -17
- package/src/host-claude-config-worker.ts +2 -2
- package/src/host-sources.ts +2 -2
- package/src/index.ts +16 -16
- package/src/launch-assembly.ts +94 -96
- package/src/liveness.ts +15 -15
- package/src/logger.ts +3 -3
- package/src/mcp-outbox.ts +3 -3
- package/src/mcp-preflight.ts +2 -2
- package/src/mcp-source-selection.ts +2 -2
- package/src/mcp-tool-deferral.ts +1 -1
- package/src/mcp-vault-env.ts +2 -2
- package/src/message-body-config/config.ts +4 -4
- package/src/mid-turn-delivery.ts +2 -2
- package/src/native-memory-lever.ts +8 -8
- package/src/outbox.ts +15 -15
- package/src/pending-permission-store.ts +1 -1
- package/src/profile-home.ts +19 -19
- package/src/profile-projection.ts +26 -24
- package/src/project-instructions.ts +5 -5
- package/src/provider-auth-warning.ts +17 -0
- package/src/provider-double.ts +1 -1
- package/src/provider-hold.ts +3 -3
- package/src/provider-home-marker.ts +1 -1
- package/src/provider-home-tombstone.ts +1 -1
- package/src/provider-input-queue.ts +2 -2
- package/src/provider-integration.ts +15 -13
- package/src/provider-setup.ts +15 -3
- package/src/providers.ts +1 -1
- package/src/provisioning.ts +37 -40
- package/src/question-decision.ts +2 -2
- package/src/quota.ts +0 -6
- package/src/rate-limit.ts +24 -13
- package/src/refused-prompt-store.ts +2 -2
- package/src/relay-injection-events.ts +2 -2
- package/src/relay-instructions.ts +5 -5
- package/src/relay-mcp-proxy.long-turn-e2e.ts +13 -13
- package/src/relay-mcp-proxy.ts +57 -58
- package/src/relay-mcp.ts +55 -69
- package/src/reply-obligation-cache.ts +9 -9
- package/src/response-capture-report.ts +9 -9
- package/src/response-routing.ts +1 -1
- package/src/runner-core.ts +209 -222
- package/src/runner-exit.ts +1 -1
- package/src/runner-helpers.ts +10 -10
- package/src/runner-insights.ts +4 -2
- package/src/runner-mcp-proxy.ts +3 -3
- package/src/runner-spawn-config.ts +3 -3
- package/src/runtime-token-file.ts +1 -1
- package/src/runtime-token-remint.ts +1 -1
- package/src/service-access.ts +2 -2
- package/src/session-destroy.ts +1 -1
- package/src/session-insights.ts +1 -1
- package/src/session-scratch.ts +24 -37
- package/src/spawn-input.ts +6 -6
- package/src/startup-config-fetch.ts +4 -4
- package/src/subreaper.ts +3 -5
- package/src/test-run-dedup.ts +13 -13
- package/src/transcript-context.ts +2 -2
- package/src/transport-errors.ts +1 -1
- package/src/turn-opener.ts +1 -1
- package/src/turn-reconcile-event.ts +2 -2
- package/src/vault-proxy-hop.ts +2 -2
- package/src/web-tools.ts +2 -2
- package/agent-bin/agent-relay-browser +0 -10
- package/plugins/claude/skills/disconnect/SKILL.md +0 -16
- package/plugins/claude/skills/pair/SKILL.md +0 -30
- package/plugins/codex/skills/disconnect/SKILL.md +0 -15
- package/plugins/codex/skills/pair/SKILL.md +0 -29
- package/src/adapters/provider-quota-harvest.ts +0 -76
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
3
|
# POST a JSON body over stdin. A curl argv argument is capped by the kernel (~128 KiB), and a
|
|
4
|
-
# prompt or assistant message can exceed it; curl then never starts and the report is lost
|
|
4
|
+
# prompt or assistant message can exceed it; curl then never starts and the report is lost.
|
|
5
5
|
relay_post_json() {
|
|
6
6
|
local port="$1" path="$2" max_time="${3:-}"
|
|
7
7
|
curl -fsS ${max_time:+--max-time "$max_time"} -X POST "http://127.0.0.1:${port}${path}" \
|
|
@@ -21,11 +21,11 @@ relay_post_status() {
|
|
|
21
21
|
# responsible for producing valid JSON (see relay_background_task_metadata). Empty/unset
|
|
22
22
|
# omits the field entirely so ordinary status posts are unaffected.
|
|
23
23
|
local metadata_json="${9:-}"
|
|
24
|
-
local port="${
|
|
24
|
+
local port="${HUMWICK_RUNNER_PORT:-}"
|
|
25
25
|
[ -z "$port" ] && return 0
|
|
26
26
|
[ -z "$status" ] && return 0
|
|
27
27
|
local body="{\"status\":\"$(relay_json_escape "$status")\""
|
|
28
|
-
[ -n "${
|
|
28
|
+
[ -n "${HUMWICK_PROVIDER_SESSION_ID:-}" ] && body="${body},\"providerSessionId\":\"$(relay_json_escape "${HUMWICK_PROVIDER_SESSION_ID-}")\""
|
|
29
29
|
[ -n "$reason" ] && body="${body},\"reason\":\"$(relay_json_escape "$reason")\""
|
|
30
30
|
[ -n "$clear_kind" ] && body="${body},\"clear\":[\"$(relay_json_escape "$clear_kind")\"]"
|
|
31
31
|
[ -n "$timeline_status" ] && body="${body},\"timeline\":{\"status\":\"$(relay_json_escape "$timeline_status")\"}"
|
|
@@ -42,7 +42,7 @@ relay_post_status_clearing_subagents() {
|
|
|
42
42
|
relay_post_status "$1" "${2:-}" "${3:-}" "${4:-}" "${5:-}" "${6:-}" subagent
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
#
|
|
45
|
+
# count user background shells still running at turn end.
|
|
46
46
|
# Claude's Stop payload carries a `background_tasks` array (CC v2.1.157+) listing
|
|
47
47
|
# shells launched with run_in_background that have not yet finished. It can also
|
|
48
48
|
# include the long-lived Humwick monitor; that monitor is notification
|
|
@@ -56,9 +56,9 @@ relay_background_task_count() {
|
|
|
56
56
|
n="$(printf '%s' "$input" | jq -r '
|
|
57
57
|
(.background_tasks // [])
|
|
58
58
|
| map(select(
|
|
59
|
-
(.name // "")
|
|
59
|
+
((.name // "") | test("^(humwick|agent-relay)-runner-monitor$") | not)
|
|
60
60
|
and ((.command // "") | contains("relay-monitor.ts") | not)
|
|
61
|
-
and ((.command // "") |
|
|
61
|
+
and ((.command // "") | test("(humwick|agent-relay)-runner-monitor") | not)
|
|
62
62
|
))
|
|
63
63
|
| length
|
|
64
64
|
' 2>/dev/null)"
|
|
@@ -69,7 +69,7 @@ relay_background_task_count() {
|
|
|
69
69
|
local compact
|
|
70
70
|
compact="$(printf '%s' "$input" | tr -d '[:space:]')"
|
|
71
71
|
if printf '%s' "$compact" | grep -q '"background_tasks":\[{'; then
|
|
72
|
-
if printf '%s' "$compact" | grep -qE '("name":"agent-relay-runner-monitor"|relay-monitor\.ts)' \
|
|
72
|
+
if printf '%s' "$compact" | grep -qE '("name":"(humwick|agent-relay)-runner-monitor"|relay-monitor\.ts)' \
|
|
73
73
|
&& ! printf '%s' "$compact" | grep -q '},{'; then
|
|
74
74
|
printf '0'
|
|
75
75
|
return 0
|
|
@@ -80,7 +80,7 @@ relay_background_task_count() {
|
|
|
80
80
|
fi
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
#
|
|
83
|
+
# cap on the per-task detail list — bounds task count and per-command
|
|
84
84
|
# length so a pathological Stop payload (a runaway number of concurrent background shells,
|
|
85
85
|
# or an absurdly long command string) can never bloat the persisted work record.
|
|
86
86
|
# relay_background_task_count above (the busy/label signal) stays uncapped; only this
|
|
@@ -88,7 +88,7 @@ relay_background_task_count() {
|
|
|
88
88
|
RELAY_BACKGROUND_TASK_LIST_MAX=20
|
|
89
89
|
RELAY_BACKGROUND_TASK_COMMAND_MAX=200
|
|
90
90
|
|
|
91
|
-
#
|
|
91
|
+
# shape Claude's Stop-payload background_tasks[] into the compact
|
|
92
92
|
# {id, command} detail list the runner keeps as the background-script work record's
|
|
93
93
|
# `metadata.backgroundTasks` — the per-task identity a watchdog can name directly instead
|
|
94
94
|
# of just a count. The runner (not this hook) stamps startedAt: each hook invocation is a
|
|
@@ -105,9 +105,9 @@ relay_background_task_list() {
|
|
|
105
105
|
--argjson cmdmax "$RELAY_BACKGROUND_TASK_COMMAND_MAX" '
|
|
106
106
|
(.background_tasks // [])
|
|
107
107
|
| map(select(
|
|
108
|
-
(.name // "")
|
|
108
|
+
((.name // "") | test("^(humwick|agent-relay)-runner-monitor$") | not)
|
|
109
109
|
and ((.command // "") | contains("relay-monitor.ts") | not)
|
|
110
|
-
and ((.command // "") |
|
|
110
|
+
and ((.command // "") | test("(humwick|agent-relay)-runner-monitor") | not)
|
|
111
111
|
))
|
|
112
112
|
| map({
|
|
113
113
|
id: ((.id // "") | tostring),
|
|
@@ -120,7 +120,7 @@ relay_background_task_list() {
|
|
|
120
120
|
|
|
121
121
|
# Wrap a capped task list into the `metadata` object relay_post_status expects, or print
|
|
122
122
|
# nothing when there is nothing to report (no task list, empty list, or jq-less host) so an
|
|
123
|
-
# ordinary background-script status post is byte-for-byte what it was before
|
|
123
|
+
# ordinary background-script status post is byte-for-byte what it was before.
|
|
124
124
|
relay_background_task_metadata() {
|
|
125
125
|
local list_json="${1:-}"
|
|
126
126
|
case "$list_json" in
|
|
@@ -129,14 +129,14 @@ relay_background_task_metadata() {
|
|
|
129
129
|
esac
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
#
|
|
133
|
-
# critical path
|
|
132
|
+
# end the provider turn (idle) clearing any stale subagent work — the
|
|
133
|
+
# critical path — then report the live background-script snapshot so an
|
|
134
134
|
# agent whose turn ended while a `run_in_background` shell keeps running stays
|
|
135
135
|
# busy instead of falsely reading idle. A single full-snapshot post per turn-end:
|
|
136
136
|
# >0 → busy(background-script); 0 → idle(background-script) which clears any prior.
|
|
137
137
|
relay_post_turn_end_status() {
|
|
138
138
|
local bg_count="${1:-0}"
|
|
139
|
-
#
|
|
139
|
+
# the capped {id, command} detail list (see relay_background_task_list) — only
|
|
140
140
|
# relevant on the busy branch; a bg_count of 0 always resolves to idle regardless.
|
|
141
141
|
local bg_tasks_json="${2:-[]}"
|
|
142
142
|
local bg_metadata
|
|
@@ -149,7 +149,7 @@ relay_post_turn_end_status() {
|
|
|
149
149
|
esac
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
# Report a provider usage/rate-limit hold to the runner
|
|
152
|
+
# Report a provider usage/rate-limit hold to the runner. The runner turns
|
|
153
153
|
# this into a `providerState: blocked` (reason rate_limit), posts a chat notice,
|
|
154
154
|
# and auto-resumes the agent once the window resets. reset_time (unix seconds,
|
|
155
155
|
# only "if available" per CC StopFailure docs) drives the resume; the runner falls
|
|
@@ -158,7 +158,7 @@ relay_post_rate_limit() {
|
|
|
158
158
|
local error_type="${1:-}"
|
|
159
159
|
local reset_time="${2:-}"
|
|
160
160
|
local error_message="${3:-}"
|
|
161
|
-
local port="${
|
|
161
|
+
local port="${HUMWICK_RUNNER_PORT:-}"
|
|
162
162
|
[ -z "$port" ] && return 0
|
|
163
163
|
local body="{\"errorType\":\"$(relay_json_escape "$error_type")\""
|
|
164
164
|
case "$reset_time" in ''|*[!0-9]*) ;; *) body="${body},\"resetTime\":${reset_time}" ;; esac
|
|
@@ -179,7 +179,7 @@ relay_post_session_turn() {
|
|
|
179
179
|
local transcript_path="${1:-}"
|
|
180
180
|
local last_assistant_message="${2:-}"
|
|
181
181
|
local prompt_id="${3:-}"
|
|
182
|
-
local port="${
|
|
182
|
+
local port="${HUMWICK_RUNNER_PORT:-}"
|
|
183
183
|
[ -z "$port" ] && return 0
|
|
184
184
|
# transcript_path is the primary capture source, but the Stop payload can omit it
|
|
185
185
|
# (known CC edge case). last_assistant_message is the fallback for exactly that —
|
|
@@ -191,7 +191,7 @@ relay_post_session_turn() {
|
|
|
191
191
|
[ "$body" != "{" ] && body="${body},"
|
|
192
192
|
body="${body}\"lastAssistantMessage\":${last_assistant_message}"
|
|
193
193
|
fi
|
|
194
|
-
# prompt_id
|
|
194
|
+
# prompt_id: Claude's native turn identifier — the stable id the runner
|
|
195
195
|
# uses instead of a minted random UUID, and the deterministic key it slices the
|
|
196
196
|
# transcript by for full-mode enrichment.
|
|
197
197
|
if [ -n "$prompt_id" ]; then
|
|
@@ -202,11 +202,11 @@ relay_post_session_turn() {
|
|
|
202
202
|
printf '%s' "$body" | relay_post_json "$port" /session-turn >/dev/null 2>&1 || true
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
# Forward a hook's raw stdin JSON unchanged, stamped with when the hook ran
|
|
205
|
+
# Forward a hook's raw stdin JSON unchanged, stamped with when the hook ran. No jq: a
|
|
206
206
|
# missing jq must not silence the prompt hook the runner confirms submits by.
|
|
207
207
|
relay_forward_hook_payload() {
|
|
208
208
|
local path="${1:-}" payload="${2:-}" fired_at="${3:-}"
|
|
209
|
-
local port="${
|
|
209
|
+
local port="${HUMWICK_RUNNER_PORT:-}"
|
|
210
210
|
[ -z "$port" ] && return 0
|
|
211
211
|
case "$fired_at" in ''|*[!0-9]*) fired_at="" ;; esac
|
|
212
212
|
printf '%s' "$payload" | relay_post_json "$port" "${path}${fired_at:+?firedAt=${fired_at}}" 3 >/dev/null 2>&1 || true
|
|
@@ -223,13 +223,13 @@ relay_post_user_prompt() {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
relay_post_session_boundary() {
|
|
226
|
-
# Pre-destroy seam
|
|
227
|
-
# (compact/clear/logout/…) so it can run end-of-session work — the
|
|
226
|
+
# Pre-destroy seam: tell the runner a context reset or termination is imminent
|
|
227
|
+
# (compact/clear/logout/…) so it can run end-of-session work — the context-gathering
|
|
228
228
|
# ratio capture — from the full transcript before the invasive op. Fire-and-forget; the
|
|
229
229
|
# transcript path is optional (the runner falls back to the last path it saw).
|
|
230
230
|
local transcript_path="${1:-}"
|
|
231
231
|
local reason="${2:-}"
|
|
232
|
-
local port="${
|
|
232
|
+
local port="${HUMWICK_RUNNER_PORT:-}"
|
|
233
233
|
[ -z "$port" ] && return 0
|
|
234
234
|
local body="{"
|
|
235
235
|
[ -n "$transcript_path" ] && body="${body}\"transcriptPath\":\"$(relay_json_escape "$transcript_path")\""
|
|
@@ -241,14 +241,14 @@ relay_post_session_boundary() {
|
|
|
241
241
|
printf '%s' "$body" | relay_post_json "$port" /session-boundary 3 >/dev/null 2>&1 || true
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
# --- Hook FATAL surfacing
|
|
244
|
+
# --- Hook FATAL surfacing -------------------------------------------
|
|
245
245
|
# A hook that dies unexpectedly must never be silent. relay_install_hook_guard
|
|
246
246
|
# arms an ERR trap that reports the failure FATAL to the runner control port,
|
|
247
247
|
# which logs it to the dashboard-surfaced per-agent log. Best-effort and bounded
|
|
248
248
|
# (--max-time 2) so the report itself can never blow the hook's timeout budget.
|
|
249
249
|
relay_hook_fatal_report() {
|
|
250
250
|
local hook="${1:-unknown}" detail="${2:-}"
|
|
251
|
-
local port="${
|
|
251
|
+
local port="${HUMWICK_RUNNER_PORT:-}"
|
|
252
252
|
[ -z "$port" ] && return 0
|
|
253
253
|
local body="{\"hook\":\"$(relay_json_escape "$hook")\",\"error\":\"$(relay_json_escape "$detail")\"}"
|
|
254
254
|
printf '%s' "$body" | relay_post_json "$port" /hook-fatal 2 >/dev/null 2>&1 || true
|
|
@@ -258,7 +258,7 @@ relay_install_hook_guard() {
|
|
|
258
258
|
RELAY_HOOK_NAME="${1:-unknown}"
|
|
259
259
|
# The pipe adapter owns native turn boundaries and final-result reporting.
|
|
260
260
|
# Permission and safety hooks still run; TUI lifecycle hooks must not compete.
|
|
261
|
-
if [ "${
|
|
261
|
+
if [ "${HUMWICK_CLAUDE_TRANSPORT:-}" = "stream-json" ]; then
|
|
262
262
|
case "$RELAY_HOOK_NAME" in
|
|
263
263
|
user-prompt-submit|stop|stop-failure|session-end|pre-compact|post-compact|subagent-start|subagent-stop) exit 0 ;;
|
|
264
264
|
esac
|
|
@@ -271,12 +271,12 @@ relay_install_hook_guard() {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
relay_pending_reply_stop_decision() {
|
|
274
|
-
local port="${
|
|
274
|
+
local port="${HUMWICK_RUNNER_PORT:-}"
|
|
275
275
|
[ -z "$port" ] && return 0
|
|
276
276
|
local response
|
|
277
277
|
# --max-time guards the Claude Stop hook's 5s budget: a slow runner/server (e.g. an
|
|
278
278
|
# un-indexed obligation query) must never block past the timeout, or Claude SIGKILLs
|
|
279
|
-
# the hook before it clears the turn -> stuck "busy"
|
|
279
|
+
# the hook before it clears the turn -> stuck "busy". On timeout: no block.
|
|
280
280
|
response="$(curl -fsS --max-time 2 "http://127.0.0.1:${port}/reply-obligations/claude-stop" 2>/dev/null || true)"
|
|
281
281
|
case "$response" in
|
|
282
282
|
*'"decision":"block"'*|*'"decision": "block"'*) ;;
|
|
@@ -337,10 +337,10 @@ relay_json_escape() {
|
|
|
337
337
|
# an isolated workspace whose branch has committed work not yet integrated into
|
|
338
338
|
# base. Prints nothing otherwise — so plain chat, shared-mode, and no-change
|
|
339
339
|
# sessions never pay context/token cost. Reads the workspace from
|
|
340
|
-
#
|
|
340
|
+
# HUMWICK_WORKSPACE_JSON (set by the orchestrator at spawn) and does a local,
|
|
341
341
|
# network-free git count. Always returns 0 (never aborts its caller).
|
|
342
342
|
relay_review_reminder_text() {
|
|
343
|
-
local ws="${
|
|
343
|
+
local ws="${HUMWICK_WORKSPACE_JSON:-}"
|
|
344
344
|
[ -z "$ws" ] && return 0
|
|
345
345
|
local mode worktree base id branch ahead
|
|
346
346
|
mode="$(relay_json_string_field mode "$ws")"
|
|
@@ -356,7 +356,7 @@ relay_review_reminder_text() {
|
|
|
356
356
|
ahead="$(git -C "$worktree" rev-list --count "${base}..HEAD" 2>/dev/null || echo 0)"
|
|
357
357
|
case "$ahead" in ''|*[!0-9]*) ahead=0 ;; esac
|
|
358
358
|
[ "$ahead" -gt 0 ] || return 0
|
|
359
|
-
printf '[
|
|
359
|
+
printf '[humwick] You have %s committed change(s) on `%s` that are not yet integrated into base. If your task is complete, request review so Humwick can auto-land it: POST /api/workspaces/%s/actions with {"action":"request-review"}. If you are still working, ignore this — it only appears while there is unmerged committed work.' \
|
|
360
360
|
"$ahead" "${branch:-this branch}" "$id"
|
|
361
361
|
return 0
|
|
362
362
|
}
|
|
@@ -11,7 +11,7 @@ case "$reason" in
|
|
|
11
11
|
clear)
|
|
12
12
|
relay_post_timeline_status idle provider-turn subagent clearing-context
|
|
13
13
|
# /clear wipes the context — the most common work boundary for a long-lived agent.
|
|
14
|
-
# Capture this segment's Insights
|
|
14
|
+
# Capture this segment's Insights before it's gone.
|
|
15
15
|
relay_post_session_boundary "$transcript_path" clear
|
|
16
16
|
;;
|
|
17
17
|
resume)
|
|
@@ -19,7 +19,7 @@ case "$reason" in
|
|
|
19
19
|
;;
|
|
20
20
|
logout|prompt_input_exit|bypass_permissions_disabled|other|*)
|
|
21
21
|
relay_post_status_clearing_subagents offline
|
|
22
|
-
# Real session termination: capture end-of-session Insights
|
|
22
|
+
# Real session termination: capture end-of-session Insights. Order after the
|
|
23
23
|
# status post is arbitrary — the runner reads the transcript file regardless.
|
|
24
24
|
relay_post_session_boundary "$transcript_path" "$reason"
|
|
25
25
|
;;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
source "${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/hooks/relay-status.sh"
|
|
4
4
|
relay_install_hook_guard session-start
|
|
5
|
-
#
|
|
5
|
+
# a stamped check-in proves the hook path works before the first prompt is submitted.
|
|
6
6
|
relay_forward_hook_payload /hook-check "{}" "$(relay_now_ms)"
|
|
7
7
|
|
|
8
8
|
payload="$(cat || true)"
|
|
@@ -13,8 +13,8 @@ case "$source_kind" in
|
|
|
13
13
|
clear)
|
|
14
14
|
# A queued /clear can commit after the runner's confirmation timed out. Restore
|
|
15
15
|
# its continuation in THIS fresh context, even if the backstop hit the old one.
|
|
16
|
-
if [ -n "${
|
|
17
|
-
continuation="$(printf '{"source":"clear","providerSessionId":"%s"}' "$(relay_json_escape "${
|
|
16
|
+
if [ -n "${HUMWICK_RUNNER_PORT:-}" ]; then
|
|
17
|
+
continuation="$(printf '{"source":"clear","providerSessionId":"%s"}' "$(relay_json_escape "${HUMWICK_PROVIDER_SESSION_ID:-}")" | relay_post_json "${HUMWICK_RUNNER_PORT-}" /session-start 3 || true)"
|
|
18
18
|
fi
|
|
19
19
|
relay_post_timeline_status idle provider-turn subagent context-cleared
|
|
20
20
|
;;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Shared "is this plain simple commands?" pre-filter for the PreToolUse guards
|
|
1
|
+
// Shared "is this plain simple commands?" pre-filter for the PreToolUse guards.
|
|
2
2
|
// Heredocs, comments, functions, control-flow keywords, continuations, substitutions and
|
|
3
3
|
// per-line unbalanced quotes (a string spanning lines) all mean "unsure": the caller allows.
|
|
4
4
|
// `do`/`done` are deliberately not keywords here: they never appear without a header keyword
|
|
@@ -13,11 +13,11 @@ error_message="$(relay_json_string_field error_message "$payload")"
|
|
|
13
13
|
[ -z "$error_message" ] && error_message="$(relay_json_string_field error_details "$payload")"
|
|
14
14
|
[ -z "$error_message" ] && error_message="$(relay_json_string_field last_assistant_message "$payload")"
|
|
15
15
|
|
|
16
|
-
#
|
|
16
|
+
# a usage/rate-limit stall is transient and time-bounded — hold the agent
|
|
17
17
|
# (providerState blocked) and auto-resume at reset, instead of silently going idle.
|
|
18
18
|
# The disambiguator for billing_error is reset_time: a subscription rolling-window
|
|
19
19
|
# limit carries one (and is auto-resumable); true credit exhaustion does not (fatal).
|
|
20
|
-
#
|
|
20
|
+
# a refused proxy credential (ERR_PROXY_TUNNEL) also posts here; the runner classifies it.
|
|
21
21
|
case "$error" in
|
|
22
22
|
rate_limit|overloaded|server_error|api_error)
|
|
23
23
|
relay_post_rate_limit "$error" "$reset_time" "$error_message"
|
|
@@ -3,15 +3,15 @@ set -euo pipefail
|
|
|
3
3
|
source "${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/hooks/relay-status.sh"
|
|
4
4
|
relay_install_hook_guard stop
|
|
5
5
|
|
|
6
|
-
# Clearing the turn's busy state is the critical path
|
|
6
|
+
# Clearing the turn's busy state is the critical path. Register it on EXIT
|
|
7
7
|
# so it runs even if a side-call below fails or times out under `set -e`. The one
|
|
8
8
|
# exception is the reply-obligation block path, which deliberately keeps the agent
|
|
9
9
|
# busy to answer — it opts out via the flag before exiting.
|
|
10
10
|
_relay_clear_idle_on_exit=1
|
|
11
11
|
# bg_count/bg_tasks_json are read from the payload below and consumed by the trap at exit
|
|
12
|
-
# time
|
|
12
|
+
# time: the turn-end status carries the live background-script snapshot so an agent
|
|
13
13
|
# whose turn ended while a run_in_background shell keeps running stays busy instead of
|
|
14
|
-
# falsely reading idle. bg_tasks_json
|
|
14
|
+
# falsely reading idle. bg_tasks_json is the per-task {id, command} detail behind
|
|
15
15
|
# that snapshot — the ledger the runner enriches with startedAt and a watchdog can read.
|
|
16
16
|
bg_count=0
|
|
17
17
|
bg_tasks_json="[]"
|
|
@@ -22,11 +22,11 @@ stop_hook_active="$(relay_json_bool_field stop_hook_active "$payload")"
|
|
|
22
22
|
bg_count="$(relay_background_task_count "$payload")"
|
|
23
23
|
bg_tasks_json="$(relay_background_task_list "$payload")"
|
|
24
24
|
|
|
25
|
-
# Session-mirror capture is orthogonal to reply-obligation gating
|
|
25
|
+
# Session-mirror capture is orthogonal to reply-obligation gating: a recursive Stop
|
|
26
26
|
# (stop_hook_active=true) must NOT suppress capturing this turn's response, or the live mirror
|
|
27
27
|
# silently drops frames. Always post the turn; only the reply-obligation decision is gated below.
|
|
28
28
|
last_assistant_msg="$(echo "$payload" | jq -c '.last_assistant_message // empty' 2>/dev/null || true)"
|
|
29
|
-
# prompt_id
|
|
29
|
+
# prompt_id: Claude's own native turn identifier, correlating this Stop with
|
|
30
30
|
# the same UUID stamped on every transcript entry of the turn (and the id the
|
|
31
31
|
# UserPromptSubmit hook reported at turn start) — the stable replacement for a
|
|
32
32
|
# runner-minted random UUID.
|
|
@@ -35,7 +35,7 @@ relay_post_session_turn "$(relay_json_string_field transcript_path "$payload")"
|
|
|
35
35
|
|
|
36
36
|
if [ "$stop_hook_active" != "true" ]; then
|
|
37
37
|
# `|| true`: under `set -e`, a non-zero from the obligation check must never abort
|
|
38
|
-
# the hook before the idle-clear — clearing the turn is the critical path
|
|
38
|
+
# the hook before the idle-clear — clearing the turn is the critical path.
|
|
39
39
|
stop_decision="$(relay_pending_reply_stop_decision || true)"
|
|
40
40
|
if [ -n "$stop_decision" ]; then
|
|
41
41
|
_relay_clear_idle_on_exit=0
|
|
@@ -125,8 +125,10 @@ exports.skillDenial = async function skillDenial(payload, name) {
|
|
|
125
125
|
}
|
|
126
126
|
for (const plugin of plugins) {
|
|
127
127
|
const manifest = json(path.join(plugin, '.claude-plugin/plugin.json'));
|
|
128
|
-
|
|
129
|
-
const
|
|
128
|
+
// Claude infers a manifestless plugin's namespace from its directory in either generation.
|
|
129
|
+
const namespace = manifest.name ?? path.basename(plugin);
|
|
130
|
+
if (typeof namespace !== 'string') throw new Error(`Cannot establish plugin namespace in ${plugin}`);
|
|
131
|
+
const prefix = `${namespace}:`;
|
|
130
132
|
inspect(path.join(plugin, 'skills'), false, prefix);
|
|
131
133
|
if (!stat(path.join(plugin, 'skills')) && manifest.skills === undefined) inspect(path.join(plugin, 'SKILL.md'), false, prefix);
|
|
132
134
|
if (manifest.commands === undefined) inspect(path.join(plugin, 'commands'), true, prefix);
|
|
@@ -145,7 +147,7 @@ exports.skillDenial = async function skillDenial(payload, name) {
|
|
|
145
147
|
if (typeof definition.content === 'string') inspectText(definition.content, `${plugin}:${command}`, command, prefix, definition.model);
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
|
-
//
|
|
150
|
+
// the role-provisioned copy in the managed home and its identical repo source are one skill; differing same-name skills stay ambiguous.
|
|
149
151
|
const sourced = new Set([...candidates.values()].filter((entry) => !entry.managed).map((entry) => entry.digest));
|
|
150
152
|
for (const [key, entry] of candidates) if (entry.managed && sourced.has(entry.digest)) candidates.delete(key);
|
|
151
153
|
if (candidates.size !== 1) return `skill ${name} ${candidates.size ? 'resolves ambiguously' : 'cannot be resolved'}; refusing unverified execution`;
|
|
@@ -154,7 +156,7 @@ exports.skillDenial = async function skillDenial(payload, name) {
|
|
|
154
156
|
if (typeof skill.context === 'string' && skill.context.trim() === 'fork' && (typeof skill.model !== 'string' || !skill.model.trim() || skill.model.trim() === 'inherit')) return `fork skill ${name} requires an explicit model under capped profiles`;
|
|
155
157
|
};
|
|
156
158
|
|
|
157
|
-
//
|
|
158
|
-
function skillSources() { return JSON.parse(process.env.
|
|
159
|
-
function harnessSubagentsDisabled() { return process.env.
|
|
159
|
+
// standalone CJS config adapters; this file is shipped beside subagent-guard.cjs.
|
|
160
|
+
function skillSources() { return JSON.parse((process.env.HUMWICK_CLAUDE_SKILL_SOURCES) || '{}'); }
|
|
161
|
+
function harnessSubagentsDisabled() { return (process.env.HUMWICK_HARNESS_SUBAGENTS) === '0'; }
|
|
160
162
|
exports.harnessSubagentsDisabled = harnessSubagentsDisabled;
|
|
@@ -10,10 +10,10 @@ payload="$(cat || true)"
|
|
|
10
10
|
agent_id="$(relay_json_string_field agent_id "$payload")"
|
|
11
11
|
agent_type="$(relay_json_string_field agent_type "$payload")"
|
|
12
12
|
|
|
13
|
-
if [ -n "${
|
|
14
|
-
printf '%s' "$payload" | relay_post_json "$
|
|
13
|
+
if [ -n "${HUMWICK_RUNNER_PORT:-}" ] && [ -n "$(relay_json_string_field agent_transcript_path "$payload")" ]; then
|
|
14
|
+
printf '%s' "$payload" | relay_post_json "${HUMWICK_RUNNER_PORT-}" /subagent-usage 4 >/dev/null
|
|
15
15
|
fi
|
|
16
16
|
|
|
17
|
-
if [ "${
|
|
17
|
+
if [ "${HUMWICK_CLAUDE_TRANSPORT:-}" != "stream-json" ]; then
|
|
18
18
|
relay_post_status idle subagent "${agent_id:-subagent}" "$agent_type" "$agent_type"
|
|
19
19
|
fi
|
|
@@ -4,7 +4,7 @@ source "${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/
|
|
|
4
4
|
relay_install_hook_guard user-prompt-submit
|
|
5
5
|
fired_at="$(relay_now_ms)"
|
|
6
6
|
payload="$(cat || true)"
|
|
7
|
-
# prompt_id
|
|
7
|
+
# prompt_id: Claude's own native turn identifier for the prompt starting
|
|
8
8
|
# now — the same UUID the Stop hook reports and the transcript stamps on every
|
|
9
9
|
# entry of this turn. Passed as the busy report's `id` so the runner adopts it as
|
|
10
10
|
# currentTurnId instead of minting a random UUID (runner-core.ts setProviderStatus).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
// turn; the wait belongs in one detached
|
|
1
|
+
// a FOREGROUND wait (bare long `sleep`, or an until/while loop that only sleeps between checks) holds the
|
|
2
|
+
// turn; the wait belongs in one detached humwick_run. STRICT ALLOWLIST: the command must be exactly
|
|
3
3
|
// setup* ( sleep D | (until|while) CHECK; do (sleep D | echo|printf|date|true)+; done ) rest*
|
|
4
4
|
// Anything else (loops doing work, `for`, nesting, `$(...)`, background `&`, unknown durations) is not matched,
|
|
5
5
|
// so every parse doubt fails open to "allow".
|
|
@@ -110,7 +110,7 @@ export function waitLoopDenial(command: string): string | null {
|
|
|
110
110
|
for (let depth = 0; depth <= MAX_UNWRAP_DEPTH && candidate !== undefined; depth += 1) {
|
|
111
111
|
candidate = candidate.replace(TIMEOUT_PREFIX, ""); // `timeout 600 <cmd>` is transparent
|
|
112
112
|
if (isForegroundWait(lexCommandLine(candidate))) {
|
|
113
|
-
return "Do not hold the turn on a foreground wait. Run it with the
|
|
113
|
+
return "Do not hold the turn on a foreground wait. Run it with the humwick_run MCP tool (detach: true, waitMs: 0) as ONE poll loop with a deadline "
|
|
114
114
|
+ "(e.g. `end=$((SECONDS+1800)); until <check>; do [ $SECONDS -ge $end ] && exit 1; sleep 30; done`), then end your turn with no text; "
|
|
115
115
|
+ `Relay wakes you once when it finishes. Command: ${command}`;
|
|
116
116
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
|
-
"name": "
|
|
3
|
+
"name": "humwick-runner-monitor",
|
|
4
4
|
"command": "bun run \"${CLAUDE_PLUGIN_ROOT}/monitors/relay-monitor.ts\"",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Humwick runner message delivery pipe"
|
|
6
6
|
}
|
|
7
7
|
]
|