vis-agent 0.2.54.dev7858__tar.gz → 0.2.54.dev7866__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 (147) hide show
  1. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/PKG-INFO +1 -1
  2. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/agents.json +1 -0
  3. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/pyproject.toml +1 -1
  4. vis_agent-0.2.54.dev7866/release-tag +1 -0
  5. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/_agents.py +2 -1
  6. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/_council.py +3 -2
  7. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_client.py +1 -1
  8. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_engine.py +118 -3
  9. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_rooms.py +1 -1
  10. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_shortcuts_live.py +1 -1
  11. vis_agent-0.2.54.dev7858/release-tag +0 -1
  12. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/.gitignore +0 -0
  13. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/LICENSE +0 -0
  14. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/README.md +0 -0
  15. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-arguments.json +0 -0
  16. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-cases.json +0 -0
  17. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-copy.json +0 -0
  18. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-execution.json +0 -0
  19. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-groups.json +0 -0
  20. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-read-session.json +0 -0
  21. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-reads.json +0 -0
  22. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-results.json +0 -0
  23. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-stream.json +0 -0
  24. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity-tables.json +0 -0
  25. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/activity.json +0 -0
  26. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/diff.json +0 -0
  27. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/live-primitives.json +0 -0
  28. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/session-health.json +0 -0
  29. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/fixtures/view.json +0 -0
  30. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/activity.json +0 -0
  31. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/automations.json +0 -0
  32. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/common.json +0 -0
  33. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/config.json +0 -0
  34. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/content.json +0 -0
  35. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/council.json +0 -0
  36. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/diff.json +0 -0
  37. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/evals.json +0 -0
  38. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/gateway.json +0 -0
  39. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/improve.json +0 -0
  40. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/provider.json +0 -0
  41. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/rooms.json +0 -0
  42. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/symbol.json +0 -0
  43. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/toggle.json +0 -0
  44. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/contracts/schema/view.json +0 -0
  45. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/examples/greeter/tests/test_greeter.py +0 -0
  46. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/hatch_build.py +0 -0
  47. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/__init__.py +0 -0
  48. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/_contracts.py +0 -0
  49. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/_outside.py +0 -0
  50. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/_wire.py +0 -0
  51. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/activity.py +0 -0
  52. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/__init__.py +0 -0
  53. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/_agent.py +0 -0
  54. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/_client.py +0 -0
  55. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/_extensions.py +0 -0
  56. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/_host.py +0 -0
  57. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/_install.py +0 -0
  58. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/_local.py +0 -0
  59. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/engine/_models.py +0 -0
  60. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/__init__.py +0 -0
  61. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/_gateway.py +0 -0
  62. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/agent.py +0 -0
  63. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/bench.py +0 -0
  64. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/check.py +0 -0
  65. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/compare.py +0 -0
  66. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/environment.py +0 -0
  67. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/grade.py +0 -0
  68. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/harbor.py +0 -0
  69. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/judge-rubric.toml +0 -0
  70. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/judge.py +0 -0
  71. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/labels.py +0 -0
  72. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/leaderboard.py +0 -0
  73. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/repeats.py +0 -0
  74. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/rounds.py +0 -0
  75. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/stages.py +0 -0
  76. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/stats.py +0 -0
  77. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/evals/trajectory.py +0 -0
  78. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/extension.py +0 -0
  79. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/extension_package.py +0 -0
  80. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/py.typed +0 -0
  81. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/rooms.py +0 -0
  82. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/src/blockether/vis/views.py +0 -0
  83. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/conftest.py +0 -0
  84. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/evals_atif.py +0 -0
  85. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_activity.py +0 -0
  86. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_agent.py +0 -0
  87. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_agent_extension_lifecycle.py +0 -0
  88. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_agent_extensions.py +0 -0
  89. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_agents.py +0 -0
  90. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_api_guides.py +0 -0
  91. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_attachments.py +0 -0
  92. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_authoring.py +0 -0
  93. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_catalog.py +0 -0
  94. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_catalog_recipe.py +0 -0
  95. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_ci_report_recipe.py +0 -0
  96. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_client_extensions_engine.py +0 -0
  97. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_code_quality_recipe.py +0 -0
  98. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_contracts.py +0 -0
  99. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_council.py +0 -0
  100. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_declarations.py +0 -0
  101. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_engine_docstrings.py +0 -0
  102. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_agent.py +0 -0
  103. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_bench.py +0 -0
  104. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_check.py +0 -0
  105. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_compare.py +0 -0
  106. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_environment.py +0 -0
  107. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_gateway.py +0 -0
  108. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_grade.py +0 -0
  109. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_guards.py +0 -0
  110. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_harbor.py +0 -0
  111. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_judge.py +0 -0
  112. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_labels.py +0 -0
  113. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_repeats.py +0 -0
  114. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_rounds.py +0 -0
  115. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_stages.py +0 -0
  116. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_stats.py +0 -0
  117. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_tasks.py +0 -0
  118. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_evals_trajectory.py +0 -0
  119. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_extension_package.py +0 -0
  120. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_extension_reference.py +0 -0
  121. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_gateway_performance.py +0 -0
  122. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_goals.py +0 -0
  123. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_install.py +0 -0
  124. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_live_primitives.py +0 -0
  125. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_live_wait.py +0 -0
  126. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_local.py +0 -0
  127. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_method_typing.py +0 -0
  128. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_models.py +0 -0
  129. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_monitor_recipe.py +0 -0
  130. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_namespace.py +0 -0
  131. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_outside.py +0 -0
  132. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_providers.py +0 -0
  133. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_read_session.py +0 -0
  134. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_record_arguments.py +0 -0
  135. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_registration.py +0 -0
  136. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_repair_hooks.py +0 -0
  137. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_result_sequence.py +0 -0
  138. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_scoped_settings.py +0 -0
  139. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_sdk_bootstrap.py +0 -0
  140. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_sdk_guide.py +0 -0
  141. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_sdk_typing.py +0 -0
  142. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_structured_live.py +0 -0
  143. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_symbol_contract.py +0 -0
  144. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_testing.py +0 -0
  145. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_text_list_arguments.py +0 -0
  146. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_tools.py +0 -0
  147. {vis_agent-0.2.54.dev7858 → vis_agent-0.2.54.dev7866}/tests/test_views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vis-agent
