zwarm 1.3.10__py3-none-any.whl → 2.0.0__py3-none-any.whl

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.
zwarm/watchers/builtin.py CHANGED
@@ -7,10 +7,99 @@ from __future__ import annotations
7
7
  import re
8
8
  from typing import Any
9
9
 
10
+ from wbal.helper import TOOL_CALL_TYPE, TOOL_RESULT_TYPE
10
11
  from zwarm.watchers.base import Watcher, WatcherContext, WatcherResult, WatcherAction
11
12
  from zwarm.watchers.registry import register_watcher
12
13
 
13
14
 
15
+ def _get_field(item: Any, name: str, default: Any = None) -> Any:
16
+ if isinstance(item, dict):
17
+ return item.get(name, default)
18
+ return getattr(item, name, default)
19
+
20
+
21
+ def _content_to_text(content: Any) -> str:
22
+ if content is None:
23
+ return ""
24
+ if isinstance(content, str):
25
+ return content
26
+ if isinstance(content, list):
27
+ parts = []
28
+ for part in content:
29
+ text = _content_to_text(part)
30
+ if text:
31
+ parts.append(text)
32
+ return "\n".join(parts)
33
+ if isinstance(content, dict):
34
+ text = content.get("text") or content.get("content") or content.get("refusal")
35
+ return str(text) if text is not None else ""
36
+ text = getattr(content, "text", None)
37
+ if text is None:
38
+ text = getattr(content, "refusal", None)
39
+ return str(text) if text is not None else ""
40
+
41
+
42
+ def _normalize_tool_call(tool_call: Any) -> dict[str, Any]:
43
+ if isinstance(tool_call, dict):
44
+ if isinstance(tool_call.get("function"), dict):
45
+ return tool_call
46
+ name = tool_call.get("name", "")
47
+ arguments = tool_call.get("arguments", "")
48
+ call_id = tool_call.get("call_id")
49
+ else:
50
+ name = getattr(tool_call, "name", "")
51
+ arguments = getattr(tool_call, "arguments", "")
52
+ call_id = getattr(tool_call, "call_id", None)
53
+
54
+ normalized = {
55
+ "type": "function",
56
+ "function": {"name": name, "arguments": arguments},
57
+ }
58
+ if call_id:
59
+ normalized["call_id"] = call_id
60
+ return normalized
61
+
62
+
63
+ def _normalize_messages(messages: list[Any]) -> list[dict[str, Any]]:
64
+ normalized: list[dict[str, Any]] = []
65
+ for item in messages:
66
+ item_type = _get_field(item, "type")
67
+ role = _get_field(item, "role")
68
+ content = ""
69
+ tool_calls: list[dict[str, Any]] = []
70
+
71
+ if item_type in (TOOL_CALL_TYPE, "function_call"):
72
+ tool_calls = [_normalize_tool_call(item)]
73
+ role = role or "assistant"
74
+ else:
75
+ raw_tool_calls = _get_field(item, "tool_calls") or []
76
+ if raw_tool_calls and not isinstance(raw_tool_calls, list):
77
+ raw_tool_calls = [raw_tool_calls]
78
+ if raw_tool_calls:
79
+ tool_calls = [
80
+ _normalize_tool_call(tc) for tc in raw_tool_calls
81
+ ]
82
+
83
+ if role or item_type == "message" or item_type is None:
84
+ content = _content_to_text(_get_field(item, "content"))
85
+
86
+ if item_type == TOOL_RESULT_TYPE and not content:
87
+ content = _content_to_text(_get_field(item, "output"))
88
+ role = role or "tool"
89
+
90
+ if not role and not content and not tool_calls:
91
+ continue
92
+
93
+ normalized.append(
94
+ {
95
+ "role": role,
96
+ "content": content or "",
97
+ "tool_calls": tool_calls,
98
+ }
99
+ )
100
+ return normalized
101
+
102
+
14
103
  @register_watcher("progress")
15
104
  class ProgressWatcher(Watcher):
