harness-sdk-python 0.32.0__tar.gz → 0.32.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.
Files changed (44) hide show
  1. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/PKG-INFO +1 -1
  2. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/pyproject.toml +1 -1
  3. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/src/harness_sdk/run_manager.py +15 -3
  4. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/run_helpers.py +0 -1
  5. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_input_required.py +10 -10
  6. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_restore.py +1 -1
  7. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_stopping_window.py +4 -3
  8. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/.gitignore +0 -0
  9. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/README.md +0 -0
  10. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/examples/__init__.py +0 -0
  11. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/examples/runs_app.py +0 -0
  12. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/src/harness_sdk/__init__.py +0 -0
  13. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/src/harness_sdk/checkpoints.py +0 -0
  14. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/src/harness_sdk/deepagents.py +0 -0
  15. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/src/harness_sdk/fenced_postgres.py +0 -0
  16. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/src/harness_sdk/linear_thread.py +0 -0
  17. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/src/harness_sdk/voice.py +0 -0
  18. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_admit_steer.py +0 -0
  19. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_applied_visibility.py +0 -0
  20. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_batches.py +0 -0
  21. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_branch_anchor.py +0 -0
  22. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_checkpoints.py +0 -0
  23. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_deepagents_messages.py +0 -0
  24. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_dispatching.py +0 -0
  25. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_durability.py +0 -0
  26. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_edit_dispatched.py +0 -0
  27. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_edit_reload.py +0 -0
  28. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_enqueue.py +0 -0
  29. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_fenced_postgres.py +0 -0
  30. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_linear_thread.py +0 -0
  31. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_meta.py +0 -0
  32. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_mint_concurrency.py +0 -0
  33. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_outcomes.py +0 -0
  34. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_placement.py +0 -0
  35. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_prepare_hooks.py +0 -0
  36. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_rewind_during_run.py +0 -0
  37. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_run_crash_sweep.py +0 -0
  38. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_settle.py +0 -0
  39. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_steer.py +0 -0
  40. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_stop_continue.py +0 -0
  41. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_voice.py +0 -0
  42. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_voice_delegation.py +0 -0
  43. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/test_voice_frontend_tools.py +0 -0
  44. {harness_sdk_python-0.32.0 → harness_sdk_python-0.32.1}/tests/voice_helpers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: harness-sdk-python
3
- Version: 0.32.0
3
+ Version: 0.32.1
4
4
  Summary: RunManager: the harness-sdk runs subsystem for Python Statewire hosts
5
5
  Project-URL: Repository, https://github.com/assistant-ui/harness-sdk
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "harness-sdk-python"
3
- version = "0.32.0"
3
+ version = "0.32.1"
4
4
  description = "RunManager: the harness-sdk runs subsystem for Python Statewire hosts"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -342,7 +342,6 @@ class RunManager(Tray):
342
342
  "dispatching": dispatching,
343
343
  "stopping": stopping,
344
344
  "nextDispatch": nextDispatch,
345
- "inputRequests": inputRequests,
346
345
  "error": error,
347
346
  "stopReason": stopReason,
348
347
  }
@@ -370,9 +369,14 @@ class RunManager(Tray):
370
369
  self.state["runId"] = self.id
371
370
  self._hydrate_machine()
372
371
  self._mirror_steer()
373
- unsubscribe = self.steerQueue.subscribe(lambda _op: self._mirror_steer())
372
+ self._mirror_input()
373
+ unsubscribes = [
374
+ self.steerQueue.subscribe(lambda _op: self._mirror_steer()),
375
+ self.inputRequests.subscribe(lambda _op: self._mirror_input()),
376
+ ]
374
377
  yield
375
- unsubscribe()
378
+ for unsubscribe in unsubscribes:
379
+ unsubscribe()
376
380
 
377
381
  class Thread(Protocol):
378
382
  """The thread projection RunManager reads; the host owns the tree."""
@@ -500,6 +504,14 @@ class RunManager(Tray):
500
504
  def _mirror_steer(self) -> None:
501
505
  self.steerView.value = [_public(item) for item in self.steerQueue.value]
502
506
 
