agentic-comms 0.7.1__tar.gz → 0.7.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-comms
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: CLI message board for AI agents — coordinate between sessions, projects, and machines
5
5
  Author: jazcogames
6
6
  License: MIT
@@ -110,6 +110,7 @@ class LocalIdentity:
110
110
  server_url: str
111
111
  cwd: str
112
112
  claude_pid: int | None = None
113
+ session_id: str | None = None
113
114
 
114
115
  def save(self) -> None:
115
116
  """Dual-write: pid-keyed for the current session + cwd-only-keyed as the
@@ -475,22 +475,34 @@ def _build_skill_block(handle: str, display_name: str | None, version: str) -> s
475
475
  )
476
476
 
477
477
 
478
- def _maybe_skill_block(handle: str, display_name: str | None, claude_pid: int | None, version: str) -> str | None:
479
- """Emit the onboarding skill block only on first install, session revive, or version upgrade."""
478
+ def _maybe_skill_block(handle: str, display_name: str | None, session_id: str | None, version: str) -> str | None:
479
+ """Emit the onboarding skill block only on first install, session revive, or version upgrade.
480
+
481
+ Keyed by (handle, session_id, version). session_id from hook stdin is stable per Claude
482
+ session and unique across them — strictly better than PID-walking, which lands on different
483
+ PIDs across hook subprocesses when multiple claudes are alive on the same host.
484
+
485
+ Falls back to a stale-shown_at TTL (12h) when session_id is unavailable, so we don't loop
486
+ forever on clients that don't carry one."""
480
487
  state_path = _cache_dir() / "last-skill-shown.json"
481
488
  try:
482
489
  state = json.loads(state_path.read_text()) if state_path.exists() else {}
483
490
  except Exception:
484
491
  state = {}
485
- last_pid = state.get("pid")
492
+ last_session = state.get("session_id")
486
493
  last_version = state.get("version")
487
- if last_pid == claude_pid and last_version == version:
494
+ last_handle = state.get("handle")
495
+ last_shown = state.get("shown_at") or 0
496
+ same_session = (
497
+ last_handle == handle and last_version == version
498
+ and (last_session == session_id if session_id else (time.time() - last_shown) < 12 * 3600)
499
+ )
500
+ if same_session:
488
501
  return None
489
- # Persist BEFORE returning the block so a hook crash mid-emit doesn't loop.
490
502
  try:
491
503
  state_path.parent.mkdir(parents=True, exist_ok=True)
492
504
  state_path.write_text(json.dumps({
493
- "pid": claude_pid, "version": version,
505
+ "session_id": session_id, "version": version,
494
506
  "shown_at": time.time(), "handle": handle,
495
507
  }))
496
508
  except Exception:
@@ -506,20 +518,32 @@ def _get_version() -> str:
506
518
  return "unknown"
507
519
 
508
520
 
509
- def _detect_revive(handle: str, current_pid: int | None) -> bool:
510
- """Returns True if the stored claude_pid for this identity differs from current.
511
- Updates the stored pid as a side-effect."""
521
+ def _detect_revive(handle: str, current_session_id: str | None, current_pid: int | None) -> bool:
522
+ """Returns True if the stored session_id for this identity differs from current.
523
+ Falls back to PID comparison if session_id is unavailable on either side.
524
+ Updates stored session_id + claude_pid as a side-effect."""
512
525
  try:
513
526
  ident = config.load_identity()
514
527
  if not ident:
515
528
  return False
516
- prior = ident.claude_pid
517
- if prior == current_pid:
529
+ prior_session = getattr(ident, "session_id", None)
530
+ prior_pid = ident.claude_pid
531
+ # Prefer session_id when both sides have one (stable across hook subprocesses)
532
+ if current_session_id and prior_session:
533
+ same = (prior_session == current_session_id)
534
+ else:
535
+ same = (prior_pid == current_pid)
536
+ if same:
537
+ # Refresh stored values opportunistically (no revive)
538
+ if current_session_id and prior_session != current_session_id:
539
+ ident.session_id = current_session_id
540
+ ident.save()
518
541
  return False
519
- # PID changed — update stored identity
542
+ # Real change — update + signal revive (only if there WAS a prior record)
543
+ ident.session_id = current_session_id
520
544
  ident.claude_pid = current_pid
521
545
  ident.save()
522
- return prior is not None # only "revive" if there WAS a prior PID
546
+ return (prior_session is not None) or (prior_pid is not None)
523
547
  except Exception:
524
548
  return False
525
549
 
@@ -572,10 +596,11 @@ def main() -> int:
572
596
  early_pieces: list[str] = []
573
597
 
574
598
  if event_name in ("PostToolUse", "UserPromptSubmit"):
575
- revived = _detect_revive(handle, claude_pid)
576
- # Skill onboarding block — fires once per (pid, version) tuple
599
+ session_id = hook_input.get("session_id")
600
+ revived = _detect_revive(handle, session_id, claude_pid)
601
+ # Skill onboarding block — fires once per (handle, session_id, version) tuple
577
602
  skill_block = _maybe_skill_block(handle, ident.display_name if hasattr(ident, "display_name") else None,
578
- claude_pid, _get_version())
603
+ session_id, _get_version())
579
604
  if skill_block:
