Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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.13",
"version": "0.9.14",
"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.13",
"version": "0.9.14",
"description": "Board-aware parallel issue and PR review skills, with lifecycle hooks and a per-repository contract.",
"author": {
"name": "wrzonance",
Expand Down
17 changes: 16 additions & 1 deletion agentkit/skills/.shared/scripts/agent-preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ARG_ENSURE=0
ARG_ACTIVATION_SESSION=""
ARG_ACTIVATION_ORIGIN=""
ARG_WORKFLOW=""
ARG_ACTIVATION_NONCE=""
ARG_MEASURED_FROM_SET=0
ARG_INHERIT_SESSION=""
ARG_INHERIT_SESSION_SET=0
Expand Down Expand Up @@ -94,7 +95,7 @@ Options:
--ensure Reuse and print a trusted existing contract; run the
preflight probes only when that contract is missing or
fails contract-read provenance checks.
--activation-session ID --activation-origin PATH --workflow NAME
--activation-session ID --activation-origin PATH --workflow NAME [--activation-nonce NONCE]
Require acknowledged workflow receipt and matching installed
content before any probes or cached-contract reuse. Missing
receipt is an error; installed bytes alone are not activation.
Expand Down Expand Up @@ -261,6 +262,7 @@ parse_args() {
--activation-session) need_value "$@"; ARG_ACTIVATION_SESSION="$2"; shift 2 ;;
--activation-origin) need_value "$@"; ARG_ACTIVATION_ORIGIN="$2"; shift 2 ;;
--workflow) need_value "$@"; ARG_WORKFLOW="$2"; shift 2 ;;
--activation-nonce) need_value "$@"; ARG_ACTIVATION_NONCE="$2"; shift 2 ;;
--measured-from)
need_value "$@"
ARG_MEASURED_FROM_SET=1
Expand Down Expand Up @@ -1298,6 +1300,19 @@ main() {
if (( ARG_ENSURE && (ARG_WRITE_SET || ARG_REPO_SET || ARG_MEASURED_FROM_SET || ARG_INHERIT_SESSION_SET) )); then
die '--ensure cannot be combined with --write, --repo, --measured-from, or --inherit-session'
fi
if [[ -n $ARG_ACTIVATION_NONCE ]]; then
local -a missing=()
[[ -n $ARG_ACTIVATION_SESSION ]] || missing+=("--activation-session")
[[ -n $ARG_WORKFLOW ]] || missing+=("--workflow")
(( ${#missing[@]} == 0 )) || die "--activation-nonce requires $(join_by ' and ' "${missing[@]}")"
"$SCRIPT_DIR/workflow-activation.sh" ack \
--repo-root "${ARG_ACTIVATION_ORIGIN:-${ARG_WORKTREE:-$PWD}}" \
--session "$ARG_ACTIVATION_SESSION" --skill "$ARG_WORKFLOW" \
--nonce "$ARG_ACTIVATION_NONCE" >/dev/null || {
printf 'agent-preflight: activation receipt failed; rerun with the exact --activation-nonce from your context\n' >&2
return 1
}
fi
if [[ -n $ARG_ACTIVATION_SESSION || -n $ARG_WORKFLOW ]]; then
"$SCRIPT_DIR/workflow-activation.sh" check --repo-root "${ARG_ACTIVATION_ORIGIN:-${ARG_WORKTREE:-$PWD}}" \
--target-root "${ARG_WORKTREE:-$PWD}" --session "$ARG_ACTIVATION_SESSION" \
Expand Down
116 changes: 97 additions & 19 deletions agentkit/skills/.shared/scripts/lib/workflow-activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,17 @@ def validate(args, record, skill=None, require=()):


def ack_command(args, record):
return shlex.join([str(Path(args.skills) / ".shared/scripts/workflow-activation.sh"), "ack",
"--repo-root", record["repoRoot"], "--session", record["session"],
"--skill", record["workflow"], "--nonce", record["nonce"]])
return shlex.join([str(Path(args.skills) / ".shared/scripts/agent-preflight.sh"),
"--activation-session", record["session"],
"--activation-origin", record["repoRoot"],
"--workflow", record["workflow"],
"--activation-nonce", record["nonce"]])


def deliver(args, evidence, workflow, source, capabilities, recovery=False):
def deliver(args, evidence, workflow, source, capabilities, recovery=False, native=True):
"""Build the pending record and the delivered context. `native` is False when the
workflow was selected from the operator's words rather than a `$`/`/` invocation:
no harness injected the skill body in that case, so the delivery must require it."""
skill = Path(args.skills) / workflow / "SKILL.md"
if not skill.is_file() or skill.is_symlink():
fail("workflow-unavailable: " + workflow)
Expand All @@ -242,21 +247,32 @@ def deliver(args, evidence, workflow, source, capabilities, recovery=False):
"nonce": secrets.token_hex(24), "capabilities": capabilities}
evidence.write(record)
if recovery:
lead = ("agentkit root-mediated activation recovery: current workflow bytes are delivered "
"only to refresh this receipt. Do not run or dispatch the orchestration workflow; "
"run this exact receipt command, then resume the assigned work in the same worktree:\n")
else:
lead = ("agentkit root-mediated activation recovery: the workflow content changed. "
"Read " + str(skill) + " in full now (reads are permitted while the receipt "
"is pending), then run this exact receipt command and resume the assigned "
"work in the same worktree:\n")
elif native:
lead = ("agentkit invocation boundary: explicit workflow delivery, not native registry evidence. "
"Before any dispatch, edits, or other workflow, run this exact receipt command. "
"You may inspect the installed helper first; its first receipt stdout line is the workflow identity:\n")
context = (lead + ack_command(args, record)
+ "\nMissing capability remains unknown. Do not substitute another workflow.\n"
+ "Installed skills root: " + args.skills + "\n\n" + body.decode())
"Run this exact preflight command first; it records the session receipt:\n")
else:
lead = ("agentkit invocation boundary: this workflow was selected from your words, so no "
"skill body was loaded natively. Run this exact preflight command first; it records "
"the session receipt. Then read " + str(skill) + " in full before any dispatch:\n")
context = (lead + ack_command(args, record) + "\n"
+ "agentkit: skill=" + workflow + " version=" + record["version"]
+ " hash=" + args.digest[:12] + "\n"
+ "Installed skills root: " + args.skills + "\n"
+ "Missing capability remains unknown. Do not substitute another workflow.")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return record, context


def inspection(args, root, tool, tool_input):
"""Permit a bounded file inspection, never a general shell expression."""
"""Permit a bounded file inspection, never a general shell expression.

Serves the stale-active path only: a content-mismatched active record still
permits bounded diagnostic reads and searches before validate() raises
ContentMismatch.
"""
directory = False
if tool == "Read":
paths = [tool_input.get("file_path", "")]
Expand Down Expand Up @@ -298,6 +314,62 @@ def inspection(args, root, tool, tool_input):
return True


DISPATCH_TOOLS = ("Agent", "Task", "spawn_agent", "Skill")
# Command position: the start of the text or a shell operator, then any number of the wrappers
# agents actually compose (env, VAR=x, timeout N, nohup, sudo, xargs, exec, command, time, and the
# loop/conditional keywords do/then/else). A word that is not a wrapper (echo, printf, grep) means
# the text after it is an argument, never a command, so `echo git push` is not dispatch.
COMMAND_POSITION = (
r"(?:^|[;&|(\n{])\s*"
r"(?:(?:do|then|else|exec|command|time|nohup|sudo|env|xargs|timeout\s+\S+|\w+=\S*)\s+)*"
)
DISPATCH_COMMANDS = (
COMMAND_POSITION + r"(?:\S*/)?create-issue-worktree\.sh(?:\s|$)",
COMMAND_POSITION + r"(?:\S*/)?worktree-commit\.sh(?:\s|$)",
COMMAND_POSITION + r"(?:\S*/)?chain-advance\.sh(?:\s|$)",
COMMAND_POSITION + r"(?:\S*/)?git\s+(?:-[cC]\s+\S+\s+)*(push|worktree\s+add)\b",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
COMMAND_POSITION + r"(?:\S*/)?gh\s+(?:-R\s+\S+\s+|--repo\s+\S+\s+)?pr\s+(create|ready|merge)\b",
)


def executed_text(command):
"""Strip heredoc bodies and quoted strings so patterns only match executed text,
never inert data (a commit message, a README snippet, an example CLI invocation).
A single-token quoted string (no internal whitespace) is unwrapped first, not
stripped, because it is the kit's own documented form for an absolute helper
path or invocation and must still match as executed text."""
# Drop only the heredoc BODY and its terminator; the rest of the header line is
# executed text (`cat <<EOF; git push origin main` runs the push after cat).
stripped = re.sub(r"(<<-?\s*['\"]?(\w+)['\"]?[^\n]*\n).*?^\t*\2\s*$", r"\1", command,
flags=re.DOTALL | re.MULTILINE)
# Unwrap `bash -c '...'` (and sh/zsh/dash, single or double quoted, and bundled forms
# such as `-lc`) into executed text BEFORE quoted strings are stripped as data: the
# kit's own recipes wrap commands this way (the harness shell is zsh), so the -c body
# is executed, not inert. One pass only; a `bash -c` nested inside another `bash -c`
# body stays unwrapped as a known gap.
shell_c = re.sub(
r"(?:^|(?<=[\s;&|(]))(?:bash|sh|zsh|dash)\s+(?:-[a-zA-Z]+\s+)*?-[a-zA-Z]*c\s+"
r"(?:'([^']*)'|\"([^\"]*)\")",
lambda m: " " + (m.group(1) if m.group(1) is not None else m.group(2)) + " ",
stripped)
unwrapped = re.sub(r"'([^'\s]*)'|\"([^\"\s]*)\"", r"\1\2", shell_c)
# A multi-token quoted string is inert data, but it still occupies an argument slot:
# `git -C '/path/my repo' push` must keep `push` as the subcommand, so the string
# becomes a placeholder token rather than vanishing into whitespace.
return re.sub(r"'[^']*'|\"[^\"]*\"", " _quoted_ ", unwrapped)


def dispatch_class(tool, tool_input):
"""A dispatch-class call spends slots, opens PRs, or pushes; those wait for the receipt."""
if tool in DISPATCH_TOOLS:
return True
if tool in ("Bash", "exec_command"):
command = tool_input.get("command", tool_input.get("cmd", ""))
text = executed_text(command)
return any(re.search(pattern, text) for pattern in DISPATCH_COMMANDS)
return False


def hook(args):
payload = json.load(sys.stdin)
event = payload.get("hook_event_name", "UserPromptSubmit")
Expand Down Expand Up @@ -329,7 +401,8 @@ def hook(args):
"agentkit activation unchanged: acknowledged workflow=" + workflow
+ "; reuse durable session receipt; do not repeat discovery."}}
_, context = deliver(args, evidence, workflow, "UserPromptSubmit.additionalContext",
{"user-prompt-submit": "observed", "pre-tool-use": "unknown"})
{"user-prompt-submit": "observed", "pre-tool-use": "unknown"},
native=bool(re.match(r"^\s*[$/]", prompt)))
return {"hookSpecificOutput": {"hookEventName": event, "additionalContext": context}}
try:
evidence = Evidence(root, session)
Expand All @@ -341,11 +414,14 @@ def hook(args):
evidence.write(record)
tool = payload.get("tool_name", "")
tool_input = payload.get("tool_input", {})
# The challenge response must remain reachable while delivery is pending.
command = tool_input.get("command", tool_input.get("cmd", ""))
if tool in ("Bash", "exec_command") and command.strip() == ack_command(args, record):
if record.get("status") != "active":
# Pending delivery gates dispatch only; reads, edits, and inspection proceed.
if dispatch_class(tool, tool_input):
validate(args, record)
return {}
if inspection(args, evidence.root, tool, tool_input):
# A stale (content-mismatched) active record still permits bounded
# diagnostic reads; validate() below is what raises ContentMismatch.
return {}
validate(args, record)
if tool == "Skill":
Expand All @@ -366,7 +442,9 @@ def hook(args):
record["capabilities"]["pre-tool-use"] = "unknown"
evidence.write(record)
return {"hookSpecificOutput": {"hookEventName": event, "additionalContext":
"agentkit durable activation: " + json.dumps(record, sort_keys=True)
"agentkit durable activation: workflow=" + record["workflow"]
+ " status=" + record.get("status", "unknown")
+ " version=" + record.get("version", "unknown")
+ "; historical session receipt only, not proof of this context's native registry. "
+ ("" if record.get("status") == "active" else "Run: " + ack_command(args, record))}}
return {}
Expand Down
8 changes: 4 additions & 4 deletions agentkit/skills/onboard-repo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ description: >-

## Step 0 prerequisite: verified activation

First run UserPromptSubmit's exact `$agentkit/.shared/scripts/workflow-activation.sh ack` command;
stdout begins `agentkit: skill=onboard-repo version=<v> hash=<first12>` (receipt, not registry proof).
Before work, require `workflow-activation.sh check --require pre-tool-use --repo-root R --session ID --skill onboard-repo`;
`check` needs no other flags here. `$agentkit/.shared/scripts/agent-preflight.sh` separately takes `--activation-session ID --activation-origin R --workflow onboard-repo`, with the same R.
First run UserPromptSubmit's exact `$agentkit/.shared/scripts/agent-preflight.sh` command;
stdout begins `skills=` (contract, not registry proof).
Before work, require `$agentkit/.shared/scripts/workflow-activation.sh check --require pre-tool-use --repo-root R --session ID --skill onboard-repo`;
`check` needs no other flags here. `$agentkit/.shared/scripts/agent-preflight.sh` carries `--activation-session ID --activation-origin R --workflow onboard-repo --activation-nonce N`; run it once.
Missing challenge: report `agentkit: activation-unavailable` and stop without substituting unless the
user's own message explicitly requests the no-delivery reference use described below.
Recovery: resubmit `$agentkit:onboard-repo`; natural triggers also deliver.
Expand Down
8 changes: 4 additions & 4 deletions agentkit/skills/parallel-issues/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ description: >-

