@ssheleg/agent-sync 1.3.3 → 1.3.4
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 +16 -0
- package/package.json +2 -2
- package/plugins/agent-sync/.claude-plugin/plugin.json +2 -2
- package/plugins/agent-sync/skills/agent-sync/SKILL.md +1 -1
- package/plugins/agent-sync/skills/agent-sync/scripts/__pycache__/agent_sync.cpython-314.pyc +0 -0
- package/plugins/agent-sync/skills/agent-sync/scripts/agent_sync.py +37 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
This project adheres to [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## 1.3.4 — 2026-07-29
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- **`release` reported success for a lease it did not release, and cleared the
|
|
10
|
+
board claim on the way.** The lease plane refused correctly — a lease held by
|
|
11
|
+
another run stayed held, and `_git_release` printed a note saying so — but the
|
|
12
|
+
command printed `released <key>` over the top of it and exited 0, and
|
|
13
|
+
`write_claim` had already blanked the claim cell before the refusal was
|
|
14
|
+
reached. The result was the board advertising a task as free while the lease
|
|
15
|
+
still held it: the exact disagreement a lease exists to prevent, manufactured
|
|
16
|
+
by the tool. Ownership is now checked **first**, in whichever plane arbitrates
|
|
17
|
+
it; nothing is written when the answer is no; the command exits non-zero and
|
|
18
|
+
says who holds it. Both lease modes were affected.
|
|
19
|
+
- A regression check covers it in `local` and `git` mode, and was probed against
|
|
20
|
+
the old code in both before being trusted.
|
|
21
|
+
|
|
6
22
|
## 1.3.3 — 2026-07-29
|
|
7
23
|
|
|
8
24
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/agent-sync",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "Let concurrent coding agents share one project without colliding
|
|
3
|
+
"version": "1.3.4",
|
|
4
|
+
"description": "Let concurrent coding agents share one project without colliding \u2014 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"
|
|
7
7
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sync",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "Coordination layer for multi-agent repositories
|
|
3
|
+
"version": "1.3.4",
|
|
4
|
+
"description": "Coordination layer for multi-agent repositories \u2014 leases with TTL, race-free ID reservation, a run journal, a cross-repo signal feed and a generated board, over a pluggable knowledge cloud.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "appvillis-com"
|
|
7
7
|
},
|
|
@@ -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.3.
|
|
7
|
+
version: "1.3.4"
|
|
8
8
|
author: appvillis-com
|
|
9
9
|
---
|
|
10
10
|
|
|
Binary file
|
|
@@ -32,7 +32,7 @@ from datetime import datetime, timezone
|
|
|
32
32
|
from pathlib import Path
|
|
33
33
|
from typing import Any
|
|
34
34
|
|
|
35
|
-
VERSION = "1.3.
|
|
35
|
+
VERSION = "1.3.4"
|
|
36
36
|
|
|
37
37
|
CONFIG_PATH = Path(".claude/agent-sync.json")
|
|
38
38
|
ENV_FILE = Path(".env.agent-sync")
|
|
@@ -960,7 +960,36 @@ class Sync:
|
|
|
960
960
|
marker.parent.mkdir(parents=True, exist_ok=True)
|
|
961
961
|
marker.write_text(now_iso())
|
|
962
962
|
|
|
963
|
-
def
|
|
963
|
+
def _lease_holder(self, key: str) -> str | None:
|
|
964
|
+
"""Who holds this lease right now, in whichever plane arbitrates it."""
|
|
965
|
+
if self.lease_mode == "git":
|
|
966
|
+
sha, held = self._git_read_lease(key)
|
|
967
|
+
return held.get("run") if sha else None
|
|
968
|
+
lock = self._local_lock(key)
|
|
969
|
+
if not lock.exists():
|
|
970
|
+
return None
|
|
971
|
+
try:
|
|
972
|
+
return json.loads(lock.read_text()).get("run")
|
|
973
|
+
except (json.JSONDecodeError, OSError):
|
|
974
|
+
return None
|
|
975
|
+
|
|
976
|
+
def release(self, key: str) -> bool:
|
|
977
|
+
"""Release only what this run holds, and say so plainly when it does not.
|
|
978
|
+
|
|
979
|
+
This used to clear the board claim and report success unconditionally. The lease
|
|
980
|
+
plane refused correctly — `_git_release` prints a note and returns — but the
|
|
981
|
+
caller printed "released" over the top of it and exited 0, and `write_claim` had
|
|
982
|
+
already blanked the claim cell on the way in. The board then said the task was
|
|
983
|
+
free while the lease said it was taken: the exact disagreement a lease exists to
|
|
984
|
+
prevent, manufactured by the tool. Ownership is therefore checked FIRST, and
|
|
985
|
+
nothing is written when the answer is no.
|
|
986
|
+
"""
|
|
987
|
+
holder = self._lease_holder(key)
|
|
988
|
+
if holder is not None and holder != self.rid:
|
|
989
|
+
print(f"note: {key} is held by {holder}, not this run — nothing released",
|
|
990
|
+
file=sys.stderr)
|
|
991
|
+
return False
|
|
992
|
+
|
|
964
993
|
for n in self.write_claim(key, None):
|
|
965
994
|
print(f" {n}")
|
|
966
995
|
if self.lease_mode == "git":
|
|
@@ -978,6 +1007,7 @@ class Sync:
|
|
|
978
1007
|
fmt_line("release", key, self.rid))
|
|
979
1008
|
except Fail as exc:
|
|
980
1009
|
print(f"note: released locally, not published ({exc})", file=sys.stderr)
|
|
1010
|
+
return True
|
|
981
1011
|
|
|
982
1012
|
def held(self) -> list[str]:
|
|
983
1013
|
d = self.root / STATE_DIR / "leases"
|
|
@@ -2008,7 +2038,11 @@ def cmd_renew(args: argparse.Namespace) -> int:
|
|
|
2008
2038
|
|
|
2009
2039
|
|
|
2010
2040
|
def cmd_release(args: argparse.Namespace) -> int:
|
|
2011
|
-
|
|
2041
|
+
# Exit non-zero when nothing was released. A caller that scripts `release` in a
|
|
2042
|
+
# cleanup path has no other way to learn the lease is still out there.
|
|
2043
|
+
if not Sync().release(args.key):
|
|
2044
|
+
print(f"NOT released: {args.key} is held by another run", file=sys.stderr)
|
|
2045
|
+
return 1
|
|
2012
2046
|
print(f"released {args.key}")
|
|
2013
2047
|
return 0
|
|
2014
2048
|
|