vis-agent 0.2.39.dev7593__tar.gz → 0.2.40.dev7598__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 (108) hide show
  1. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/PKG-INFO +1 -1
  2. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/pyproject.toml +1 -1
  3. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/_contracts.py +21 -0
  4. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/_outside.py +11 -4
  5. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/extension.py +14 -14
  6. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_live_primitives.py +25 -0
  7. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/.gitignore +0 -0
  8. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/LICENSE +0 -0
  9. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/README.md +0 -0
  10. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity-arguments.json +0 -0
  11. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity-cases.json +0 -0
  12. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity-copy.json +0 -0
  13. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity-execution.json +0 -0
  14. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity-groups.json +0 -0
  15. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity-read-session.json +0 -0
  16. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity-reads.json +0 -0
  17. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity-results.json +0 -0
  18. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity-tables.json +0 -0
  19. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/activity.json +0 -0
  20. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/diff.json +0 -0
  21. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/live-primitives.json +0 -0
  22. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/session-health.json +0 -0
  23. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/fixtures/view.json +0 -0
  24. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/activity.json +0 -0
  25. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/agents.json +0 -0
  26. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/automations.json +0 -0
  27. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/common.json +0 -0
  28. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/config.json +0 -0
  29. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/content.json +0 -0
  30. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/council.json +0 -0
  31. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/diff.json +0 -0
  32. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/gateway.json +0 -0
  33. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/improve.json +0 -0
  34. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/provider.json +0 -0
  35. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/rooms.json +0 -0
  36. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/symbol.json +0 -0
  37. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/toggle.json +0 -0
  38. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/contracts/schema/view.json +0 -0
  39. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/examples/greeter/tests/test_greeter.py +0 -0
  40. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/hatch_build.py +0 -0
  41. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/__init__.py +0 -0
  42. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/_wire.py +0 -0
  43. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/activity.py +0 -0
  44. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/engine/__init__.py +0 -0
  45. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/engine/_agent.py +0 -0
  46. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/engine/_agents.py +0 -0
  47. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/engine/_client.py +0 -0
  48. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/engine/_council.py +0 -0
  49. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/engine/_extensions.py +0 -0
  50. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/engine/_local.py +0 -0
  51. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/evals/__init__.py +0 -0
  52. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/evals/harbor.py +0 -0
  53. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/evals/stats.py +0 -0
  54. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/evals/trajectory.py +0 -0
  55. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/extension_package.py +0 -0
  56. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/py.typed +0 -0
  57. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/rooms.py +0 -0
  58. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/src/blockether/vis/views.py +0 -0
  59. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/conftest.py +0 -0
  60. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_activity.py +0 -0
  61. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_agent.py +0 -0
  62. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_agent_extension_lifecycle.py +0 -0
  63. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_agent_extensions.py +0 -0
  64. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_agents.py +0 -0
  65. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_api_guides.py +0 -0
  66. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_attachments.py +0 -0
  67. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_authoring.py +0 -0
  68. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_catalog.py +0 -0
  69. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_catalog_recipe.py +0 -0
  70. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_ci_report_recipe.py +0 -0
  71. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_client.py +0 -0
  72. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_client_extensions_engine.py +0 -0
  73. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_code_quality_recipe.py +0 -0
  74. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_contracts.py +0 -0
  75. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_council.py +0 -0
  76. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_declarations.py +0 -0
  77. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_engine.py +0 -0
  78. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_engine_docstrings.py +0 -0
  79. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_evals_harbor.py +0 -0
  80. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_evals_stats.py +0 -0
  81. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_evals_trajectory.py +0 -0
  82. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_extension_package.py +0 -0
  83. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_extension_reference.py +0 -0
  84. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_gateway_performance.py +0 -0
  85. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_goals.py +0 -0
  86. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_live_wait.py +0 -0
  87. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_local.py +0 -0
  88. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_method_typing.py +0 -0
  89. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_monitor_recipe.py +0 -0
  90. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_namespace.py +0 -0
  91. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_outside.py +0 -0
  92. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_providers.py +0 -0
  93. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_read_session.py +0 -0
  94. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_record_arguments.py +0 -0
  95. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_registration.py +0 -0
  96. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_repair_hooks.py +0 -0
  97. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_result_sequence.py +0 -0
  98. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_rooms.py +0 -0
  99. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_scoped_settings.py +0 -0
  100. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_sdk_bootstrap.py +0 -0
  101. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_sdk_guide.py +0 -0
  102. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_sdk_typing.py +0 -0
  103. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_structured_live.py +0 -0
  104. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_symbol_contract.py +0 -0
  105. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_testing.py +0 -0
  106. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_text_list_arguments.py +0 -0
  107. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/tests/test_tools.py +0 -0
  108. {vis_agent-0.2.39.dev7593 → vis_agent-0.2.40.dev7598}/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.39.dev7593
3
+ Version: 0.2.40.dev7598
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
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
8
 
9
9
  [project]
10
10
  name = "vis-agent"
11
- version = "0.2.39.dev7593"
11
+ version = "0.2.40.dev7598"
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.11"
@@ -41,6 +41,27 @@ def definition(name: str, key: str) -> dict[str, Any]:
41
41
  return schema(name)["$defs"][key]
42
42
 
43
43
 
44
+ # An appended live item replaces the item with its id. The host fills these keys
45
+ # when the item leaves them out; every other required key must come with it.
46
+ _LIVE_ITEM_DEFAULTS = {"step": {"tone": "idle"}, "link": {"target_kind": "url"}}
47
+ _LIVE_ITEMS = {"stats": "stat", "steps": "step", "rows": "row", "links": "link"}
48
+
49
+
50
+ def live_items(key: str, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
51
+ """Return appended live items as the host keeps them; refuse a missing key."""
52
+ item = _LIVE_ITEMS[key]
53
+ required = definition("view", item)["required"]
54
+ result = []
55
+ for one in items:
56
+ one = {**_LIVE_ITEM_DEFAULTS.get(item, {}), **one}
57
+ if missing := [name for name in required if name not in one]:
58
+ raise ValueError(
59
+ f"{item} {one.get('id')}: {', '.join(missing)} is missing (required)"
60
+ )
61
+ result.append(one)
62
+ return result
63
+
64
+
44
65
  @lru_cache(maxsize=64)
45
66
  def _validator(name: str, definition: str):
46
67
  from jsonschema import Draft202012Validator, FormatChecker
@@ -1141,9 +1141,10 @@ def _live_find(nodes, node_id):
1141
1141
  return None
1142
1142
 
1143
1143
 
1144
- def _live_upsert(held, incoming):
1144
+ def _live_upsert(held, incoming, *, is_merged=False):
1145
1145
  # An item keeps the position it was first given: a row that updates in place
1146
- # is what makes a table readable while it fills.
1146
+ # is what makes a table readable while it fills. The host replaces an item;
1147
+ # only a group declaration merges onto the one before it.
1147
1148
  merged = list(held or [])
1148
1149
  for item in incoming or []:
1149
1150
  item_id = item.get("id") if isinstance(item, dict) else None
@@ -1155,7 +1156,7 @@ def _live_upsert(held, incoming):
1155
1156
  if at is None:
1156
1157
  merged.append(item)
1157
1158
  else:
1158
- merged[at] = {**merged[at], **item}
1159
+ merged[at] = {**merged[at], **item} if is_merged else item
1159
1160
  return merged
1160
1161
 
1161
1162
 
@@ -1248,12 +1249,18 @@ def _live_apply(view, op):
1248
1249
  )
1249
1250
  node[key] = list(node.get(key) or []) + payload
1250
1251
  else:
1252
+ try:
1253
+ payload = _contracts.live_items(key, payload)
1254
+ except ValueError as error:
1255
+ raise Refused(str(error)) from None
1251
1256
  node[key] = _live_upsert(node.get(key), payload)
1252
1257
  if "groups" in op:
1253
1258
  # A table upserts its group DECLARATIONS in the same append that
1254
1259
  # carries the rows hanging under them, and a second declaration
1255
1260
  # merges onto the first: re-toning a head keeps its label.
1256
- node["groups"] = _live_upsert(node.get("groups"), op["groups"])
1261
+ node["groups"] = _live_upsert(
1262
+ node.get("groups"), op["groups"], is_merged=True
1263
+ )
1257
1264
  elif name == "clear":
1258
1265
  node[_live_items_key(node, "clear")] = []
1259
1266
  if node.get("type") == "log":
@@ -4120,8 +4120,9 @@ class Progress(_Node):
4120
4120
 
4121
4121
 
4122
4122
  class Stat(_Node):
