harness-sdk-python 0.4.3__tar.gz → 0.4.4__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.
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/PKG-INFO +1 -1
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/pyproject.toml +1 -1
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/src/harness_sdk/run_manager.py +19 -23
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_edit_dispatched.py +13 -4
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_edit_reload.py +10 -13
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_input_required.py +38 -8
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_outcomes.py +19 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_rewind_during_run.py +30 -8
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_settle.py +30 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/.gitignore +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/README.md +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/src/harness_sdk/__init__.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/src/harness_sdk/fenced_postgres.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/run_helpers.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_batches.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_branch_anchor.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_enqueue.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_facade.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_fenced_postgres.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_meta.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_placement.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_run_leaf.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_steer.py +0 -0
- {harness_sdk_python-0.4.3 → harness_sdk_python-0.4.4}/tests/test_stop_continue.py +0 -0
|
@@ -184,8 +184,15 @@ class RunManager:
|
|
|
184
184
|
self._run_futures: list["asyncio.Future[Any]"] = []
|
|
185
185
|
self._input_requests: list[dict[str, Any]] = []
|
|
186
186
|
self._input_answers: dict[str, Any] = {}
|
|
187
|
+
self._idle = asyncio.Event()
|
|
188
|
+
self._idle.set()
|
|
187
189
|
self._init_state()
|
|
188
190
|
|
|
191
|
+
@property
|
|
192
|
+
def idle(self) -> asyncio.Event:
|
|
193
|
+
"""Set while no run is active and nothing is about to dispatch; cleared for the whole drain chain."""
|
|
194
|
+
return self._idle
|
|
195
|
+
|
|
189
196
|
def _init_state(self) -> None:
|
|
190
197
|
# Runs state is write-only and not durable: overwrite whatever is there.
|
|
191
198
|
self._state["status"] = "ready"
|
|
@@ -247,6 +254,7 @@ class RunManager:
|
|
|
247
254
|
self._dispatched_ids = ()
|
|
248
255
|
if self._staged_rewinds:
|
|
249
256
|
rewind = self._staged_rewinds.pop(0)
|
|
257
|
+
self._clear_input()
|
|
250
258
|
self._dispatch(
|
|
251
259
|
rewind.type,
|
|
252
260
|
rewind.messages,
|
|
@@ -276,6 +284,8 @@ class RunManager:
|
|
|
276
284
|
for future in fx.continues:
|
|
277
285
|
if not future.done():
|
|
278
286
|
future.set_result(None)
|
|
287
|
+
if self._task is None:
|
|
288
|
+
self._idle.set()
|
|
279
289
|
|
|
280
290
|
def _apply(self, entry: Any, fx: _Effects) -> None:
|
|
281
291
|
if isinstance(entry, _Stop):
|
|
@@ -400,6 +410,7 @@ class RunManager:
|
|
|
400
410
|
)
|
|
401
411
|
self._ctx = ctx
|
|
402
412
|
self._sync_steering()
|
|
413
|
+
self._idle.clear()
|
|
403
414
|
self._task = self._create_task(self._run(ctx))
|
|
404
415
|
|
|
405
416
|
async def _run(self, ctx: "RunManager.StartContext") -> None:
|
|
@@ -424,6 +435,13 @@ class RunManager:
|
|
|
424
435
|
raise RuntimeError(
|
|
425
436
|
"run settled without acking its messages (call ctx.ack_messages())"
|
|
426
437
|
)
|
|
438
|
+
except asyncio.CancelledError:
|
|
439
|
+
self._settle(ctx)
|
|
440
|
+
self._state["status"] = "stopped"
|
|
441
|
+
self._settle_initiators(_reject("stopped", "run cancelled"))
|
|
442
|
+
self._revert_dispatching()
|
|
443
|
+
self._idle.set()
|
|
444
|
+
raise # no drain, no freeze
|
|
427
445
|
except Exception as exc:
|
|
428
446
|
self._settle(ctx)
|
|
429
447
|
message = str(exc) or type(exc).__name__
|
|
@@ -741,8 +759,6 @@ class RunManager:
|
|
|
741
759
|
raise _reject("capability-missing", "the rewind capability is not enabled")
|
|
742
760
|
if e.source_meta is None:
|
|
743
761
|
raise _reject("unknown-id", f"message {e.message_id} is unknown")
|
|
744
|
-
if self._task is not None and not e.source_meta["isLeaf"]:
|
|
745
|
-
raise _reject("not-leaf", "only the leaf may be edited during a run")
|
|
746
762
|
self._check_anchor(e)
|
|
747
763
|
self._staged_rewinds.append(
|
|
748
764
|
_Rewind(
|
|
@@ -788,7 +804,6 @@ class RunManager:
|
|
|
788
804
|
return None
|
|
789
805
|
|
|
790
806
|
def _apply_edit(self, e: _Edit) -> Any:
|
|
791
|
-
self._check_leaf_lanes(e.source_meta, "run/edit")
|
|
792
807
|
if (
|
|
793
808
|
e.message["id"] != e.source_id
|
|
794
809
|
and self._lane_of(e.message["id"]) is not None
|
|
@@ -808,7 +823,6 @@ class RunManager:
|
|
|
808
823
|
return _PARKED
|
|
809
824
|
|
|
810
825
|
def _apply_reload(self, e: _Reload) -> Any:
|
|
811
|
-
self._check_leaf_lanes(e.source_meta, "run/reload")
|
|
812
826
|
self._staged_rewinds.append(
|
|
813
827
|
_Rewind(
|
|
814
828
|
"message-reload",
|
|
@@ -821,15 +835,6 @@ class RunManager:
|
|
|
821
835
|
)
|
|
822
836
|
return _PARKED
|
|
823
837
|
|
|
824
|
-
def _check_leaf_lanes(self, meta: dict[str, Any], command: str) -> None:
|
|
825
|
-
if meta["isLeaf"]:
|
|
826
|
-
return
|
|
827
|
-
if self._lane_items("steerQueue") or self._lane_items("queue"):
|
|
828
|
-
raise _reject(
|
|
829
|
-
"not-leaf",
|
|
830
|
-
f"only the leaf accepts {command} while the queue is non-empty",
|
|
831
|
-
)
|
|
832
|
-
|
|
833
838
|
# ─── Command handlers ───────────────────────────────────
|
|
834
839
|
|
|
835
840
|
async def _send(
|
|
@@ -912,10 +917,7 @@ class RunManager:
|
|
|
912
917
|
return await self._stage(_Dequeue(message_id))
|
|
913
918
|
|
|
914
919
|
def _check_rewind_gate(self, command: str) -> None:
|
|
915
|
-
|
|
916
|
-
if status == "input-required":
|
|
917
|
-
raise _reject("wrong-state", f"{command} is rejected in input-required")
|
|
918
|
-
if status == "running":
|
|
920
|
+
if self._status() == "running":
|
|
919
921
|
if "rewind-during-run" not in self._capabilities:
|
|
920
922
|
raise _reject(
|
|
921
923
|
"capability-missing",
|
|
@@ -924,10 +926,6 @@ class RunManager:
|
|
|
924
926
|
elif "rewind" not in self._capabilities:
|
|
925
927
|
raise _reject("capability-missing", "the rewind capability is not enabled")
|
|
926
928
|
|
|
927
|
-
def _check_leaf_running(self, meta: dict[str, Any], command: str) -> None:
|
|
928
|
-
if not meta["isLeaf"] and self._status() == "running":
|
|
929
|
-
raise _reject("not-leaf", f"only the leaf accepts {command} during a run")
|
|
930
|
-
|
|
931
929
|
async def edit(
|
|
932
930
|
self, params: Any, *, meta: Any = None, ack: Callable[[], None]
|
|
933
931
|
) -> Any:
|
|
@@ -938,7 +936,6 @@ class RunManager:
|
|
|
938
936
|
source_meta = await self._get_message_meta(source_id)
|
|
939
937
|
if source_meta is None:
|
|
940
938
|
raise _reject("unknown-id", f"message {source_id} is unknown")
|
|
941
|
-
self._check_leaf_running(source_meta, "run/edit")
|
|
942
939
|
if source_meta["role"] != "user" and "assistant-edit" not in self._capabilities:
|
|
943
940
|
raise _reject(
|
|
944
941
|
"capability-missing", "the assistant-edit capability is not enabled"
|
|
@@ -965,7 +962,6 @@ class RunManager:
|
|
|
965
962
|
raise _reject("unknown-id", f"message {source_id} is unknown")
|
|
966
963
|
if source_meta["role"] != "assistant":
|
|
967
964
|
raise _reject("invalid-message", "sourceId must name an assistant message")
|
|
968
|
-
self._check_leaf_running(source_meta, "run/reload")
|
|
969
965
|
if source_meta["parentId"] is not None:
|
|
970
966
|
parent = await self._get_message_meta(source_meta["parentId"])
|
|
971
967
|
if (
|
|
@@ -47,15 +47,24 @@ async def test_edit_of_dispatched_item_requires_rewind_during_run(capabilities):
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@pytest.mark.parametrize("command", ["run/enqueue", "run/steer"])
|
|
50
|
-
async def
|
|
50
|
+
async def test_edit_of_dispatched_non_leaf_stops_and_reruns(command):
|
|
51
51
|
script = Script()
|
|
52
52
|
async with run_host(script, capabilities=("rewind", "rewind-during-run")) as (drv, host):
|
|
53
53
|
await drv.command("run/enqueue", add("m1"), terminal=False)
|
|
54
|
-
await script.next_call()
|
|
54
|
+
call = await script.next_call()
|
|
55
55
|
script.thread["m1"] = {"parentId": None, "role": "user", "isLeaf": False}
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
call.ack()
|
|
57
|
+
pending = await drv.command(
|
|
58
|
+
command, {"message": msg("m1", "edited")}, terminal=False
|
|
58
59
|
)
|
|
60
|
+
assert pending["type"] == "pending"
|
|
61
|
+
call.finish(RunManager.Stop(dispatch_queue=False))
|
|
62
|
+
rerun = await script.next_call()
|
|
63
|
+
assert rerun.ctx.type == "message-edit"
|
|
64
|
+
assert [m["parts"][0]["text"] for m in rerun.ctx.messages] == ["edited"]
|
|
65
|
+
rerun.ack()
|
|
66
|
+
rerun.finish(RunManager.Stop(dispatch_queue=False))
|
|
67
|
+
assert (await drv.res(pending["seq"]))["type"] == "accepted"
|
|
59
68
|
|
|
60
69
|
|
|
61
70
|
async def test_edit_of_dispatched_item_with_queued_items_stops_and_reruns():
|
|
@@ -49,7 +49,7 @@ async def test_edit_while_running_requires_rewind_during_run():
|
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
async def
|
|
52
|
+
async def test_deep_edit_with_queue_non_empty_reruns_and_keeps_queue():
|
|
53
53
|
script = Script()
|
|
54
54
|
thread_with_turn(script)
|
|
55
55
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
@@ -59,14 +59,12 @@ async def test_deep_edit_rejected_while_queue_non_empty():
|
|
|
59
59
|
call.ack()
|
|
60
60
|
call.fail(RuntimeError("boom"))
|
|
61
61
|
await drv.wait_status("error")
|
|
62
|
-
assert_rejected(
|
|
63
|
-
await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}, terminal=False),
|
|
64
|
-
"not-leaf",
|
|
65
|
-
)
|
|
66
|
-
await drv.command("run/dequeue", {"messageId": "m2"})
|
|
67
62
|
res = await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}, terminal=False)
|
|
68
63
|
assert res["type"] == "pending"
|
|
69
|
-
|
|
64
|
+
rerun = await script.next_call()
|
|
65
|
+
assert rerun.ctx.type == "message-edit"
|
|
66
|
+
assert rerun.ctx.rollback_to is None
|
|
67
|
+
assert [item["id"] for item in drv.replica["queue"]] == ["m2"]
|
|
70
68
|
|
|
71
69
|
|
|
72
70
|
async def test_leaf_edit_in_error_accepted_with_queue_non_empty():
|
|
@@ -164,7 +162,7 @@ async def test_reload_without_rewind_rejects():
|
|
|
164
162
|
)
|
|
165
163
|
|
|
166
164
|
|
|
167
|
-
async def
|
|
165
|
+
async def test_deep_reload_with_queue_non_empty_reruns_and_keeps_queue():
|
|
168
166
|
script = Script()
|
|
169
167
|
script.thread.update(
|
|
170
168
|
{
|
|
@@ -181,13 +179,12 @@ async def test_reload_leaf_only_while_queue_non_empty():
|
|
|
181
179
|
call.ack()
|
|
182
180
|
call.fail(RuntimeError("boom"))
|
|
183
181
|
await drv.wait_status("error")
|
|
184
|
-
|
|
185
|
-
await drv.command("run/reload", {"sourceId": "a0"}, terminal=False), "not-leaf"
|
|
186
|
-
)
|
|
187
|
-
res = await drv.command("run/reload", {"sourceId": "a1"}, terminal=False)
|
|
182
|
+
res = await drv.command("run/reload", {"sourceId": "a0"}, terminal=False)
|
|
188
183
|
assert res["type"] == "pending"
|
|
189
184
|
call = await script.next_call()
|
|
190
|
-
assert call.ctx.
|
|
185
|
+
assert call.ctx.type == "message-reload"
|
|
186
|
+
assert call.ctx.rollback_to == "u0"
|
|
187
|
+
assert [item["id"] for item in drv.replica["queue"]] == ["m2"]
|
|
191
188
|
|
|
192
189
|
|
|
193
190
|
async def test_reload_of_assistant_parented_response_needs_continuation():
|
|
@@ -257,24 +257,54 @@ async def test_queue_while_parked_dispatches_after_resume():
|
|
|
257
257
|
await drv.wait_status("ready")
|
|
258
258
|
|
|
259
259
|
|
|
260
|
-
async def
|
|
260
|
+
async def test_continue_rejected_while_parked():
|
|
261
261
|
script = Script()
|
|
262
|
+
async with run_host(script) as (drv, host):
|
|
263
|
+
await park(drv, script, [tool_call("r1")])
|
|
264
|
+
assert_rejected(await drv.command("run/continue"), "wrong-state")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def parked_thread(script):
|
|
262
268
|
script.thread.update(
|
|
263
269
|
{
|
|
264
270
|
"u1": {"parentId": None, "role": "user", "onActiveBranch": True},
|
|
265
271
|
"a1": {"parentId": "u1", "role": "assistant", "isLeaf": True, "onActiveBranch": True},
|
|
266
272
|
}
|
|
267
273
|
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
async def test_edit_while_parked_clears_requests_and_reruns():
|
|
277
|
+
script = Script()
|
|
278
|
+
parked_thread(script)
|
|
268
279
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
269
280
|
await park(drv, script, [tool_call("r1")], anchor="a1")
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}, terminal=False),
|
|
273
|
-
"wrong-state",
|
|
274
|
-
)
|
|
275
|
-
assert_rejected(
|
|
276
|
-
await drv.command("run/reload", {"sourceId": "a1"}, terminal=False), "wrong-state"
|
|
281
|
+
res = await drv.command(
|
|
282
|
+
"run/edit", {"sourceId": "u1", "message": msg("u2")}, terminal=False
|
|
277
283
|
)
|
|
284
|
+
assert res["type"] == "pending"
|
|
285
|
+
rerun = await script.next_call()
|
|
286
|
+
assert rerun.ctx.type == "message-edit"
|
|
287
|
+
assert rerun.ctx.rollback_to is None
|
|
288
|
+
await drv.wait(lambda s: "inputRequests" not in s)
|
|
289
|
+
rerun.ack()
|
|
290
|
+
rerun.finish(RunManager.Complete())
|
|
291
|
+
await drv.wait_status("ready")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
async def test_reload_while_parked_clears_requests_and_reruns():
|
|
295
|
+
script = Script()
|
|
296
|
+
parked_thread(script)
|
|
297
|
+
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
298
|
+
await park(drv, script, [tool_call("r1")], anchor="a1")
|
|
299
|
+
res = await drv.command("run/reload", {"sourceId": "a1"}, terminal=False)
|
|
300
|
+
assert res["type"] == "pending"
|
|
301
|
+
rerun = await script.next_call()
|
|
302
|
+
assert rerun.ctx.type == "message-reload"
|
|
303
|
+
assert rerun.ctx.rollback_to == "u1"
|
|
304
|
+
await drv.wait(lambda s: "inputRequests" not in s)
|
|
305
|
+
rerun.ack()
|
|
306
|
+
rerun.finish(RunManager.Complete())
|
|
307
|
+
await drv.wait_status("ready")
|
|
278
308
|
|
|
279
309
|
|
|
280
310
|
async def test_steer_while_parked_stays_queued():
|
|
@@ -138,3 +138,22 @@ async def test_unknown_capability_rejects_at_construction():
|
|
|
138
138
|
schedule=lambda fn: None,
|
|
139
139
|
capabilities=("time-travel",),
|
|
140
140
|
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
async def test_idle_spans_the_drain_chain():
|
|
144
|
+
script = Script()
|
|
145
|
+
async with run_host(script) as (drv, host):
|
|
146
|
+
runs = host.live.runs
|
|
147
|
+
assert runs.idle.is_set()
|
|
148
|
+
await drv.command("run/enqueue", add("m1"), terminal=False)
|
|
149
|
+
first = await script.next_call()
|
|
150
|
+
assert not runs.idle.is_set()
|
|
151
|
+
await drv.command("run/enqueue", add("m2", anchor="m1"), terminal=False)
|
|
152
|
+
first.ack()
|
|
153
|
+
first.finish(RunManager.Complete())
|
|
154
|
+
second = await script.next_call()
|
|
155
|
+
assert not runs.idle.is_set()
|
|
156
|
+
second.ack()
|
|
157
|
+
second.finish(RunManager.Complete())
|
|
158
|
+
await drv.wait_status("ready")
|
|
159
|
+
assert runs.idle.is_set()
|
|
@@ -89,17 +89,39 @@ async def test_running_edit_and_reload_reject_without_capability(capabilities):
|
|
|
89
89
|
)
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
async def
|
|
92
|
+
async def test_running_non_leaf_edit_stops_settles_then_rewinds():
|
|
93
93
|
script = Script()
|
|
94
94
|
async with run_host(script, capabilities=("rewind", "rewind-during-run")) as (drv, host):
|
|
95
|
-
await start_run(drv, script, leaf=False)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"not-leaf",
|
|
99
|
-
)
|
|
100
|
-
assert_rejected(
|
|
101
|
-
await drv.command("run/reload", {"sourceId": "a1"}, terminal=False), "not-leaf"
|
|
95
|
+
call = await start_run(drv, script, leaf=False)
|
|
96
|
+
pending = await drv.command(
|
|
97
|
+
"run/edit", {"sourceId": "m1", "message": msg("m2")}, terminal=False
|
|
102
98
|
)
|
|
99
|
+
assert pending["type"] == "pending"
|
|
100
|
+
await asyncio.wait_for(call.ctx.stop_requested.wait(), 5)
|
|
101
|
+
call.finish(RunManager.Stop(dispatch_queue=False))
|
|
102
|
+
rerun = await script.next_call()
|
|
103
|
+
assert rerun.ctx.type == "message-edit"
|
|
104
|
+
assert [m["id"] for m in rerun.ctx.messages] == ["m2"]
|
|
105
|
+
assert rerun.ctx.rollback_to is None
|
|
106
|
+
rerun.ack()
|
|
107
|
+
rerun.finish(RunManager.Stop(dispatch_queue=False))
|
|
108
|
+
assert (await drv.res(pending["seq"]))["type"] == "accepted"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def test_running_non_leaf_reload_stops_settles_then_rewinds():
|
|
112
|
+
script = Script()
|
|
113
|
+
async with run_host(script, capabilities=("rewind", "rewind-during-run")) as (drv, host):
|
|
114
|
+
call = await start_run(drv, script, leaf=False)
|
|
115
|
+
pending = await drv.command("run/reload", {"sourceId": "a1"}, terminal=False)
|
|
116
|
+
assert pending["type"] == "pending"
|
|
117
|
+
await asyncio.wait_for(call.ctx.stop_requested.wait(), 5)
|
|
118
|
+
call.finish(RunManager.Stop(dispatch_queue=False))
|
|
119
|
+
rerun = await script.next_call()
|
|
120
|
+
assert rerun.ctx.type == "message-reload"
|
|
121
|
+
assert rerun.ctx.rollback_to == "m1"
|
|
122
|
+
rerun.ack()
|
|
123
|
+
rerun.finish(RunManager.Stop(dispatch_queue=False))
|
|
124
|
+
assert (await drv.res(pending["seq"]))["type"] == "accepted"
|
|
103
125
|
|
|
104
126
|
|
|
105
127
|
async def test_rewind_during_run_requires_rewind():
|
|
@@ -215,3 +215,33 @@ async def test_rewind_edit_failure_settles_with_the_payload():
|
|
|
215
215
|
settled = await drv.res(pending["seq"])
|
|
216
216
|
assert settled["type"] == "rejected"
|
|
217
217
|
assert settled["payload"] == {"code": 402}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
async def test_task_cancellation_reverts_entries_and_settles_sends():
|
|
221
|
+
script = Script()
|
|
222
|
+
async with run_host(script) as (drv, host):
|
|
223
|
+
pending = await drv.command("run/enqueue", add("m1"), terminal=False)
|
|
224
|
+
await script.next_call()
|
|
225
|
+
await drv.command("run/enqueue", add("m2"))
|
|
226
|
+
task = host.live.runs._task
|
|
227
|
+
task.cancel()
|
|
228
|
+
assert (await drv.res(pending["seq"]))["type"] == "accepted"
|
|
229
|
+
await drv.wait_status("stopped")
|
|
230
|
+
assert queue_ids(drv.replica, "queue") == ["m1", "m2"]
|
|
231
|
+
assert drv.replica["runLeafMessageId"] is None
|
|
232
|
+
assert task.cancelled()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
async def test_task_cancellation_rejects_a_rewind_initiator():
|
|
236
|
+
script = Script()
|
|
237
|
+
script.thread["u1"] = {"parentId": None, "role": "user", "isLeaf": True}
|
|
238
|
+
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
239
|
+
pending = await drv.command(
|
|
240
|
+
"run/edit", {"sourceId": "u1", "message": msg("u2")}, terminal=False
|
|
241
|
+
)
|
|
242
|
+
await script.next_call()
|
|
243
|
+
host.live.runs._task.cancel()
|
|
244
|
+
settled = await drv.res(pending["seq"])
|
|
245
|
+
assert settled["type"] == "rejected"
|
|
246
|
+
assert settled["payload"] == {"reason": "stopped"}
|
|
247
|
+
await drv.wait_status("stopped")
|
|
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
|