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.
- {superwait-0.3.1 → superwait-0.3.2}/PKG-INFO +1 -1
- {superwait-0.3.1 → superwait-0.3.2}/pyproject.toml +1 -1
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/SKILL.md +9 -8
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/engine.py +7 -1
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/references/api.md +31 -3
- {superwait-0.3.1 → superwait-0.3.2}/tests/test_observations.py +33 -0
- {superwait-0.3.1 → superwait-0.3.2}/uv.lock +1 -1
- {superwait-0.3.1 → superwait-0.3.2}/.gitignore +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/CONTRIBUTING.md +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/LICENSE +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/README.md +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/docs/assets/superwait-banner.png +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/docs/hosts.md +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/docs/reference.md +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/docs/savings.md +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/__init__.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/__main__.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/cli.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/hooks.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/models.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/server.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/setup.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/src/superwait/store.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/tests/test_ergonomics.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/tests/test_hooks_setup.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/tests/test_transport.py +0 -0
- {superwait-0.3.1 → superwait-0.3.2}/tests/test_wait.py +0 -0
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
27
|
+
`functions.wait` and `yield_time_ms: 60000`. Print `result.structuredContent`
|
|
28
|
+
once, not the entire MCP envelope.
|
|
27
29
|
|
|
28
|
-
For
|
|
29
|
-
read the [local API reference](references/api.md)
|
|
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.
|
|
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.
|
|
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")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|