16
105
  """
@@ -26,14 +115,15 @@ class ProgressWatcher(Watcher):
26
115
  description = "Detects when agent is stuck or spinning"
27
116
 
28
117
  async def observe(self, ctx: WatcherContext) -> WatcherResult:
118
+ messages = _normalize_messages(ctx.messages)
29
119
  config = self.config
30
120
  max_same_calls = config.get("max_same_calls", 3)
31
121
  min_progress_steps = config.get("min_progress_steps", 5)
32
122
 
33
123
  # Check for repeated tool calls
34
- if len(ctx.messages) >= max_same_calls * 2:
124
+ if len(messages) >= max_same_calls * 2:
35
125
  recent_assistant = [
36
- m for m in ctx.messages[-max_same_calls * 2 :]
126
+ m for m in messages[-max_same_calls * 2 :]
37
127
  if m.get("role") == "assistant"
38
128
  ]
39
129
  if len(recent_assistant) >= max_same_calls:
@@ -144,9 +234,10 @@ class ScopeWatcher(Watcher):
144
234
 
145
235
  # Check last few messages for avoid keywords
146
236
  if avoid_keywords:
237
+ messages = _normalize_messages(ctx.messages)
147
238
  recent_content = " ".join(
148
239
  m.get("content", "") or ""
149
- for m in ctx.messages[-max_tangent_steps * 2:]
240
+ for m in messages[-max_tangent_steps * 2:]
150
241
  ).lower()
151
242
 
152
243
  for keyword in avoid_keywords:
@@ -174,6 +265,7 @@ class PatternWatcher(Watcher):
174
265
  description = "Watches for configurable patterns in output"
175
266
 
176
267
  async def observe(self, ctx: WatcherContext) -> WatcherResult:
268
+ messages = _normalize_messages(ctx.messages)
177
269
  config = self.config
178
270
  patterns = config.get("patterns", [])
179
271
 
@@ -189,7 +281,7 @@ class PatternWatcher(Watcher):
189
281
  continue
190
282
 
191
283
  # Check recent messages
192
- for msg in ctx.messages[-10:]:
284
+ for msg in messages[-10:]:
193
285
  content = msg.get("content", "") or ""
194
286
  if compiled.search(content):
195
287
  action = pattern_config.get("action", "nudge")
@@ -236,11 +328,12 @@ class DelegationWatcher(Watcher):
236
328
  ]
237
329
 
238
330
  async def observe(self, ctx: WatcherContext) -> WatcherResult:
331
+ messages = _normalize_messages(ctx.messages)
239
332
  config = self.config
240
333
  strict = config.get("strict", True) # If True, nudge. If False, just warn.
241
334
 
242
335
  # Check recent messages for bash tool calls
243
- for msg in ctx.messages[-10:]:
336
+ for msg in messages[-10:]:
244
337
  if msg.get("role") != "assistant":
245
338
  continue
246
339
 
@@ -370,6 +463,7 @@ class DelegationReminderWatcher(Watcher):
370
463
  }
371
464
 
372
465
  async def observe(self, ctx: WatcherContext) -> WatcherResult:
466
+ messages = _normalize_messages(ctx.messages)
373
467
  config = self.config
374
468
  threshold = config.get("threshold", 10) # Max consecutive non-delegation calls
375
469
  lookback = config.get("lookback", 30) # How many messages to check
@@ -378,7 +472,7 @@ class DelegationReminderWatcher(Watcher):
378
472
  consecutive_non_delegation = 0
379
473
 
380
474
  # Look through recent messages in reverse order
381
- for msg in reversed(ctx.messages[-lookback:]):
475
+ for msg in reversed(messages[-lookback:]):
382
476
  if msg.get("role") != "assistant":
383
477
  continue
384
478
 
@@ -0,0 +1,309 @@
1
+ Metadata-Version: 2.4
2
+ Name: zwarm
3
+ Version: 2.0.0
4
+ Summary: Multi-Agent CLI Orchestration Research Platform
5
+ Requires-Python: <3.14,>=3.13
6
+ Requires-Dist: python-dotenv>=1.0.0
7
+ Requires-Dist: pyyaml>=6.0
8
+ Requires-Dist: rich>=13.0.0
9
+ Requires-Dist: typer>=0.9.0
10
+ Requires-Dist: wbal>=0.4.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ # zwarm
14
+
15
+ Multi-agent CLI for orchestrating coding agents. Spawn, manage, and converse with multiple Codex sessions in parallel.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ # From the workspace
21
+ cd /path/to/labs
22
+ uv sync
23
+
24
+ # Or install directly
25
+ uv pip install -e ./zwarm
26
+ ```
27
+
28
+ **Requirements:**
29
+ - Python 3.13+
30
+ - `codex` CLI installed and authenticated
31
+
32
+ **Environment:**
33
+ ```bash
34
+ export OPENAI_API_KEY="sk-..." # Required for Codex
35
+ export WEAVE_PROJECT="entity/zwarm" # Optional: Weave tracing
36
+ ```
37
+
38
+ ## Two Modes
39
+
40
+ zwarm has two ways to orchestrate coding agents:
41
+
42
+ | Mode | Who's in charge | Use case |
43
+ |------|-----------------|----------|
44
+ | `zwarm interactive` | **You** | Manual control, experimentation |
45
+ | `zwarm orchestrate` | **LLM** | Autonomous task execution |
46
+
47
+ Both use the **same underlying session manager** - the orchestrator LLM has access to the exact same tools you do.
48
+
49
+ ---
50
+
51
+ ## Interactive Mode
52
+
53
+ **You are the orchestrator.** Spawn sessions, check on them, continue conversations.
54
+
55
+ ```bash
56
+ zwarm interactive
57
+ ```
58
+
59
+ ### Commands
60
+
61
+ | Command | Description |
62
+ |---------|-------------|
63
+ | `spawn "task"` | Start a session (waits for completion) |
64
+ | `spawn --async "task"` | Start async (returns immediately) |
65
+ | `spawn -d /path "task"` | Start in specific directory |
66
+ | `ls` | List all sessions |
67
+ | `? <id>` | Quick peek: status + latest message |
68
+ | `show <id>` | Full details: all messages, tokens, etc. |
69
+ | `c <id> "msg"` | Continue conversation (waits) |
70
+ | `ca <id> "msg"` | Continue async (returns immediately) |
71
+ | `kill <id>` | Stop a running session |
72
+ | `rm <id>` | Delete session entirely |
73
+ | `killall` | Stop all running sessions |
74
+ | `clean` | Remove sessions older than 7 days |
75
+ | `q` | Quit |
76
+
77
+ ### Example Session
78
+
79
+ ```
80
+ $ zwarm interactive
81
+
82
+ > spawn "Add a login function to auth.py"
83
+ ✓ Started session a1b2c3d4, waiting...
84
+ [a1b2c3d4] codex (completed) - 32s
85
+ Response: I've added a login function with JWT support...
86
+
87
+ > spawn --async "Fix the type errors in utils.py"
88
+ ✓ Session: b2c3d4e5 (running in background)
89
+
90
+ > spawn --async "Add unit tests for auth.py"
91
+ ✓ Session: c3d4e5f6 (running in background)
92
+
93
+ > ls
94
+ 1 running | 2 done
95
+
96
+ ID │ │ T │ Task │ Updated │ Last Message
97
+ a1b2c3d4 │ ✓ │ 1 │ Add a login function... │ 2m │ I've added a login function...
98
+ b2c3d4e5 │ ✓ │ 1 │ Fix the type errors... │ 30s ★ │ Fixed 3 type errors in...
99
+ c3d4e5f6 │ ● │ 1 │ Add unit tests... │ 5s │ (working...)
100
+
101
+ > ? b2c3d4e5
102
+ ✓ b2c3d4e5 Fixed 3 type errors: Optional[str] -> str | None, added missing...
103
+
104
+ > c a1b2c3d4 "Now add password hashing with bcrypt"
105
+ Continuing session a1b2c3d4...
106
+ [a1b2c3d4] codex (completed) - 28s
107
+ Response: Done! I've updated the login function to use bcrypt...
108
+
109
+ > rm b2c3d4e5
110
+ ✓ Deleted session b2c3d4e5
111
+
112
+ > q
113
+ ```
114
+
115
+ ### Session Status Icons
116
+
117
+ | Icon | Status |
118
+ |------|--------|
119
+ | `●` | Running |
120
+ | `✓` | Completed |
121
+ | `✗` | Failed |
122
+ | `○` | Killed |
123
+ | `★` | Recently completed (< 60s) |
124
+
125
+ ---
126
+
127
+ ## Orchestrate Mode
128
+
129
+ **An LLM is the orchestrator.** Give it a task and it delegates to coding agents.
130
+
131
+ ```bash
132
+ zwarm orchestrate --task "Build a REST API with authentication"
133
+ ```
134
+
135
+ The orchestrator LLM uses the same tools available in interactive mode:
136
+
137
+ | Tool | Description |
138
+ |------|-------------|
139
+ | `delegate(task, ...)` | Start a new session |
140
+ | `converse(id, msg)` | Continue a conversation |
141
+ | `peek_session(id)` | Quick status check |
142
+ | `check_session(id)` | Full session details |
143
+ | `list_sessions()` | List all sessions with `needs_attention` flags |
144
+ | `end_session(id, delete=False)` | Kill/delete a session |
145
+
146
+ ### Task Input
147
+
148
+ ```bash
149
+ # Direct
150
+ zwarm orchestrate --task "Build a REST API"
151
+
152
+ # From file
153
+ zwarm orchestrate --task-file task.md
154
+
155
+ # From stdin
156
+ echo "Fix the bug in auth.py" | zwarm orchestrate
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Configuration
162
+
163
+ zwarm looks for config in this order:
164
+ 1. `--config` flag
165
+ 2. `.zwarm/config.toml`
166
+ 3. `config.toml` in working directory
167
+
168
+ ### Minimal Config
169
+
170
+ ```toml
171
+ [weave]
172
+ enabled = true
173
+ project = "your-entity/zwarm"
174
+
175
+ [executor]
176
+ adapter = "codex_mcp"
177
+ model = "gpt-5.1-codex-mini"
178
+ ```
179
+
180
+ ### Full Config Reference
181
+
182
+ ```toml
183
+ [orchestrator]
184
+ lm = "gpt-5-mini"
185
+ max_steps = 100
186
+
187
+ [orchestrator.compaction]
188
+ enabled = true
189
+ max_tokens = 100000
190
+ threshold_pct = 0.85
191
+ target_pct = 0.7
192
+
193
+ [executor]
194
+ adapter = "codex_mcp"
195
+ model = "gpt-5.1-codex-mini"
196
+ sandbox = "workspace-write"
197
+ timeout = 300
198
+
199
+ [weave]
200
+ enabled = true
201
+ project = "your-entity/zwarm"
202
+
203
+ [watchers]
204
+ enabled = true
205
+ watchers = [
206
+ { name = "progress" },
207
+ { name = "budget", config = { max_steps = 50, max_sessions = 10 } },
208
+ { name = "delegation_reminder", config = { threshold = 10 } },
209
+ ]
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Session Management
215
+
216
+ Sessions are the core abstraction. Each session is a conversation with a Codex agent.
217
+
218
+ ### Lifecycle
219
+
220
+ ```
221
+ spawn → running → completed/failed
222
+
223
+ continue → running → completed
224
+
225
+ continue → ...
226
+ ```
227
+
228
+ ### Storage
229
+
230
+ ```
231
+ .zwarm/sessions/<uuid>/
232
+ ├── meta.json # Status, task, model, messages, tokens
233
+ └── turns/
234
+ ├── turn_1.jsonl # Raw codex output for turn 1
235
+ ├── turn_2.jsonl # Output after first continue
236
+ └── ...
237
+ ```
238
+
239
+ ### Sync vs Async
240
+
241
+ | Mode | Spawn | Continue | Use case |
242
+ |------|-------|----------|----------|
243
+ | **Sync** | `spawn "task"` | `c id "msg"` | Sequential work, immediate feedback |
244
+ | **Async** | `spawn --async "task"` | `ca id "msg"` | Parallel work, batch processing |
245
+
246
+ Async sessions return immediately. Poll with `ls` or `?` to check status.
247
+
248
+ ---
249
+
250
+ ## Watchers
251
+
252
+ Watchers monitor agent behavior and intervene when needed.
253
+
254
+ | Watcher | Purpose |
255
+ |---------|---------|
256
+ | `progress` | Detects stuck/spinning agents |
257
+ | `budget` | Enforces step/session limits |
258
+ | `scope` | Detects scope creep |
259
+ | `delegation_reminder` | Nudges orchestrator to delegate |
260
+
261
+ Configure in `config.toml`:
262
+
263
+ ```toml
264
+ [watchers]
265
+ enabled = true
266
+ watchers = [
267
+ { name = "progress" },
268
+ { name = "budget", config = { max_steps = 50 } },
269
+ ]
270
+ ```
271
+
272
+ ---
273
+
274
+ ## CLI Reference
275
+
276
+ ```bash
277
+ zwarm init # Initialize .zwarm/ in current directory
278
+ zwarm interactive # Start interactive REPL
279
+ zwarm orchestrate # Start LLM orchestrator
280
+ zwarm exec # Run single executor directly (testing)
281
+ zwarm status # Show current state
282
+ zwarm history # Show event history
283
+ zwarm clean # Remove old sessions
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Project Structure
289
+
290
+ ```
291
+ zwarm/
292
+ ├── src/zwarm/
293
+ │ ├── sessions/ # Session management (core)
294
+ │ │ ├── manager.py # CodexSessionManager
295
+ │ │ └── __init__.py
296
+ │ ├── tools/
297
+ │ │ └── delegation.py # Orchestrator tools (delegate, converse, etc.)
298
+ │ ├── cli/
299
+ │ │ └── main.py # CLI commands and interactive REPL
300
+ │ ├── core/
301
+ │ │ ├── config.py # Configuration loading
302
+ │ │ ├── compact.py # Context window management
303
+ │ │ └── state.py # State persistence
304
+ │ ├── watchers/ # Trajectory alignment
305
+ │ └── orchestrator.py # Orchestrator agent
306
+ ├── docs/
307
+ │ └── INTERNALS.md # Technical architecture
308
+ └── README.md
309
+ ```
@@ -1,18 +1,18 @@
1
1
  zwarm/__init__.py,sha256=3i3LMjHwIzE-LFIS2aUrwv3EZmpkvVMe-xj1h97rcSM,837
2
- zwarm/orchestrator.py,sha256=38PKWwT79U0VY9aqZe5atf9C5EAFGgMXE7PXPX5QJ0w,21359
2
+ zwarm/orchestrator.py,sha256=FGr-_MpG_UGUFAa19oenh7MVifE6frK6URS8ii0x0IA,22952
3
3
  zwarm/test_orchestrator_watchers.py,sha256=QpoaehPU7ekT4XshbTOWnJ2H0wRveV3QOZjxbgyJJLY,807
4
4
  zwarm/adapters/__init__.py,sha256=O0b-SfZpb6txeNqFkXZ2aaf34yLFYreznyrAV25jF_Q,656
5
5
  zwarm/adapters/base.py,sha256=fZlQviTgVvOcwnxduTla6WuM6FzQJ_yoHMW5SxwVgQg,2527
6
6
  zwarm/adapters/claude_code.py,sha256=vAjsjD-_JjARmC4_FBSILQZmQCBrk_oNHo18a9ubuqk,11481
7
- zwarm/adapters/codex_mcp.py,sha256=qX6blFC_rZwl3JaS9TLIG1yxIvWhgX42Goq2DdcJjbU,38474
7
+ zwarm/adapters/codex_mcp.py,sha256=EhdkM3gj5hc01AcM1ERhtfZbydK390yN4Pg3dawKIGU,48791
8
8
  zwarm/adapters/registry.py,sha256=EdyHECaNA5Kv1od64pYFBJyA_r_6I1r_eJTNP1XYLr4,1781
9
9
  zwarm/adapters/test_codex_mcp.py,sha256=0qhVzxn_KF-XUS30gXSJKwMdR3kWGsDY9iPk1Ihqn3w,10698
10
10
  zwarm/adapters/test_registry.py,sha256=otxcVDONwFCMisyANToF3iy7Y8dSbCL8bTmZNhxNuF4,2383
11
11
  zwarm/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- zwarm/cli/main.py,sha256=NKTq-PxIu6cy9MaPzNPr3T7T62BL7cZdGWU7lT9FNvg,73512
12
+ zwarm/cli/main.py,sha256=-kxDul3cPv9vqLLS9ePTjSf1TXo8E3TCuS6p-t4u4hQ,87985
13
13
  zwarm/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  zwarm/core/compact.py,sha256=Y8C7Gs-5-WOU43WRvQ863Qzd5xtuEqR6Aw3r2p8_-i8,10907
15
- zwarm/core/config.py,sha256=7mzxrWvHmTjwiUWAoE4NYS_1yWj85-vWkpT6X6kiMIg,11579
15
+ zwarm/core/config.py,sha256=331i4io9uEnloFwUMjTPJ5_lQFKJR1nhTpA4SPfSpiI,11748
16
16
  zwarm/core/environment.py,sha256=HVDpDZEpDSfyh9-wHZMzMKVUPKvioBkPVWeiME2JmFo,5435
17
17
  zwarm/core/models.py,sha256=PrC3okRBVJxISUa1Fax4KkagqLT6Xub-kTxC9drN0sY,10083
18
18
  zwarm/core/state.py,sha256=MzrvODKEiJovI7YI1jajW4uukineZ3ezmW5oQinMgjg,11563
@@ -20,18 +20,18 @@ zwarm/core/test_compact.py,sha256=WSdjCB5t4YMcknsrkmJIUsVOPY28s4y9GnDmu3Z4BFw,11
20
20
  zwarm/core/test_config.py,sha256=26ozyiFOdjFF2c9Q-HDfFM6GOLfgw_5FZ55nTDMNYA8,4888
21
21
  zwarm/core/test_models.py,sha256=sWTIhMZvuLP5AooGR6y8OR2EyWydqVfhmGrE7NPBBnk,8450
22
22
  zwarm/prompts/__init__.py,sha256=FiaIOniLrIyfD3_osxT6I7FfyKjtctbf8jNs5QTPs_s,213
23
- zwarm/prompts/orchestrator.py,sha256=af5547L2g6HwGz-PxlKCAXJzqXEdjcwSAIdVH7_4LSk,15412
24
- zwarm/sessions/__init__.py,sha256=NOOkyspJ2lpr5LwlZw2e9wvJPdRgSCQRkULgayrio5U,526
25
- zwarm/sessions/manager.py,sha256=0CrexNwMKiaKYESlWM_oDePj7xL8BRk-9XY5NS2ONlc,19391
23
+ zwarm/prompts/orchestrator.py,sha256=-VZ3B5t-2ALOTpdZyNZGSjjzaHiTufAuLzrTLgwg70M,15442
24
+ zwarm/sessions/__init__.py,sha256=jRibY8IfmNcnkgNmrgK2T81oa1w71wP_KQp9A1hPL7Q,568
25
+ zwarm/sessions/manager.py,sha256=slCDE0n9-pw6iZ08YZMjxZRwH0aoEi6MAAChLlAsuPw,22212
26
26
  zwarm/tools/__init__.py,sha256=FpqxwXJA6-fQ7C-oLj30jjK_0qqcE7MbI0dQuaB56kU,290
27
- zwarm/tools/delegation.py,sha256=dV-fDoxA01RqjW90SLCLhBFQ2kVn8-68JdL0ssYYg_k,19583
27
+ zwarm/tools/delegation.py,sha256=axGuDROdJM9xYDG8IscfyQpNGrkDQSFPi9_ElfL0Fzw,20661
28
28
  zwarm/watchers/__init__.py,sha256=yYGTbhuImQLESUdtfrYbHYBJNvCNX3B-Ei-vY5BizX8,760
29
29
  zwarm/watchers/base.py,sha256=r1GoPlj06nOT2xp4fghfSjxbRyFFFQUB6HpZbEyO2OY,3834
30
- zwarm/watchers/builtin.py,sha256=k1pCnQBEmLHeuCo8t6UXoenJUpfWY7AuGt_aEk8syew,15828
30
+ zwarm/watchers/builtin.py,sha256=IL5QwwKOIqWEfJ_uQWb321Px4i5OLtI_vnWQMudqKoA,19064
31
31
  zwarm/watchers/manager.py,sha256=XZjBVeHjgCUlkTUeHqdvBvHoBC862U1ik0fG6nlRGog,5587
32
32
  zwarm/watchers/registry.py,sha256=A9iBIVIFNtO7KPX0kLpUaP8dAK7ozqWLA44ocJGnOw4,1219
33
33
  zwarm/watchers/test_watchers.py,sha256=zOsxumBqKfR5ZVGxrNlxz6KcWjkcdp0QhW9WB0_20zM,7855
34
- zwarm-1.3.10.dist-info/METADATA,sha256=58PTp7Hkwt3DeL8h7BkSaCcCaOBxuSkIheimqs861o8,16115
35
- zwarm-1.3.10.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
36
- zwarm-1.3.10.dist-info/entry_points.txt,sha256=u0OXq4q8d3yJ3EkUXwZfkS-Y8Lcy0F8cWrcQfoRxM6Q,46
37
- zwarm-1.3.10.dist-info/RECORD,,
34
+ zwarm-2.0.0.dist-info/METADATA,sha256=v94KN8eICvONW3-UvteIdL0suEu6Pu3ur3FMxGd9lS0,7680
35
+ zwarm-2.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
36
+ zwarm-2.0.0.dist-info/entry_points.txt,sha256=u0OXq4q8d3yJ3EkUXwZfkS-Y8Lcy0F8cWrcQfoRxM6Q,46
37
+ zwarm-2.0.0.dist-info/RECORD,,