507
+ def _mirror_input(self) -> None:
508
+ # None publishes as an absent key, matching the TS harness shape
509
+ requests = self.inputRequests.value
510
+ if requests is None:
511
+ self.state.pop("inputRequests", None)
512
+ else:
513
+ self.state["inputRequests"] = requests
514
+
503
515
  def _sync_steering(self) -> None:
504
516
  ctx = self._run_live
505
517
  if ctx is None:
@@ -408,7 +408,6 @@ def run_of(replica: dict[str, Any]) -> dict[str, Any]:
408
408
  "dispatching": None,
409
409
  "stopping": None,
410
410
  "nextDispatch": None,
411
- "inputRequests": None,
412
411
  "error": None,
413
412
  "stopReason": None,
414
413
  }
@@ -54,7 +54,7 @@ async def test_partial_answer_stays_parked_and_echoes_response():
54
54
  )
55
55
  assert res["type"] == "accepted"
56
56
  await drv.wait(
57
- lambda s: "response" in run_of(s)["inputRequests"][0]
57
+ lambda s: "response" in run_of(s).get("inputRequests", [{}])[0]
58
58
  )
59
59
  assert drv.replica["status"] == "input-required"
60
60
  assert drv.run["inputRequests"][0]["response"] == {"output": "ok"}
@@ -82,7 +82,7 @@ async def test_completing_answer_resumes_with_ordered_responses():
82
82
  (tool_call("r1"), {"output": "ok"}, None),
83
83
  (approval("r2"), {"decision": "approve"}, None),
84
84
  )
85
- assert drv.run["inputRequests"] is None
85
+ assert "inputRequests" not in drv.run
86
86
  resumed.applied()
87
87
  resumed.finish(RunManager.Complete())
88
88
  await drv.wait_status("ready")
@@ -133,7 +133,7 @@ async def test_completing_batch_resumes_once_in_the_same_envelope():
133
133
  # the resume dispatched in the batch's own flush: by the time the accepts
134
134
  # settle the replica already shows the run in flight
135
135
  assert drv.replica["status"] == "running"
136
- assert drv.run["inputRequests"] is None
136
+ assert "inputRequests" not in drv.run
137
137
  resumed = await script.next_call()
138
138
  assert resumed.ctx.trigger == "input-resume"
139
139
  assert [r["id"] for r, _, _ in resumed.ctx.input_outcomes] == ["r1", "r2"]
@@ -324,7 +324,7 @@ async def test_edit_while_parked_clears_requests_and_reruns():
324
324
  rerun = await script.next_call()
325
325
  assert rerun.ctx.trigger == "message-edit"
326
326
  assert rerun.ctx.rollback_to is None
327
- await drv.wait(lambda s: run_of(s)["inputRequests"] is None)
327
+ await drv.wait(lambda s: "inputRequests" not in run_of(s))
328
328
  rerun.applied()
329
329
  rerun.finish(RunManager.Complete())
330
330
  await drv.wait_status("ready")
@@ -339,7 +339,7 @@ async def test_reload_while_parked_clears_requests_and_reruns():
339
339
  rerun = await script.next_call()
340
340
  assert rerun.ctx.trigger == "message-reload"
341
341
  assert rerun.ctx.rollback_to == "u1"
342
- await drv.wait(lambda s: run_of(s)["inputRequests"] is None)
342
+ await drv.wait(lambda s: "inputRequests" not in run_of(s))
343
343
  rerun.applied()
344
344
  rerun.finish(RunManager.Complete())
345
345
  await drv.wait_status("ready")
@@ -358,7 +358,7 @@ async def test_steer_add_while_parked_abandons_input_and_dispatches():
358
358
  (approval("r2"), None, None),
359
359
  )
360
360
  await drv.wait_status("running")
361
- assert drv.run["inputRequests"] is None
361
+ assert "inputRequests" not in drv.run
362
362
  run.applied()
363
363
  run.finish(RunManager.Complete())
364
364
  await drv.wait_status("ready")
@@ -379,7 +379,7 @@ async def test_steer_add_onto_nonempty_lane_while_parked_dispatches_whole_lane()
379
379
  assert [m["id"] for m in run.ctx.messages] == ["s1", "s2"]
380
380
  assert run.ctx.input_outcomes == ((tool_call("r1"), None, None),)
381
381
  await drv.wait_status("running")
