agent-chat-plugin 0.4.0__tar.gz → 0.5.0__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.
Files changed (24) hide show
  1. {agent_chat_plugin-0.4.0/agent_chat_plugin.egg-info → agent_chat_plugin-0.5.0}/PKG-INFO +1 -1
  2. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0/agent_chat_plugin.egg-info}/PKG-INFO +1 -1
  3. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/chat.py +71 -47
  4. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/pyproject.toml +1 -1
  5. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/LICENSE +0 -0
  6. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/README.md +0 -0
  7. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/agent_chat/lease_store.py +0 -0
  8. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/agent_chat/path_locks.py +0 -0
  9. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/agent_chat/state_store.py +0 -0
  10. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/agent_chat/task_model.py +0 -0
  11. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/agent_chat/task_store.py +0 -0
  12. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/agent_chat_plugin.egg-info/SOURCES.txt +0 -0
  13. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/agent_chat_plugin.egg-info/dependency_links.txt +0 -0
  14. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/agent_chat_plugin.egg-info/entry_points.txt +0 -0
  15. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/agent_chat_plugin.egg-info/top_level.txt +0 -0
  16. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/setup.cfg +0 -0
  17. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/tests/test_capabilities.py +0 -0
  18. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/tests/test_chat.py +0 -0
  19. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/tests/test_hooks.py +0 -0
  20. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/tests/test_leases.py +0 -0
  21. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/tests/test_path_locks.py +0 -0
  22. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/tests/test_plugin_manifest.py +0 -0
  23. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/tests/test_state.py +0 -0
  24. {agent_chat_plugin-0.4.0 → agent_chat_plugin-0.5.0}/tests/test_tasks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-chat-plugin
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Peer AI agents coordinate through markdown messages in shared channel folders — no orchestrator, autonomous zero-token waiting, cross-platform.
5
5
  Author: n24q02m
6
6
  License: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-chat-plugin
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Peer AI agents coordinate through markdown messages in shared channel folders — no orchestrator, autonomous zero-token waiting, cross-platform.
5
5
  Author: n24q02m
6
6
  License: Apache-2.0
@@ -420,8 +420,24 @@ def cmd_channels(root: Path, a):
420
420
  print(f"(no channels yet under {root})")
421
421
  return
422
422
  rows = []
423
- for meta_path in sorted(root.glob("*/_meta.json")):
424
- chan = meta_path.parent
423
+ found_channels = []
424
+ # Optimization: Use os.scandir instead of Path.glob("*/_meta.json") to discover channels.
425
+ # This avoids instantiating thousands of Path objects for discarded subdirectories.
426
+ # Filters out hidden directories (starting with '.') to maintain parity with glob("*").
427
+ try:
428
+ with os.scandir(root) as it:
429
+ for entry in it:
430
+ if (
431
+ not entry.name.startswith(".")
432
+ and entry.is_dir()
433
+ and os.path.exists(os.path.join(entry.path, "_meta.json"))
434
+ ):
435
+ found_channels.append(entry.name)
436
+ except OSError:
437
+ pass
438
+ for chan_name in sorted(found_channels):
439
+ chan = root / chan_name
440
+ meta_path = chan / "_meta.json"
425
441
  try:
426
442
  meta = json.loads(meta_path.read_text(encoding="utf-8"))
427
443
  except (OSError, ValueError):
@@ -594,24 +610,32 @@ def cmd_wait(root: Path, a):
594
610
  d = require_channel(root, a.channel)
595
611
  cur = read_cursor(d, a.agent)
596
612
  deadline = time.time() + a.timeout
613
+ last_mtime = 0
597
614
  while True:
598
- found = []
599
- # Optimization: use os.scandir to avoid Path instantiation overhead for
600
- # thousands of old messages per tick.
601
615
  try:
602
- with os.scandir(d) as it:
603
- for entry in it:
604
- if not entry.name.endswith(".md"):
605
- continue
606
- seq = _seq_from_name(entry.name)
607
- if seq is None or seq <= cur:
608
- continue
609
- p = Path(entry.path)
610
- meta = parse_frontmatter(p)
611
- if is_relevant(meta, a.agent):
612
- found.append(p)
616
+ mtime = d.stat().st_mtime
613
617
  except OSError:
