@ssheleg/agent-sync 1.19.1 → 1.19.2

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 CHANGED
@@ -1,3 +1,25 @@
1
+ ## [1.19.2] - 2026-09-04
2
+
3
+ ### Fixed
4
+
5
+ - **An expired foreign lease printed at every SessionStart for ever, and the block it sat
6
+ in stopped being read.** Measured on the machine that filed the issue: sixteen expired
7
+ leases, none newer than two days, the oldest **thirteen days old** — six printed in full
8
+ plus *"… and 11 more"*, identically, every session for a fortnight. None of it was
9
+ actionable: a foreign lease is reported and deliberately never touched, so the operator
10
+ could not clear a single line of it. A block reprinted verbatim is a block an operator
11
+ learns to skim, **including the lines that would have mattered**.
12
+
13
+ Age splits the list now. A lease expired inside `STALE_DETAIL_SECONDS` (24h) may still
14
+ mean a run is in trouble and keeps its own line; anything older collapses into **one**
15
+ summary — which still **names the keys**, because a summary that hides which leases are
16
+ stuck replaces noise with a different uselessness. Sixteen lines became one.
17
+
18
+ Watched failing before it shipped: with the split removed the new check reports
19
+ *"6 ancient lease(s) still print a line each"* and *"nine ancient leases produced no
20
+ summary line at all, so they vanished"* — the second assertion exists because collapsing
21
+ and hiding are one edit apart.
22
+
1
23
  ## v1.19.1 — re-acquiring your own lease now refreshes it
2
24
 
3
25
  **`renew` was fixed for exactly this and `acquire` was not, in the same file.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssheleg/agent-sync",
3
- "version": "1.19.1",
3
+ "version": "1.19.2",
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"
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "agent-sync",
4
4
  "displayName": "Agent Sync",
5
- "version": "1.19.1",
5
+ "version": "1.19.2",
6
6
  "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.",
7
7
  "author": {
8
8
  "name": "ssheleg",
@@ -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.19.1"
7
+ version: "1.19.2"
8
8
  author: ssheleg
9
9
  ---
10
10
 
@@ -33,11 +33,16 @@ from datetime import datetime, timezone
33
33
  from pathlib import Path
34
34
  from typing import Any
35
35
 
36
- VERSION = "1.19.1"
36
+ VERSION = "1.19.2"
37
37
 
38
38
  CONFIG_PATH = Path(".claude/agent-sync.json")
39
39
  ENV_FILE = Path(".env.agent-sync")
40
40
  STATE_DIR = Path(".agent-sync")
41
+ # How recent an expired lease has to be to earn a line of its own. Anything older
42
+ # collapses into one summary that still names the keys. Twenty-four hours is chosen
43
+ # because it is the span in which an expired lease can still mean "a run is in
44
+ # trouble"; beyond it, it means "somebody's machine was closed".
45
+ STALE_DETAIL_SECONDS = 24 * 60 * 60
41
46
  GENERATED_MARKER = "<!-- agent-sync:generated"
42
47
  MERGE_LOG_MARKER = "<!-- agent-sync:merge-log -->"
43
48
  DEFAULT_MERGE_LOG = "docs/MERGES.md"
@@ -3392,10 +3397,29 @@ def cmd_status(_args: argparse.Namespace) -> int:
3392
3397
  f"{len(left_alone)} left alone")
3393
3398
  print("\n Expired leases still on disk. Nobody holds these: the TTL ended the "
3394
3399
  "lease\n and left the file.")
3395
- for e in stale[:6]:
3400
+ # Age splits the list, because age is what decides whether a line is news.
3401
+ # A lease that expired an hour ago may still be a run in trouble; one that
3402
+ # expired thirteen days ago is residue nobody will ever clear, and printing
3403
+ # it in full at every SessionStart is how an operator learns to skim the
3404
+ # whole block — including the recent lines that mattered. Measured on the
3405
+ # machine that filed this: sixteen entries, none newer than two days, the
3406
+ # oldest thirteen days old, printed identically every session for a fortnight.
3407
+ fresh = [e for e in stale
3408
+ if e.get("expired_for") is not None and e["expired_for"] < STALE_DETAIL_SECONDS]
3409
+ old_ones = [e for e in stale if e not in fresh]
3410
+ for e in fresh[:6]:
3396
3411
  print(f" · {e['key']} [{e['state']}] {e['why']} ({spent(e)})")
3397
- if len(stale) > 6:
3398
- print(f" · … and {len(stale) - 6} more agent_sync.py residue")
3412
+ if len(fresh) > 6:
3413
+ print(f" · … and {len(fresh) - 6} more expired in the last "
3414
+ f"{since(STALE_DETAIL_SECONDS)}")
3415
+ if old_ones:
3416
+ # One line, and it still names them: a summary that hides WHICH keys
3417
+ # cannot be acted on, and the point of collapsing is to keep the block
3418
+ # readable rather than to stop reporting.
3419
+ keys = ", ".join(e["key"] for e in old_ones[:8])
3420
+ more = f", +{len(old_ones) - 8} more" if len(old_ones) > 8 else ""
3421
+ print(f" · {len(old_ones)} expired over {since(STALE_DETAIL_SECONDS)} ago "
3422
+ f"— {keys}{more}")
3399
3423
  if reapable:
3400
3424
  print(" Clear what this run owns: agent_sync.py reap")
3401
3425
  if left_alone: