superwait 0.3.1__tar.gz → 0.3.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.
Files changed (27) hide show
  1. {superwait-0.3.1 → superwait-0.3.2}/PKG-INFO +1 -1
  2. {superwait-0.3.1 → superwait-0.3.2}/pyproject.toml +1 -1
  3. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/SKILL.md +9 -8
  4. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/engine.py +7 -1
  5. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/references/api.md +31 -3
  6. {superwait-0.3.1 → superwait-0.3.2}/tests/test_observations.py +33 -0
  7. {superwait-0.3.1 → superwait-0.3.2}/uv.lock +1 -1
  8. {superwait-0.3.1 → superwait-0.3.2}/.gitignore +0 -0
  9. {superwait-0.3.1 → superwait-0.3.2}/CONTRIBUTING.md +0 -0
  10. {superwait-0.3.1 → superwait-0.3.2}/LICENSE +0 -0
  11. {superwait-0.3.1 → superwait-0.3.2}/README.md +0 -0
  12. {superwait-0.3.1 → superwait-0.3.2}/docs/assets/superwait-banner.png +0 -0
  13. {superwait-0.3.1 → superwait-0.3.2}/docs/hosts.md +0 -0
  14. {superwait-0.3.1 → superwait-0.3.2}/docs/reference.md +0 -0
  15. {superwait-0.3.1 → superwait-0.3.2}/docs/savings.md +0 -0
  16. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/__init__.py +0 -0
  17. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/__main__.py +0 -0
  18. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/cli.py +0 -0
  19. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/hooks.py +0 -0
  20. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/models.py +0 -0
  21. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/server.py +0 -0
  22. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/setup.py +0 -0
  23. {superwait-0.3.1 → superwait-0.3.2}/src/superwait/store.py +0 -0
  24. {superwait-0.3.1 → superwait-0.3.2}/tests/test_ergonomics.py +0 -0
  25. {superwait-0.3.1 → superwait-0.3.2}/tests/test_hooks_setup.py +0 -0
  26. {superwait-0.3.1 → superwait-0.3.2}/tests/test_transport.py +0 -0
  27. {superwait-0.3.1 → superwait-0.3.2}/tests/test_wait.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: superwait
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Conditional waiting for coding agents
5
5
  Project-URL: Repository, https://github.com/ctxrs/superwait
6
6
  Project-URL: Issues, https://github.com/ctxrs/superwait/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "superwait"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  description = "Conditional waiting for coding agents"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -8,7 +8,7 @@ description: Prefer superwait for waiting on agents, files, services, or signals
8
8
  Prefer superwait for observable conditions. Use native tools to spawn or steer
9
9
  agents, or when superwait reports unobserved workers. Don't retry missing hooks.
10
10
 
11
- Call the `wait_for` MCP tool:
11
+ Prefer a direct `wait_for` MCP call (avoids code-mode wrapper wakeups):
12
12
 
13
13
  ```json
14
14
  {"request":{"agents":["worker-a-id","worker-b-id"],"timeout":"10m"}}
@@ -17,14 +17,15 @@ Call the `wait_for` MCP tool:
17
17
  Use handles returned by the host. For Codex `/root/...` paths, add `session`
18
18
  from startup context. The default waits for all agents. Read the `ready`
19
19
  reports; use `continue_wait` as the next `request` for remaining work.
20
+ For a resumed worker, use an agent target with `after` set to its last cursor.
20
21
  Choose the real task deadline, not repeated 30–60-second timeouts. An expired
21
- continuation stays expired. Native process handles still need their native tools.
22
+ continuation stays expired. On an agent timeout, check native status before
23
+ extending: recorded state can be stale. Native process handles need native tools.
22
24
 
23
- In Codex code mode, start `functions.exec` with
25
+ If only Codex code mode is available, start `functions.exec` with
24
26
  `// @exec: {"yield_time_ms":60000}`. If it yields, resume that cell with
25
- `functions.wait` and `yield_time_ms: 60000`.
26
- The wrapper can still wake the model; prefer a direct tool call when available.
27
+ `functions.wait` and `yield_time_ms: 60000`. Print `result.structuredContent`
28
+ once, not the entire MCP envelope.
27
29
 