3
- Version: 0.2.54.dev7858
3
+ Version: 0.2.54.dev7866
4
4
  Summary: The Vis Python SDK — author extensions and control local or remote engines.
5
5
  Project-URL: Homepage, https://vis.blockether.com
6
6
  Project-URL: Documentation, https://vis.blockether.com
@@ -413,6 +413,7 @@
413
413
  "queued",
414
414
  "running",
415
415
  "asking",
416
+ "held",
416
417
  "completed",
417
418
  "failed",
418
419
  "cancelled",
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
8
 
9
9
  [project]
10
10
  name = "vis-agent"
11
- version = "0.2.54.dev7858"
11
+ version = "0.2.54.dev7866"
12
12
  description = "The Vis Python SDK — author extensions and control local or remote engines."
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.12"
@@ -0,0 +1 @@
1
+ beta-f324befbb3cd5dca342324d0c8eee76689e040ff
@@ -38,7 +38,8 @@ class SubagentRecord:
38
38
  """The state of one subagent after a control: answer, open question or request.
39
39
 
40
40
  ``status`` is ``asking`` when the child waits for its parent. Then ``question``,
41
- ``budget_request`` or ``files_request`` tells what it needs. ``unknown`` names a
41
+ ``budget_request`` or ``files_request`` tells what it needs. ``held`` means that a
42
+ human holds the queue of the child; only a person can release it. ``unknown`` names a
42
43
  session that is not a subagent of the caller. ``warning`` reports a file overlap.
43
44
  """
44
45
 
@@ -320,8 +320,9 @@ class Council:
320
320
  ) -> SubagentRecord:
321
321
  """Give a finished subagent a follow-up task in its own session, then wait for it.
322
322
 
323
- The child keeps its whole context. A child without iterations left needs
324
- ``extend`` first. A running child needs ``wait``.
323
+ The parent must be active, as for ``spawn``: an idle parent gets
324
+ ``inactive-session``. The child keeps its whole context. A child without
325
+ iterations left needs ``extend`` first. A running child needs ``wait``.
325
326
  """
326
327
  body = {"session_id": session_id, "task": task, "timeout_secs": timeout_secs}
327
328
  return self._records("/resume", body, "resume", timeout_secs)[0]
@@ -62,7 +62,7 @@ def endpoint(respond):
62
62
  do_GET = do_POST = do_PATCH = do_PUT = do_DELETE = handle_request
63
63
 
64
64
  server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
65
- thread = threading.Thread(target=server.serve_forever, daemon=True)
65
+ thread = threading.Thread(target=server.serve_forever, args=(0.01,), daemon=True)
66
66
  thread.start()
67
67
  try:
68
68
  yield f"http://127.0.0.1:{server.server_port}", calls
