@ssheleg/agent-sync 1.16.0 → 1.17.0
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/CHANGELOG.md +28 -0
- package/README.md +4 -0
- package/package.json +2 -2
- package/plugins/agent-sync/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-sync/hooks/session-start.sh +28 -6
- package/plugins/agent-sync/skills/agent-sync/SKILL.md +1 -1
- package/plugins/agent-sync/skills/agent-sync/scripts/agent_sync.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
## v1.17.0 — the identity that had never once been established
|
|
2
|
+
|
|
3
|
+
`_session_key()` falls back to one `shared` entry per checkout when it cannot tell which
|
|
4
|
+
session is asking, and everything downstream hedges about it: `classify_lock` answers
|
|
5
|
+
`ambiguous` rather than `reapable`, so an expired lease can never be cleared by the run that
|
|
6
|
+
took it. The fallback was documented as the rare case.
|
|
7
|
+
|
|
8
|
+
It was the only case. Measured 2026-08-25 in a checkout that had been running the tool all
|
|
9
|
+
day: `.agent-sync/sessions` did not exist, and `.agent-sync/run-id` held exactly one key —
|
|
10
|
+
`shared`. The stamping block in `session-start.sh` required `CLAUDE_SESSION_ID` in the hook's
|
|
11
|
+
**environment**, and Claude Code delivers the id to a hook on **stdin as JSON**, which is how
|
|
12
|
+
`guard.sh` next to it has always read its own payload. So the block had never run, in any
|
|
13
|
+
session, since it was written. A fallback that is always taken is not a fallback.
|
|
14
|
+
|
|
15
|
+
The hook reads its payload now, with the environment variable still winning where it exists.
|
|
16
|
+
`test/hooks_session_test.py` runs the real hook as a process — the only way this was ever
|
|
17
|
+
going to be caught, because every unit around it was correct — and covers the four ways it
|
|
18
|
+
must behave: an id from stdin is stamped, an id from the environment still wins, a payload
|
|
19
|
+
carrying no id stamps nothing rather than keying every session alike, and a payload that is
|
|
20
|
+
not JSON leaves the hook exit 0, because a SessionStart hook that throws takes the session
|
|
21
|
+
with it. A fifth case walks the whole chain: stamp, then the descendant's key, then the
|
|
22
|
+
run-id map, then `classify_lock` returning `reapable` where it used to return `ambiguous`.
|
|
23
|
+
|
|
24
|
+
Watched failing against the previous hook: 2 of the 5 cases red, and green after.
|
|
25
|
+
|
|
26
|
+
Also: the README no longer tells a reader to run `python3 test/validate.py` and `npm test`
|
|
27
|
+
from a package that ships no `test/` directory. It names where they run.
|
|
28
|
+
|
|
1
29
|
## Unreleased — both `AS-01` halves exercised outside their fixtures
|
|
2
30
|
|
|
3
31
|
The two rows sat at priority `unverified`: shipped, and confirmed by nothing but their own
|
package/README.md
CHANGED
|
@@ -444,6 +444,10 @@ and `.agent-sync/` if you want the project clean too.
|
|
|
444
444
|
|
|
445
445
|
## Develop and verify
|
|
446
446
|
|
|
447
|
+
<!-- commands-run-in: a clone -->
|
|
448
|
+
These run **in a clone of this repository**. The published npm package ships no
|
|
449
|
+
`test/` directory, so from an install they are names, not commands.
|
|
450
|
+
|
|
447
451
|
```bash
|
|
448
452
|
python3 test/validate.py # manifests, version sync, no host/credential leaks
|
|
449
453
|
python3 test/validate.py --self-test # the validator must still be able to fail
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/agent-sync",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "Let concurrent coding agents share one project without colliding — leases with TTL, race-free id reservation, a run journal and a generated board, over a pluggable knowledge cloud.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agent-sync": "bin/agent-sync.js"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "python3 test/validate.py && python3 test/validate.py --self-test && python3 test/claim_cell_test.py",
|
|
20
|
+
"test": "python3 test/validate.py && python3 test/validate.py --self-test && python3 test/claim_cell_test.py && python3 test/hooks_session_test.py",
|
|
21
21
|
"prepublishOnly": "python3 test/validate.py"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sync",
|
|
3
3
|
"displayName": "Agent Sync",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.17.0",
|
|
5
5
|
"description": "Coordination layer for multi-agent repositories — leases with TTL, race-free ID reservation, a run journal, a cross-repo signal feed and a generated board, over a pluggable knowledge cloud.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ssheleg",
|
|
@@ -6,14 +6,36 @@ S="$AGENT_SYNC_PY"
|
|
|
6
6
|
agent_sync_configured || exit 0
|
|
7
7
|
|
|
8
8
|
# Stamp who this session is, keyed by the process every command in it descends from.
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
9
|
+
# Without this a second session in the same checkout adopts the first one's identity: both
|
|
10
|
+
# acquire and release as one run, and the lease stops separating the exact case it exists for.
|
|
11
|
+
# $PPID here is the CLI process, which is the one ancestor every later command shares.
|
|
12
|
+
#
|
|
13
|
+
# The id arrives on STDIN as JSON, the way every other hook here reads its payload -- see
|
|
14
|
+
# guard.sh. This block used to require CLAUDE_SESSION_ID in the ENVIRONMENT and nothing else,
|
|
15
|
+
# so on this machine it never ran once: measured 2026-08-25, `.agent-sync/sessions` had never
|
|
16
|
+
# been created and the run-id map held a single `shared` key, which is the weak identity that
|
|
17
|
+
# makes an expired lease unattributable and `reap` refuse it forever. A fallback that is always
|
|
18
|
+
# taken is not a fallback.
|
|
19
|
+
if [ -t 0 ]; then
|
|
20
|
+
payload="" # no stdin (invoked by hand) -- do not block on `cat`
|
|
21
|
+
else
|
|
22
|
+
payload=$(cat 2>/dev/null || true)
|
|
23
|
+
fi
|
|
24
|
+
sid="${CLAUDE_SESSION_ID:-}"
|
|
25
|
+
if [ -z "$sid" ] && [ -n "$payload" ]; then
|
|
26
|
+
sid=$(printf '%s' "$payload" | python3 -c '
|
|
27
|
+
import json,sys
|
|
28
|
+
try:
|
|
29
|
+
d = json.load(sys.stdin)
|
|
30
|
+
except Exception:
|
|
31
|
+
sys.exit(0)
|
|
32
|
+
print(d.get("session_id") or "")
|
|
33
|
+
' 2>/dev/null)
|
|
34
|
+
fi
|
|
35
|
+
if [ -n "$sid" ]; then
|
|
14
36
|
d="$(git rev-parse --show-toplevel 2>/dev/null)/.agent-sync/sessions"
|
|
15
37
|
if mkdir -p "$d" 2>/dev/null; then
|
|
16
|
-
printf '%s' "$
|
|
38
|
+
printf '%s' "$sid" > "$d/$PPID" 2>/dev/null || true
|
|
17
39
|
# forget the stamps of processes that are gone, so the directory cannot grow without bound
|
|
18
40
|
for f in "$d"/*; do
|
|
19
41
|
b="$(basename "$f")"
|
|
@@ -4,7 +4,7 @@ description: "Use when several coding agents work one repository at the same tim
|
|
|
4
4
|
compatibility: "Requires the task-pipeline skill for its stages (npx sshlg-skills install). Needs python3 3.9+ (stdlib only, HTTP included - nothing to pip install) and bash for the hooks. The knowledge backend is configured per project; with none configured it degrades to git-file leases. Enforcement hooks are Claude Code only - on other agents the same checks run as a self-check."
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.17.0"
|
|
8
8
|
author: ssheleg
|
|
9
9
|
---
|
|
10
10
|
|