614
- pass
618
+ mtime = 0
619
+
620
+ found = []
621
+ if mtime == 0 or mtime != last_mtime:
622
+ last_mtime = mtime
623
+ # Optimization: use os.scandir to avoid Path instantiation overhead for
624
+ # thousands of old messages per tick.
625
+ try:
626
+ with os.scandir(d) as it:
627
+ for entry in it:
628
+ if not entry.name.endswith(".md"):
629
+ continue
630
+ seq = _seq_from_name(entry.name)
631
+ if seq is None or seq <= cur:
632
+ continue
633
+ p = Path(entry.path)
634
+ meta = parse_frontmatter(p)
635
+ if is_relevant(meta, a.agent):
636
+ found.append(p)
637
+ except OSError:
638
+ pass
615
639
  if found:
616
640
  # Sort only the newly found messages
617
641
  found.sort(key=lambda p: _seq_from_name(p.name))
@@ -777,12 +801,12 @@ def cmd_compact(root: Path, a):
777
801
  print(f"compacted state for {a.channel} -> {a.channel}/state.md (open_tasks={len(summary.open_tasks)}, locks={len(summary.path_locks)}, decisions={len(summary.decisions)})")
778
802
 
779
803
  def _event_body(path: Path) -> dict:
780
- raw = path.read_text(encoding="utf-8")
781
- parts = raw.split("---", 2)
782
- body = parts[2].strip() if len(parts) >= 3 else ""
783
804
  try:
805
+ raw = path.read_text(encoding="utf-8")
806
+ parts = raw.split("---", 2)
807
+ body = parts[2].strip() if len(parts) >= 3 else ""
784
808
  return validate_adapter_event(json.loads(body))
785
- except (json.JSONDecodeError, UnicodeError) as error:
809
+ except (json.JSONDecodeError, UnicodeError, OSError) as error:
786
810
  raise AdapterEventError("EVENT_MALFORMED_BODY", path.name) from error
787
811
 
788
812
 
@@ -1118,50 +1142,50 @@ def build_parser() -> argparse.ArgumentParser:
1118
1142
 
1119
1143
 
1120
1144
  s = sub.add_parser("init", help="create a channel")
1121
- s.add_argument("channel")
1145
+ s.add_argument("channel", help="name of the channel to create")
1122
1146
  s.add_argument("--members", help="comma-separated agent names")
1123
- s.add_argument("--topic")
1147
+ s.add_argument("--topic", help="initial topic of the channel")
1124
1148
  s.set_defaults(func=cmd_init)
1125
1149
 
1126
1150
  s = sub.add_parser("channels", help="list channels")
1127
1151
  s.set_defaults(func=cmd_channels)
1128
1152
 
1129
1153
  s = sub.add_parser("roster", help="show a channel's members")
1130
- s.add_argument("channel")
1154
+ s.add_argument("channel", help="channel to inspect")
1131
1155
  s.set_defaults(func=cmd_roster)
1132
1156
 
1133
1157
  s = sub.add_parser(
1134
1158
  "post", help="post a message (body via --body/--body-file/stdin)"
1135
1159
  )
1136
- s.add_argument("channel")
1137
- s.add_argument("--from", dest="sender", required=True)
1160
+ s.add_argument("channel", help="channel to post in")
1161
+ s.add_argument("--from", dest="sender", required=True, help="sender agent name")
1138
1162
  s.add_argument("--to", help="recipient agent, or 'all' (default all)")
1139
- s.add_argument("--title", required=True)
1163
+ s.add_argument("--title", required=True, help="message title")
1140
1164
  s.add_argument("--reply", type=int, help="seq this replies to")
1141
- s.add_argument("--status", default="discussion")
1142
- s.add_argument("--body")
1143
- s.add_argument("--body-file")
1165
+ s.add_argument("--status", default="discussion", help="message status (default: discussion)")
1166
+ s.add_argument("--body", help="literal message body content")
1167
+ s.add_argument("--body-file", help="read message body from file")
1144
1168
  s.set_defaults(func=cmd_post)
1145
1169
 
1146
1170
  event = sub.add_parser("event", help="post/read adapter-neutral events")
1147
1171
  event_sub = event.add_subparsers(dest="event_cmd", required=True)