28
- For other conditions, quorum, early wakeups, continuation details, or CLI usage,
29
- read the [local API reference](references/api.md) only when needed, including
30
- the CLI fallback if the MCP tool is not exposed.
30
+ For target syntax, direct-tool configuration, quorum, early wakeups, or the CLI
31
+ fallback, read the [local API reference](references/api.md) when needed.
@@ -182,7 +182,7 @@ async def wait_for(request: WaitRequest, store: Store, progress=None):
182
182
  if e := r.get("event"):
183
183
  c["cursor"] = e["seq"]
184
184
  if isinstance(t, Agent):
185
- c.update(id=e["key"], handle=t.id, session=e["session"])
185
+ c.update(id=e["key"], handle=t.id, session=e["session"], last_observed_at=e["at"])
186
186
  if r["matched"]:
187
187
  data = e["data"]
188
188
  field = "report" if data.get("report") else "summary"
@@ -212,6 +212,12 @@ async def wait_for(request: WaitRequest, store: Store, progress=None):
212
212
  ready = [card(t, r) for t, r, _ in main if r["matched"]]
213
213
  pending = [card(t, r) for t, r, _ in main if not r["matched"]]
214
214
  triggered = [card(t, r) for t, r, _ in wake if r["matched"]]
215
+ if status == "timed_out":
216
+ for c in pending:
217
+ if c["kind"] == "agent" and "cursor" in c:
218
+ c["hint"] = ("This is the last recorded lifecycle observation, not live worker status. "
219
+ "A stop or interruption may have gone unrecorded. Check the worker with "
220
+ "the host's native tools before extending the deadline.")
215
221
  reason = {
216
222
  "matched": f"{len(ready)} of {len(primary)} targets ready; {needed} required.",
217
223
  "interrupted": "Wake condition matched: " + ", ".join(str(c["label"]) for c in triggered),
@@ -110,6 +110,13 @@ Codex task paths can remain pending while a worker in the specified parent is
110
110
  running, because their aliases are learned at stop. Already observed workers
111
111
  can run for the full requested deadline.
112
112
 
113
+ Hook observations are not a live worker-status feed. In Codex, interruption or
114
+ a missing stop hook can leave a worker recorded as `running`; follow-up turns
115
+ can leave the previous `stopped` observation in place until the next stop.
116
+ On an agent timeout, inspect the worker with native tools before extending the
117
+ deadline. Superwait does not infer completion or failure from elapsed time.
118
+ Use `after` for follow-up responses, as shown below.
119
+
113
120
  ### Other MCP tools
114
121
 
115
122
  `list_agents(provider, session?)` returns `agents` and `limit` (50) for recent
@@ -144,7 +151,9 @@ The result includes:
144
151
  - `continue_wait`: a ready-to-use request for remaining work, or `null`
145
152
 
146
153
  Reports include the observed state, available worker result, and event cursor
147
- where applicable. `details: true` adds `checks` and raw `targets` / `wake_on`
154
+ where applicable. Agent cards with an observation include `last_observed_at`
155
+ (Unix seconds); a timed-out pending agent also includes a native-status check
156
+ hint. `details: true` adds `checks` and raw `targets` / `wake_on`
148
157
  observations. An early wake condition wins if it and the main threshold match
149
158
  in the same check.
150
159
 
@@ -180,14 +189,33 @@ deadline survives continuation; a timed-out continuation remains expired.
180
189
  Choose the actual task deadline rather than repeatedly issuing short waits.
181
190
  Use `wake_on` for blockers that should interrupt it.
182
191
 
183
- In Codex code mode, start `functions.exec` with
192
+ Prefer a direct MCP call when exposed. In Codex code mode, start `functions.exec` with
184
193
  `// @exec: {"yield_time_ms":60000}`. If it yields, resume that cell with
185
194
  `functions.wait` and `yield_time_ms: 60000`. This avoids unnecessary model
186
195
  turns collecting an unfinished tool call.
187
196
  The wrapper still requires model interactions when it yields; superwait cannot
188
- remove the host's execution-cell limit. Prefer a direct MCP call when exposed.
197
+ remove the host's execution-cell limit. When printing a nested MCP result, print
198
+ `result.structuredContent` once; the full envelope repeats it as text too.
189
199
  Native terminal/process handles still require the native collection tool.
190
200
 
201
+ ### Direct tools in Codex code mode
202
+
203
+ Codex 0.153.1 supports exposing selected namespaces directly. If code mode is
204
+ enabled, add `mcp__superwait` to this list in your Codex configuration:
205
+
206
+ ```toml
207
+ [features.code_mode]
208
+ direct_only_tool_namespaces = ["mcp__superwait"]
209
+ ```
210
+
211
+ Edit an existing table/list in place and preserve any other namespaces; do not
212
+ add a duplicate TOML table. Start a fresh session and verify `wait_for` is a
213
+ direct tool. This removes execution-cell resume calls for that wait, not MCP
214
+ timeouts or turns needed for other work. Setup does not change this optional,
215
+ Codex-specific setting.
216
+
217
+ ### CLI fallback
218
+
191
219
  For long Cursor waits, or waits beyond a host's configured MCP timeout, run the
192
220
  same request through the host's background terminal:
193
221
 
@@ -84,6 +84,39 @@ async def test_other_parent_cannot_keep_unknown_path_wait_alive(tmp_path, monkey
84
84
  assert result["status"] == "error"
85
85
 
86
86
 
87
+ @pytest.mark.parametrize("state,after", [("running", 0), ("stopped", 1)])
88
+ async def test_agent_timeout_exposes_observation_age_and_native_fallback(tmp_path, state, after):
89
+ store = Store(tmp_path / "db")
90
+ cursor = store.emit("agent", "worker", state, provider="codex", session="parent")
91
+ observed_at = store.latest("agent", "worker", provider="codex")["at"]
92
+ result = await wait_for(WaitRequest(targets=[{"kind": "agent", "provider": "codex",
93
+ "id": "worker", "after": after}], timeout="100ms", interval=.05), store)
94
+ assert result["status"] == "timed_out"
95
+ pending = result["pending"][0]
96
+ assert pending["state"] == ("running" if state == "running" else "awaiting_new_event")
97
+ assert pending["cursor"] == cursor
98
+ assert pending["last_observed_at"] == observed_at
99
+ assert "native tools" in pending["hint"]
100
+ # A timeout must neither manufacture a terminal event nor lose its deadline.
101
+ assert store.latest("agent", "worker", provider="codex")["seq"] == cursor
102
+ assert result["continue_wait"]["deadline"] == result["deadline"]
103
+
104
+
105
+ async def test_matched_agent_and_file_timeout_do_not_get_stale_worker_hint(tmp_path):
106
+ store = Store(tmp_path / "db")
107
+ store.emit("agent", "worker", "stopped", provider="codex", session="parent", data={"summary": "done"})
108
+ result = await wait_for(WaitRequest(agents=["worker"], timeout="1s"), store)
109
+ assert result["status"] == "matched"
110
+ assert result["ready"][0]["result"] == "done"
111
+ assert "last_observed_at" in result["ready"][0]
112
+ assert "hint" not in result["ready"][0]
113
+ result = await wait_for(WaitRequest(targets=[{"kind": "file", "path": str(tmp_path / "absent")}],
114
+ timeout="100ms"), store)
115
+ assert result["status"] == "timed_out"
116
+ assert "hint" not in result["pending"][0]
117
+ assert "last_observed_at" not in result["pending"][0]
118
+
119
+
87
120
  async def test_unknown_agent_does_not_override_ready_quorum_or_wake(tmp_path):
88
121
  store = Store(tmp_path / "db")
89
122
  store.emit("signal", "ready", "ready")
@@ -746,7 +746,7 @@ wheels = [
746
746
 
747
747
  [[package]]
748
748
  name = "superwait"
749
- version = "0.3.1"
749
+ version = "0.3.2"
750
750
  source = { editable = "." }
751
751
  dependencies = [
752
752
  { name = "httpx2" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes