Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0476eb2
chore(agents): bump codex-acp adapter range
kelvinschen Jul 21, 2026
c5a9723
chore(agents): refresh built-in adapter ranges
TheAngryPit Jul 21, 2026
e8681c9
docs(changelog): credit adapter refresh contributors
steipete Jul 22, 2026
26b740e
chore(release): prepare acpx 0.12.1
steipete Jul 23, 2026
bbd5519
ci: add ClawSweeper dispatch workflow
steipete Jul 27, 2026
f64e2ca
fix(windows): require structured agent argv (#473)
steipete Jul 27, 2026
3405b0e
chore(deps): bump actions/checkout (#462)
dependabot[bot] Jul 27, 2026
6eba584
chore(deps): bump actions/setup-node from 4 to 7 (#452)
dependabot[bot] Jul 27, 2026
272d934
chore(deps): refresh dependencies and security fixes (#475)
steipete Jul 27, 2026
9866c76
fix(flows): swallow heartbeat write rejections from setInterval (#442)
SebTardif Jul 27, 2026
1d17a61
fix(session): prevent concurrent save failures (#447)
henkterharmsel Jul 27, 2026
2f9fd03
feat: add filesystem capability opt-out (#469)
zgxkbtl Jul 27, 2026
865709b
feat: add Pool ACP agent
dan-roberts-poolside Jul 27, 2026
69c20c2
fix(cli): preserve positive timer durations (#459)
realmehmetali Jul 27, 2026
ac8753b
fix(cli): report a cold-start session as "agent starting", not "needs…
guettlibot Jul 27, 2026
b34d7be
feat: add ZeroClaw ACP agent (#440)
JordanTheJet Jul 27, 2026
47dc1c5
chore(release): prepare acpx 0.13.0
steipete Jul 27, 2026
504040f
chore(release): open 0.13.1 development
steipete Jul 27, 2026
e7efc14
fix: terminate ACP adapter process groups
trumpyla Jul 28, 2026
93b6be1
fix: retry queue owner lease-before-bind races
trumpyla Jul 28, 2026
f68abe0
fix: harden lifecycle race handling
trumpyla Jul 28, 2026
f158b26
fix: wait for terminal output drain
trumpyla Jul 28, 2026
fdf12ea
fix: publish queue owner leases atomically
trumpyla Jul 29, 2026
006e333
fix: close lifecycle review races
trumpyla Jul 29, 2026
2a4e074
fix: validate remembered process identities
trumpyla Jul 29, 2026
2537ffb
fix: rescan process groups during shutdown
trumpyla Jul 29, 2026
79457ae
fix: close remaining process tree races
trumpyla Jul 29, 2026
a85da81
fix: serialize queue lease mutations
trumpyla Jul 29, 2026
ec048e5
fix: close adapter lifecycle races
trumpyla Jul 29, 2026
475bdb8
fix: preserve concurrent queue lease claims
trumpyla Jul 29, 2026
5d3063b
fix: close adversarial lifecycle races
trumpyla Jul 29, 2026
ecdec7c
fix: close remaining lifecycle and persistence races
trumpyla Jul 30, 2026
e93dc0c
test: preserve unverifiable legacy queue owners
trumpyla Jul 30, 2026
09eb602
fix: make lifecycle cleanup and persistence fail closed
trumpyla Jul 30, 2026
c4a85d4
perf(startup): cut ACP SDK from eager graph, add teardown and capabil…
trumpyla Jul 26, 2026
239e424
fix(ci): allow src/acp to import perf metrics
trumpyla Jul 30, 2026
59f2d55
fix: preserve live owners and publish locks atomically
trumpyla Jul 30, 2026
09e3da8
fix: close final lifecycle review races
trumpyla Aug 1, 2026
ed969f1
perf: resolve Windows capability commands through PATHEXT
trumpyla Aug 1, 2026
92b7f03
test: preserve alternate queue and persistence shields
trumpyla Aug 1, 2026
46b5194
chore: merge Artagon main into performance branch
trumpyla Aug 1, 2026
5ebe116
chore: align performance branch pnpm toolchain
trumpyla Aug 1, 2026
6fd8846
docs: design reusable performance benchmark
trumpyla Aug 2, 2026
89a59a3
docs: plan reusable performance benchmark
trumpyla Aug 2, 2026
b744410
feat: add reusable benchmark core
trumpyla Aug 2, 2026
e35b2c2
fix: handle benchmark trace edge cases
trumpyla Aug 2, 2026
f28ae00
test: add shared benchmark ACP agent
trumpyla Aug 2, 2026
7f47d1e
fix: reject benchmark trace option values
trumpyla Aug 2, 2026
113f790
test: restore benchmark initialization trace coverage
trumpyla Aug 2, 2026
ef18af2
refactor: model exact benchmark pairs
trumpyla Aug 2, 2026
edc9ce4
test: strengthen benchmark statistics contract
trumpyla Aug 2, 2026
dfac6f3
docs: use Tinybench benchmark engine
trumpyla Aug 2, 2026
70dbe9b
docs: complete Tinybench test contract
trumpyla Aug 2, 2026
96d1b58
perf: add reusable benchmark runner
trumpyla Aug 3, 2026
6c0ea1b
fix: retain anchored process snapshots
trumpyla Aug 3, 2026
72b46f9
fix: stabilize process ownership cleanup checks
trumpyla Aug 4, 2026
cb4cbf8
fix: update vulnerable transitive dependencies
trumpyla Aug 4, 2026
c427460
ci: elect EPL license for elkjs
trumpyla Aug 4, 2026
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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ concurrency:

env:
NODE_VERSION: 24
PNPM_VERSION: 10.33.2
PNPM_VERSION: 10.34.5

jobs:
scope:
Expand Down
202 changes: 202 additions & 0 deletions .github/workflows/clawsweeper-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
name: ClawSweeper Dispatch

on:
issues:
types: [opened, reopened, edited, labeled, unlabeled]
issue_comment:
types: [created, edited]
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned external dispatch; no checkout or untrusted PR code execution
types: [opened, reopened, synchronize, ready_for_review, edited, labeled, unlabeled]

permissions:
contents: read

concurrency:
group: clawsweeper-dispatch-${{ github.repository }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event.action == 'edited' || github.event.action == 'synchronize' || github.event.action == 'ready_for_review' }}

jobs:
dispatch:
runs-on: ubuntu-latest
if: ${{ !(endsWith(github.actor, '[bot]') && (github.event.action == 'labeled' || github.event.action == 'unlabeled')) }}
env:
HAS_CLAWSWEEPER_APP_PRIVATE_KEY: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY != '' }}
CLAWSWEEPER_APP_CLIENT_ID: Iv23liOECG0slfuhz093
SUPERSEDES_IN_PROGRESS: ${{ (github.event.action == 'edited' || github.event.action == 'synchronize' || github.event.action == 'ready_for_review') && 'true' || 'false' }}
steps:
- name: Debounce bursty metadata events
if: ${{ github.event.action == 'labeled' || github.event.action == 'unlabeled' }}
run: sleep 20

- name: Create ClawSweeper dispatch token
id: token
if: ${{ env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
owner: openclaw
repositories: clawsweeper
permission-contents: write

- name: Pre-filter ClawSweeper comment
id: comment_filter
if: ${{ github.event_name == 'issue_comment' }}
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -euo pipefail
if grep -Eiq '(^|[[:space:]])@(clawsweeper|openclaw-clawsweeper)\b(\[bot\])?|(^|[[:space:]])/(clawsweeper|review|autoclose|auto([[:space:]]+|-)?merge)\b' <<< "$COMMENT_BODY"; then
echo "is_command=true" >> "$GITHUB_OUTPUT"
else
echo "is_command=false" >> "$GITHUB_OUTPUT"
fi

- name: Create target comment token
id: target_token
if: >-
${{
github.event_name == 'issue_comment' &&
steps.comment_filter.outputs.is_command == 'true' &&
env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true'
}}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-issues: write
permission-pull-requests: read

- name: Dispatch exact ClawSweeper review
if: ${{ github.event_name != 'issue_comment' }}
env:
GH_TOKEN: ${{ steps.token.outputs.token }}
TARGET_REPO: ${{ github.repository }}
ITEM_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
ITEM_KIND: ${{ github.event_name == 'pull_request_target' && 'pull_request' || 'issue' }}
SOURCE_EVENT: ${{ github.event_name }}
SOURCE_ACTION: ${{ github.event.action }}
run: |
if [ -z "$GH_TOKEN" ]; then
echo "::notice::Skipping ClawSweeper dispatch because no dispatch credential is configured."
exit 0
fi
ingress_fingerprint="$(node <<'NODE'
const crypto = require("node:crypto");
const fs = require("node:fs");
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
const pullRequest = event.pull_request && typeof event.pull_request === "object"
? event.pull_request
: {};
const headSha = String(pullRequest.head?.sha || "").trim().toLowerCase();
const updatedAt = String(pullRequest.updated_at || "").trim();
if (
process.env.ITEM_KIND !== "pull_request" ||
!/^[0-9a-f]{40}$/.test(headSha) ||
!updatedAt
) {
process.stdout.write("");
} else {
process.stdout.write(
crypto
.createHash("sha256")
.update(
JSON.stringify({
version: 1,
target_repo: String(process.env.TARGET_REPO || "").toLowerCase(),
item_number: Number(process.env.ITEM_NUMBER),
action: String(process.env.SOURCE_ACTION || ""),
head_sha: headSha,
updated_at: updatedAt,
body: typeof pullRequest.body === "string" ? pullRequest.body : "",
label: String(event.label?.name || ""),
}),
)
.digest("hex"),
);
}
NODE
)"
payload="$(jq -nc \
--arg target_repo "$TARGET_REPO" \
--argjson item_number "$ITEM_NUMBER" \
--arg item_kind "$ITEM_KIND" \
--arg source_event "$SOURCE_EVENT" \
--arg source_action "$SOURCE_ACTION" \
--arg ingress_fingerprint "$ingress_fingerprint" \
--argjson supersedes_in_progress "$SUPERSEDES_IN_PROGRESS" \
'{event_type:"clawsweeper_item",client_payload:({target_repo:$target_repo,item_number:$item_number,item_kind:$item_kind,source_event:$source_event,source_action:$source_action,supersedes_in_progress:$supersedes_in_progress} + (if $ingress_fingerprint != "" then {ingress_route:"target_dispatcher",ingress_fingerprint:$ingress_fingerprint} else {} end))}')"
gh api repos/openclaw/clawsweeper/dispatches \
--method POST \
--input - <<< "$payload"

- name: Acknowledge and dispatch ClawSweeper comment
if: >-
${{
github.event_name == 'issue_comment' &&
steps.comment_filter.outputs.is_command == 'true'
}}
env:
DISPATCH_TOKEN: ${{ steps.token.outputs.token }}
TARGET_TOKEN: ${{ steps.target_token.outputs.token }}
TARGET_REPO: ${{ github.repository }}
ITEM_NUMBER: ${{ github.event.issue.number }}
COMMENT_ID: ${{ github.event.comment.id }}
COMMENT_BODY: ${{ github.event.comment.body }}
AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }}
SOURCE_ACTION: ${{ github.event.action }}
run: |
if [ -z "$DISPATCH_TOKEN" ]; then
echo "::notice::Skipping ClawSweeper dispatch because no dispatch credential is configured."
exit 0
fi
body_file="$RUNNER_TEMP/clawsweeper-comment-body.txt"
printf '%s\n' "$COMMENT_BODY" > "$body_file"
if grep -Eiq '<!--[[:space:]]*clawsweeper-proof-nudge([[:space:]]|-->)' "$body_file"; then
echo "Ignoring ClawSweeper proof-nudge comment."
exit 0
fi
if [ -n "$TARGET_TOKEN" ]; then
GH_TOKEN="$TARGET_TOKEN" gh api -X POST \
-H "Accept: application/vnd.github+json" \
"repos/$TARGET_REPO/issues/comments/$COMMENT_ID/reactions" \
-f content="eyes" >/dev/null || true
fi
status_comment_id=""
if [ -n "$TARGET_TOKEN" ]; then
case "$AUTHOR_ASSOCIATION" in
OWNER|MEMBER|COLLABORATOR)
status_body="$(printf '%s\n' \
"<!-- clawsweeper-command-ack:$COMMENT_ID -->" \
"🦞👀" \
"ClawSweeper picked this up." \
"" \
"Command router queued. I will update this comment with the next step.")"
status_payload="$(jq -nc --arg body "$status_body" '{body:$body}')"
status_err="$(mktemp)"
if status_response="$(GH_TOKEN="$TARGET_TOKEN" gh api \
"repos/$TARGET_REPO/issues/$ITEM_NUMBER/comments" \
--method POST \
--input - <<< "$status_payload" 2>"$status_err")"; then
status_comment_id="$(jq -r '.id // empty' <<< "$status_response")"
else
cat "$status_err" >&2
echo "::warning::Could not create ClawSweeper queued status comment; dispatching command router without one."
fi
rm -f "$status_err"
;;
esac
fi
payload="$(jq -nc \
--arg target_repo "$TARGET_REPO" \
--argjson item_number "$ITEM_NUMBER" \
--argjson comment_id "$COMMENT_ID" \
--arg status_comment_id "$status_comment_id" \
--arg source_event "issue_comment" \
--arg source_action "$SOURCE_ACTION" \
'{event_type:"clawsweeper_comment",client_payload:({target_repo:$target_repo,item_number:$item_number,comment_id:$comment_id,source_event:$source_event,source_action:$source_action,max_comments:"1"} + (if $status_comment_id != "" then {status_comment_id:($status_comment_id|tonumber)} else {} end))}')"
GH_TOKEN="$DISPATCH_TOKEN" gh api repos/openclaw/clawsweeper/dispatches \
--method POST \
--input - <<< "$payload"
2 changes: 1 addition & 1 deletion .github/workflows/conformance-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ concurrency:

env:
NODE_VERSION: 24
PNPM_VERSION: 10.33.2
PNPM_VERSION: 10.34.5

jobs:
conformance-mock:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/crabbox-hydrate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ permissions:
contents: read

env:
PNPM_VERSION: 10.33.2
PNPM_VERSION: 10.34.5

jobs:
hydrate:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ permissions:
contents: read

env:
PNPM_VERSION: 10.33.2
PNPM_VERSION: 10.34.5
# Pinned exactly. The shipped binary *is* this Node build with a blob injected,
# so a floating version silently changes the release's largest attack surface
# (V8, OpenSSL, zlib) and makes builds non-reproducible. The SBOM records this
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ concurrency:

env:
NODE_VERSION: 24
PNPM_VERSION: 10.33.2
PNPM_VERSION: 10.34.5

jobs:
release:
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/supply-chain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ permissions:
contents: read

env:
PNPM_VERSION: 10.33.2
PNPM_VERSION: 10.34.5
NODE_VERSION: 24.11.0

jobs:
Expand Down Expand Up @@ -175,6 +175,11 @@ jobs:
# Copyleft terms would change acpx's distribution terms, and the
# single executable ships every dependency inside one file.
deny-licenses: AGPL-3.0, GPL-3.0
# Dependency Review reported EPL-2.0 OR GPL-3.0-or-later for the
# dev-only replay viewer dependency; acpx elects EPL-2.0.
# This exemption skips every license check for exactly that package version;
# any elkjs upgrade must be reviewed and re-pinned explicitly.
allow-dependencies-licenses: pkg:npm/elkjs@0.12.0

audit:
name: Dependency audit
Expand Down Expand Up @@ -203,8 +208,8 @@ jobs:
# the build on one trains people to ignore this job.
run: |
set -uo pipefail
# Distinguish "found advisories" from "could not run". pnpm 10.33.2
# crashes on a gzipped audit response with
# Distinguish "found advisories" from "could not run". Some pnpm
# versions crash on a gzipped audit response with
# `Unexpected token '\x1f' ... is not valid JSON`, which is a
# transport bug, not a finding. Failing the build on it would make a
# red audit job mean nothing, and a real advisory would be ignored
Expand Down
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Use these files for the other concerns:

- [`README.md`](README.md) for user-facing install and usage
- [`docs/CLI.md`](docs/CLI.md) for CLI reference
- [`docs/PERFORMANCE.md`](docs/PERFORMANCE.md) for reusable benchmark guidance
- [`VISION.md`](VISION.md) for product direction and boundaries
- [`CONTRIBUTING.md`](CONTRIBUTING.md) for PR expectations
- [`skills/acpx/SKILL.md`](skills/acpx/SKILL.md) for agent-usage guidance
Expand All @@ -22,9 +23,9 @@ instead of expanding this file into a full technical spec.
- npm: `https://www.npmjs.com/package/acpx`
- Default branch: `main`
- Runtime: Node.js `>=22.13.0`
- Package manager: `pnpm@10.33.2`
- Package manager: `pnpm@10.34.5`
- Clean Node 22.13 setups can have stale Corepack signing keys; install pnpm
with `npm install -g pnpm@10.33.2` if `corepack prepare` fails.
with `npm install -g pnpm@10.34.5` if `corepack prepare` fails.

## Product Direction

Expand Down Expand Up @@ -171,6 +172,7 @@ Harness documentation synchronization policy:
- `pnpm run mutate` — Stryker mutation check for the configured target
- `pnpm run check` — format, typecheck, lint, build, and coverage tests
- `pnpm run check:docs` — docs format and markdown lint
- `pnpm run perf:benchmark` — compare already-built worktrees with paired measurements
- `pnpm run perf:report` — performance reporting helper

## Testing And Changelog Guidelines
Expand Down
69 changes: 69 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,75 @@ Repo: https://github.com/openclaw/acpx

### Fixes

- Runtime/agents: terminate owned adapter process groups on POSIX and
best-effort tracked process trees on Windows during normal and failed startup
cleanup so package-exec wrappers do not leave captured descendants running
after ACPX exits; exit-triggered snapshots are observed before cleanup
completes, POSIX descendants are captured before signaling so later session
or group changes cannot escape forced cleanup, owned POSIX group members
created during shutdown are discovered and re-signaled, external process-list
discovery is bounded, Linux process identities come from procfs, other POSIX
snapshots use a fixed locale, adapter wrappers are sampled with bounded
exponential backoff through multi-stage launches, escaped descendants are
extended from identity-validated parents after root exit, transient discovery
failures preserve and still signal captured targets, and Windows parent edges
plus remembered descendants are identity-checked before later signaling.

- Runtime/queue: publish queue-owner leases atomically, preserve fresh malformed
locks during collisions, serialize cross-process refresh, release, and stale
cleanup through inode-qualified, crash-recoverable claims, revalidate stale
owners while holding cleanup claims, retry explicit cleanup across heartbeat
contention while making unresolved explicit cleanup fail visibly, reacquire
atomically with fresh timestamps after retiring a stale collision, and treat
only a fresh, live lease-before-bind owner as a fast startup miss while
retaining a retry window that covers the full startup grace period, preventing
premature `QUEUE_NOT_ACCEPTING_REQUESTS` errors without masking older
unreachable owners, letting released owners overwrite successors, or leaving
stale dangling-symlink locks unrecoverable.

## 2026.7.27 (v0.13.0)

### Highlights

- Windows agent launches now use structured argv end to end. Unambiguous legacy `command` plus `args` entries migrate automatically; ambiguous/raw commands and `.sh` wrappers must move to `agents.<name>.argv`, and existing saved custom-agent sessions without argv must be recreated.
- Built-in Pool and ZeroClaw support makes both native ACP stdio servers available without custom registry configuration.
- The new `--no-fs` flag lets compatible agents use their native filesystem implementation instead of ACP client filesystem methods.
- The dependency and pnpm refresh resolves all four known PostCSS, fast-uri, js-yaml, and brace-expansion advisories.

### Changes

- Agents/built-ins: add Pool via `pool acp`. Thanks @dan-roberts-poolside and @osolmaz.

- Agents/built-ins: add ZeroClaw via `zeroclaw acp`, ZeroClaw's native ACP v1 stdio server. Thanks @JordanTheJet.

- CLI/ACP: add `--no-fs` to disable advertised ACP file read/write capabilities so compatible agents can use their native filesystem implementation. Thanks @zgxkbtl.

- CLI/timers: preserve tiny positive timeout and TTL values from flags or config as 1 ms instead of disabling timers, and reject delays beyond Node's supported timer range. Thanks @realmehmetali.

- Dependencies/tooling: refresh the ACP SDK, runtime and development toolchain, update pnpm to 10.34.5, and resolve the PostCSS, fast-uri, js-yaml, and brace-expansion advisories.

### Breaking

- Windows agent launches now require structured `agents.<name>.argv`; unambiguous legacy `command` plus `args` entries migrate automatically, while raw, ambiguous, or directly executable `.sh` commands fail with explicit migration guidance instead of lossy parsing or CreateProcess ENOENT. Existing custom-agent sessions without saved argv must be recreated. Fixes #466. Thanks @MarcelCFritsche.

### Fixes

- Flows: swallow best-effort heartbeat write failures at the timer boundary so storage errors do not become unhandled promise rejections. Thanks @SebTardif.

- Runtime/sessions: use collision-resistant temporary paths for concurrent atomic session and index writes. Thanks @henkterharmsel.

- CLI/status: report a normal cold-start session as `agent starting` while preserving `needs reconnect` for an unreachable live owner. Thanks @guettli.

## 2026.7.23 (v0.12.1)

### Changes

- Agents/built-ins: refresh the default Pi, Codex, Claude, and Mux adapter ranges. Thanks @kelvinschen and @TheAngryPit.

### Breaking

### Fixes

- Session queue owner: capture a bounded owner stderr tail and exit status during cold start only (stop retaining at first IPC accept; keep draining the pipe so long-lived owners are not killed by EPIPE) so a dead owner reports the real failure instead of a silent timeout. Thanks @SebTardif.

## 2026.7.4 (v0.12.0)
Expand Down
Loading