382
- assert drv.run["inputRequests"] is None
382
+ assert "inputRequests" not in drv.run
383
383
  run.applied()
384
384
  run.finish(RunManager.Complete())
385
385
  await drv.wait_status("ready")
@@ -397,7 +397,7 @@ async def test_promote_to_steer_while_parked_abandons_input_and_dispatches():
397
397
  assert run.ctx.trigger == "message-send"
398
398
  assert [m["id"] for m in run.ctx.messages] == ["m2"]
399
399
  assert run.ctx.input_outcomes == ((tool_call("r1"), None, None),)
400
- assert drv.run["inputRequests"] is None
400
+ assert "inputRequests" not in drv.run
401
401
  run.applied()
402
402
  run.finish(RunManager.Complete())
403
403
  await drv.wait_status("ready")
@@ -430,7 +430,7 @@ async def test_steer_add_with_partial_answers_hands_over_the_answered_subset():
430
430
  (approval("r2"), None, None),
431
431
  )
432
432
  await drv.wait_status("running")
433
- assert drv.run["inputRequests"] is None
433
+ assert "inputRequests" not in drv.run
434
434
  run.applied()
435
435
  run.finish(RunManager.Complete())
436
436
  await drv.wait_status("ready")
@@ -444,7 +444,7 @@ async def test_answer_meta_replicates_only_when_carried():
444
444
  "run/input",
445
445
  {"requestId": "r1", "response": {"output": "ok"}, "meta": {"src": "c1"}},
446
446
  )
447
- await drv.wait(lambda s: "response" in run_of(s)["inputRequests"][0])
447
+ await drv.wait(lambda s: "response" in run_of(s).get("inputRequests", [{}])[0])
448
448
  assert drv.run["inputRequests"][0] == {
449
449
  **tool_call("r1"),
450
450
  "response": {"output": "ok"},
@@ -107,7 +107,7 @@ async def test_steer_after_a_restart_abandons_input_and_dispatches():
107
107
  assert [m["id"] for m in run.ctx.messages] == ["s1"]
108
108
  assert run.ctx.input_outcomes == ((tool_call("r1"), None, None),)
109
109
  await drv2.wait_status("running")
110
- assert drv2.run["inputRequests"] is None
110
+ assert "inputRequests" not in drv2.run
111
111
  run.applied()
112
112
  run.finish(RunManager.Complete())
113
113
  await drv2.wait_status("ready")
@@ -373,13 +373,14 @@ ENTRY_KEYS = {
373
373
  "dispatching",
374
374
  "stopping",
375
375
  "nextDispatch",
376
- "inputRequests",
377
376
  "error",
378
377
  "stopReason",
379
378
  }
380
379
 
381
380
 
382
381
  async def test_entry_keys_are_always_present_and_null_when_empty():
382
+ """inputRequests is the one absent-when-empty key: present only while a
383
+ request set is open, matching the TS harness shape."""
383
384
  script = Script()
384
385
  async with run_host(script) as (drv, host):
385
386
  drv.post("run/enqueue", add("m1"))
@@ -388,7 +389,7 @@ async def test_entry_keys_are_always_present_and_null_when_empty():
388
389
  assert set(drv.run.keys()) == ENTRY_KEYS
389
390
  assert drv.run["dispatching"] is not None
390
391
  assert (drv.run["stopping"], drv.run["nextDispatch"]) == (None, None)
391
- assert (drv.run["inputRequests"], drv.run["error"]) == (None, None)
392
+ assert drv.run["error"] is None
392
393
  call.applied()
393
394
  await drv.wait(lambda s: run_of(s)["dispatching"] is None)
394
395
  call.fail(RuntimeError("boom"))
@@ -396,5 +397,5 @@ async def test_entry_keys_are_always_present_and_null_when_empty():
396
397
  assert set(drv.run.keys()) == ENTRY_KEYS
397
398
  assert drv.run["error"] == {"message": "boom"}
398
399
  assert (drv.run["dispatching"], drv.run["stopping"]) == (None, None)
399
- assert (drv.run["nextDispatch"], drv.run["inputRequests"]) == (None, None)
400
+ assert drv.run["nextDispatch"] is None
400
401
  assert (drv.run["queue"], drv.run["steerQueue"]) == ([], [])