1148
1172
  s = event_sub.add_parser("post", help="post a capability or status event")
1149
- s.add_argument("channel")
1150
- s.add_argument("--from", dest="sender", required=True)
1151
- s.add_argument("--type", dest="event_type", choices=EVENT_TYPES, required=True)
1152
- s.add_argument("--harness", required=True)
1153
- s.add_argument("--status", choices=STATUS_VALUES)
1154
- s.add_argument("--detail")
1155
- s.add_argument("--primitives", action="append")
1173
+ s.add_argument("channel", help="channel to post the event in")
1174
+ s.add_argument("--from", dest="sender", required=True, help="sender agent name")
1175
+ s.add_argument("--type", dest="event_type", choices=EVENT_TYPES, required=True, help="type of the event")
1176
+ s.add_argument("--harness", required=True, help="harness name")
1177
+ s.add_argument("--status", choices=STATUS_VALUES, help="status of the agent")
1178
+ s.add_argument("--detail", help="optional details about the status")
1179
+ s.add_argument("--primitives", action="append", help="primitives supported by the agent")
1156
1180
  s.set_defaults(func=cmd_event_post)
1157
1181
  s = event_sub.add_parser("read", help="read validated adapter-neutral events")
1158
- s.add_argument("channel")
1159
- s.add_argument("--type", dest="event_type", choices=EVENT_TYPES)
1182
+ s.add_argument("channel", help="channel to read events from")
1183
+ s.add_argument("--type", dest="event_type", choices=EVENT_TYPES, help="filter by event type")
1160
1184
  s.set_defaults(func=cmd_event_read)
1161
1185
 
1162
1186
  s = sub.add_parser("read", help="print new messages for an agent (advances cursor)")
1163
- s.add_argument("channel")
1164
- s.add_argument("--as", dest="agent", required=True)
1187
+ s.add_argument("channel", help="channel to read from")
1188
+ s.add_argument("--as", dest="agent", required=True, help="agent reading the messages")
1165
1189
  s.add_argument(
1166
1190
  "--all", action="store_true", help="show entire thread, ignore relevance"
1167
1191
  )
@@ -1171,15 +1195,15 @@ def build_parser() -> argparse.ArgumentParser:
1171
1195
  s = sub.add_parser(
1172
1196
  "wait", help="block (sleep-poll, 0 tokens) until a reply arrives"
1173
1197
  )
1174
- s.add_argument("channel")
1175
- s.add_argument("--as", dest="agent", required=True)
1176
- s.add_argument("--timeout", type=float, default=900.0)
1177
- s.add_argument("--interval", type=float, default=5.0)
1198
+ s.add_argument("channel", help="channel to wait on")
1199
+ s.add_argument("--as", dest="agent", required=True, help="agent waiting for messages")
1200
+ s.add_argument("--timeout", type=float, default=900.0, help="maximum wait time in seconds")
1201
+ s.add_argument("--interval", type=float, default=5.0, help="polling interval in seconds")
1178
1202
  s.set_defaults(func=cmd_wait)
1179
1203
 
1180
1204
  s = sub.add_parser("peek", help="show last N messages without touching the cursor")
1181
- s.add_argument("channel")
1182
- s.add_argument("-n", type=int, default=3)
1205
+ s.add_argument("channel", help="channel to peek into")
1206
+ s.add_argument("-n", type=int, default=3, help="number of messages to show")
1183
1207
  s.set_defaults(func=cmd_peek)
1184
1208
 
1185
1209
  s = sub.add_parser("claim", help="atomically claim a task-<id>.md marker")
@@ -10,7 +10,7 @@ requires-python = ">=3.8"
10
10
  license = { text = "Apache-2.0" }
11
11
  authors = [{ name = "n24q02m" }]
12
12
  keywords = ["multi-agent", "coordination", "markdown", "blackboard", "claude-code", "agent-skills", "peer-to-peer"]
13
- version = "0.4.0" # managed by python-semantic-release; do not hand-edit
13
+ version = "0.5.0" # managed by python-semantic-release; do not hand-edit
14
14
  classifiers = [
15
15
  "Programming Language :: Python :: 3",
16
16
  "License :: OSI Approved :: Apache Software License",