580
605
  early_pieces.append(skill_block)
581
606
  # Monitor liveness — fire every time until heartbeat is fresh
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-comms
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: CLI message board for AI agents — coordinate between sessions, projects, and machines
5
5
  Author: jazcogames
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentic-comms"
3
- version = "0.7.1"
3
+ version = "0.7.2"
4
4
  description = "CLI message board for AI agents — coordinate between sessions, projects, and machines"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -214,20 +214,31 @@ def test_skill_block_fires_on_first_install_then_suppressed(env, tmp_path, monke
214
214
  monkeypatch.setenv("AGENT_COMMS_CACHE_DIR", str(tmp_path / "cache"))
215
215
  from agent_comms.hook import _maybe_skill_block
216
216
  # First call — no record yet, should emit
217
- out1 = _maybe_skill_block("alpha", "Bob", 1234, "0.7.0")
217
+ out1 = _maybe_skill_block("alpha", "Bob", "session-aaa", "0.7.0")
218
218
  assert out1 is not None
219
219
  assert "agent_comms_onboarding" in out1
220
- # Second call same pid+version — suppressed
221
- out2 = _maybe_skill_block("alpha", "Bob", 1234, "0.7.0")
220
+ # Second call same session_id+version — suppressed
221
+ out2 = _maybe_skill_block("alpha", "Bob", "session-aaa", "0.7.0")
222
222
  assert out2 is None
223
- # PID changed (revive) — re-emit
224
- out3 = _maybe_skill_block("alpha", "Bob", 5678, "0.7.0")
223
+ # session_id changed (revive) — re-emit
224
+ out3 = _maybe_skill_block("alpha", "Bob", "session-bbb", "0.7.0")
225
225
  assert out3 is not None
226
226
  # Version changed (upgrade) — re-emit
227
- out4 = _maybe_skill_block("alpha", "Bob", 5678, "0.7.1")
227
+ out4 = _maybe_skill_block("alpha", "Bob", "session-bbb", "0.7.1")
228
228
  assert out4 is not None
229
229
 
230
230
 
231
+ def test_skill_block_no_session_id_uses_ttl_fallback(env, tmp_path, monkeypatch):
232
+ monkeypatch.setenv("AGENT_COMMS_CACHE_DIR", str(tmp_path / "cache"))
233
+ from agent_comms.hook import _maybe_skill_block
234
+ # No session_id — first call emits
235
+ out1 = _maybe_skill_block("alpha", "Bob", None, "0.7.0")
236
+ assert out1 is not None
237
+ # Subsequent call within TTL — suppressed
238
+ out2 = _maybe_skill_block("alpha", "Bob", None, "0.7.0")
239
+ assert out2 is None
240
+
241
+
231
242
  def test_monitor_warning_block_built(env):
232
243
  from agent_comms.hook import _build_monitor_warning
233
244
  s = _build_monitor_warning("test reason", "alpha")
@@ -241,17 +252,19 @@ def test_monitor_warning_block_built(env):
241
252
  def test_detect_revive(env, tmp_path, monkeypatch):
242
253
  from agent_comms import config as cfg
243
254
  from agent_comms.hook import _detect_revive
244
- # Force PID-walk to a fixed value for deterministic identity-file keying
245
255
  monkeypatch.setattr(cfg, "find_claude_pid", lambda: 1234)
246
- cwd = str(tmp_path / "work") # the env fixture chdirs here
247
- cfg.LocalIdentity(handle="alpha", server_url="x", cwd=cwd, claude_pid=1234).save()
248
- # Same pid → not a revive
249
- assert _detect_revive("alpha", 1234) is False
250
- # Different pid → revive (and load_identity must find the existing record via cwd fallback)
251
- monkeypatch.setattr(cfg, "find_claude_pid", lambda: 9999)
252
- assert _detect_revive("alpha", 9999) is True
253
- # After revive, PID is updated, so subsequent same-pid call is not a revive
254
- assert _detect_revive("alpha", 9999) is False
256
+ cwd = str(tmp_path / "work")
257
+ cfg.LocalIdentity(handle="alpha", server_url="x", cwd=cwd, claude_pid=1234, session_id="s-aaa").save()
258
+ # Same session_id → not a revive
259
+ assert _detect_revive("alpha", "s-aaa", 1234) is False
260
+ # Different session_id → revive (independent of PID)
261
+ monkeypatch.setattr(cfg, "find_claude_pid", lambda: 1234)
262
+ assert _detect_revive("alpha", "s-bbb", 1234) is True
263
+ # After revive, session_id is updated, so subsequent same call not a revive
264
+ assert _detect_revive("alpha", "s-bbb", 1234) is False
265
+ # PID thrashing while session_id stable should NOT trigger revive
266
+ assert _detect_revive("alpha", "s-bbb", 9999) is False
267
+ assert _detect_revive("alpha", "s-bbb", 7777) is False
255
268
 
256
269
 
257
270
  def _suppress_warnings(tmp_path):
File without changes
File without changes