@@ -95,7 +95,7 @@ def activity_rows(projection):
95
95
 
96
96
 
97
97
  @contextmanager
98
- def model_endpoint(*, tool_code=None, before_reply=None):
98
+ def model_endpoint(*, tool_code=None, before_reply=None, refuse=None):
99
99
  requests = []
100
100
  positions = count(1)
101
101
 
@@ -105,6 +105,14 @@ def model_endpoint(*, tool_code=None, before_reply=None):
105
105
 
106
106
  def do_POST(self):
107
107
  body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
108
+ if refuse is not None and body.get("tools") and refuse(body):
109
+ error = json.dumps({"error": {"message": "fixture refusal"}}).encode()
110
+ self.send_response(400)
111
+ self.send_header("Content-Type", "application/json")
112
+ self.send_header("Content-Length", str(len(error)))
113
+ self.end_headers()
114
+ self.wfile.write(error)
115
+ return
108
116
  if not body.get("tools"):
109
117
  delta, finish = {"content": "SDK fixture"}, "stop"
110
118
  else:
@@ -160,7 +168,7 @@ def model_endpoint(*, tool_code=None, before_reply=None):
160
168
  self.wfile.write(data)
161
169
 
162
170
  server = ThreadingHTTPServer(("127.0.0.1", 0), Model)
163
- thread = threading.Thread(target=server.serve_forever, daemon=True)
171
+ thread = threading.Thread(target=server.serve_forever, args=(0.01,), daemon=True)
164
172
  thread.start()
165
173
  try:
166
174
  yield f"http://127.0.0.1:{server.server_port}/v1", requests
@@ -260,6 +268,7 @@ def sdk_fixture(
260
268
  subagents=False,
261
269
  tool_code=None,
262
270
  before_reply=None,
271
+ refuse=None,
263
272
  project_extensions=None,
264
273
  on_process=None,
265
274
  ):
