Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agentkit/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentkit",
"version": "0.9.11",
"version": "0.9.12",
"description": "Board-aware parallel issue and PR review skills, with lifecycle hooks and a per-repository contract.",
"author": {
"name": "wrzonance",
Expand Down
2 changes: 1 addition & 1 deletion agentkit/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentkit",
"version": "0.9.11",
"version": "0.9.12",
"description": "Board-aware parallel issue and PR review skills, with lifecycle hooks and a per-repository contract.",
"author": {
"name": "wrzonance",
Expand Down
165 changes: 131 additions & 34 deletions agentkit/skills/.shared/scripts/agent-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,75 @@ if [[ -z ${BASH_VERSION:-} || ${BASH_VERSINFO[0]:-0} -lt 4 ]]; then
exit 2
fi

current_process_start() {
local pid=$1 identity
if [[ -r /proc/$pid/stat ]] && identity=$(awk '{print $22}' "/proc/$pid/stat" 2> /dev/null) &&
[[ $identity =~ ^[0-9]+$ ]]; then
printf '%s' "$identity"
return 0
fi
identity=$(LC_ALL=C ps -o lstart= -p "$pid" 2>/dev/null | tr -d '[:space:]') || return 1
[[ $identity =~ ^[[:alnum:]:]+$ ]] || return 1
printf '%s' "$identity"
}

epoch_seconds() { date -u +%s 2>/dev/null || printf '0'; }

status_agent_log() {
local requested=$1 log fallback_dir last header pid start epoch current elapsed now
[[ -f $requested && ! -L $requested && -O $requested ]] || {
printf 'agent-run: error: status requires an owned regular log: %s\n' "$requested" >&2; exit 2;
}
log=$(realpath -e -- "$requested") || exit 2
case $log in
*/.agent/logs/*.log) ;;
*)
fallback_dir=$(realpath -e -- "${TMPDIR:-/tmp}/agent-logs-$(id -u)" 2>/dev/null || true)
[[ -n $fallback_dir && $log == "$fallback_dir/"*.log ]] || {
printf 'agent-run: error: status path is not an agent log: %s\n' "$requested" >&2; exit 2;
}
;;
esac
last=$(tail -n 1 -- "$log")
if [[ $last =~ ^===\ agent-run\ exited\ rc=([0-9]+)\ after\ [0-9]+s$ ]]; then
((BASH_REMATCH[1] == 0)) && printf 'pass\n' || printf 'fail rc=%s\n' "${BASH_REMATCH[1]}"
exit 0
fi
[[ $last != '=== agent-run interrupted by '* ]] || { printf 'interrupted\n'; exit 0; }
header=$(sed -n '2p' -- "$log")
pid=$(sed -n 's/.* pid=\([0-9][0-9]*\) .*/\1/p' <<< "$header")
start=$(sed -n 's/.* process-start=\([^ ]*\) .*/\1/p' <<< "$header")
epoch=$(sed -n 's/.* epoch=\([0-9][0-9]*\) .*/\1/p' <<< "$header")
current=$(current_process_start "$pid" 2>/dev/null || true)
if [[ -n $pid && -n $start && $current == "$start" && $epoch =~ ^[0-9]+$ ]]; then
now=$(epoch_seconds); elapsed=$((now - epoch)); ((elapsed >= 0)) || elapsed=0
printf 'running pid=%s elapsed=%ss\n' "$pid" "$elapsed"
else
printf 'interrupted\n'
fi
exit 0
}

if [[ ${1:-} == status ]]; then
(($# == 2)) || { printf 'agent-run: error: usage: agent-run.sh status LOG\n' >&2; exit 2; }
status_agent_log "$2"
fi

usage() {
cat <<'EOF'
Usage: agent-run.sh [--dir PATH] [--label NAME] [--resolve NAME] [--force] [--summary] [--only NAME[,NAME...]]
Usage: agent-run.sh status LOG
agent-run.sh [--dir PATH] [--label NAME] [--resolve NAME] [--force] [--summary] [--only NAME[,NAME...]]
[--baseline-ref REF --baseline-path PATH --baseline-id ID]
(--cmd NAME | [--] <command> ...)

Runs one command with a sandbox-safe environment and a compact result summary.
status LOG Print running, pass, fail, or interrupted for an agent log.
--dir PATH Working directory for the command (default: current directory).
--repo-root PATH is a silent alias, accepted for
compatibility with the kit's other checkout-path helpers.
--label NAME Label used in the log file name (default: the command's basename).
--force Require fresh execution, including recovery of unknown evidence.
An identical in-flight local command still returns its handle.
An identical in-flight command is still refused with its log.
--summary End with status, exit code, duration, log path, digest, and receipt.
--verification-key Read-only query for one local, generic, full-checkout
command. Prints only its current fingerprint; creates no execution
Expand All @@ -41,8 +97,8 @@ Runs one command with a sandbox-safe environment and a compact result summary.
--if-declared With --cmd, exit 0 quietly when the repository declares no such
command. For a command a skill treats as optional.
--resolve NAME Query a named command without executing it. Prints declared,
runner, or unresolved and exits 0, 4, or 3 respectively; exit 2
is reserved for a fatal unsupported-interpreter guard.
runner, or unresolved and exits 0, 4, or 3 respectively. The
fatal unsupported-interpreter guard exits 2.
--cmd NAME Run the command this repository declares under that name, instead
of spelling one out. Repeatable: each --cmd runs only after the
previous one exits 0 (re-execs itself for the rest); --if-declared
Expand Down Expand Up @@ -93,7 +149,8 @@ Output:
verification miss: no reusable evidence; a following command executes freshly,
or a completed result was not stored for later reuse
verification current/reused: prior evidence, never a fresh PASS
verification running/unknown: durable handle and exit 75; inspect before retry
active duplicate exits 2 with its original log; wait instead of relaunching
verification unknown: unknown abandoned handle exits 75; inspect before retry

Examples:
agent-run.sh --cmd test
Expand All @@ -117,6 +174,7 @@ failure_result() {
"${failure_evidence:-${log_file:-stderr}}" "$failure_state" "$failure_action" >&2
fi
if declare -F cleanup_suite_run >/dev/null; then cleanup_suite_run; fi
if declare -F cleanup_active_run >/dev/null; then cleanup_active_run; fi
if ((summary_ready)); then
printf 'agent-run-summary status=%s rc=%s duration_seconds=%s log=%q log-sha256=%s receipt=%q\n' \
"$summary_status" "$status" "$elapsed" "$log_file" "$log_sha256" "$log_sha256_receipt"
Expand Down Expand Up @@ -162,6 +220,7 @@ finish() {
[[ -z ${verification_fd:-} ]] || exec {verification_fd}>&-
if ((rc == 0)) && ((${#remaining_queue[@]})); then
if declare -F cleanup_suite_run >/dev/null; then cleanup_suite_run; fi
if declare -F cleanup_active_run >/dev/null; then cleanup_active_run; fi
build_chain_argv
exec "$0" "${chain_argv[@]}"
fi
Expand Down Expand Up @@ -1051,7 +1110,8 @@ apply_test_focus() {
# --------------------------------------------------------------------- logs ---
choose_log() {
local log_dir stamp log
if [[ -n $git_top ]] && dir_writable "$git_top/.agent/logs"; then
if [[ -n $git_top && ! -L $git_top/.agent && ! -L $git_top/.agent/logs ]] &&
dir_writable "$git_top/.agent/logs"; then
log_dir=$git_top/.agent/logs
else
# Failing commands routinely echo tokens and credentialed URLs into these
Expand Down Expand Up @@ -1083,22 +1143,10 @@ concurrent_suites=1
timeout_scale=1
suite_marker_dir=${TMPDIR:-/tmp}/agent-run-suites-$(id -u)

current_process_start() {
local pid=$1
if [[ -r /proc/$pid/stat ]]; then
awk '{print $22}' "/proc/$pid/stat" 2> /dev/null
else
# macOS has no /proc; kill -0 is the portable liveness fallback. The
# marker is still short-lived and is removed by the EXIT trap.
kill -0 "$pid" 2> /dev/null || return 1
printf 'alive'
fi
}

suite_marker_live() {
local marker=$1 pid start current
read -r pid start < "$marker" 2> /dev/null || return 1
[[ $pid =~ ^[0-9]+$ && ($start =~ ^[0-9]+$ || $start == alive) ]] || return 1
[[ $pid =~ ^[0-9]+$ && $start =~ ^[[:alnum:]:]+$ ]] || return 1
current=$(current_process_start "$pid")
[[ -n $current && $current == "$start" ]]
}
Expand All @@ -1116,7 +1164,7 @@ register_suite_run() {
die "cannot create active-suite marker in $suite_marker_dir"
pid=$$
start=$(current_process_start "$pid")
[[ $start =~ ^[0-9]+$ || $start == alive ]] || {
[[ $start =~ ^[[:alnum:]:]+$ ]] || {
rm -f -- "$marker"
die "cannot identify active-suite process $pid"
}
Expand Down Expand Up @@ -1146,6 +1194,40 @@ cleanup_suite_run() {
suite_marker=''
}

active_run_handle='' active_run_fd='' active_run_owned=0
claim_active_run() {
local root key prior
[[ -n ${git_top:-} && -z $verification_handle ]] || return 0
command -v flock >/dev/null || return 0
[[ ! -L $git_top/.agent ]] || return 0
root=$git_top/.agent/run-records
assert_private_dir "$root"
key=$(printf '%s\0' "$work_dir" "${cmd[@]}" | sha256sum | awk '{print $1}')
active_run_handle=$root/$key
assert_private_dir "$active_run_handle"
[[ ! -L $active_run_handle/lock && ! -L $active_run_handle/running ]] ||
refuse_boundary "active run record is a symlink: $active_run_handle"
exec {active_run_fd}>"$active_run_handle/lock" || refuse_boundary "cannot open active run lock: $active_run_handle/lock"
if ! flock -n "$active_run_fd"; then
IFS= read -r prior < "$active_run_handle/running" 2>/dev/null || true
[[ -n $prior ]] || prior=$active_run_handle
failure_class=usage failure_state=command-already-running failure_action=wait-for-existing-log
failure_evidence=$prior
printf 'agent-run: already running: %s; wait for its exited line or run status on it\n' "$prior" >&2
finish 2
fi
printf '%s\n' "$log_file" > "$active_run_handle/running"
active_run_owned=1
}

# shellcheck disable=SC2329 # Invoked indirectly by the EXIT trap.
cleanup_active_run() {
((active_run_owned)) || return 0
rm -f -- "$active_run_handle/running" 2>/dev/null || true
[[ -z $active_run_fd ]] || exec {active_run_fd}>&-
active_run_owned=0
}

# A worker may ask for one failed verification to be checked against the chain
# base. The source path must be the same blob at both commits, and the base
# checkout must produce matching failure evidence. Deliberately opt-in: ordinary
Expand Down Expand Up @@ -1647,9 +1729,16 @@ claim_verification() {
[[ ! -L $verification_handle/running ]] || refuse_boundary "verification running record is a symlink: $verification_handle/running"
exec {verification_fd}>"$verification_handle/lock" || refuse_boundary "cannot open verification lock: $verification_handle/lock"
if ! flock -n "$verification_fd"; then
local running_log=''
IFS= read -r running_log < "$verification_handle/running" 2>/dev/null || true
failure_evidence=$verification_handle
failure_state=verification-running
failure_action=inspect-running-handle-before-retry
failure_state=command-already-running
failure_action=wait-for-existing-log
if [[ $running_log == "$git_top/.agent/logs/"*.log ]]; then
failure_evidence=$running_log
printf 'agent-run: already running: %s; wait for its exited line or run status on it\n' "$running_log" >&2
finish 2
fi
printf 'agent-run: verification running: handle=%s\n' "$verification_handle"
finish 75
fi
Expand Down Expand Up @@ -1822,25 +1911,27 @@ fi

log_file=$(choose_log)
[[ -z $verification_handle ]] || printf '%s\n' "$log_file" > "$verification_handle/running"
claim_active_run
register_suite_run
trap failure_result EXIT
process_start=$(current_process_start "$$") || die "cannot identify active process $$"

# Announce the log before captured output makes a long run look hung.
if ((summary_cmd)); then
printf 'running: %s (wait for the terminal agent-run-summary marker)\n' "$cmd_str" >&2
else
printf 'running: %s\n log: %s (grows while this runs; tail it instead of waiting blind)\n' \
"$cmd_str" "$log_file" >&2
fi
printf ' a log with no "=== agent-run exited" line has NOT finished\n' >&2
printf 'running: %s\n log: %s\n' "$cmd_str" "$log_file" >&2
printf ' if this call returns before "=== agent-run exited", the run is still going: resume this same call; never relaunch; status: %q status %q\n' \
"$0" "$log_file" >&2

# The closing marker distinguishes completed logs; exclude bookkeeping lines.
readonly LOG_HEADER_LINES=2
log_head=none log_clean=no
[[ -z $git_top ]] || log_head=$(git -C "$git_top" rev-parse --verify -q HEAD 2> /dev/null) || log_head=none
[[ -z $git_top || -n $(git -C "$git_top" status --porcelain --untracked-files=no 2> /dev/null || printf x) ]] || log_clean=yes
{
printf '=== agent-run %s\n' "$cmd_str"
printf '=== started %s pid=%s cwd=%s concurrent-suites=%s\n' \
"$(date -u +%Y-%m-%dT%H:%M:%SZ 2> /dev/null || printf 'unknown')" "$$" "$work_dir" \
"$concurrent_suites"
printf '=== started %s pid=%s process-start=%s epoch=%s cwd=%s concurrent-suites=%s head=%s tracked-clean=%s\n' \
"$(date -u +%Y-%m-%dT%H:%M:%SZ 2> /dev/null || printf 'unknown')" "$$" \
"$process_start" "$(epoch_seconds)" "$work_dir" "$concurrent_suites" \
"$log_head" "$log_clean"
} > "$log_file"

started_at=$SECONDS
Expand All @@ -1859,15 +1950,21 @@ trap 'log_interrupted SIGINT' INT
trap 'log_interrupted SIGTERM' TERM
rc=0
attempt_start_line=3
(cd -- "$work_dir" && exec "${cmd[@]}") >> "$log_file" 2>&1 || rc=$?
(
[[ -z $active_run_fd ]] || exec {active_run_fd}>&-
cd -- "$work_dir" && exec "${cmd[@]}"
) >> "$log_file" 2>&1 || rc=$?
load_flake_retry=0
if ((rc != 0)) && probe_timeout_load_flake "$log_file"; then
load_flake_retry=1
printf '=== finding load-flake: probe timeout under concurrent-suites=%s; retried 1/1\n' \
"$concurrent_suites" >> "$log_file"
attempt_start_line=$(($(wc -l < "$log_file" | tr -d '[:space:]') + 1))
rc=0
(cd -- "$work_dir" && exec "${cmd[@]}") >> "$log_file" 2>&1 || rc=$?
(
[[ -z $active_run_fd ]] || exec {active_run_fd}>&-
cd -- "$work_dir" && exec "${cmd[@]}"
) >> "$log_file" 2>&1 || rc=$?
fi
trap - INT TERM
elapsed=$((SECONDS - started_at))
Expand Down
33 changes: 27 additions & 6 deletions agentkit/skills/.shared/scripts/run-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ print_summary() {
positive_ids("queued"; true) as $queued |
positive_ids("receipt_prs"; true) as $receipts |
positive_ids("skipped_prs"; true) as $skipped |
has("auto_review") as $has_auto_review |
.auto_review as $auto_review |
has("root_turns") as $has_root_turns |
has("first_completion") as $has_first_completion |
.root_turns as $root_turns |
Expand All @@ -242,14 +244,19 @@ print_summary() {
elif (($skipped - $prs) | length) > 0 then error("skipped_prs must be a subset of opened_prs")
elif (($receipts + $skipped | length) != ($receipts + $skipped | unique | length))
then error("receipt_prs and skipped_prs must be disjoint")
elif $has_auto_review and ($auto_review | type) != "boolean"
then error("auto_review must be a boolean")
elif $has_root_turns and (($has_first_completion | not) or ($root_turns | type) != "array"
or any($root_turns[]; . != true) or ($first_completion | type) != "boolean")
then error("invalid root-turn summary evidence")
elif $has_first_completion and (($first_completion | type) != "boolean")
then error("invalid first-completion evidence")
else (if $has_root_turns | not then "unavailable"
elif $first_completion then ($root_turns | length | tostring) else "unlatched" end) as $telemetry |
[($prs | length), ($receipts | length), ($skipped | length), ($queued | length), $telemetry] | @tsv end
($prs - ($receipts + $skipped)) as $missing |
[($prs | length), ($receipts | length), ($skipped | length), ($queued | length),
(if $has_auto_review then ($auto_review | tostring) else "false" end),
($missing | if length == 0 then "-" else map(tostring) | join(",") end), $telemetry] | @tsv end
' <<<"$STATE" 2>/dev/null) ||
die 'summary state requires valid opened_prs, queued, receipt_prs, and skipped_prs collections'
[[ ! -L $LEDGER && -f $LEDGER && -r $LEDGER && -O $LEDGER ]] ||
Expand All @@ -271,22 +278,35 @@ print_summary() {
then . else error("invalid handback evidence") end] | sort_by(.issue)
' "$LEDGER" 2>/dev/null) || die "unparseable active-workers evidence: $LEDGER"
parked_count=$(jq 'length' <<<"$parked_rows")
local prs receipts skipped queued root_turns
IFS=$'\t' read -r prs receipts skipped queued root_turns <<<"$counts"
local prs receipts skipped queued auto_review missing_review_prs root_turns review_resume coverage_failure=''
IFS=$'\t' read -r prs receipts skipped queued auto_review missing_review_prs root_turns <<<"$counts"
if [[ $auto_review == true && $missing_review_prs != - ]]; then
review_resume="/review-remote-pr --auto-review ${missing_review_prs//,/; /review-remote-pr --auto-review }"
coverage_failure="auto-review coverage missing for PRs: $missing_review_prs; resume: $review_resume"
fi
printf 'coverage= prs=%s receipts=%s skipped=%s parked=%s queued=%s root-turns-before-first-completion=%s\n' \
"$prs" "$receipts" "$skipped" "$parked_count" "$queued" "$root_turns"
jq -r '.[] | "blocked=\(.issue):\(.evidence)"' <<<"$parked_rows"

[[ -n $REPORTS_DIR ]] || return 0
if [[ -z $REPORTS_DIR ]]; then
[[ -z $coverage_failure ]] || die "$coverage_failure"
return 0
fi
[[ ! -L $REPORTS_DIR ]] || die "verification reports directory must not be a symlink: $REPORTS_DIR"
[[ ! -e $REPORTS_DIR || (-d $REPORTS_DIR && -O $REPORTS_DIR) ]] ||
die "verification reports must be an owned directory: $REPORTS_DIR"
[[ -e $REPORTS_DIR ]] || return 0
if [[ ! -e $REPORTS_DIR ]]; then
[[ -z $coverage_failure ]] || die "$coverage_failure"
return 0
fi
local reports_mode report report_mode report_text report_issue content_issue
reports_mode=$(stat -c %a -- "$REPORTS_DIR") || die "could not inspect verification reports: $REPORTS_DIR"
(( (8#$reports_mode & 8#077) == 0 )) || die "verification reports directory must be owner-private: $REPORTS_DIR"
local -a reports=("$REPORTS_DIR"/issue-*.report)
[[ -e ${reports[0]} ]] || return 0
if [[ ! -e ${reports[0]} ]]; then
[[ -z $coverage_failure ]] || die "$coverage_failure"
return 0
fi
for report in "${reports[@]}"; do
[[ ${report##*/} =~ ^issue-([1-9][0-9]*)\.report$ ]] ||
die "verification report filename must be issue-POSITIVE_INTEGER.report: $report"
Expand All @@ -304,6 +324,7 @@ print_summary() {
die "verification report filename issue does not match content issue: $report"
cat -- "$report"
done
[[ -z $coverage_failure ]] || die "$coverage_failure"
}

main() {
Expand Down
Loading
Loading