instar 1.3.478 → 1.3.479
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/.claude/skills/autonomous/SKILL.md +10 -0
- package/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh +418 -23
- package/.claude/skills/autonomous/scripts/setup-autonomous.sh +29 -0
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +25 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +35 -3
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +1 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +20 -20
- package/src/scaffold/templates.ts +1 -0
- package/upgrades/1.3.479.md +53 -0
- package/upgrades/side-effects/autonomous-completion-real-checks.md +88 -0
|
@@ -100,6 +100,10 @@ Write this content:
|
|
|
100
100
|
<!-- COMPLETION_CONDITION_DEFAULT — the Write-tool template defaults to a verifiable
|
|
101
101
|
completion_condition (judged by an INDEPENDENT model), NOT the self-declared
|
|
102
102
|
promise. The promise is the recorded fallback. Spec: AUTONOMOUS-COMPLETION-DISCIPLINE.md -->
|
|
103
|
+
<!-- REALCHECK_VERIFY — the template ALSO supports an OPTIONAL verification_command (ACT-152):
|
|
104
|
+
when set, the stop-hook RUNS it on a met:true verdict and only allows the exit if it ALSO
|
|
105
|
+
passes (fail/timeout → keep working). This sentinel is the PostUpdateMigrator marker that
|
|
106
|
+
re-deploys this SKILL.md to existing agents. Spec: autonomous-completion-real-checks.md -->
|
|
103
107
|
|
|
104
108
|
```markdown
|
|
105
109
|
---
|
|
@@ -120,6 +124,12 @@ completion_mode: condition # "condition" (default) | "promise-fallback"
|
|
|
120
124
|
promise_fallback_reason: "" # one line, REQUIRED iff completion_mode == promise-fallback
|
|
121
125
|
completion_promise: "ALL_TASKS_COMPLETE" # retained as the fallback token
|
|
122
126
|
hard_blocker_nonce: "{a random per-run token — get via: openssl rand -hex 8}"
|
|
127
|
+
# OPTIONAL real-check (ACT-152): a command that must exit 0 before the run may stop. When set, a
|
|
128
|
+
# met:true verdict RUNS it and gates the exit on it (fail/timeout → keep working). Omit if the
|
|
129
|
+
# goal isn't checkable by a single command. work_dir is captured by setup so a relative command
|
|
130
|
+
# (e.g. `npm test`) runs in the right tree; override with verification_cwd if the build is elsewhere.
|
|
131
|
+
# verification_command: "npm test --silent"
|
|
132
|
+
# verification_cwd: "/path/to/build/worktree"
|
|
123
133
|
---
|
|
124
134
|
|
|
125
135
|
# Autonomous Session
|
|
@@ -40,6 +40,10 @@ for _arg in "$@"; do [[ "$_arg" == "--codex" ]] && IS_CODEX=1; done
|
|
|
40
40
|
|
|
41
41
|
# hook-capability: codex-stdout-json-safe — see emit() below (migration marker; bumped so
|
|
42
42
|
# existing #28 installs re-deploy this fixed hook even though they already have CODEX_LOOP_ENABLED).
|
|
43
|
+
# hook-capability: REALCHECK_VERIFY — ACT-152 real-check gate (realcheck_gate runs an opt-in
|
|
44
|
+
# verification_command on a met:true verdict and gates the exit on it; fail/timeout/breaker-open →
|
|
45
|
+
# keep working, the safe direction). This sentinel is the PostUpdateMigrator marker that re-deploys
|
|
46
|
+
# this hook to existing agents that carry COMPLETION_DISCIPLINE but not REALCHECK_VERIFY.
|
|
43
47
|
# emit — human-facing approve/status text. In codex mode the Stop hook's STDOUT must be
|
|
44
48
|
# ONLY valid decision-JSON (the `{"decision":"block",...}` case far below) or empty:
|
|
45
49
|
# codex rejects ANY other stdout as "invalid stop hook JSON output" and reports the stop
|
|
@@ -187,6 +191,17 @@ fm_get() {
|
|
|
187
191
|
local key="$1"
|
|
188
192
|
printf '%s\n' "$FRONTMATTER" | grep "^${key}:" | head -1 | sed "s/^${key}: *//" | tr -d '"' || true
|
|
189
193
|
}
|
|
194
|
+
# Quote-PRESERVING field read — identical to fm_get but WITHOUT the `tr -d '"'`,
|
|
195
|
+
# so a value containing literal quotes (e.g. a verification_command with quoted
|
|
196
|
+
# args) survives intact. Strips ONLY a single pair of wrapping double-quotes (the
|
|
197
|
+
# YAML frontmatter the setup script writes as `key: "value"`), never inner quotes.
|
|
198
|
+
fm_get_raw() {
|
|
199
|
+
local key="$1" val
|
|
200
|
+
val=$(printf '%s\n' "$FRONTMATTER" | grep "^${key}:" | head -1 | sed "s/^${key}: *//" || true)
|
|
201
|
+
# Strip one leading + one trailing double-quote if both present (preserve inner).
|
|
202
|
+
if [[ "$val" == \"*\" ]]; then val="${val#\"}"; val="${val%\"}"; fi
|
|
203
|
+
printf '%s' "$val"
|
|
204
|
+
}
|
|
190
205
|
|
|
191
206
|
ACTIVE=$(fm_get active)
|
|
192
207
|
if [[ "$ACTIVE" != "true" ]]; then
|
|
@@ -213,6 +228,13 @@ COMPLETION_PROMISE=$(fm_get completion_promise)
|
|
|
213
228
|
COMPLETION_CONDITION=$(fm_get completion_condition)
|
|
214
229
|
GOAL_MODE=$(fm_get goal_mode) # "native" = the framework's own /goal loop drives completion
|
|
215
230
|
RUN_GOAL=$(fm_get goal)
|
|
231
|
+
# Real-check verification (ACT-152) — opt-in declared command + its build dir.
|
|
232
|
+
# Read QUOTE-PRESERVING: a verification_command may contain literal quotes that
|
|
233
|
+
# fm_get's `tr -d '"'` would mangle. Absent on older state files → empty → the
|
|
234
|
+
# real-check gate self-disables for that run (byte-identical to today).
|
|
235
|
+
VERIFICATION_COMMAND=$(fm_get_raw verification_command)
|
|
236
|
+
VERIFICATION_CWD=$(fm_get_raw verification_cwd)
|
|
237
|
+
WORK_DIR=$(fm_get_raw work_dir)
|
|
216
238
|
# COMPLETION_DISCIPLINE — per-run nonce that authenticates a <hard-blocker> exit
|
|
217
239
|
# marker (mirrors the completion_promise exact-match guard). Absent on older
|
|
218
240
|
# state files → the marker branch self-disables for that run (no false exit).
|
|
@@ -244,25 +266,57 @@ try:
|
|
|
244
266
|
bc = a.get('judgeFailCooldownMs', 600000)
|
|
245
267
|
mc = a.get('markerFieldMaxChars', 500)
|
|
246
268
|
rb = a.get('hardBlockerLogRotateBytes', 1048576)
|
|
247
|
-
|
|
269
|
+
# Real-check verification (ACT-152) — nested under completionDiscipline.
|
|
270
|
+
rc = a.get('realCheck') or {}
|
|
271
|
+
rce = rc.get('enabled', True)
|
|
272
|
+
rct = rc.get('timeoutMs', 120000)
|
|
273
|
+
try:
|
|
274
|
+
rct = int(rct)
|
|
275
|
+
except Exception:
|
|
276
|
+
rct = 120000
|
|
277
|
+
if rct < 5000:
|
|
278
|
+
rct = 5000
|
|
279
|
+
rcm = rc.get('maxChars', 2000)
|
|
280
|
+
rcc = rc.get('captureBytes', 65536)
|
|
281
|
+
rcbt = rc.get('failBreakerThreshold', 3)
|
|
282
|
+
rcbw = rc.get('failWindowMs', 600000)
|
|
283
|
+
rcbc = rc.get('failCooldownMs', 600000)
|
|
284
|
+
print('%s %d %d %d %d %d %d %s %d %d %d %d %d %d' % (
|
|
248
285
|
'1' if en else '0', jt,
|
|
249
286
|
int(bt) if str(bt).isdigit() else 3,
|
|
250
287
|
int(bw) if str(bw).isdigit() else 600000,
|
|
251
288
|
int(bc) if str(bc).isdigit() else 600000,
|
|
252
289
|
int(mc) if str(mc).isdigit() else 500,
|
|
253
290
|
int(rb) if str(rb).isdigit() else 1048576,
|
|
291
|
+
'1' if rce else '0', rct,
|
|
292
|
+
int(rcm) if str(rcm).isdigit() else 2000,
|
|
293
|
+
int(rcc) if str(rcc).isdigit() else 65536,
|
|
294
|
+
int(rcbt) if str(rcbt).isdigit() else 3,
|
|
295
|
+
int(rcbw) if str(rcbw).isdigit() else 600000,
|
|
296
|
+
int(rcbc) if str(rcbc).isdigit() else 600000,
|
|
254
297
|
))
|
|
255
298
|
except Exception:
|
|
256
|
-
print('1 35000 3 600000 600000 500 1048576')
|
|
257
|
-
" 2>/dev/null || echo "1 35000 3 600000 600000 500 1048576")
|
|
258
|
-
read -r CD_ENABLED JUDGE_TIMEOUT_MS CD_BREAKER_THRESHOLD CD_BREAKER_WINDOW_MS CD_BREAKER_COOLDOWN_MS CD_MARKER_MAX_CHARS CD_LOG_ROTATE_BYTES <<< "$CD_CFG"
|
|
299
|
+
print('1 35000 3 600000 600000 500 1048576 1 120000 2000 65536 3 600000 600000')
|
|
300
|
+
" 2>/dev/null || echo "1 35000 3 600000 600000 500 1048576 1 120000 2000 65536 3 600000 600000")
|
|
301
|
+
read -r CD_ENABLED JUDGE_TIMEOUT_MS CD_BREAKER_THRESHOLD CD_BREAKER_WINDOW_MS CD_BREAKER_COOLDOWN_MS CD_MARKER_MAX_CHARS CD_LOG_ROTATE_BYTES RC_ENABLED RC_TIMEOUT_MS RC_MAX_CHARS RC_CAPTURE_BYTES RC_BREAKER_THRESHOLD RC_BREAKER_WINDOW_MS RC_BREAKER_COOLDOWN_MS <<< "$CD_CFG"
|
|
259
302
|
[[ "$CD_ENABLED" =~ ^[01]$ ]] || CD_ENABLED=1
|
|
260
303
|
[[ "$JUDGE_TIMEOUT_MS" =~ ^[0-9]+$ ]] || JUDGE_TIMEOUT_MS=35000
|
|
261
304
|
# curl -m takes SECONDS; convert ms→s (ceil), floor 5s.
|
|
262
305
|
JUDGE_TIMEOUT_S=$(( (JUDGE_TIMEOUT_MS + 999) / 1000 ))
|
|
263
306
|
[[ $JUDGE_TIMEOUT_S -lt 5 ]] && JUDGE_TIMEOUT_S=5
|
|
307
|
+
# ── Real-check verification dials (ACT-152) — validated + ms→s for the command timeout ──
|
|
308
|
+
[[ "$RC_ENABLED" =~ ^[01]$ ]] || RC_ENABLED=1
|
|
309
|
+
[[ "$RC_TIMEOUT_MS" =~ ^[0-9]+$ ]] || RC_TIMEOUT_MS=120000
|
|
310
|
+
RC_TIMEOUT_S=$(( (RC_TIMEOUT_MS + 999) / 1000 ))
|
|
311
|
+
[[ $RC_TIMEOUT_S -lt 5 ]] && RC_TIMEOUT_S=5
|
|
312
|
+
[[ "$RC_MAX_CHARS" =~ ^[0-9]+$ ]] || RC_MAX_CHARS=2000
|
|
313
|
+
[[ "$RC_CAPTURE_BYTES" =~ ^[0-9]+$ ]] || RC_CAPTURE_BYTES=65536
|
|
314
|
+
[[ "$RC_BREAKER_THRESHOLD" =~ ^[0-9]+$ ]] || RC_BREAKER_THRESHOLD=3
|
|
315
|
+
[[ "$RC_BREAKER_WINDOW_MS" =~ ^[0-9]+$ ]] || RC_BREAKER_WINDOW_MS=600000
|
|
316
|
+
[[ "$RC_BREAKER_COOLDOWN_MS" =~ ^[0-9]+$ ]] || RC_BREAKER_COOLDOWN_MS=600000
|
|
264
317
|
# logs/ resolves against the agent home we cd'd into above (CWD is the agent home).
|
|
265
318
|
HARD_BLOCKER_LOG="logs/autonomous-hard-blocker.jsonl"
|
|
319
|
+
REALCHECK_LOG="logs/autonomous-realcheck.jsonl"
|
|
266
320
|
|
|
267
321
|
# ── Layer A: notify-on-stop (2026-05-27 silent-stalls postmortem, Task 2) ──────
|
|
268
322
|
# When an autonomous run reaches a TERMINAL exit (completion / duration / emergency),
|
|
@@ -743,6 +797,329 @@ hb_leak_hit() {
|
|
|
743
797
|
printf '%s' "$1" | grep -qiE 'sk-[a-zA-Z0-9]{16,}|xox[baprs]-[a-zA-Z0-9-]{8,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|AIza[0-9A-Za-z_-]{20,}|eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}' 2>/dev/null && echo 1 || echo 0
|
|
744
798
|
}
|
|
745
799
|
|
|
800
|
+
# ── REAL-CHECK VERIFICATION (ACT-152) — helpers ──────────────────────────────
|
|
801
|
+
# A failing/flaky/mis-scoped verification_command would otherwise re-run on EVERY
|
|
802
|
+
# met:true verdict for the whole duration — and because the judge fires first to
|
|
803
|
+
# produce the met verdict, it also re-spends the LLM judge every iteration. The P19
|
|
804
|
+
# brake (§4) reuses the CD_BACKOFF_STATE sidecar with SIBLING counters so a stuck
|
|
805
|
+
# command is bounded to ~one judge+command cycle per cooldown window.
|
|
806
|
+
|
|
807
|
+
# realcheck_breaker_open → echoes 1 if the real-check breaker is within its cooldown
|
|
808
|
+
# window, else 0. MUST fail CLOSED (echo 0 = "run the check", the SAFE direction for
|
|
809
|
+
# this feature) on ANY sidecar read/parse error — mirroring cd_breaker_open's
|
|
810
|
+
# echo-0-on-failure default. (Fail-OPEN here would silently suppress the check and let
|
|
811
|
+
# an unverified transcript-met exit through — the exact failure this feature prevents.)
|
|
812
|
+
realcheck_breaker_open() {
|
|
813
|
+
[[ "$RC_ENABLED" != "1" ]] && { echo 0; return; }
|
|
814
|
+
local now fails winstart
|
|
815
|
+
now=$(date +%s)
|
|
816
|
+
fails=$(jq -r '.realCheckFailures // 0' "$CD_BACKOFF_STATE" 2>/dev/null || echo 0)
|
|
817
|
+
winstart=$(jq -r '.realCheckFailWindowStart // 0' "$CD_BACKOFF_STATE" 2>/dev/null || echo 0)
|
|
818
|
+
[[ "$fails" =~ ^[0-9]+$ ]] || fails=0
|
|
819
|
+
[[ "$winstart" =~ ^[0-9]+$ ]] || winstart=0
|
|
820
|
+
local cooldown_s=$(( RC_BREAKER_COOLDOWN_MS / 1000 ))
|
|
821
|
+
if [[ $fails -ge $RC_BREAKER_THRESHOLD ]] && [[ $winstart -gt 0 ]] && [[ $(( now - winstart )) -lt $cooldown_s ]]; then
|
|
822
|
+
echo 1; return
|
|
823
|
+
fi
|
|
824
|
+
echo 0
|
|
825
|
+
}
|
|
826
|
+
# Record a real-check failure into the sidecar (atomic .tmp.$$ + mv; preserves other keys).
|
|
827
|
+
realcheck_record_failure() {
|
|
828
|
+
[[ "$RC_ENABLED" != "1" ]] && return 0
|
|
829
|
+
local now fails winstart
|
|
830
|
+
now=$(date +%s)
|
|
831
|
+
fails=$(jq -r '.realCheckFailures // 0' "$CD_BACKOFF_STATE" 2>/dev/null || echo 0)
|
|
832
|
+
winstart=$(jq -r '.realCheckFailWindowStart // 0' "$CD_BACKOFF_STATE" 2>/dev/null || echo 0)
|
|
833
|
+
[[ "$fails" =~ ^[0-9]+$ ]] || fails=0
|
|
834
|
+
[[ "$winstart" =~ ^[0-9]+$ ]] || winstart=0
|
|
835
|
+
local window_s=$(( RC_BREAKER_WINDOW_MS / 1000 ))
|
|
836
|
+
if [[ $winstart -le 0 ]] || [[ $(( now - winstart )) -ge $window_s ]]; then
|
|
837
|
+
winstart=$now; fails=1
|
|
838
|
+
else
|
|
839
|
+
fails=$(( fails + 1 ))
|
|
840
|
+
fi
|
|
841
|
+
if [[ -f "$CD_BACKOFF_STATE" ]]; then
|
|
842
|
+
jq --argjson f "$fails" --argjson w "$winstart" '.realCheckFailures=$f | .realCheckFailWindowStart=$w' "$CD_BACKOFF_STATE" \
|
|
843
|
+
> "${CD_BACKOFF_STATE}.tmp.$$" 2>/dev/null && mv "${CD_BACKOFF_STATE}.tmp.$$" "$CD_BACKOFF_STATE" || true
|
|
844
|
+
else
|
|
845
|
+
printf '{"realCheckFailures":%s,"realCheckFailWindowStart":%s}\n' "$fails" "$winstart" > "$CD_BACKOFF_STATE" 2>/dev/null || true
|
|
846
|
+
fi
|
|
847
|
+
# Cross the threshold → raise ONE deduped Attention item (Close the Loop / P19 cap).
|
|
848
|
+
if [[ $fails -ge $RC_BREAKER_THRESHOLD ]]; then
|
|
849
|
+
realcheck_raise_attention "$fails"
|
|
850
|
+
fi
|
|
851
|
+
}
|
|
852
|
+
# Reset the real-check breaker on a PASS (atomic .tmp.$$ + mv).
|
|
853
|
+
realcheck_reset() {
|
|
854
|
+
[[ "$RC_ENABLED" != "1" ]] && return 0
|
|
855
|
+
[[ -f "$CD_BACKOFF_STATE" ]] || return 0
|
|
856
|
+
jq '.realCheckFailures=0 | .realCheckFailWindowStart=0' "$CD_BACKOFF_STATE" \
|
|
857
|
+
> "${CD_BACKOFF_STATE}.tmp.$$" 2>/dev/null && mv "${CD_BACKOFF_STATE}.tmp.$$" "$CD_BACKOFF_STATE" || true
|
|
858
|
+
}
|
|
859
|
+
# Raise ONE /ack-able Attention item when the real-check breaker crosses the threshold
|
|
860
|
+
# (Close the Loop). Deduped per the Bounded Notification Surface: source-tagged
|
|
861
|
+
# `autonomous-realcheck-stuck`, one per run via a started-at-keyed id. Best-effort.
|
|
862
|
+
realcheck_raise_attention() {
|
|
863
|
+
[[ "$RC_ENABLED" != "1" ]] && return 0
|
|
864
|
+
local fails="$1"
|
|
865
|
+
if [[ -n "${INSTAR_HOOK_ATTENTION_RECORD:-}" ]]; then
|
|
866
|
+
printf '{"id":"autonomous-realcheck-stuck-%s-%s","title":"Autonomous real check failing repeatedly","fails":%s}\n' \
|
|
867
|
+
"${REPORT_TOPIC:-none}" "$(printf '%s' "${STARTED_AT:-}" | tr -cd '0-9')" "${fails:-0}" \
|
|
868
|
+
>> "$INSTAR_HOOK_ATTENTION_RECORD" 2>/dev/null || true
|
|
869
|
+
return 0
|
|
870
|
+
fi
|
|
871
|
+
local at_port at_auth at_id
|
|
872
|
+
at_port=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('port',4040))" 2>/dev/null || echo 4040)
|
|
873
|
+
at_auth=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null || echo "")
|
|
874
|
+
at_id="autonomous-realcheck-stuck-${REPORT_TOPIC:-none}-$(printf '%s' "${STARTED_AT:-}" | tr -cd '0-9')"
|
|
875
|
+
jq -nc \
|
|
876
|
+
--arg id "$at_id" \
|
|
877
|
+
--arg title "Autonomous real check failing repeatedly — \"$(goal_snippet)\"" \
|
|
878
|
+
--arg summary "The declared real check (${VERIFICATION_COMMAND}) has failed ${fails} times — likely an authoring problem (wrong directory, stale, or testing the wrong thing). The run keeps working (bounded by the duration limit) until it passes or you fix it." \
|
|
879
|
+
'{id:$id, title:$title, summary:$summary, priority:"medium", source:"autonomous-realcheck-stuck", sourceContext:"autonomous-realcheck-stuck", category:"autonomous"}' \
|
|
880
|
+
| curl -s -m 8 -H "Authorization: Bearer $at_auth" -H 'Content-Type: application/json' \
|
|
881
|
+
--data-binary @- "http://localhost:${at_port}/attention" >/dev/null 2>&1 || true
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
# realcheck_audit_row — append ONE JSONL row to REALCHECK_LOG per run (same size-rotate
|
|
885
|
+
# as the hard-blocker log). Args: outcome exitCode durationMs command cwd breakerOpen.
|
|
886
|
+
realcheck_audit_row() {
|
|
887
|
+
local outcome="$1" excode="$2" durms="$3" cmd="$4" cwd="$5" bopen="$6"
|
|
888
|
+
mkdir -p logs 2>/dev/null || true
|
|
889
|
+
if [[ -f "$REALCHECK_LOG" ]]; then
|
|
890
|
+
local rc_sz; rc_sz=$(stat -c %s "$REALCHECK_LOG" 2>/dev/null || stat -f %z "$REALCHECK_LOG" 2>/dev/null || echo 0)
|
|
891
|
+
[[ "$rc_sz" =~ ^[0-9]+$ ]] || rc_sz=0
|
|
892
|
+
[[ $rc_sz -ge $CD_LOG_ROTATE_BYTES ]] && mv -f "$REALCHECK_LOG" "${REALCHECK_LOG}.1" 2>/dev/null || true
|
|
893
|
+
fi
|
|
894
|
+
local cmd_clamped; cmd_clamped=$(printf '%s' "$cmd" | cut -c1-"${RC_MAX_CHARS}")
|
|
895
|
+
jq -nc \
|
|
896
|
+
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg topic "${REPORT_TOPIC:-}" \
|
|
897
|
+
--arg iter "${ITERATION:-}" --arg command "$cmd_clamped" --arg cwd "$cwd" \
|
|
898
|
+
--argjson exitCode "$([[ "$excode" =~ ^-?[0-9]+$ ]] && echo "$excode" || echo 'null')" \
|
|
899
|
+
--argjson durationMs "$([[ "$durms" =~ ^[0-9]+$ ]] && echo "$durms" || echo 0)" \
|
|
900
|
+
--arg outcome "$outcome" \
|
|
901
|
+
--argjson breakerOpen "$([[ "$bopen" == "1" ]] && echo true || echo false)" \
|
|
902
|
+
'{ts:$ts,topic:$topic,iteration:$iter,command:$command,cwd:$cwd,exitCode:$exitCode,durationMs:$durationMs,outcome:$outcome,breakerOpen:$breakerOpen}' \
|
|
903
|
+
>> "$REALCHECK_LOG" 2>/dev/null || true
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
# realcheck_destructive — echoes 1 if the raw command string matches a high-signal
|
|
907
|
+
# destructive shape (L12 pre-block, §6.3). Literal-shape guard on the raw string only;
|
|
908
|
+
# honestly bypassable by obfuscation (the §6.1/§6.2 honest-mistake posture). Catches a
|
|
909
|
+
# fat-fingered/compacted-agent destructive "check", NOT an adversarial agent.
|
|
910
|
+
realcheck_destructive() {
|
|
911
|
+
local cmd="$1"
|
|
912
|
+
# rm -rf | git reset --hard | git clean -f | git push --force(/-f) | truncate (`:>`) |
|
|
913
|
+
# redirect into /dev | mkfs | a write redirect into the instar source tree.
|
|
914
|
+
if printf '%s' "$cmd" | grep -qE 'rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*f|rm[[:space:]]+-[a-zA-Z]*f[a-zA-Z]*r|git[[:space:]]+reset[[:space:]]+--hard|git[[:space:]]+clean[[:space:]]+-[a-zA-Z]*f|git[[:space:]]+push[[:space:]].*(--force|-f\b)|:[[:space:]]*>|>[[:space:]]*/dev|mkfs' 2>/dev/null; then
|
|
915
|
+
echo 1; return
|
|
916
|
+
fi
|
|
917
|
+
# Write redirect (`> path` / `>> path`) into the instar source tree (src/ at the repo root).
|
|
918
|
+
if printf '%s' "$cmd" | grep -qE '>>?[[:space:]]*(\./)?src/' 2>/dev/null; then
|
|
919
|
+
echo 1; return
|
|
920
|
+
fi
|
|
921
|
+
echo 0
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
# run_verification(cmd, cwd) — the core. Runs the declared command in a SUBSHELL with
|
|
925
|
+
# the resolved CWD (so the hook's own anchored CWD is undisturbed), a scrubbed env, and
|
|
926
|
+
# a portable, GUARANTEED timeout. Sets RC_OUTCOME (pass|fail|timeout|refused-destructive|
|
|
927
|
+
# unavailable), RC_EXIT, RC_DUR_MS, and RC_SANITIZED_OUTPUT (sanitize→UTF-8-scrub→
|
|
928
|
+
# leak-scrub→clamp). CARDINAL INVARIANT: ANY failure mode routes to keep-working — only
|
|
929
|
+
# RC_OUTCOME==pass allows the exit; everything else (including a missing perl) is a
|
|
930
|
+
# keep-working FAIL/unavailable. There is NO path here that CAUSES a premature exit.
|
|
931
|
+
RC_OUTCOME=""
|
|
932
|
+
RC_EXIT=""
|
|
933
|
+
RC_DUR_MS=0
|
|
934
|
+
RC_SANITIZED_OUTPUT=""
|
|
935
|
+
run_verification() {
|
|
936
|
+
local cmd="$1" cwd="$2"
|
|
937
|
+
RC_OUTCOME=""; RC_EXIT=""; RC_DUR_MS=0; RC_SANITIZED_OUTPUT=""
|
|
938
|
+
|
|
939
|
+
# ── TEST SEAM: short-circuit the real command in CI (no real exec). ──
|
|
940
|
+
if [[ -n "${INSTAR_HOOK_VERIFY_OVERRIDE:-}" ]]; then
|
|
941
|
+
case "$INSTAR_HOOK_VERIFY_OVERRIDE" in
|
|
942
|
+
pass) RC_OUTCOME="pass"; RC_EXIT=0 ;;
|
|
943
|
+
fail) RC_OUTCOME="fail"; RC_EXIT=1; RC_SANITIZED_OUTPUT="simulated failure output" ;;
|
|
944
|
+
timeout) RC_OUTCOME="timeout"; RC_EXIT=124; RC_SANITIZED_OUTPUT="simulated timeout" ;;
|
|
945
|
+
unavailable) RC_OUTCOME="unavailable"; RC_EXIT=127; RC_SANITIZED_OUTPUT="simulated unavailable (no timeout binary)" ;;
|
|
946
|
+
*) RC_OUTCOME="fail"; RC_EXIT=1 ;;
|
|
947
|
+
esac
|
|
948
|
+
realcheck_audit_row "$RC_OUTCOME" "$RC_EXIT" "0" "$cmd" "$cwd" "0"
|
|
949
|
+
return 0
|
|
950
|
+
fi
|
|
951
|
+
|
|
952
|
+
# ── L12 destructive-pattern pre-block (refuse → unavailable → keep working). ──
|
|
953
|
+
if [[ "$(realcheck_destructive "$cmd")" == "1" ]]; then
|
|
954
|
+
RC_OUTCOME="refused-destructive"; RC_EXIT=126
|
|
955
|
+
RC_SANITIZED_OUTPUT="[real check refused: the declared command matched a high-signal destructive pattern and was NOT run]"
|
|
956
|
+
echo "[autonomous] real-check REFUSED (destructive pattern) — keeping working" >&2
|
|
957
|
+
realcheck_audit_row "refused-destructive" "126" "0" "$cmd" "$cwd" "0"
|
|
958
|
+
return 0
|
|
959
|
+
fi
|
|
960
|
+
|
|
961
|
+
# ── Scrubbed env: fixed PATH; strip authToken + npm_config_* + NODE_OPTIONS so a
|
|
962
|
+
# failing check that dumps `env` can't self-leak the agent's own bearer token. Build
|
|
963
|
+
# a proper `env -u VAR` arg array (each `-u` and VAR as SEPARATE args). ──
|
|
964
|
+
local rc_path="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
965
|
+
local -a rc_env_args=(-u authToken -u NODE_OPTIONS)
|
|
966
|
+
local _v
|
|
967
|
+
for _v in $(compgen -e 2>/dev/null); do
|
|
968
|
+
case "$_v" in
|
|
969
|
+
npm_config_*) rc_env_args+=(-u "$_v") ;;
|
|
970
|
+
esac
|
|
971
|
+
done
|
|
972
|
+
|
|
973
|
+
# ── Portable timeout LADDER (§5.1). Each rung bounds the command; NONE runs unbounded. ──
|
|
974
|
+
# Resolve the runner to its ABSOLUTE path during detection (with the hook's full PATH)
|
|
975
|
+
# and invoke it by absolute path inside the scrubbed-PATH subshell — otherwise a runner
|
|
976
|
+
# living outside the fixed PATH (e.g. Homebrew's /opt/homebrew/bin/timeout on macOS)
|
|
977
|
+
# would resolve here but be `command not found` under the scrubbed env. The fixed PATH
|
|
978
|
+
# still scrubs the USER command's env; only the runner binary is referenced absolutely.
|
|
979
|
+
# A test seam (INSTAR_HOOK_VERIFY_NO_TIMEOUT=1) forces the timeout/gtimeout rungs to be
|
|
980
|
+
# treated as absent so the perl path can be exercised even on a box that has GNU timeout.
|
|
981
|
+
local rc_start rc_end rc_raw rc_code rc_runner="" rc_runner_bin=""
|
|
982
|
+
rc_start=$(date +%s)
|
|
983
|
+
if [[ "${INSTAR_HOOK_VERIFY_NO_TIMEOUT:-0}" != "1" ]] && command -v timeout >/dev/null 2>&1; then
|
|
984
|
+
rc_runner="timeout"; rc_runner_bin="$(command -v timeout)"
|
|
985
|
+
elif [[ "${INSTAR_HOOK_VERIFY_NO_TIMEOUT:-0}" != "1" ]] && command -v gtimeout >/dev/null 2>&1; then
|
|
986
|
+
rc_runner="gtimeout"; rc_runner_bin="$(command -v gtimeout)"
|
|
987
|
+
elif command -v perl >/dev/null 2>&1; then
|
|
988
|
+
rc_runner="perl"; rc_runner_bin="$(command -v perl)"
|
|
989
|
+
else
|
|
990
|
+
# No bounded runner at all → UNAVAILABLE → keep working. NEVER run unbounded.
|
|
991
|
+
RC_OUTCOME="unavailable"; RC_EXIT=127
|
|
992
|
+
RC_SANITIZED_OUTPUT="[real check unavailable: no timeout/gtimeout/perl on PATH to bound the command — keeping working]"
|
|
993
|
+
echo "[autonomous] real-check UNAVAILABLE: no timeout/gtimeout/perl to bound the command — keeping working" >&2
|
|
994
|
+
realcheck_audit_row "unavailable" "127" "0" "$cmd" "$cwd" "0"
|
|
995
|
+
return 0
|
|
996
|
+
fi
|
|
997
|
+
|
|
998
|
+
# Run in a subshell so the resolved CWD + scrubbed env never disturb the hook itself.
|
|
999
|
+
# Combined stdout+stderr is byte-capped AT THE SOURCE (head -c) so a runaway log can
|
|
1000
|
+
# never buffer whole. Exit code via ${PIPESTATUS[0]} (the command's, not head's).
|
|
1001
|
+
if [[ "$rc_runner" == "perl" ]]; then
|
|
1002
|
+
rc_raw=$(
|
|
1003
|
+
env "${rc_env_args[@]}" PATH="$rc_path" \
|
|
1004
|
+
bash -c '
|
|
1005
|
+
cd "$1" 2>/dev/null || true
|
|
1006
|
+
"$5" -e '\''my($t,@c)=@ARGV; my $p=fork; if($p==0){setpgrp(0,0); exec @c or exit 127} $SIG{ALRM}=sub{kill("-KILL",$p); exit 124}; alarm($t); waitpid($p,0); exit($?>>8)'\'' "$2" bash -c "$3" 2>&1 | head -c "$4"
|
|
1007
|
+
exit "${PIPESTATUS[0]}"
|
|
1008
|
+
' _ "$cwd" "$RC_TIMEOUT_S" "$cmd" "$RC_CAPTURE_BYTES" "$rc_runner_bin"
|
|
1009
|
+
)
|
|
1010
|
+
rc_code=$?
|
|
1011
|
+
else
|
|
1012
|
+
rc_raw=$(
|
|
1013
|
+
env "${rc_env_args[@]}" PATH="$rc_path" \
|
|
1014
|
+
bash -c '
|
|
1015
|
+
cd "$1" 2>/dev/null || true
|
|
1016
|
+
"$2" -k 5 "$3" bash -c "$4" 2>&1 | head -c "$5"
|
|
1017
|
+
exit "${PIPESTATUS[0]}"
|
|
1018
|
+
' _ "$cwd" "$rc_runner_bin" "$RC_TIMEOUT_S" "$cmd" "$RC_CAPTURE_BYTES"
|
|
1019
|
+
)
|
|
1020
|
+
rc_code=$?
|
|
1021
|
+
fi
|
|
1022
|
+
rc_end=$(date +%s)
|
|
1023
|
+
RC_DUR_MS=$(( (rc_end - rc_start) * 1000 ))
|
|
1024
|
+
RC_EXIT=$rc_code
|
|
1025
|
+
|
|
1026
|
+
# ── Output handling — PINNED ORDER: sanitize → UTF-8 scrub → leak-scrub → clamp. ──
|
|
1027
|
+
local rc_san rc_utf8 rc_clamped
|
|
1028
|
+
# 1a. sanitize: strip control chars, collapse whitespace (hb_sanitize clamps to
|
|
1029
|
+
# CD_MARKER_MAX_CHARS, so re-implement the strip WITHOUT that clamp here).
|
|
1030
|
+
rc_san=$(printf '%s' "$rc_raw" | tr -d '\000-\010\013\014\016-\037' | tr '\n\r\t' ' ' | sed 's/ */ /g; s/^ *//; s/ *$//')
|
|
1031
|
+
# 1b. UTF-8 scrub: a source head -c byte-cap can split a multibyte char, leaving a lone
|
|
1032
|
+
# continuation byte that would later break jq --arg. iconv -c drops invalid bytes;
|
|
1033
|
+
# fall back to an LC_ALL=C printable-only filter when iconv is absent.
|
|
1034
|
+
if command -v iconv >/dev/null 2>&1; then
|
|
1035
|
+
rc_utf8=$(printf '%s' "$rc_san" | iconv -c -f utf-8 -t utf-8 2>/dev/null || printf '%s' "$rc_san" | LC_ALL=C tr -cd '\11\12\15\40-\176')
|
|
1036
|
+
else
|
|
1037
|
+
rc_utf8=$(printf '%s' "$rc_san" | LC_ALL=C tr -cd '\11\12\15\40-\176')
|
|
1038
|
+
fi
|
|
1039
|
+
# 2. leak-scrub on the SANITIZED text, BEFORE clamp (a credential split across the
|
|
1040
|
+
# clamp boundary can't evade the regex). hb_leak_hit patterns + the agent's own
|
|
1041
|
+
# authToken literal + a generic Bearer token.
|
|
1042
|
+
local rc_leak=0 rc_auth_val
|
|
1043
|
+
if [[ "$(hb_leak_hit "$rc_utf8")" == "1" ]]; then rc_leak=1; fi
|
|
1044
|
+
rc_auth_val=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null || echo "")
|
|
1045
|
+
if [[ $rc_leak -eq 0 ]] && [[ -n "$rc_auth_val" ]] && printf '%s' "$rc_utf8" | grep -qF "$rc_auth_val" 2>/dev/null; then rc_leak=1; fi
|
|
1046
|
+
if [[ $rc_leak -eq 0 ]] && printf '%s' "$rc_utf8" | grep -qE 'Bearer [A-Za-z0-9._-]{20,}' 2>/dev/null; then rc_leak=1; fi
|
|
1047
|
+
if [[ $rc_leak -eq 1 ]]; then
|
|
1048
|
+
rc_utf8="[output withheld: possible credential in check output]"
|
|
1049
|
+
fi
|
|
1050
|
+
# 3. clamp to RC_MAX_CHARS.
|
|
1051
|
+
rc_clamped=$(printf '%s' "$rc_utf8" | cut -c1-"${RC_MAX_CHARS}")
|
|
1052
|
+
RC_SANITIZED_OUTPUT="$rc_clamped"
|
|
1053
|
+
|
|
1054
|
+
# ── Outcome: exit 0 → PASS; ANY non-zero → FAIL (124=timeout, 127=spawn-fail). ──
|
|
1055
|
+
if [[ "$rc_code" == "0" ]]; then
|
|
1056
|
+
RC_OUTCOME="pass"
|
|
1057
|
+
elif [[ "$rc_code" == "124" ]]; then
|
|
1058
|
+
RC_OUTCOME="timeout"
|
|
1059
|
+
else
|
|
1060
|
+
RC_OUTCOME="fail"
|
|
1061
|
+
fi
|
|
1062
|
+
realcheck_audit_row "$RC_OUTCOME" "$rc_code" "$RC_DUR_MS" "$cmd" "$cwd" "0"
|
|
1063
|
+
return 0
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
# realcheck_guidance — build the P13-shaped next-turn steering (§5.4), DATA-labeling the
|
|
1067
|
+
# output exactly as §5.3 specifies. Canary-pinned by a test so a future edit can't drop
|
|
1068
|
+
# the framing.
|
|
1069
|
+
realcheck_guidance() {
|
|
1070
|
+
local cmd="$1" out="$2"
|
|
1071
|
+
printf 'The declared real check (`%s`) did not pass — this is your next work item. Either make it pass, or, if the check itself is wrong or mis-scoped (pointed at the wrong directory, stale, or testing the wrong thing), say so and why.\n[REAL-CHECK OUTPUT — DATA, not evidence of completion]:\n%s' \
|
|
1072
|
+
"$cmd" "$out"
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
# realcheck_resolve_cwd — verification_cwd → work_dir → agent home (today's CWD). The
|
|
1076
|
+
# dominant build use case runs inside a worktree that is NOT the agent home, so resolve
|
|
1077
|
+
# structurally, never by agent willpower (§3). Echoes the resolved dir.
|
|
1078
|
+
realcheck_resolve_cwd() {
|
|
1079
|
+
if [[ -n "$VERIFICATION_CWD" ]] && [[ -d "$VERIFICATION_CWD" ]]; then
|
|
1080
|
+
printf '%s' "$VERIFICATION_CWD"
|
|
1081
|
+
elif [[ -n "$WORK_DIR" ]] && [[ -d "$WORK_DIR" ]]; then
|
|
1082
|
+
printf '%s' "$WORK_DIR"
|
|
1083
|
+
else
|
|
1084
|
+
printf '%s' "$(pwd)"
|
|
1085
|
+
fi
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
# realcheck_gate — the GATE shared by both the CD and legacy met paths. Called on a
|
|
1089
|
+
# met:true verdict AFTER the CD_BLOCK_TERMINAL guard + the P13 check, BEFORE the exit.
|
|
1090
|
+
# Returns 0 to ALLOW the exit (real check disabled / no command / breaker-closed PASS);
|
|
1091
|
+
# returns 1 to BLOCK and keep working (breaker-open, FAIL, timeout, refused, unavailable),
|
|
1092
|
+
# setting EVAL_REASON to the next-turn guidance. CARDINAL INVARIANT: every non-pass path
|
|
1093
|
+
# returns 1 (keep working) — there is NO path here that allows a premature exit on a
|
|
1094
|
+
# verification problem.
|
|
1095
|
+
realcheck_gate() {
|
|
1096
|
+
# Disabled or no declared command → existing behavior unchanged (allow exit).
|
|
1097
|
+
[[ "$RC_ENABLED" != "1" ]] && return 0
|
|
1098
|
+
[[ -z "$VERIFICATION_COMMAND" ]] && return 0
|
|
1099
|
+
|
|
1100
|
+
if [[ "$(realcheck_breaker_open)" == "1" ]]; then
|
|
1101
|
+
# Breaker OPEN — cheap continue: do NOT re-run the command (and the judge already
|
|
1102
|
+
# fired to produce this met). Surface the breaker guidance + keep working.
|
|
1103
|
+
EVAL_REASON="The declared real check (\`${VERIFICATION_COMMAND}\`) has failed repeatedly — paused re-running it for a cooldown. This is likely an authoring problem (wrong directory, stale, or testing the wrong thing): fix the check or the work, then continue. I've queued it for the operator."
|
|
1104
|
+
realcheck_audit_row "fail" "" "0" "$VERIFICATION_COMMAND" "$(realcheck_resolve_cwd)" "1"
|
|
1105
|
+
echo "[autonomous] real-check breaker OPEN — keeping working (no command run, no judge re-fire)" >&2
|
|
1106
|
+
return 1
|
|
1107
|
+
fi
|
|
1108
|
+
|
|
1109
|
+
local rc_cwd; rc_cwd=$(realcheck_resolve_cwd)
|
|
1110
|
+
run_verification "$VERIFICATION_COMMAND" "$rc_cwd"
|
|
1111
|
+
if [[ "$RC_OUTCOME" == "pass" ]]; then
|
|
1112
|
+
realcheck_reset
|
|
1113
|
+
echo "[autonomous] real-check PASSED — exit allowed (judge MET + real check PASSED)" >&2
|
|
1114
|
+
return 0
|
|
1115
|
+
fi
|
|
1116
|
+
# FAIL | timeout | refused-destructive | unavailable → record + keep working.
|
|
1117
|
+
realcheck_record_failure
|
|
1118
|
+
EVAL_REASON="$(realcheck_guidance "$VERIFICATION_COMMAND" "$RC_SANITIZED_OUTPUT")"
|
|
1119
|
+
echo "[autonomous] real-check ${RC_OUTCOME} (exit=${RC_EXIT}) — keeping working" >&2
|
|
1120
|
+
return 1
|
|
1121
|
+
}
|
|
1122
|
+
|
|
746
1123
|
if [[ "$CD_ENABLED" == "1" ]] && [[ -n "$HARD_BLOCKER_NONCE" ]] && [[ "$HARD_BLOCKER_NONCE" != "null" ]] \
|
|
747
1124
|
&& [[ -n "$CD_FINAL_TURN" ]] && [[ "$CD_FINAL_TURN" == *"<hard-blocker"* ]]; then
|
|
748
1125
|
HB_OK="false"
|
|
@@ -919,21 +1296,31 @@ if [[ "${CD_BLOCK_TERMINAL:-}" != "true" ]] && [[ -n "$COMPLETION_CONDITION" ]]
|
|
|
919
1296
|
# the completion judge (the SINGLE critical-path call). A "met" verdict here is
|
|
920
1297
|
# the judge's all-things-considered decision → allow. No standalone P13 call on
|
|
921
1298
|
# the condition path (spec §2b.2 — folded once the canary verifies the block).
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1299
|
+
# ── REAL-CHECK GATE (ACT-152): judge MET + a declared command → RUN it; the
|
|
1300
|
+
# exit is allowed ONLY if the command ALSO passes. Any fail/timeout/refused/
|
|
1301
|
+
# unavailable/breaker-open → keep working (realcheck_gate sets EVAL_REASON). ──
|
|
1302
|
+
if realcheck_gate; then
|
|
1303
|
+
emit "✅ Autonomous mode: completion condition met (independent evaluator): ${EVAL_REASON}"
|
|
1304
|
+
notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — the goal was met."
|
|
1305
|
+
rm -f "$STATE_FILE" "$CD_BACKOFF_STATE" 2>/dev/null || true
|
|
1306
|
+
exit 0
|
|
1307
|
+
fi
|
|
1308
|
+
# Real check did not pass → keep working; EVAL_REASON now carries the guidance.
|
|
1309
|
+
elif p13_stop_allowed; then
|
|
1310
|
+
# ── REAL-CHECK GATE (ACT-152) on the legacy (CD-disabled) condition path. ──
|
|
1311
|
+
if realcheck_gate; then
|
|
1312
|
+
emit "✅ Autonomous mode: completion condition met (independent evaluator): ${EVAL_REASON}"
|
|
1313
|
+
notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — the goal was met."
|
|
1314
|
+
rm -f "$STATE_FILE"
|
|
1315
|
+
exit 0
|
|
1316
|
+
fi
|
|
1317
|
+
# Real check did not pass → keep working; EVAL_REASON now carries the guidance.
|
|
1318
|
+
else
|
|
1319
|
+
# P13 "The Stop Reason Is the Work": the condition reads as met, but the stop
|
|
1320
|
+
# rests on a judgment-call / needs-engineering deferral → keep working. The
|
|
1321
|
+
# P13 steering becomes the next-turn guidance (surfaced via EVAL_REASON below).
|
|
1322
|
+
EVAL_REASON="$P13_GUIDANCE"
|
|
932
1323
|
fi
|
|
933
|
-
# P13 "The Stop Reason Is the Work": the condition reads as met, but the stop
|
|
934
|
-
# rests on a judgment-call / needs-engineering deferral → keep working. The
|
|
935
|
-
# P13 steering becomes the next-turn guidance (surfaced via EVAL_REASON below).
|
|
936
|
-
EVAL_REASON="$P13_GUIDANCE"
|
|
937
1324
|
fi
|
|
938
1325
|
# Not met / unreachable → keep working; EVAL_REASON (if any) becomes next-turn guidance.
|
|
939
1326
|
fi
|
|
@@ -949,11 +1336,19 @@ if [[ "${CD_BLOCK_TERMINAL:-}" != "true" ]] && [[ -n "$TRANSCRIPT_PATH" ]] && [[
|
|
|
949
1336
|
PROMISE_TEXT=$(printf '%s' "$LAST_OUTPUT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "")
|
|
950
1337
|
if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then
|
|
951
1338
|
if p13_stop_allowed; then
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1339
|
+
# ── REAL-CHECK GATE (ACT-152) on the legacy-promise met path (§2.2). The
|
|
1340
|
+
# gate is scoped to the completion-condition AND legacy-promise met paths;
|
|
1341
|
+
# a declared command must ALSO pass before a self-declared promise exits.
|
|
1342
|
+
if realcheck_gate; then
|
|
1343
|
+
emit "✅ Autonomous mode: Completion promise detected — <promise>$COMPLETION_PROMISE</promise>"
|
|
1344
|
+
emit " Session is free to exit. Good work!"
|
|
1345
|
+
notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — all the work is done."
|
|
1346
|
+
rm -f "$STATE_FILE"
|
|
1347
|
+
exit 0
|
|
1348
|
+
fi
|
|
1349
|
+
# Real check did not pass → keep working; EVAL_REASON carries the guidance,
|
|
1350
|
+
# surfaced via the promise-path system message below (P13_GUIDANCE path).
|
|
1351
|
+
P13_GUIDANCE="$EVAL_REASON"
|
|
957
1352
|
fi
|
|
958
1353
|
# P13: a completion promise was emitted, but the stop rests on a
|
|
959
1354
|
# judgment-call / needs-engineering deferral → keep working. P13_GUIDANCE
|
|
@@ -16,6 +16,8 @@ TASKS=""
|
|
|
16
16
|
COMPLETION_PROMISE=""
|
|
17
17
|
COMPLETION_CONDITION="" # verifiable end-state; an independent judge decides "done" (mirrors /goal). Preferred over the self-declared promise.
|
|
18
18
|
REPORT_INTERVAL="30m"
|
|
19
|
+
VERIFICATION_COMMAND="" # opt-in real check (ACT-152): the stop-hook RUNS this on a met:true verdict and gates the exit on exit-0.
|
|
20
|
+
VERIFICATION_CWD="" # directory the verification command runs in (resolves verification_cwd → work_dir → agent home).
|
|
19
21
|
|
|
20
22
|
while [[ $# -gt 0 ]]; do
|
|
21
23
|
case $1 in
|
|
@@ -51,6 +53,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
51
53
|
COMPLETION_CONDITION="$2"
|
|
52
54
|
shift 2
|
|
53
55
|
;;
|
|
56
|
+
--verification-command)
|
|
57
|
+
VERIFICATION_COMMAND="$2"
|
|
58
|
+
shift 2
|
|
59
|
+
;;
|
|
60
|
+
--verification-cwd)
|
|
61
|
+
VERIFICATION_CWD="$2"
|
|
62
|
+
shift 2
|
|
63
|
+
;;
|
|
54
64
|
--report-interval)
|
|
55
65
|
REPORT_INTERVAL="$2"
|
|
56
66
|
shift 2
|
|
@@ -158,6 +168,24 @@ else
|
|
|
158
168
|
STATE_PATH=".instar/autonomous-state.local.md"
|
|
159
169
|
fi
|
|
160
170
|
|
|
171
|
+
# ── REALCHECK_VERIFY — Real-check verification fields (ACT-152). work_dir is ALWAYS
|
|
172
|
+
# captured so the hook resolves the command's CWD structurally (verification_cwd →
|
|
173
|
+
# work_dir → agent home) — the worktree-default build runs OUTSIDE the agent home,
|
|
174
|
+
# and a relative `npm test` from the home would test the wrong tree. The verification_*
|
|
175
|
+
# fields are OMITTED when the flags are absent (→ byte-identical to today). This
|
|
176
|
+
# REALCHECK_VERIFY sentinel is the PostUpdateMigrator marker that re-deploys this setup
|
|
177
|
+
# to existing agents carrying COMPLETION_DISCIPLINE but not REALCHECK_VERIFY. ──
|
|
178
|
+
WORK_DIR="$(pwd)"
|
|
179
|
+
VERIFICATION_FIELDS="work_dir: \"$WORK_DIR\""
|
|
180
|
+
if [[ -n "$VERIFICATION_COMMAND" ]]; then
|
|
181
|
+
VERIFICATION_FIELDS="verification_command: \"$VERIFICATION_COMMAND\"
|
|
182
|
+
$VERIFICATION_FIELDS"
|
|
183
|
+
fi
|
|
184
|
+
if [[ -n "$VERIFICATION_CWD" ]]; then
|
|
185
|
+
VERIFICATION_FIELDS="verification_cwd: \"$VERIFICATION_CWD\"
|
|
186
|
+
$VERIFICATION_FIELDS"
|
|
187
|
+
fi
|
|
188
|
+
|
|
161
189
|
cat > "$STATE_PATH" <<EOF
|
|
162
190
|
---
|
|
163
191
|
active: true
|
|
@@ -176,6 +204,7 @@ level_up: $LEVEL_UP
|
|
|
176
204
|
completion_promise: "$COMPLETION_PROMISE"
|
|
177
205
|
completion_condition: "$COMPLETION_CONDITION"
|
|
178
206
|
hard_blocker_nonce: "$HARD_BLOCKER_NONCE"
|
|
207
|
+
$VERIFICATION_FIELDS
|
|
179
208
|
---
|
|
180
209
|
|
|
181
210
|
# Autonomous Session
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigDefaults.d.ts","sourceRoot":"","sources":["../../src/config/ConfigDefaults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"ConfigDefaults.d.ts","sourceRoot":"","sources":["../../src/config/ConfigDefaults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAi0BH,MAAM,MAAM,SAAS,GAAG,iBAAiB,GAAG,YAAY,CAAC;AAEzD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO7E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIlF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAuC5D"}
|
|
@@ -648,6 +648,31 @@ const SHARED_DEFAULTS = {
|
|
|
648
648
|
judgeFailCooldownMs: 600000,
|
|
649
649
|
// Per-field clamp on the <hard-blocker> marker fields before JSON-encoding.
|
|
650
650
|
markerFieldMaxChars: 500,
|
|
651
|
+
// Real-check verification (ACT-152 / autonomous-completion-real-checks spec).
|
|
652
|
+
// When an autonomous job declares a `verification_command`, the stop-hook RUNS
|
|
653
|
+
// it on a met:true verdict and gates the exit on exit-0 (fail/timeout → keep
|
|
654
|
+
// working — the SAFE direction; never causes a premature exit). A NO-OP unless
|
|
655
|
+
// a job actually declares a command, so `enabled:true` costs nothing for jobs
|
|
656
|
+
// that don't use it. Read LIVE at the chokepoint (no restart to toggle), nested
|
|
657
|
+
// under completionDiscipline so applyDefaults backfills it per-leaf to existing
|
|
658
|
+
// agents (Migration Parity — no migrateConfig block needed).
|
|
659
|
+
realCheck: {
|
|
660
|
+
enabled: true,
|
|
661
|
+
// Bounded per-run command timeout (ms). Timeout → FAIL → keep working.
|
|
662
|
+
timeoutMs: 120000,
|
|
663
|
+
// Tail-clamp on the captured output surfaced as next-turn guidance.
|
|
664
|
+
maxChars: 2000,
|
|
665
|
+
// Source-bound: cap the captured stdout+stderr at read time (a runaway
|
|
666
|
+
// command can never buffer GB into the hook before the clamp).
|
|
667
|
+
captureBytes: 65536,
|
|
668
|
+
// P19 breaker: after K consecutive real-check failures in the window, the
|
|
669
|
+
// real-check breaker OPENS for the cooldown — cheap checkbox-only continue
|
|
670
|
+
// (no judge re-fire, no command run) so a stuck/flaky command can't spin the
|
|
671
|
+
// judge + command every iteration to duration.
|
|
672
|
+
failBreakerThreshold: 3,
|
|
673
|
+
failWindowMs: 600000,
|
|
674
|
+
failCooldownMs: 600000,
|
|
675
|
+
},
|
|
651
676
|
},
|
|
652
677
|
},
|
|
653
678
|
// Cartographer doc-tree — hierarchical semantic map with git-hash staleness
|