## Step 0 prerequisite: verified activation

First run UserPromptSubmit's exact `$agentkit/.shared/scripts/workflow-activation.sh ack` command;
stdout begins `agentkit: skill=parallel-issues version=<v> hash=<first12>` (receipt, not registry proof).
Before dispatch, require `workflow-activation.sh check --require pre-tool-use --repo-root R --session ID --skill parallel-issues`;
`check` needs no other flags here. `$agentkit/.shared/scripts/agent-preflight.sh` separately takes `--activation-session ID --activation-origin R --workflow parallel-issues`, with the same R.
First run UserPromptSubmit's exact `$agentkit/.shared/scripts/agent-preflight.sh` command;
stdout begins `skills=` (contract, not registry proof).
Before dispatch, require `$agentkit/.shared/scripts/workflow-activation.sh check --require pre-tool-use --repo-root R --session ID --skill parallel-issues`;
`check` needs no other flags here. `$agentkit/.shared/scripts/agent-preflight.sh` carries `--activation-session ID --activation-origin R --workflow parallel-issues --activation-nonce N`; run it once.
Missing challenge: report `agentkit: activation-unavailable` and stop without substituting unless the
user's own message explicitly requests the no-delivery reference use described below.
For recovery, resubmit `$agentkit:parallel-issues`; advertised natural triggers also deliver.
Expand Down
8 changes: 4 additions & 4 deletions agentkit/skills/pr-to-green/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ description: >-

