harness-sdk-python 0.3.0__tar.gz → 0.3.1__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.3.0 → harness_sdk_python-0.3.1}/.gitignore +3 -0
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/PKG-INFO +1 -1
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/pyproject.toml +1 -1
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/src/harness_sdk/run_manager.py +49 -5
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_batches.py +12 -9
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_caller.py +21 -21
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_edit_dispatched.py +11 -9
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_edit_reload.py +36 -36
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_enqueue.py +41 -41
- harness_sdk_python-0.3.1/tests/test_facade.py +84 -0
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_input_required.py +6 -6
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_outcomes.py +11 -11
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_placement.py +18 -18
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_rewind_during_run.py +14 -11
- harness_sdk_python-0.3.1/tests/test_settle.py +131 -0
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_steer.py +16 -16
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_stop_continue.py +15 -15
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/README.md +0 -0
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/src/harness_sdk/__init__.py +0 -0
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/src/harness_sdk/fenced_postgres.py +0 -0
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/run_helpers.py +0 -0
- {harness_sdk_python-0.3.0 → harness_sdk_python-0.3.1}/tests/test_fenced_postgres.py +0 -0
|
@@ -161,6 +161,8 @@ class RunManager:
|
|
|
161
161
|
self._staged_stops: list["asyncio.Future[Any]"] = []
|
|
162
162
|
self._stop_reason: str | None = None
|
|
163
163
|
self._staged_rewinds: list[_Rewind] = []
|
|
164
|
+
self._entity_futures: dict[str, "asyncio.Future[Any]"] = {}
|
|
165
|
+
self._run_futures: list["asyncio.Future[Any]"] = []
|
|
164
166
|
self._input_requests: list[dict[str, Any]] = []
|
|
165
167
|
self._input_answers: dict[str, Any] = {}
|
|
166
168
|
self._init_state()
|
|
@@ -217,8 +219,9 @@ class RunManager:
|
|
|
217
219
|
self._dispatch(
|
|
218
220
|
rewind.type, rewind.messages, rollback_to=rewind.rollback_to
|
|
219
221
|
)
|
|
222
|
+
rewind.ack()
|
|
220
223
|
if not rewind.future.done():
|
|
221
|
-
rewind.future
|
|
224
|
+
self._run_futures.append(rewind.future)
|
|
222
225
|
elif outcome is not None:
|
|
223
226
|
self._settle_outcome(outcome)
|
|
224
227
|
else:
|
|
@@ -318,6 +321,7 @@ class RunManager:
|
|
|
318
321
|
]
|
|
319
322
|
for message in messages:
|
|
320
323
|
self._callers.pop(message["id"], None)
|
|
324
|
+
self._adopt_entities(message["id"] for message in messages)
|
|
321
325
|
record: dict[str, Any] = {"type": type, "messages": list(messages)}
|
|
322
326
|
if rollback_to is not _ABSENT:
|
|
323
327
|
record["rollbackTo"] = rollback_to
|
|
@@ -359,10 +363,21 @@ class RunManager:
|
|
|
359
363
|
)
|
|
360
364
|
except Exception as exc:
|
|
361
365
|
self._settle(ctx)
|
|
362
|
-
|
|
366
|
+
message = str(exc) or type(exc).__name__
|
|
367
|
+
if isinstance(exc, StatewireReject):
|
|
368
|
+
self._freeze(exc.message, exc.payload)
|
|
369
|
+
self._settle_entities(exc)
|
|
370
|
+
else:
|
|
371
|
+
self._freeze(message)
|
|
372
|
+
self._settle_entities(_reject("run-error", message))
|
|
363
373
|
self._drain()
|
|
364
374
|
return
|
|
365
375
|
self._settle(ctx)
|
|
376
|
+
self._settle_entities(
|
|
377
|
+
_reject("run-error", "run ended in error")
|
|
378
|
+
if isinstance(outcome, RunManager.Error)
|
|
379
|
+
else None
|
|
380
|
+
)
|
|
366
381
|
self._outcome = outcome
|
|
367
382
|
self._drain()
|
|
368
383
|
|
|
@@ -372,6 +387,22 @@ class RunManager:
|
|
|
372
387
|
self._task = None
|
|
373
388
|
self._dispatch_record = None
|
|
374
389
|
|
|
390
|
+
def _settle_entities(self, error: StatewireReject | None) -> None:
|
|
391
|
+
futures, self._run_futures = self._run_futures, []
|
|
392
|
+
for future in futures:
|
|
393
|
+
if future.done():
|
|
394
|
+
continue
|
|
395
|
+
if error is None:
|
|
396
|
+
future.set_result(None)
|
|
397
|
+
else:
|
|
398
|
+
future.set_exception(error)
|
|
399
|
+
|
|
400
|
+
def _adopt_entities(self, ids: Iterable[str]) -> None:
|
|
401
|
+
for id in ids:
|
|
402
|
+
future = self._entity_futures.pop(id, None)
|
|
403
|
+
if future is not None:
|
|
404
|
+
self._run_futures.append(future)
|
|
405
|
+
|
|
375
406
|
def _pop_dispatchable(self) -> bool:
|
|
376
407
|
steer = self._lane_items("steerQueue")
|
|
377
408
|
if steer:
|
|
@@ -385,9 +416,14 @@ class RunManager:
|
|
|
385
416
|
return True
|
|
386
417
|
return False
|
|
387
418
|
|
|
388
|
-
def _freeze(self, message: str) -> None:
|
|
419
|
+
def _freeze(self, message: str, payload: Any = None) -> None:
|
|
389
420
|
self._state["status"] = "error"
|
|
390
|
-
|
|
421
|
+
if isinstance(payload, dict):
|
|
422
|
+
self._state["error"] = {**payload, "message": message}
|
|
423
|
+
elif payload is not None:
|
|
424
|
+
self._state["error"] = {"message": message, "payload": payload}
|
|
425
|
+
else:
|
|
426
|
+
self._state["error"] = {"message": message}
|
|
391
427
|
|
|
392
428
|
# ─── Message and placement validation ───────────────────
|
|
393
429
|
|
|
@@ -556,10 +592,12 @@ class RunManager:
|
|
|
556
592
|
)
|
|
557
593
|
with self._caller_stamp(e.message_id, e.caller):
|
|
558
594
|
self._insert_new(e.lane, e.message, e.params)
|
|
595
|
+
self._entity_futures[e.message_id] = e.future
|
|
596
|
+
e.ack()
|
|
559
597
|
fx.new_added = True
|
|
560
598
|
if e.lane == "steerQueue":
|
|
561
599
|
fx.steer_added = True
|
|
562
|
-
return
|
|
600
|
+
return _PARKED
|
|
563
601
|
|
|
564
602
|
def _apply_move(self, e: _Send, fx: _Effects) -> Any:
|
|
565
603
|
current = self._lane_of(e.message_id)
|
|
@@ -608,6 +646,11 @@ class RunManager:
|
|
|
608
646
|
self._state[lane] = [
|
|
609
647
|
item for item in self._lane_items(lane) if item["id"] != e.message_id
|
|
610
648
|
]
|
|
649
|
+
entity = self._entity_futures.pop(e.message_id, None)
|
|
650
|
+
if entity is not None and not entity.done():
|
|
651
|
+
entity.set_exception(
|
|
652
|
+
_reject("removed", f"message {e.message_id} was removed from the queue")
|
|
653
|
+
)
|
|
611
654
|
return None
|
|
612
655
|
|
|
613
656
|
def _apply_input(self, e: _Input) -> Any:
|
|
@@ -956,6 +999,7 @@ class RunManager:
|
|
|
956
999
|
self._manager._state["steerQueue"] = []
|
|
957
1000
|
for item in items:
|
|
958
1001
|
self._manager._callers.pop(item["id"], None)
|
|
1002
|
+
self._manager._adopt_entities(item["id"] for item in items)
|
|
959
1003
|
return tuple(
|
|
960
1004
|
{k: v for k, v in item.items() if k != "caller"} for item in items
|
|
961
1005
|
)
|
|
@@ -21,23 +21,26 @@ async def test_multi_steer_batch_places_all_and_dispatches_once():
|
|
|
21
21
|
]
|
|
22
22
|
)
|
|
23
23
|
for offset in range(3):
|
|
24
|
-
assert (await drv.res(first + offset))["type"] == "
|
|
24
|
+
assert (await drv.res(first + offset, terminal=False))["type"] == "pending"
|
|
25
25
|
call = await script.next_call()
|
|
26
26
|
assert call.ctx.type == "message-send"
|
|
27
27
|
assert [m["id"] for m in call.ctx.messages] == ["s1", "s2", "s3"]
|
|
28
28
|
script.no_call()
|
|
29
29
|
assert queue_ids(drv.replica, "steerQueue") == []
|
|
30
|
+
call.finish(RunManager.Complete())
|
|
31
|
+
for offset in range(3):
|
|
32
|
+
assert (await drv.res(first + offset))["type"] == "accepted"
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
async def test_steer_and_stop_in_one_batch_nets_to_stop():
|
|
33
36
|
script = Script()
|
|
34
37
|
async with run_host(script) as (drv, host):
|
|
35
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
38
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
36
39
|
call = await script.next_call()
|
|
37
40
|
first = await drv.batch(
|
|
38
41
|
[("run/steer", {"message": msg("s1")}), ("run/stop", None)]
|
|
39
42
|
)
|
|
40
|
-
assert (await drv.res(first))["type"] == "
|
|
43
|
+
assert (await drv.res(first, terminal=False))["type"] == "pending"
|
|
41
44
|
assert (await drv.res(first + 1, terminal=False))["type"] == "pending"
|
|
42
45
|
await asyncio.wait_for(call.ctx.stop_requested.wait(), 5)
|
|
43
46
|
call.finish(RunManager.Stop(dispatch_queue=False))
|
|
@@ -50,7 +53,7 @@ async def test_steer_and_stop_in_one_batch_nets_to_stop():
|
|
|
50
53
|
async def test_stop_settles_only_after_the_run_task_ends():
|
|
51
54
|
script = Script()
|
|
52
55
|
async with run_host(script) as (drv, host):
|
|
53
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
56
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
54
57
|
call = await script.next_call()
|
|
55
58
|
pending = await drv.command("run/stop", terminal=False)
|
|
56
59
|
assert pending["type"] == "pending"
|
|
@@ -75,8 +78,8 @@ class TailScript(Script):
|
|
|
75
78
|
async def test_handler_tail_staging_decides_in_its_own_envelope():
|
|
76
79
|
script = TailScript()
|
|
77
80
|
async with run_host(script) as (drv, host):
|
|
78
|
-
res = await drv.command("run/enqueue", {"message": msg("m1")})
|
|
79
|
-
assert res["type"] == "
|
|
81
|
+
res = await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
82
|
+
assert res["type"] == "pending"
|
|
80
83
|
call = await script.next_call()
|
|
81
84
|
assert [m["id"] for m in call.ctx.messages] == ["m1"]
|
|
82
85
|
dispatch_env = next(
|
|
@@ -88,6 +91,6 @@ async def test_handler_tail_staging_decides_in_its_own_envelope():
|
|
|
88
91
|
)
|
|
89
92
|
)
|
|
90
93
|
# The decide ran outside the batch segment: its ops flush in an
|
|
91
|
-
# envelope of their own,
|
|
92
|
-
assert "res"
|
|
93
|
-
assert dispatch_env
|
|
94
|
+
# envelope of their own, alongside the pending verdict and its ack.
|
|
95
|
+
assert dispatch_env["res"] == [{"seq": res["seq"], "type": "pending"}]
|
|
96
|
+
assert dispatch_env["ack"] >= res["seq"]
|
|
@@ -7,9 +7,9 @@ from harness_sdk import RunManager
|
|
|
7
7
|
async def test_immediate_dispatch_carries_caller():
|
|
8
8
|
script = Script()
|
|
9
9
|
async with run_host(script) as (drv, _):
|
|
10
|
-
assert (await drv.command("run/enqueue", {"message": msg("m1")}))[
|
|
10
|
+
assert (await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False))[
|
|
11
11
|
"type"
|
|
12
|
-
] == "
|
|
12
|
+
] == "pending"
|
|
13
13
|
call = await script.next_call()
|
|
14
14
|
assert call.ctx.caller is not None
|
|
15
15
|
assert call.ctx.caller.client_id == "c1"
|
|
@@ -19,9 +19,9 @@ async def test_immediate_dispatch_carries_caller():
|
|
|
19
19
|
async def test_queue_entry_projects_caller_client_id():
|
|
20
20
|
script = Script()
|
|
21
21
|
async with run_host(script) as (drv, _):
|
|
22
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
22
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
23
23
|
call = await script.next_call()
|
|
24
|
-
await drv.command("run/enqueue", {"message": msg("m2")})
|
|
24
|
+
await drv.command("run/enqueue", {"message": msg("m2")}, terminal=False)
|
|
25
25
|
await drv.wait(lambda s: len(s["queue"]) == 1)
|
|
26
26
|
assert drv.replica["queue"][0]["caller"] == {"clientId": "c1"}
|
|
27
27
|
call.finish(RunManager.Complete())
|
|
@@ -30,9 +30,9 @@ async def test_queue_entry_projects_caller_client_id():
|
|
|
30
30
|
async def test_caller_context_is_read_at_dispatch_time():
|
|
31
31
|
script = Script()
|
|
32
32
|
async with run_host(script) as (drv, _):
|
|
33
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
33
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
34
34
|
call = await script.next_call()
|
|
35
|
-
await drv.command("run/enqueue", {"message": msg("m2")})
|
|
35
|
+
await drv.command("run/enqueue", {"message": msg("m2")}, terminal=False)
|
|
36
36
|
assert (await drv.context({"user": "simon"}))["type"] == "accepted"
|
|
37
37
|
call.finish(RunManager.Complete())
|
|
38
38
|
dispatched = await script.next_call()
|
|
@@ -47,9 +47,9 @@ async def test_dispatch_without_queue_entry_has_no_caller():
|
|
|
47
47
|
script = Script()
|
|
48
48
|
async with run_host(script, capabilities=("rewind",)) as (drv, _):
|
|
49
49
|
script.thread["a1"] = {"role": "assistant", "parentId": None, "isLeaf": True}
|
|
50
|
-
assert (await drv.command("run/reload", {"sourceId": "a1"}))[
|
|
50
|
+
assert (await drv.command("run/reload", {"sourceId": "a1"}, terminal=False))[
|
|
51
51
|
"type"
|
|
52
|
-
] == "
|
|
52
|
+
] == "pending"
|
|
53
53
|
call = await script.next_call()
|
|
54
54
|
assert call.ctx.caller is None
|
|
55
55
|
call.finish(RunManager.Complete())
|
|
@@ -58,9 +58,9 @@ async def test_dispatch_without_queue_entry_has_no_caller():
|
|
|
58
58
|
async def test_take_steered_strips_the_caller_stamp():
|
|
59
59
|
script = Script()
|
|
60
60
|
async with run_host(script) as (drv, _):
|
|
61
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
61
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
62
62
|
call = await script.next_call()
|
|
63
|
-
await drv.command("run/steer", {"message": msg("s1")})
|
|
63
|
+
await drv.command("run/steer", {"message": msg("s1")}, terminal=False)
|
|
64
64
|
await drv.wait(lambda s: len(s["steerQueue"]) == 1)
|
|
65
65
|
assert drv.replica["steerQueue"][0]["caller"] == {"clientId": "c1"}
|
|
66
66
|
[taken] = call.ctx.take_steered()
|
|
@@ -71,19 +71,19 @@ async def test_take_steered_strips_the_caller_stamp():
|
|
|
71
71
|
async def test_max_queued_caps_each_lane():
|
|
72
72
|
script = Script()
|
|
73
73
|
async with run_host(script, max_queued=1) as (drv, _):
|
|
74
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
74
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
75
75
|
call = await script.next_call()
|
|
76
|
-
assert (await drv.command("run/enqueue", {"message": msg("m2")}))[
|
|
76
|
+
assert (await drv.command("run/enqueue", {"message": msg("m2")}, terminal=False))[
|
|
77
77
|
"type"
|
|
78
|
-
] == "
|
|
79
|
-
full = await drv.command("run/enqueue", {"message": msg("m3")})
|
|
78
|
+
] == "pending"
|
|
79
|
+
full = await drv.command("run/enqueue", {"message": msg("m3")}, terminal=False)
|
|
80
80
|
assert full["type"] == "rejected"
|
|
81
81
|
assert full["payload"] == {"reason": "queue-full"}
|
|
82
82
|
# The steer lane has its own budget.
|
|
83
|
-
assert (await drv.command("run/steer", {"message": msg("s1")}))[
|
|
83
|
+
assert (await drv.command("run/steer", {"message": msg("s1")}, terminal=False))[
|
|
84
84
|
"type"
|
|
85
|
-
] == "
|
|
86
|
-
steer_full = await drv.command("run/steer", {"message": msg("s2")})
|
|
85
|
+
] == "pending"
|
|
86
|
+
steer_full = await drv.command("run/steer", {"message": msg("s2")}, terminal=False)
|
|
87
87
|
assert steer_full["payload"] == {"reason": "queue-full"}
|
|
88
88
|
call.finish(RunManager.Complete())
|
|
89
89
|
|
|
@@ -91,11 +91,11 @@ async def test_max_queued_caps_each_lane():
|
|
|
91
91
|
async def test_lane_change_into_a_full_lane_rejects():
|
|
92
92
|
script = Script()
|
|
93
93
|
async with run_host(script, max_queued=1) as (drv, _):
|
|
94
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
94
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
95
95
|
call = await script.next_call()
|
|
96
|
-
await drv.command("run/enqueue", {"message": msg("m2")})
|
|
97
|
-
await drv.command("run/steer", {"message": msg("s1")})
|
|
98
|
-
moved = await drv.command("run/steer", {"messageId": "m2"})
|
|
96
|
+
await drv.command("run/enqueue", {"message": msg("m2")}, terminal=False)
|
|
97
|
+
await drv.command("run/steer", {"message": msg("s1")}, terminal=False)
|
|
98
|
+
moved = await drv.command("run/steer", {"messageId": "m2"}, terminal=False)
|
|
99
99
|
assert moved["payload"] == {"reason": "queue-full"}
|
|
100
100
|
call.finish(RunManager.Complete())
|
|
101
101
|
|
|
@@ -10,7 +10,7 @@ from harness_sdk import RunManager
|
|
|
10
10
|
async def test_edit_of_dispatched_item_stops_rewinds_reruns(command):
|
|
11
11
|
script = Script()
|
|
12
12
|
async with run_host(script, capabilities=("rewind", "rewind-during-run")) as (drv, host):
|
|
13
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
13
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
14
14
|
call = await script.next_call()
|
|
15
15
|
script.thread["m1"] = {"parentId": "p0", "role": "user", "isLeaf": True}
|
|
16
16
|
pending = await drv.command(
|
|
@@ -25,20 +25,21 @@ async def test_edit_of_dispatched_item_stops_rewinds_reruns(command):
|
|
|
25
25
|
assert [m["parts"][0]["text"] for m in rerun.ctx.messages] == ["edited"]
|
|
26
26
|
assert rerun.ctx.has_rollback
|
|
27
27
|
assert rerun.ctx.rollback_to == "p0"
|
|
28
|
+
await drv.wait_status("running")
|
|
29
|
+
rerun.finish(RunManager.Stop(dispatch_queue=False))
|
|
28
30
|
settled = await drv.res(pending["seq"])
|
|
29
31
|
assert settled["type"] == "accepted"
|
|
30
|
-
await drv.wait_status("running")
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
@pytest.mark.parametrize("capabilities", [(), ("rewind",)])
|
|
34
35
|
async def test_edit_of_dispatched_item_requires_rewind_during_run(capabilities):
|
|
35
36
|
script = Script()
|
|
36
37
|
async with run_host(script, capabilities=capabilities) as (drv, host):
|
|
37
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
38
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
38
39
|
await script.next_call()
|
|
39
40
|
script.thread["m1"] = {"parentId": None, "role": "user", "isLeaf": True}
|
|
40
41
|
assert_rejected(
|
|
41
|
-
await drv.command("run/enqueue", {"message": msg("m1", "edited")}),
|
|
42
|
+
await drv.command("run/enqueue", {"message": msg("m1", "edited")}, terminal=False),
|
|
42
43
|
"capability-missing",
|
|
43
44
|
)
|
|
44
45
|
|
|
@@ -47,7 +48,7 @@ async def test_edit_of_dispatched_item_requires_rewind_during_run(capabilities):
|
|
|
47
48
|
async def test_edit_of_dispatched_non_leaf_rejects(command):
|
|
48
49
|
script = Script()
|
|
49
50
|
async with run_host(script, capabilities=("rewind", "rewind-during-run")) as (drv, host):
|
|
50
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
51
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
51
52
|
await script.next_call()
|
|
52
53
|
script.thread["m1"] = {"parentId": None, "role": "user", "isLeaf": False}
|
|
53
54
|
assert_rejected(
|
|
@@ -58,10 +59,10 @@ async def test_edit_of_dispatched_non_leaf_rejects(command):
|
|
|
58
59
|
async def test_edit_of_dispatched_item_with_queued_items_stops_and_reruns():
|
|
59
60
|
script = Script()
|
|
60
61
|
async with run_host(script, capabilities=("rewind", "rewind-during-run")) as (drv, host):
|
|
61
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
62
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
62
63
|
call = await script.next_call()
|
|
63
64
|
script.thread["m1"] = {"parentId": None, "role": "user", "isLeaf": True}
|
|
64
|
-
await drv.command("run/enqueue", {"message": msg("m2")})
|
|
65
|
+
await drv.command("run/enqueue", {"message": msg("m2")}, terminal=False)
|
|
65
66
|
pending = await drv.command(
|
|
66
67
|
"run/enqueue", {"message": msg("m1", "edited")}, terminal=False
|
|
67
68
|
)
|
|
@@ -69,6 +70,7 @@ async def test_edit_of_dispatched_item_with_queued_items_stops_and_reruns():
|
|
|
69
70
|
call.finish(RunManager.Stop(dispatch_queue=False))
|
|
70
71
|
rerun = await script.next_call()
|
|
71
72
|
assert rerun.ctx.type == "message-edit"
|
|
73
|
+
rerun.finish(RunManager.Stop(dispatch_queue=False))
|
|
72
74
|
assert (await drv.res(pending["seq"]))["type"] == "accepted"
|
|
73
75
|
assert [item["id"] for item in drv.replica["queue"]] == ["m2"]
|
|
74
76
|
|
|
@@ -76,12 +78,12 @@ async def test_edit_of_dispatched_item_with_queued_items_stops_and_reruns():
|
|
|
76
78
|
async def test_edit_of_dispatched_item_in_error_reruns_without_stop():
|
|
77
79
|
script = Script()
|
|
78
80
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
79
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
81
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
80
82
|
call = await script.next_call()
|
|
81
83
|
script.thread["m1"] = {"parentId": None, "role": "user", "isLeaf": True}
|
|
82
84
|
call.fail(RuntimeError("boom"))
|
|
83
85
|
await drv.wait_status("error")
|
|
84
|
-
await drv.command("run/enqueue", {"message": msg("m1", "edited")})
|
|
86
|
+
await drv.command("run/enqueue", {"message": msg("m1", "edited")}, terminal=False)
|
|
85
87
|
rerun = await script.next_call()
|
|
86
88
|
assert rerun.ctx.type == "message-edit"
|
|
87
89
|
assert rerun.ctx.rollback_to is None
|
|
@@ -17,9 +17,9 @@ async def test_edit_in_ready_dispatches_message_edit():
|
|
|
17
17
|
thread_with_turn(script)
|
|
18
18
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
19
19
|
res = await drv.command(
|
|
20
|
-
"run/edit", {"sourceId": "u1", "message": msg("u2", "v2")}
|
|
20
|
+
"run/edit", {"sourceId": "u1", "message": msg("u2", "v2")}, terminal=False
|
|
21
21
|
)
|
|
22
|
-
assert res["type"] == "
|
|
22
|
+
assert res["type"] == "pending"
|
|
23
23
|
call = await script.next_call()
|
|
24
24
|
assert call.ctx.type == "message-edit"
|
|
25
25
|
assert [m["id"] for m in call.ctx.messages] == ["u2"]
|
|
@@ -32,7 +32,7 @@ async def test_edit_requires_rewind():
|
|
|
32
32
|
thread_with_turn(script)
|
|
33
33
|
async with run_host(script) as (drv, host):
|
|
34
34
|
assert_rejected(
|
|
35
|
-
await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}),
|
|
35
|
+
await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}, terminal=False),
|
|
36
36
|
"capability-missing",
|
|
37
37
|
)
|
|
38
38
|
|
|
@@ -41,10 +41,10 @@ async def test_edit_while_running_requires_rewind_during_run():
|
|
|
41
41
|
script = Script()
|
|
42
42
|
thread_with_turn(script)
|
|
43
43
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
44
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
44
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
45
45
|
await script.next_call()
|
|
46
46
|
assert_rejected(
|
|
47
|
-
await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}),
|
|
47
|
+
await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}, terminal=False),
|
|
48
48
|
"capability-missing",
|
|
49
49
|
)
|
|
50
50
|
|
|
@@ -53,32 +53,32 @@ async def test_deep_edit_rejected_while_queue_non_empty():
|
|
|
53
53
|
script = Script()
|
|
54
54
|
thread_with_turn(script)
|
|
55
55
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
56
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
56
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
57
57
|
call = await script.next_call()
|
|
58
|
-
await drv.command("run/enqueue", {"message": msg("m2")})
|
|
58
|
+
await drv.command("run/enqueue", {"message": msg("m2")}, terminal=False)
|
|
59
59
|
call.fail(RuntimeError("boom"))
|
|
60
60
|
await drv.wait_status("error")
|
|
61
61
|
assert_rejected(
|
|
62
|
-
await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}),
|
|
62
|
+
await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}, terminal=False),
|
|
63
63
|
"not-leaf",
|
|
64
64
|
)
|
|
65
65
|
await drv.command("run/dequeue", {"messageId": "m2"})
|
|
66
|
-
res = await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")})
|
|
67
|
-
assert res["type"] == "
|
|
66
|
+
res = await drv.command("run/edit", {"sourceId": "u1", "message": msg("u2")}, terminal=False)
|
|
67
|
+
assert res["type"] == "pending"
|
|
68
68
|
assert (await script.next_call()).ctx.type == "message-edit"
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
async def test_leaf_edit_in_error_accepted_with_queue_non_empty():
|
|
72
72
|
script = Script()
|
|
73
73
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
74
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
74
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
75
75
|
call = await script.next_call()
|
|
76
76
|
script.thread["m1"] = {"parentId": None, "role": "user", "isLeaf": True}
|
|
77
|
-
await drv.command("run/enqueue", {"message": msg("m2")})
|
|
77
|
+
await drv.command("run/enqueue", {"message": msg("m2")}, terminal=False)
|
|
78
78
|
call.fail(RuntimeError("boom"))
|
|
79
79
|
await drv.wait_status("error")
|
|
80
|
-
res = await drv.command("run/edit", {"sourceId": "m1", "message": msg("u2")})
|
|
81
|
-
assert res["type"] == "
|
|
80
|
+
res = await drv.command("run/edit", {"sourceId": "m1", "message": msg("u2")}, terminal=False)
|
|
81
|
+
assert res["type"] == "pending"
|
|
82
82
|
call = await script.next_call()
|
|
83
83
|
assert call.ctx.type == "message-edit"
|
|
84
84
|
assert call.ctx.rollback_to is None
|
|
@@ -90,15 +90,15 @@ async def test_edit_unknown_source_and_duplicate_replacement():
|
|
|
90
90
|
thread_with_turn(script)
|
|
91
91
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
92
92
|
assert_rejected(
|
|
93
|
-
await drv.command("run/edit", {"sourceId": "zz", "message": msg("u2")}),
|
|
93
|
+
await drv.command("run/edit", {"sourceId": "zz", "message": msg("u2")}, terminal=False),
|
|
94
94
|
"unknown-id",
|
|
95
95
|
)
|
|
96
96
|
assert_rejected(
|
|
97
|
-
await drv.command("run/edit", {"sourceId": "u1", "message": msg("a1")}),
|
|
97
|
+
await drv.command("run/edit", {"sourceId": "u1", "message": msg("a1")}, terminal=False),
|
|
98
98
|
"duplicate-id",
|
|
99
99
|
)
|
|
100
|
-
res = await drv.command("run/edit", {"sourceId": "u1", "message": msg("u1", "v2")})
|
|
101
|
-
assert res["type"] == "
|
|
100
|
+
res = await drv.command("run/edit", {"sourceId": "u1", "message": msg("u1", "v2")}, terminal=False)
|
|
101
|
+
assert res["type"] == "pending"
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
async def test_edit_of_assistant_message_requires_assistant_edit():
|
|
@@ -106,7 +106,7 @@ async def test_edit_of_assistant_message_requires_assistant_edit():
|
|
|
106
106
|
thread_with_turn(script)
|
|
107
107
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
108
108
|
assert_rejected(
|
|
109
|
-
await drv.command("run/edit", {"sourceId": "a1", "message": msg("u2")}),
|
|
109
|
+
await drv.command("run/edit", {"sourceId": "a1", "message": msg("u2")}, terminal=False),
|
|
110
110
|
"capability-missing",
|
|
111
111
|
)
|
|
112
112
|
script = Script()
|
|
@@ -115,8 +115,8 @@ async def test_edit_of_assistant_message_requires_assistant_edit():
|
|
|
115
115
|
drv,
|
|
116
116
|
host,
|
|
117
117
|
):
|
|
118
|
-
res = await drv.command("run/edit", {"sourceId": "a1", "message": msg("u2")})
|
|
119
|
-
assert res["type"] == "
|
|
118
|
+
res = await drv.command("run/edit", {"sourceId": "a1", "message": msg("u2")}, terminal=False)
|
|
119
|
+
assert res["type"] == "pending"
|
|
120
120
|
call = await script.next_call()
|
|
121
121
|
assert call.ctx.rollback_to == "u1"
|
|
122
122
|
|
|
@@ -125,8 +125,8 @@ async def test_reload_dispatches_with_empty_messages():
|
|
|
125
125
|
script = Script()
|
|
126
126
|
thread_with_turn(script)
|
|
127
127
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
128
|
-
res = await drv.command("run/reload", {"sourceId": "a1"})
|
|
129
|
-
assert res["type"] == "
|
|
128
|
+
res = await drv.command("run/reload", {"sourceId": "a1"}, terminal=False)
|
|
129
|
+
assert res["type"] == "pending"
|
|
130
130
|
call = await script.next_call()
|
|
131
131
|
assert call.ctx.type == "message-reload"
|
|
132
132
|
assert call.ctx.messages == ()
|
|
@@ -140,15 +140,15 @@ async def test_reload_rejections():
|
|
|
140
140
|
thread_with_turn(script)
|
|
141
141
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
142
142
|
assert_rejected(
|
|
143
|
-
await drv.command("run/reload", {"sourceId": "u1"}), "invalid-message"
|
|
143
|
+
await drv.command("run/reload", {"sourceId": "u1"}, terminal=False), "invalid-message"
|
|
144
144
|
)
|
|
145
145
|
assert_rejected(
|
|
146
|
-
await drv.command("run/reload", {"sourceId": "zz"}), "unknown-id"
|
|
146
|
+
await drv.command("run/reload", {"sourceId": "zz"}, terminal=False), "unknown-id"
|
|
147
147
|
)
|
|
148
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
148
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
149
149
|
await script.next_call()
|
|
150
150
|
assert_rejected(
|
|
151
|
-
await drv.command("run/reload", {"sourceId": "a1"}), "capability-missing"
|
|
151
|
+
await drv.command("run/reload", {"sourceId": "a1"}, terminal=False), "capability-missing"
|
|
152
152
|
)
|
|
153
153
|
|
|
154
154
|
|
|
@@ -157,7 +157,7 @@ async def test_reload_without_rewind_rejects():
|
|
|
157
157
|
thread_with_turn(script)
|
|
158
158
|
async with run_host(script) as (drv, host):
|
|
159
159
|
assert_rejected(
|
|
160
|
-
await drv.command("run/reload", {"sourceId": "a1"}), "capability-missing"
|
|
160
|
+
await drv.command("run/reload", {"sourceId": "a1"}, terminal=False), "capability-missing"
|
|
161
161
|
)
|
|
162
162
|
|
|
163
163
|
|
|
@@ -170,18 +170,18 @@ async def test_reload_leaf_only_while_queue_non_empty():
|
|
|
170
170
|
}
|
|
171
171
|
)
|
|
172
172
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
173
|
-
await drv.command("run/enqueue", {"message": msg("m1")})
|
|
173
|
+
await drv.command("run/enqueue", {"message": msg("m1")}, terminal=False)
|
|
174
174
|
call = await script.next_call()
|
|
175
175
|
script.thread["m1"] = {"parentId": "a0", "role": "user", "isLeaf": False}
|
|
176
176
|
script.thread["a1"] = {"parentId": "m1", "role": "assistant", "isLeaf": True}
|
|
177
|
-
await drv.command("run/enqueue", {"message": msg("m2")})
|
|
177
|
+
await drv.command("run/enqueue", {"message": msg("m2")}, terminal=False)
|
|
178
178
|
call.fail(RuntimeError("boom"))
|
|
179
179
|
await drv.wait_status("error")
|
|
180
180
|
assert_rejected(
|
|
181
|
-
await drv.command("run/reload", {"sourceId": "a0"}), "not-leaf"
|
|
181
|
+
await drv.command("run/reload", {"sourceId": "a0"}, terminal=False), "not-leaf"
|
|
182
182
|
)
|
|
183
|
-
res = await drv.command("run/reload", {"sourceId": "a1"})
|
|
184
|
-
assert res["type"] == "
|
|
183
|
+
res = await drv.command("run/reload", {"sourceId": "a1"}, terminal=False)
|
|
184
|
+
assert res["type"] == "pending"
|
|
185
185
|
call = await script.next_call()
|
|
186
186
|
assert call.ctx.rollback_to == "m1"
|
|
187
187
|
|
|
@@ -196,12 +196,12 @@ async def test_reload_of_assistant_parented_response_needs_continuation():
|
|
|
196
196
|
)
|
|
197
197
|
async with run_host(script, capabilities=("rewind",)) as (drv, host):
|
|
198
198
|
assert_rejected(
|
|
199
|
-
await drv.command("run/reload", {"sourceId": "a2"}), "capability-missing"
|
|
199
|
+
await drv.command("run/reload", {"sourceId": "a2"}, terminal=False), "capability-missing"
|
|
200
200
|
)
|
|
201
201
|
async with run_host(script, capabilities=("rewind", "assistant-continuation")) as (
|
|
202
202
|
drv,
|
|
203
203
|
host,
|
|
204
204
|
):
|
|
205
|
-
res = await drv.command("run/reload", {"sourceId": "a2"})
|
|
206
|
-
assert res["type"] == "
|
|
205
|
+
res = await drv.command("run/reload", {"sourceId": "a2"}, terminal=False)
|
|
206
|
+
assert res["type"] == "pending"
|
|
207
207
|
assert (await script.next_call()).ctx.rollback_to == "a1"
|