vis-agent 0.3.4.dev8082__tar.gz → 0.3.4.dev8092__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.
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/PKG-INFO +1 -1
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/gateway.json +0 -20
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/view.json +3 -4
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/pyproject.toml +1 -1
- vis_agent-0.3.4.dev8092/release-tag +1 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_client.py +2 -50
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_local.py +20 -1
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/extension.py +105 -54
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_client.py +5 -42
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_contracts.py +4 -4
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_engine.py +2 -2
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_live_primitives.py +52 -41
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_live_wait.py +4 -4
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_local.py +34 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_outside.py +35 -35
- vis_agent-0.3.4.dev8082/release-tag +0 -1
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/.gitignore +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/LICENSE +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/README.md +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-arguments.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-cases.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-copy.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-execution.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-groups.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-read-session.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-reads.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-results.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-stream.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity-tables.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/activity.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/diff.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/live-primitives.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/session-health.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/fixtures/view.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/activity.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/agents.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/automations.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/common.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/config.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/content.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/council.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/diff.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/evals.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/provider.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/rooms.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/symbol.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/contracts/schema/toggle.json +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/examples/greeter/tests/test_greeter.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/hatch_build.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/__init__.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/_contracts.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/_outside.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/_wire.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/activity.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/__init__.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_agent.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_agents.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_council.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_extensions.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_host.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_install.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_models.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/engine/_speech.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/__init__.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/_gateway.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/agent.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/bench.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/check.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/compare.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/environment.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/grade.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/harbor.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/judge-rubric.toml +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/judge.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/labels.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/leaderboard.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/lic.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/longmemeval.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/mrcr.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/repeats.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/report.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/reports.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/rounds.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/stages.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/stats.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/evals/trajectory.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/extension_package.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/py.typed +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/rooms.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/src/blockether/vis/views.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/conftest.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/evals_atif.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_activity.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_agent.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_agent_extension_lifecycle.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_agent_extensions.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_agents.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_api_guides.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_attachments.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_authoring.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_catalog.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_catalog_recipe.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_ci_report_recipe.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_client_extensions_engine.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_code_quality_recipe.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_council.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_declarations.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_engine_docstrings.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_agent.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_bench.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_check.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_compare.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_environment.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_gateway.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_grade.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_guards.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_harbor.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_judge.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_labels.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_lic.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_longmemeval.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_mrcr.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_repeats.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_report.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_rounds.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_stages.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_stats.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_tasks.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_evals_trajectory.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_extension_package.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_extension_reference.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_gateway_performance.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_goals.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_install.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_method_typing.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_models.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_monitor_recipe.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_namespace.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_providers.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_read_session.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_record_arguments.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_registration.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_repair_hooks.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_result_sequence.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_rooms.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_scoped_settings.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_sdk_bootstrap.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_sdk_guide.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_sdk_typing.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_shortcuts_live.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_speech.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_structured_live.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_symbol_contract.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_testing.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_text_list_arguments.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_tools.py +0 -0
- {vis_agent-0.3.4.dev8082 → vis_agent-0.3.4.dev8092}/tests/test_views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vis-agent
|
|
3
|
-
Version: 0.3.4.
|
|
3
|
+
Version: 0.3.4.dev8092
|
|
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
|
|
@@ -1300,26 +1300,6 @@
|
|
|
1300
1300
|
},
|
|
1301
1301
|
"path": "/v1/sessions/:sid/views/live/:view-id/log"
|
|
1302
1302
|
},
|
|
1303
|
-
{
|
|
1304
|
-
"audience": "sdk",
|
|
1305
|
-
"operations": {
|
|
1306
|
-
"get": {
|
|
1307
|
-
"request": "none",
|
|
1308
|
-
"response": "json"
|
|
1309
|
-
}
|
|
1310
|
-
},
|
|
1311
|
-
"path": "/v1/views/live"
|
|
1312
|
-
},
|
|
1313
|
-
{
|
|
1314
|
-
"audience": "sdk",
|
|
1315
|
-
"operations": {
|
|
1316
|
-
"post": {
|
|
1317
|
-
"request": "json",
|
|
1318
|
-
"response": "json"
|
|
1319
|
-
}
|
|
1320
|
-
},
|
|
1321
|
-
"path": "/v1/views/:view-id/actions"
|
|
1322
|
-
},
|
|
1323
1303
|
{
|
|
1324
1304
|
"audience": "sdk",
|
|
1325
1305
|
"operations": {
|
|
@@ -1666,8 +1666,7 @@
|
|
|
1666
1666
|
"type": "string",
|
|
1667
1667
|
"enum": [
|
|
1668
1668
|
"inline",
|
|
1669
|
-
"panel"
|
|
1670
|
-
"header"
|
|
1669
|
+
"panel"
|
|
1671
1670
|
],
|
|
1672
1671
|
"default": "inline"
|
|
1673
1672
|
},
|
|
@@ -1702,12 +1701,12 @@
|
|
|
1702
1701
|
"$ref": "common.json#/$defs/non_blank_string"
|
|
1703
1702
|
},
|
|
1704
1703
|
"extension": {
|
|
1705
|
-
"description": "The extension that owns this panel
|
|
1704
|
+
"description": "The extension that owns this panel view.",
|
|
1706
1705
|
"$ref": "common.json#/$defs/non_blank_string",
|
|
1707
1706
|
"readOnly": true
|
|
1708
1707
|
},
|
|
1709
1708
|
"is_stale": {
|
|
1710
|
-
"description": "True for the saved picture of an owned view after a gateway restart, until its extension
|
|
1709
|
+
"description": "True for the saved picture of an owned view after a gateway restart, until its extension loads again.",
|
|
1711
1710
|
"type": "boolean",
|
|
1712
1711
|
"readOnly": true
|
|
1713
1712
|
},
|
|
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
|
|
|
8
8
|
|
|
9
9
|
[project]
|
|
10
10
|
name = "vis-agent"
|
|
11
|
-
version = "0.3.4.
|
|
11
|
+
version = "0.3.4.dev8092"
|
|
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-aada8f93cffb9c5de516bb8b02a25a9cdb281913
|
|
@@ -418,26 +418,6 @@ class ExecutionLayer(ABC):
|
|
|
418
418
|
"""
|
|
419
419
|
return Session(self, sid)
|
|
420
420
|
|
|
421
|
-
def header_views(self) -> list[LiveView]:
|
|
422
|
-
"""Read the header views of this machine as typed `LiveView` records.
|
|
423
|
-
|
|
424
|
-
A header view belongs to no session. Its events come on the fleet stream.
|
|
425
|
-
"""
|
|
426
|
-
data = _field(self.get_views_live(), "views", list)
|
|
427
|
-
try:
|
|
428
|
-
return [LiveView.from_wire(item) for item in data]
|
|
429
|
-
except ValueError:
|
|
430
|
-
raise ProtocolError("invalid live View response") from None
|
|
431
|
-
|
|
432
|
-
def header_view_action(self, view_id: str, action: str, **values):
|
|
433
|
-
"""Send an operator action to a header view and return the JSON response.
|
|
434
|
-
|
|
435
|
-
`values` supplies the action-specific fields of the operator action schema.
|
|
436
|
-
"""
|
|
437
|
-
body = {**values, "action": action}
|
|
438
|
-
validate("view", "operator_action", body)
|
|
439
|
-
return self.post_view_actions(view_id, body=body)
|
|
440
|
-
|
|
441
421
|
def create_session(
|
|
442
422
|
self,
|
|
443
423
|
*,
|
|
@@ -3002,34 +2982,6 @@ class ExecutionLayer(ABC):
|
|
|
3002
2982
|
)
|
|
3003
2983
|
return response.json()
|
|
3004
2984
|
|
|
3005
|
-
def get_views_live(
|
|
3006
|
-
self, *, query: Query | None = None, timeout: float | None = None
|
|
3007
|
-
) -> JSONValue:
|
|
3008
|
-
"""GET /v1/views/live — json response."""
|
|
3009
|
-
response = self._request(
|
|
3010
|
-
"GET", "/v1/views/live", path={}, query=query, timeout=timeout
|
|
3011
|
-
)
|
|
3012
|
-
return response.json()
|
|
3013
|
-
|
|
3014
|
-
def post_view_actions(
|
|
3015
|
-
self,
|
|
3016
|
-
view_id: str,
|
|
3017
|
-
*,
|
|
3018
|
-
query: Query | None = None,
|
|
3019
|
-
timeout: float | None = None,
|
|
3020
|
-
body: JSONValue = None,
|
|
3021
|
-
) -> JSONValue:
|
|
3022
|
-
"""POST /v1/views/:view-id/actions — json response."""
|
|
3023
|
-
response = self._request(
|
|
3024
|
-
"POST",
|
|
3025
|
-
"/v1/views/:view-id/actions",
|
|
3026
|
-
path={"view-id": view_id},
|
|
3027
|
-
query=query,
|
|
3028
|
-
timeout=timeout,
|
|
3029
|
-
body=body,
|
|
3030
|
-
)
|
|
3031
|
-
return response.json()
|
|
3032
|
-
|
|
3033
2985
|
def post_voice(
|
|
3034
2986
|
self,
|
|
3035
2987
|
*,
|
|
@@ -3658,8 +3610,8 @@ class Session:
|
|
|
3658
3610
|
"""Read current live views as typed `blockether.vis.views.LiveView` records.
|
|
3659
3611
|
|
|
3660
3612
|
This fetches a snapshot and does not subscribe to future updates. Use
|
|
3661
|
-
`events` to follow view events. A `placement` (`inline
|
|
3662
|
-
|
|
3613
|
+
`events` to follow view events. A `placement` (`inline` or `panel`)
|
|
3614
|
+
keeps only those views. Malformed view data raises
|
|
3663
3615
|
`ProtocolError`.
|
|
3664
3616
|
"""
|
|
3665
3617
|
query = None if placement is None else {"placement": str(placement)}
|
|
@@ -4,7 +4,8 @@ Pass an executable, or keep the default `vis-agent`. The default runs the engine
|
|
|
4
4
|
this SDK's release (see `ensure_installed`). There is no gateway discovery or
|
|
5
5
|
user-server shutdown. Each engine gets a temporary database and stderr log, and
|
|
6
6
|
closing discards them.
|
|
7
|
-
Supports Linux and macOS.
|
|
7
|
+
Supports Linux and macOS. Threads can share an instance: its requests take turns on
|
|
8
|
+
the one stdio pipe.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from __future__ import annotations
|
|
@@ -17,6 +18,7 @@ import select
|
|
|
17
18
|
import signal
|
|
18
19
|
import subprocess
|
|
19
20
|
import tempfile
|
|
21
|
+
import threading
|
|
20
22
|
import time
|
|
21
23
|
from email.message import Message
|
|
22
24
|
from pathlib import Path
|
|
@@ -150,6 +152,8 @@ class LocalEngine(ExecutionLayer):
|
|
|
150
152
|
self._exit_status = None
|
|
151
153
|
self._stderr_tail = ""
|
|
152
154
|
self._cleanup_complete = False
|
|
155
|
+
# The stdio frames carry no request ids, so only one exchange can be in flight.
|
|
156
|
+
self._exchange = threading.RLock()
|
|
153
157
|
|
|
154
158
|
def session_options(self, project=".") -> dict:
|
|
155
159
|
"""Resolve an existing local directory before the caller changes directory."""
|
|
@@ -197,6 +201,10 @@ class LocalEngine(ExecutionLayer):
|
|
|
197
201
|
failure or close, create a new one. A startup failure cleans up the process and
|
|
198
202
|
temporary database before the error propagates.
|
|
199
203
|
"""
|
|
204
|
+
with self._exchange:
|
|
205
|
+
return self._connect()
|
|
206
|
+
|
|
207
|
+
def _connect(self):
|
|
200
208
|
if self._closed:
|
|
201
209
|
raise TransportError("client is closed")
|
|
202
210
|
if self._process is not None:
|
|
@@ -262,6 +270,17 @@ class LocalEngine(ExecutionLayer):
|
|
|
262
270
|
data = (json.dumps(request, allow_nan=False) + "\n").encode()
|
|
263
271
|
if len(data) > 67108864:
|
|
264
272
|
raise ValueError("local request exceeds 64 MiB")
|
|
273
|
+
if not self._exchange.acquire(timeout=max(0.0, deadline - time.monotonic())):
|
|
274
|
+
raise VisTimeout("local engine request timed out")
|
|
275
|
+
try:
|
|
276
|
+
return self._exchange_frame(data, deadline)
|
|
277
|
+
finally:
|
|
278
|
+
self._exchange.release()
|
|
279
|
+
|
|
280
|
+
def _exchange_frame(self, data, deadline):
|
|
281
|
+
"""Write one request frame and read its reply. The caller holds `_exchange`."""
|
|
282
|
+
if self._process is None or self._process.stdin is None:
|
|
283
|
+
raise TransportError("client is closed")
|
|
265
284
|
try:
|
|
266
285
|
offset = 0
|
|
267
286
|
while offset < len(data):
|
|
@@ -129,7 +129,7 @@ class Host(Protocol):
|
|
|
129
129
|
def live(self, envelope_json: str, listener: Any = None) -> str:
|
|
130
130
|
"""Open, patch, read or close a live view.
|
|
131
131
|
|
|
132
|
-
On `open`, `listener(event)` gets each human event of a panel
|
|
132
|
+
On `open`, `listener(event)` gets each human event of a panel view.
|
|
133
133
|
|
|
134
134
|
`state` accepts timeout_ms (0..86400000) and after_seq (nonnegative).
|
|
135
135
|
A positive timeout blocks until the sequence differs or the view closes.
|
|
@@ -3914,12 +3914,12 @@ def ask(title, nodes, **options):
|
|
|
3914
3914
|
# documents.
|
|
3915
3915
|
#
|
|
3916
3916
|
# answer = vis.ask('Deploy', [
|
|
3917
|
-
# vis.heading('Target'),
|
|
3918
|
-
# vis.paragraph('Staging pages nobody.'),
|
|
3919
|
-
# vis.row(vis.select('env', ['staging', 'prod'], label='Where',
|
|
3917
|
+
# vis.ui.heading('Target'),
|
|
3918
|
+
# vis.ui.paragraph('Staging pages nobody.'),
|
|
3919
|
+
# vis.ui.row(vis.ui.select('env', ['staging', 'prod'], label='Where',
|
|
3920
3920
|
# is_required=True),
|
|
3921
|
-
# vis.slider('canary', min=0, max=100, step=5)),
|
|
3922
|
-
# vis.password('token', label='Deploy token', is_required=True),
|
|
3921
|
+
# vis.ui.slider('canary', min=0, max=100, step=5)),
|
|
3922
|
+
# vis.ui.password('token', label='Deploy token', is_required=True),
|
|
3923
3923
|
# ])
|
|
3924
3924
|
#
|
|
3925
3925
|
# A builder is a plain dict, so a form stays printable and can be assembled in a
|
|
@@ -3934,56 +3934,56 @@ def _node(type_name, name, spec):
|
|
|
3934
3934
|
return node
|
|
3935
3935
|
|
|
3936
3936
|
|
|
3937
|
-
def
|
|
3937
|
+
def _ui_plaintext(name, **spec):
|
|
3938
3938
|
# One typed line, answered as a string.
|
|
3939
3939
|
return _node("plaintext", name, spec)
|
|
3940
3940
|
|
|
3941
3941
|
|
|
3942
|
-
def
|
|
3942
|
+
def _ui_password(name, **spec):
|
|
3943
3943
|
# A masked line, answered as an opaque 'vis-secret:' handle: `reveal` it on
|
|
3944
3944
|
# the trusted side, never log it.
|
|
3945
3945
|
return _node("password", name, spec)
|
|
3946
3946
|
|
|
3947
3947
|
|
|
3948
|
-
def
|
|
3948
|
+
def _ui_multiline(name, **spec):
|
|
3949
3949
|
# A text box, answered as a string that keeps its newlines.
|
|
3950
3950
|
return _node("multiline", name, spec)
|
|
3951
3951
|
|
|
3952
3952
|
|
|
3953
|
-
def
|
|
3954
|
-
# Choose exactly ONE of `options` (plain strings, or `vis.option(...)`
|
|
3953
|
+
def _ui_select(name, options, **spec):
|
|
3954
|
+
# Choose exactly ONE of `options` (plain strings, or `vis.ui.option(...)`
|
|
3955
3955
|
# pairs); answered as the chosen value.
|
|
3956
3956
|
spec["options"] = list(options)
|
|
3957
3957
|
return _node("select", name, spec)
|
|
3958
3958
|
|
|
3959
3959
|
|
|
3960
|
-
def
|
|
3960
|
+
def _ui_multiselect(name, options, **spec):
|
|
3961
3961
|
# Choose ANY of `options`; answered as a list, empty when nothing is ticked.
|
|
3962
3962
|
spec["options"] = list(options)
|
|
3963
3963
|
return _node("multiselect", name, spec)
|
|
3964
3964
|
|
|
3965
3965
|
|
|
3966
|
-
def
|
|
3966
|
+
def _ui_checkbox(name, **spec):
|
|
3967
3967
|
# One box, answered as a bool. `is_required=True` means it must end up
|
|
3968
3968
|
# TICKED, not merely present.
|
|
3969
3969
|
return _node("checkbox", name, spec)
|
|
3970
3970
|
|
|
3971
3971
|
|
|
3972
|
-
def
|
|
3972
|
+
def _ui_slider(name, **spec):
|
|
3973
3973
|
# A number on a track: min / max / step, 0 / 100 / 1 by default, answered as
|
|
3974
3974
|
# a NUMBER. It is `range` on the wire; the builder is `slider` so it never
|
|
3975
3975
|
# shadows the builtin.
|
|
3976
3976
|
return _node("range", name, spec)
|
|
3977
3977
|
|
|
3978
3978
|
|
|
3979
|
-
def
|
|
3979
|
+
def _ui_otp(name, **spec):
|
|
3980
3980
|
# A one-time code in digit boxes: min_length / max_length say how many
|
|
3981
3981
|
# (6 by default, 12 at most), digits only, paste fills the boxes. A code is a
|
|
3982
3982
|
# credential, so it answers with a `vis-secret:` handle like a password.
|
|
3983
3983
|
return _node("otp", name, spec)
|
|
3984
3984
|
|
|
3985
3985
|
|
|
3986
|
-
def
|
|
3986
|
+
def _ui_option(value, label=None):
|
|
3987
3987
|
# One entry of a select / multiselect: the value that is ANSWERED and the
|
|
3988
3988
|
# words shown for it. Given no label, the value shows itself.
|
|
3989
3989
|
return {"value": value} if label is None else {"value": value, "label": label}
|
|
@@ -4016,20 +4016,20 @@ def _group(direction, fields):
|
|
|
4016
4016
|
return group
|
|
4017
4017
|
|
|
4018
4018
|
|
|
4019
|
-
def
|
|
4020
|
-
# Lay these nodes out side by side: `vis.row(vis.text("host"), vis.text("port"))`
|
|
4021
|
-
# on a form, `vis.row("reading", vis.table("hosts", ...), vis.status("why"))`
|
|
4019
|
+
def _ui_row(*fields):
|
|
4020
|
+
# Lay these nodes out side by side: `vis.ui.row(vis.text("host"), vis.text("port"))`
|
|
4021
|
+
# on a form, `vis.ui.row("reading", vis.ui.table("hosts", ...), vis.ui.status("why"))`
|
|
4022
4022
|
# in a live view.
|
|
4023
4023
|
return _group("row", fields)
|
|
4024
4024
|
|
|
4025
4025
|
|
|
4026
|
-
def
|
|
4026
|
+
def _ui_column(*fields):
|
|
4027
4027
|
# Stack these nodes, the default arrangement: worth saying out loud inside a
|
|
4028
4028
|
# `row`.
|
|
4029
4029
|
return _group("column", fields)
|
|
4030
4030
|
|
|
4031
4031
|
|
|
4032
|
-
def
|
|
4032
|
+
def _ui_grid(*fields, max_columns=None):
|
|
4033
4033
|
"""Lay ``fields`` out in equal columns that wrap into aligned rows.
|
|
4034
4034
|
|
|
4035
4035
|
``max_columns`` (1 to 12) caps the columns. A narrow surface shows fewer
|
|
@@ -4041,7 +4041,7 @@ def grid(*fields, max_columns=None):
|
|
|
4041
4041
|
return node
|
|
4042
4042
|
|
|
4043
4043
|
|
|
4044
|
-
def
|
|
4044
|
+
def _ui_wrap(*fields):
|
|
4045
4045
|
"""Put ``fields`` side by side at their own width, wrapping onto new lines.
|
|
4046
4046
|
|
|
4047
4047
|
Use it for buttons, stats, statuses and links that keep a natural width.
|
|
@@ -4049,7 +4049,7 @@ def wrap(*fields):
|
|
|
4049
4049
|
return _group("wrap", fields)
|
|
4050
4050
|
|
|
4051
4051
|
|
|
4052
|
-
def
|
|
4052
|
+
def _ui_group(*children, direction="column", **spec):
|
|
4053
4053
|
"""Group ``children`` with optional ``label``, ``description`` and folding.
|
|
4054
4054
|
|
|
4055
4055
|
A leading string is the group id. A submit button inside a group collects
|
|
@@ -4060,7 +4060,7 @@ def group(*children, direction="column", **spec):
|
|
|
4060
4060
|
return node
|
|
4061
4061
|
|
|
4062
4062
|
|
|
4063
|
-
def
|
|
4063
|
+
def _ui_card(node_id, title, **spec):
|
|
4064
4064
|
"""A titled card with optional ``text``, ``tone`` and ``meta`` lines.
|
|
4065
4065
|
|
|
4066
4066
|
With ``is_selectable=True`` the human can activate it like a button, and
|
|
@@ -4069,7 +4069,7 @@ def card(node_id, title, **spec):
|
|
|
4069
4069
|
return _live_node("card", node_id, dict(spec, title=str(title)))
|
|
4070
4070
|
|
|
4071
4071
|
|
|
4072
|
-
def
|
|
4072
|
+
def _ui_heading(text, live_text=None, **spec):
|
|
4073
4073
|
"""Form heading, or live ``heading(id, text, level=1..6)``."""
|
|
4074
4074
|
if live_text is not None:
|
|
4075
4075
|
return _live_node(
|
|
@@ -4080,7 +4080,7 @@ def heading(text, live_text=None, **spec):
|
|
|
4080
4080
|
return {"type": "heading", "text": str(text)}
|
|
4081
4081
|
|
|
4082
4082
|
|
|
4083
|
-
def
|
|
4083
|
+
def _ui_paragraph(text, live_text=None, **spec):
|
|
4084
4084
|
"""Form Markdown prose, or live ``paragraph(id, text)`` with inline Markdown."""
|
|
4085
4085
|
if live_text is not None:
|
|
4086
4086
|
return _live_node("paragraph", text, dict(spec, text=str(live_text)))
|
|
@@ -4280,9 +4280,9 @@ def list_sessions(search=None):
|
|
|
4280
4280
|
# surface repaints, and the extension carries straight on. The human watches it
|
|
4281
4281
|
# move and may stop it; the model reads the finished picture as DATA.
|
|
4282
4282
|
#
|
|
4283
|
-
# with vis.live('CI', [vis.status('now', 'Polling'),
|
|
4284
|
-
# vis.table('jobs', columns=[vis.table_column('job', 'Job'),
|
|
4285
|
-
# vis.table_column('state', 'State')])]) as view:
|
|
4283
|
+
# with vis.live('CI', [vis.ui.status('now', 'Polling'),
|
|
4284
|
+
# vis.ui.table('jobs', columns=[vis.ui.table_column('job', 'Job'),
|
|
4285
|
+
# vis.ui.table_column('state', 'State')])]) as view:
|
|
4286
4286
|
# for job in poll():
|
|
4287
4287
|
# view['jobs'].upsert(job.id, [job.name, job.state], tone=job.tone)
|
|
4288
4288
|
# view['now'].set('Finished', tone='ok')
|
|
@@ -4692,7 +4692,7 @@ class LiveView:
|
|
|
4692
4692
|
self._seq = 0
|
|
4693
4693
|
self._cursor = 0
|
|
4694
4694
|
self._listeners = []
|
|
4695
|
-
# A panel
|
|
4695
|
+
# A panel view gets its events from the host, as callbacks.
|
|
4696
4696
|
self._is_pushed = False
|
|
4697
4697
|
if on_event is not None:
|
|
4698
4698
|
self.on_event(on_event)
|
|
@@ -4986,7 +4986,7 @@ class LiveView:
|
|
|
4986
4986
|
def on_event(self, callback):
|
|
4987
4987
|
"""Call `callback(event)` for each event that `events` or `sleep` reads.
|
|
4988
4988
|
|
|
4989
|
-
A panel
|
|
4989
|
+
A panel view opened with `vis.live(..., on_event=...)` gets its
|
|
4990
4990
|
events from the host instead. Each one runs as an extension callback with
|
|
4991
4991
|
the view session bound, also when no extension code waits on the view.
|
|
4992
4992
|
Returns the callback, so you can use this method as a decorator.
|
|
@@ -5183,17 +5183,17 @@ def live(title, nodes, placement=None, key=None, on_event=None, **options):
|
|
|
5183
5183
|
`vis.ask` documents. There is no cancellable flag: a human can always stop
|
|
5184
5184
|
watching, and the verdict says they did (`is_from_human`) and why (`note`).
|
|
5185
5185
|
|
|
5186
|
-
`placement` is `inline` (the default)
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5186
|
+
`placement` is `inline` (the default) or `panel` (the side panel of one
|
|
5187
|
+
session). Only extension code opens a panel view, and it needs a `key`:
|
|
5188
|
+
the extension and the key own one view, so opening the same key again
|
|
5189
|
+
replaces it. Such a view stays open after the call returns. It
|
|
5190
5190
|
closes on `close()` or when the extension unloads. `on_event(event)` then
|
|
5191
5191
|
runs for each human change, submit or activate, with the view session bound.
|
|
5192
5192
|
|
|
5193
5193
|
The view is mounted at once and nothing blocks — use it as a context
|
|
5194
5194
|
manager so it closes itself:
|
|
5195
5195
|
|
|
5196
|
-
with vis.live('Deploy', [vis.steps('plan', steps=[...])]) as view:
|
|
5196
|
+
with vis.live('Deploy', [vis.ui.steps('plan', steps=[...])]) as view:
|
|
5197
5197
|
view['plan'].set('build', tone='running')
|
|
5198
5198
|
|
|
5199
5199
|
Closing answers the verdict: `is_completed`, `reason`, the finished picture
|
|
@@ -5504,7 +5504,7 @@ class _LiveRecorder:
|
|
|
5504
5504
|
def live(self, envelope_json, listener=None):
|
|
5505
5505
|
"""Record and apply one envelope emitted by the extension.
|
|
5506
5506
|
|
|
5507
|
-
A `listener` gets each simulated event of a panel
|
|
5507
|
+
A `listener` gets each simulated event of a panel view, as the
|
|
5508
5508
|
host gives it to `on_event` callbacks.
|
|
5509
5509
|
"""
|
|
5510
5510
|
import json
|
|
@@ -5801,8 +5801,8 @@ testing = _Testing()
|
|
|
5801
5801
|
# here talks to the host, and `vis.live` is what carries it to the engine that
|
|
5802
5802
|
# judges it.
|
|
5803
5803
|
#
|
|
5804
|
-
# Layout is the FORM's own vocabulary, not a second one: `vis.row("reading",
|
|
5805
|
-
# table, status)` stands its nodes side by side and `vis.column(...)` stacks them,
|
|
5804
|
+
# Layout is the FORM's own vocabulary, not a second one: `vis.ui.row("reading",
|
|
5805
|
+
# table, status)` stands its nodes side by side and `vis.ui.column(...)` stacks them,
|
|
5806
5806
|
# with a live group taking the id every op addresses. It is DECLARED once and no
|
|
5807
5807
|
# op carries it, so a layout never rearranges itself while a human is reading it.
|
|
5808
5808
|
|
|
@@ -5814,7 +5814,7 @@ def _live_node(type_name, node_id, spec):
|
|
|
5814
5814
|
return node
|
|
5815
5815
|
|
|
5816
5816
|
|
|
5817
|
-
def
|
|
5817
|
+
def _ui_disclosure(node_id, label, *nodes, default_expanded=False):
|
|
5818
5818
|
"""A collapsible column.
|
|
5819
5819
|
|
|
5820
5820
|
Local choices survive updates, and receipts start collapsed.
|
|
@@ -5832,17 +5832,17 @@ def disclosure(node_id, label, *nodes, default_expanded=False):
|
|
|
5832
5832
|
)
|
|
5833
5833
|
|
|
5834
5834
|
|
|
5835
|
-
def
|
|
5835
|
+
def _ui_divider(node_id):
|
|
5836
5836
|
"""A static horizontal rule across its live container. Add or drop it by id."""
|
|
5837
5837
|
return _live_node("divider", node_id, {})
|
|
5838
5838
|
|
|
5839
5839
|
|
|
5840
|
-
def
|
|
5840
|
+
def _ui_code(node_id, text, *, language=None, **spec):
|
|
5841
5841
|
"""Literal code. Whitespace is kept, and content is never executed."""
|
|
5842
5842
|
return _live_node("code", node_id, dict(spec, text=str(text), language=language))
|
|
5843
5843
|
|
|
5844
5844
|
|
|
5845
|
-
def
|
|
5845
|
+
def _ui_spinner(node_id, text="Working", *, variant="braille", is_active=True, **spec):
|
|
5846
5846
|
"""A braille, dots, line or pulse indicator. Receipts never animate."""
|
|
5847
5847
|
return _live_node(
|
|
5848
5848
|
"spinner",
|
|
@@ -5851,7 +5851,7 @@ def spinner(node_id, text="Working", *, variant="braille", is_active=True, **spe
|
|
|
5851
5851
|
)
|
|
5852
5852
|
|
|
5853
5853
|
|
|
5854
|
-
def
|
|
5854
|
+
def _ui_button(node_id, label, *, is_disabled=False, **spec):
|
|
5855
5855
|
"""An operator action. Accepted presses increment ``clicks`` in ``view.state()``.
|
|
5856
5856
|
|
|
5857
5857
|
No callback or code crosses the wire. The producer decides how to respond.
|
|
@@ -5862,27 +5862,27 @@ def button(node_id, label, *, is_disabled=False, **spec):
|
|
|
5862
5862
|
)
|
|
5863
5863
|
|
|
5864
5864
|
|
|
5865
|
-
def
|
|
5865
|
+
def _ui_status(node_id, text=None, **spec):
|
|
5866
5866
|
# One line saying what is happening right now: text, tone, detail, label.
|
|
5867
5867
|
return _live_node("status", node_id, dict(spec, text=text))
|
|
5868
5868
|
|
|
5869
5869
|
|
|
5870
|
-
def
|
|
5870
|
+
def _ui_progress(node_id, **spec):
|
|
5871
5871
|
# A bar: value (0..1), or done/total. Neither is indeterminate.
|
|
5872
5872
|
return _live_node("progress", node_id, spec)
|
|
5873
5873
|
|
|
5874
5874
|
|
|
5875
|
-
def
|
|
5875
|
+
def _ui_stat(node_id, stats=None, **spec):
|
|
5876
5876
|
# A strip of counters, each `{'id': ..., 'label': ..., 'value_text': ...}`.
|
|
5877
5877
|
return _live_node("stat", node_id, dict(spec, stats=stats))
|
|
5878
5878
|
|
|
5879
5879
|
|
|
5880
|
-
def
|
|
5880
|
+
def _ui_steps(node_id, steps=None, **spec):
|
|
5881
5881
|
# A checklist, each `{'id': ..., 'label': ..., 'tone': ...}`.
|
|
5882
5882
|
return _live_node("steps", node_id, dict(spec, steps=steps))
|
|
5883
5883
|
|
|
5884
5884
|
|
|
5885
|
-
def
|
|
5885
|
+
def _ui_output(node_id, **spec):
|
|
5886
5886
|
"""Durable output with an independent disclosure, collapsed by default.
|
|
5887
5887
|
|
|
5888
5888
|
``default_expanded=True`` opens an active log initially. Updates preserve the local
|
|
@@ -5896,7 +5896,7 @@ def output(node_id, **spec):
|
|
|
5896
5896
|
return _live_node("log", node_id, spec)
|
|
5897
5897
|
|
|
5898
5898
|
|
|
5899
|
-
def
|
|
5899
|
+
def _ui_table(node_id, columns=None, **spec):
|
|
5900
5900
|
# Rows keyed by id. `order` declares how they paint — 'insertion' (the
|
|
5901
5901
|
# default), 'newest-first', or {'by': 'duration', 'dir': 'desc'}.
|
|
5902
5902
|
# `is_selectable=True` makes rows controls; `selected_ids` is shared state.
|
|
@@ -5905,17 +5905,17 @@ def table(node_id, columns=None, **spec):
|
|
|
5905
5905
|
return _live_node("table", node_id, dict(spec, columns=columns))
|
|
5906
5906
|
|
|
5907
5907
|
|
|
5908
|
-
def
|
|
5908
|
+
def _ui_table_column(column_id, label=None, **spec):
|
|
5909
5909
|
# One declared column: the id a cell sits under, its header, its align.
|
|
5910
5910
|
return _live_item(column_id, dict(spec, label=label))
|
|
5911
5911
|
|
|
5912
5912
|
|
|
5913
|
-
def
|
|
5913
|
+
def _ui_table_row(row_id, cells, **spec):
|
|
5914
5914
|
# One declared row: the id every later upsert addresses, and its cells.
|
|
5915
5915
|
return _live_item(row_id, dict(spec, cells=[_cell(c) for c in cells]))
|
|
5916
5916
|
|
|
5917
5917
|
|
|
5918
|
-
def
|
|
5918
|
+
def _ui_table_group(group_id, label=None, **spec):
|
|
5919
5919
|
# One declared group of a table: the id a row's `parent` names, the `label`
|
|
5920
5920
|
# painted over it instead of that id, and optionally `tone`, `order` and
|
|
5921
5921
|
# `is_open`. Renaming the label never moves the group, because rows point at
|
|
@@ -5923,6 +5923,57 @@ def table_group(group_id, label=None, **spec):
|
|
|
5923
5923
|
return _live_item(group_id, dict(spec, label=label))
|
|
5924
5924
|
|
|
5925
5925
|
|
|
5926
|
-
def
|
|
5926
|
+
def _ui_link(node_id, links=None, **spec):
|
|
5927
5927
|
# Pointers a human can open: url, path or attachment.
|
|
5928
5928
|
return _live_node("link", node_id, dict(spec, links=links))
|
|
5929
|
+
|
|
5930
|
+
|
|
5931
|
+
class _UI:
|
|
5932
|
+
"""Components for live views and forms.
|
|
5933
|
+
|
|
5934
|
+
`vis.live` and `vis.ask` take the same components, so one input or layout
|
|
5935
|
+
works in both. Each builder returns plain data and talks to no host.
|
|
5936
|
+
"""
|
|
5937
|
+
|
|
5938
|
+
# Inputs
|
|
5939
|
+
plaintext = staticmethod(_ui_plaintext)
|
|
5940
|
+
password = staticmethod(_ui_password)
|
|
5941
|
+
multiline = staticmethod(_ui_multiline)
|
|
5942
|
+
select = staticmethod(_ui_select)
|
|
5943
|
+
multiselect = staticmethod(_ui_multiselect)
|
|
5944
|
+
checkbox = staticmethod(_ui_checkbox)
|
|
5945
|
+
slider = staticmethod(_ui_slider)
|
|
5946
|
+
otp = staticmethod(_ui_otp)
|
|
5947
|
+
option = staticmethod(_ui_option)
|
|
5948
|
+
|
|
5949
|
+
# Layout
|
|
5950
|
+
row = staticmethod(_ui_row)
|
|
5951
|
+
column = staticmethod(_ui_column)
|
|
5952
|
+
grid = staticmethod(_ui_grid)
|
|
5953
|
+
wrap = staticmethod(_ui_wrap)
|
|
5954
|
+
group = staticmethod(_ui_group)
|
|
5955
|
+
card = staticmethod(_ui_card)
|
|
5956
|
+
disclosure = staticmethod(_ui_disclosure)
|
|
5957
|
+
|
|
5958
|
+
# Text and links
|
|
5959
|
+
heading = staticmethod(_ui_heading)
|
|
5960
|
+
paragraph = staticmethod(_ui_paragraph)
|
|
5961
|
+
divider = staticmethod(_ui_divider)
|
|
5962
|
+
code = staticmethod(_ui_code)
|
|
5963
|
+
link = staticmethod(_ui_link)
|
|
5964
|
+
|
|
5965
|
+
# Live state
|
|
5966
|
+
spinner = staticmethod(_ui_spinner)
|
|
5967
|
+
button = staticmethod(_ui_button)
|
|
5968
|
+
status = staticmethod(_ui_status)
|
|
5969
|
+
progress = staticmethod(_ui_progress)
|
|
5970
|
+
stat = staticmethod(_ui_stat)
|
|
5971
|
+
steps = staticmethod(_ui_steps)
|
|
5972
|
+
output = staticmethod(_ui_output)
|
|
5973
|
+
table = staticmethod(_ui_table)
|
|
5974
|
+
table_column = staticmethod(_ui_table_column)
|
|
5975
|
+
table_row = staticmethod(_ui_table_row)
|
|
5976
|
+
table_group = staticmethod(_ui_table_group)
|
|
5977
|
+
|
|
5978
|
+
|
|
5979
|
+
ui = _UI()
|
|
@@ -954,54 +954,17 @@ def test_snapshot_info_reads_the_manifest_without_a_request():
|
|
|
954
954
|
GatewayClient.snapshot_info(damaged)
|
|
955
955
|
|
|
956
956
|
|
|
957
|
-
def
|
|
958
|
-
view = {
|
|
959
|
-
"id": "v1",
|
|
960
|
-
"title": "Build",
|
|
961
|
-
"placement": "header",
|
|
962
|
-
"key": "build",
|
|
963
|
-
"extension": "forge",
|
|
964
|
-
"nodes": [
|
|
965
|
-
{
|
|
966
|
-
"id": "go",
|
|
967
|
-
"type": "button",
|
|
968
|
-
"label": "Rerun",
|
|
969
|
-
"action": "activate",
|
|
970
|
-
"is_disabled": False,
|
|
971
|
-
"clicks": 0,
|
|
972
|
-
}
|
|
973
|
-
],
|
|
974
|
-
"seq": 0,
|
|
975
|
-
"created_at": 1,
|
|
976
|
-
"timeout_ms": 0,
|
|
977
|
-
"channel_ids": ["app"],
|
|
978
|
-
}
|
|
979
|
-
|
|
957
|
+
def test_live_views_filter_by_placement_on_the_session_route():
|
|
980
958
|
def respond(method, path, body):
|
|
981
959
|
if path.startswith(("/v1/capabilities", "/v1/clients")):
|
|
982
960
|
return compatible(method, path, body)
|
|
983
|
-
|
|
984
|
-
return 200, {"views": [view]}
|
|
985
|
-
if path.startswith("/v1/sessions/s1/views/live"):
|
|
986
|
-
return 200, {"views": []}
|
|
987
|
-
return 200, {"is_accepted": True}
|
|
961
|
+
return 200, {"views": []}
|
|
988
962
|
|
|
989
963
|
with endpoint(respond) as (url, calls), GatewayClient(url) as client:
|
|
990
|
-
[header] = client.header_views()
|
|
991
|
-
assert (header.placement, header.key, header.extension) == (
|
|
992
|
-
"header",
|
|
993
|
-
"build",
|
|
994
|
-
"forge",
|
|
995
|
-
)
|
|
996
|
-
assert client.header_view_action("v1", "activate", node_id="go")["is_accepted"]
|
|
997
964
|
assert client.session("s1").live_views(placement="panel") == []
|
|
998
965
|
sent = [
|
|
999
|
-
(method, path
|
|
1000
|
-
for method, path, _,
|
|
966
|
+
(method, path)
|
|
967
|
+
for method, path, _, _ in calls
|
|
1001
968
|
if not path.startswith(("/v1/capabilities", "/v1/clients"))
|
|
1002
969
|
]
|
|
1003
|
-
assert sent == [
|
|
1004
|
-
("GET", "/v1/views/live", None),
|
|
1005
|
-
("POST", "/v1/views/v1/actions", {"node_id": "go", "action": "activate"}),
|
|
1006
|
-
("GET", "/v1/sessions/s1/views/live?placement=panel", None),
|
|
1007
|
-
]
|
|
970
|
+
assert sent == [("GET", "/v1/sessions/s1/views/live?placement=panel")]
|