## Step 0 prerequisite: verified activation

First run UserPromptSubmit's exact `$agentkit/.shared/scripts/workflow-activation.sh ack` command;
stdout begins `agentkit: skill=pr-to-green version=<v> hash=<first12>` (receipt, not registry proof).
Before work, require `workflow-activation.sh check --require pre-tool-use --repo-root R --session ID --skill pr-to-green`;
`check` needs no other flags here. `$agentkit/.shared/scripts/agent-preflight.sh` separately takes `--activation-session ID --activation-origin R --workflow pr-to-green`, with the same R.
First run UserPromptSubmit's exact `$agentkit/.shared/scripts/agent-preflight.sh` command;
stdout begins `skills=` (contract, not registry proof).
Before work, require `$agentkit/.shared/scripts/workflow-activation.sh check --require pre-tool-use --repo-root R --session ID --skill pr-to-green`;
`check` needs no other flags here. `$agentkit/.shared/scripts/agent-preflight.sh` carries `--activation-session ID --activation-origin R --workflow pr-to-green --activation-nonce N`; run it once.
Missing challenge: report `agentkit: activation-unavailable` and stop without substituting unless the
user's own message explicitly requests the no-delivery reference use described below.
Recovery: resubmit `$agentkit:pr-to-green`; natural triggers also deliver.
Expand Down
8 changes: 4 additions & 4 deletions agentkit/skills/review-remote-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ description: Use when asked to review, babysit, monitor, or clean up a remote PR

## Step 0 prerequisite: verified activation

First run UserPromptSubmit's exact `$agentkit/.shared/scripts/workflow-activation.sh ack` command;
stdout begins `agentkit: skill=review-remote-pr version=<v> hash=<first12>` (receipt, not registry proof).
Before work, require `workflow-activation.sh check --require pre-tool-use --repo-root R --session ID --skill review-remote-pr`;
`check` needs no other flags here. `$agentkit/.shared/scripts/agent-preflight.sh` separately takes `--activation-session ID --activation-origin R --workflow review-remote-pr`, with the same R.
First run UserPromptSubmit's exact `$agentkit/.shared/scripts/agent-preflight.sh` command;
stdout begins `skills=` (contract, not registry proof).
Before work, require `$agentkit/.shared/scripts/workflow-activation.sh check --require pre-tool-use --repo-root R --session ID --skill review-remote-pr`;
`check` needs no other flags here. `$agentkit/.shared/scripts/agent-preflight.sh` carries `--activation-session ID --activation-origin R --workflow review-remote-pr --activation-nonce N`; run it once.
Missing challenge: report `agentkit: activation-unavailable` and stop without substituting unless the
user's own message explicitly requests the no-delivery reference use described below.
Recovery: resubmit `$agentkit:review-remote-pr`; natural triggers also deliver.
Expand Down
Loading
Loading