4123
- def set(self, stat_id, value_text, label=None, tone=None):
4124
- # One counter of the strip, upserted by id.
4123
+ def set(self, stat_id, value_text, label, tone=None):
4124
+ # One counter of the strip, upserted by id. The View schema requires
4125
+ # the label in each update.
4125
4126
  return self._op(
4126
4127
  "append",
4127
4128
  stats=[
@@ -4140,9 +4141,10 @@ class Stat(_Node):
4140
4141
 
4141
4142
 
4142
4143
  class Steps(_Node):
4143
- def set(self, step_id, tone=None, label=None, detail=None, value=None):
4144
+ def set(self, step_id, label, tone=None, detail=None, value=None):
4144
4145
  # One step of the checklist, upserted by id: the same call marks it
4145
- # running and later done.
4146
+ # running and later done. The View schema requires the label in each
4147
+ # update.
4146
4148
  return self._op(
4147
4149
  "append",
4148
4150
  steps=[
@@ -4580,14 +4582,12 @@ class LiveView:
4580
4582
  value=value, done=done, total=total
4581
4583
  )
4582
4584
 
4583
- def stat(self, stat_id, value_text, label=None, tone=None):
4584
- return self._only("stat", "stat").set(
4585
- stat_id, value_text, label=label, tone=tone
4586
- )
4585
+ def stat(self, stat_id, value_text, label, tone=None):
4586
+ return self._only("stat", "stat").set(stat_id, value_text, label, tone=tone)
4587
4587
 
4588
- def step(self, step_id, tone=None, label=None, detail=None, value=None):
4588
+ def step(self, step_id, label, tone=None, detail=None, value=None):
4589
4589
  return self._only("steps", "step").set(
4590
- step_id, tone=tone, label=label, detail=detail, value=value
4590
+ step_id, label, tone=tone, detail=detail, value=value
4591
4591
  )
4592
4592
 
4593
4593
  def write(self, *lines: str | Sequence[str], tone: LogTone | None = None):
@@ -4809,7 +4809,7 @@ class _LiveRecorder:
4809
4809
  item = _LiveRecorder._copy(item)
4810
4810
  if item["id"] in positions:
4811
4811
  at = positions[item["id"]]
4812
- result[at] = {**result[at], **item}
4812
+ result[at] = item
4813
4813
  else:
4814
4814
  positions[item["id"]] = len(result)
4815
4815
  result.append(item)
@@ -4909,9 +4909,9 @@ class _LiveRecorder:
4909
4909
  elif action == "append":
4910
4910
  key = self._COLLECTION[node["type"]]
4911
4911
  incoming = self._copy(op.get(key) or [])
4912
- if node["type"] == "link":
4913
- for item in incoming:
4914
- item.setdefault("target_kind", "url")
4912
+ from blockether.vis import _contracts
4913
+
4914
+ incoming = _contracts.live_items(key, incoming)
4915
4915
  node[key] = self._upsert(node[key], incoming)
4916
4916
  if "groups" in op:
4917
4917
  # `live/apply-append`: a group already declared KEEPS the keys the
@@ -358,3 +358,28 @@ def test_a_group_arranging_nothing_is_refused_where_it_was_written():
358
358
  ):
359
359
  with pytest.raises(ValueError, match="at least one node"):
360
360
  build()
361
+
362
+
363
+ @pytest.mark.parametrize("host_kind", ["outside", "recorder"])
364
+ def test_stat_and_step_updates_carry_the_label_the_schema_requires(
365
+ monkeypatch, host_kind
366
+ ):
367
+ # The evals watch view sent a counter update without its label, and the host
368
+ # refused it. The recorder now refuses the same patch, as the View schema says.
369
+ host = (
370
+ _outside.host if host_kind == "outside" else vis.testing.LiveRecorder(vis._host)
371
+ )
372
+ monkeypatch.setattr(vis, "_host", host)
373
+ nodes = [
374
+ vis.stat("counts", stats=[{"id": "ok", "label": "Passed", "value_text": "0"}]),
375
+ vis.steps("plan", steps=[{"id": "build", "label": "Build"}]),
376
+ ]
377
+ with vis.live("Counts", nodes, flush_ms=0) as view:
378
+ with pytest.raises((ValueError, _outside.Refused), match="label"):
379
+ view["counts"]._op("append", stats=[{"id": "ok", "value_text": "1"}])
380
+ view["counts"].set("ok", "2", "Passed")
381
+ view["plan"].set("build", "Build", tone="ok")
382
+ counts, plan = view.state()["nodes"]
383
+ stats, steps = counts["stats"], plan["steps"]
384
+ assert stats == [{"id": "ok", "label": "Passed", "value_text": "2"}]
385
+ assert steps[0]["label"] == "Build" and steps[0]["tone"] == "ok"