@@ -278,7 +287,9 @@ def sdk_fixture(
278
287
  monkeypatch.setenv("HOME", str(home))
279
288
  monkeypatch.setenv("JAVA_TOOL_OPTIONS", f"-Duser.home={home}")
280
289
  monkeypatch.delenv("VIS_GATEWAY_URL", raising=False)
281
- with model_endpoint(tool_code=tool_code, before_reply=before_reply) as (
290
+ with model_endpoint(
291
+ tool_code=tool_code, before_reply=before_reply, refuse=refuse
292
+ ) as (
282
293
  model_url,
283
294
  requests,
284
295
  ):
@@ -1052,3 +1063,107 @@ def test_real_concurrent_sessions_keep_repeated_turns_isolated(tmp_path, monkeyp
1052
1063
  with ThreadPoolExecutor(max_workers=len(markers)) as executor:
1053
1064
  list(executor.map(exercise, range(len(markers))))
1054
1065
  assert len(requests) == 24
1066
+
1067
+
1068
+ def _last_user(body):
1069
+ """The text of the newest user message in a model request."""
1070
+ users = [m for m in body["messages"] if m["role"] == "user"]
1071
+ return json.dumps(users[-1]["content"]) if users else ""
1072
+
1073
+
1074
+ def _asked_with(requests, request, marker):
1075
+ """True when the model request for `request` carried `marker` in its messages."""
1076
+ return any(
1077
+ request in _last_user(body) and marker in json.dumps(body["messages"])
1078
+ for body in requests
1079
+ )
1080
+
1081
+
1082
+ def _finish(session, request, *, cancel=False, during=None):
1083
+ """Send `request` and return its status. Once a block runs, call `during`, then
1084
+ cancel the turn when `cancel` is true."""
1085
+ turn = session.send(request)
1086
+ if cancel:
1087
+ with session.events(cursor=turn.cursor, reconnects=0) as events:
1088
+ for event in events:
1089
+ if event.type.startswith("block."):
1090
+ if during is not None:
1091
+ during()
1092
+ turn.cancel()
1093
+ break
1094
+ return turn.wait(timeout=60)["status"]
1095
+
1096
+
1097
+ def test_real_snapshot_continues_on_another_engine(tmp_path, monkeypatch):
1098
+ # Two engines with their own homes, databases and model endpoints stand for two
1099
+ # computers (#405). A runs over HTTP, B over stdio.
1100
+ seen = {}
1101
+
1102
+ def code(_position):
1103
+ if "slow-turn" in _last_user(seen["now"][-1]):
1104
+ return "import asyncio\nawait asyncio.sleep(120)"
1105
+ return "print('noted')"
1106
+
1107
+ def refuse(body):
1108
+ return "broken-turn" in _last_user(body)
1109
+
1110
+ (tmp_path / "a").mkdir()
1111
+ (tmp_path / "b").mkdir()
1112
+ with (
1113
+ sdk_fixture(
1114
+ tmp_path / "a", monkeypatch, "http", tool_code=code, refuse=refuse
1115
+ ) as (a, a_work, a_requests),
1116
+ sdk_fixture(
1117
+ tmp_path / "b", monkeypatch, "stdio", tool_code="print('noted')"
1118
+ ) as (b, b_work, b_requests),
1119
+ ):
1120
+ seen["now"] = a_requests
1121
+ original = a.create_session(title="Durable talk", root=str(a_work))
1122
+ assert _finish(original, "remember the word ORCA-4127") == "completed"
1123
+ running = []
1124
+ assert (
1125
+ _finish(
1126
+ original,
1127
+ "slow-turn: wait",
1128
+ cancel=True,
1129
+ during=lambda: running.append(original.export()),
1130
+ )
1131
+ == "cancelled"
1132
+ )
1133
+ assert _finish(original, "broken-turn: fail") == "failed"
1134
+ snapshot = tmp_path / "talk.vissnap"
1135
+ snapshot.write_bytes(original.export())
1136
+ data = snapshot.read_bytes()
1137
+ assert GatewayClient.snapshot_info(data)["counts"]["turns"] == 3
1138
+
1139
+ restored = b.restore_session(data, root=str(b_work))
1140
+ listed = [row["id"] for row in b.list_sessions(limit=20)["sessions"]]
1141
+ assert restored.id in listed
1142
+ assert restored.id != original.id
1143
+ assert [t["status"] for t in restored.turns()] == [
1144
+ "completed",
1145
+ "cancelled",
1146
+ "failed",
1147
+ ]
1148
+ assert restored.set_model("sdk-fixture", "sdk-test").model == "sdk-test"
1149
+ assert _finish(restored, "follow-up-on-b: which word?") == "completed"
1150
+ assert _asked_with(b_requests, "follow-up-on-b", "ORCA-4127")
1151
+
1152
+ # A snapshot taken while a turn ran restores that turn as stopped and continues.
1153
+ paused = b.restore_session(running[0], root=str(b_work))
1154
+ assert [t["status"] for t in paused.turns()] == ["completed", "cancelled"]
1155
+ assert _finish(paused, "follow-up-on-paused: which word?") == "completed"
1156
+ assert _asked_with(b_requests, "follow-up-on-paused", "ORCA-4127")
1157
+
1158
+ # The same file restores again as a second session; a key restores it once.
1159
+ assert b.restore_session(data, root=str(b_work)).id != restored.id
1160
+ keyed = b.restore_session(data, root=str(b_work), key="talk-1")
1161
+ assert b.restore_session(data, root=str(b_work), key="talk-1").id == keyed.id
1162
+
1163
+ # Reimport: the session that B continued goes back to A and continues there.
1164
+ back = a.restore_session(restored.export(), root=str(a_work))
1165
+ seen["now"] = a_requests
1166
+ assert len(back.turns()) == 4
1167
+ assert _finish(back, "follow-up-on-a: which word?") == "completed"
1168
+ assert _asked_with(a_requests, "follow-up-on-a", "ORCA-4127")
1169
+ assert _asked_with(a_requests, "follow-up-on-a", "follow-up-on-b")
@@ -184,7 +184,7 @@ def test_real_http_redirect_never_forwards_credentials():
184
184
  pass
185
185
 
186
186
  with ThreadingHTTPServer(("127.0.0.1", 0), Handler) as server:
187
- thread = Thread(target=server.serve_forever, daemon=True)
187
+ thread = Thread(target=server.serve_forever, args=(0.01,), daemon=True)
188
188
  thread.start()
189
189
  try:
190
190
  client = RoomsClient(
@@ -65,7 +65,7 @@ def scripted_model():
65
65
  self.wfile.write(data)
66
66
 
67
67
  server = ThreadingHTTPServer(("127.0.0.1", 0), Model)
68
- thread = Thread(target=server.serve_forever, daemon=True)
68
+ thread = Thread(target=server.serve_forever, args=(0.01,), daemon=True)
69
69
  thread.start()
70
70
  try:
71
71
  yield f"http://127.0.0.1:{server.server_address[1]}/v1"
@@ -1 +0,0 @@
1
- beta-2a427e2872e9593ef6076d55573ec209fbc6dab3