voidx 1.1.0__tar.gz → 2.0.0__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.
- {voidx-1.1.0 → voidx-2.0.0}/PKG-INFO +30 -6
- voidx-2.0.0/README.md +61 -0
- {voidx-1.1.0 → voidx-2.0.0}/pyproject.toml +6 -3
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/__init__.py +1 -1
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/agents.py +13 -6
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/compaction.py +12 -3
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/contracts.py +2 -2
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/core.py +37 -6
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/run_loop.py +108 -31
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/subagent.py +10 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/tool_execution.py +115 -11
- voidx-2.0.0/src/voidx/agent/intent_refinement.py +241 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/runtime_context.py +46 -17
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/slash/handler.py +3 -2
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/slash/mcp.py +73 -9
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/slash/model.py +43 -29
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/slash/runtime.py +2 -2
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/state.py +9 -3
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/task_state.py +76 -52
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/config.py +53 -31
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/llm/catalog.py +4 -4
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/llm/instruction.py +28 -1
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/main.py +4 -9
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/mcp/__init__.py +2 -1
- voidx-2.0.0/src/voidx/mcp/client.py +723 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/mcp/manager.py +104 -50
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/mcp/schema.py +51 -3
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/mcp/tool.py +7 -54
- voidx-2.0.0/src/voidx/memory/__init__.py +18 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/memory/context_frames.py +1 -2
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/memory/model_profiles.py +36 -41
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/memory/runtime_state.py +140 -31
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/memory/session.py +46 -55
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/memory/store.py +33 -9
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/memory/transcript.py +1 -6
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/permission/engine.py +168 -31
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/permission/sandbox.py +27 -3
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/skills/__init__.py +5 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/brainstorming/SKILL.md +51 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/receiving-code-review/SKILL.md +60 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/requesting-code-review/SKILL.md +59 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/systematic-debugging/SKILL.md +64 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/test-driven-development/SKILL.md +61 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/verification-before-completion/SKILL.md +69 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/SKILL.md +87 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/api-doc.md +64 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/prd.md +117 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/readme.md +55 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/rfc.md +38 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/tech-design.md +68 -0
- voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-plans/SKILL.md +54 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/skills/policy.py +7 -2
- voidx-2.0.0/src/voidx/skills/runtime.py +90 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/skills/service.py +35 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/base.py +42 -2
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/bash.py +56 -1
- voidx-2.0.0/src/voidx/tools/clarify.py +139 -0
- voidx-2.0.0/src/voidx/tools/doc_template.py +96 -0
- voidx-2.0.0/src/voidx/tools/on_intent.py +122 -0
- voidx-2.0.0/src/voidx/tools/plan_checkpoint.py +161 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/registry.py +5 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/web_mcp.py +1 -1
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/browse.py +2 -2
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/console/app.py +1 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/dock/app.py +72 -1
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/dock/nodes.py +27 -1
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/events/__init__.py +2 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/tree.py +55 -24
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tools/clipboard_image.py +32 -31
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tui/app.py +54 -15
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tui/parser.py +17 -1
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tui/renderer.py +122 -55
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx.egg-info/PKG-INFO +30 -6
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx.egg-info/SOURCES.txt +13 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx.egg-info/requires.txt +1 -1
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_compaction.py +9 -12
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_config.py +52 -18
- voidx-2.0.0/tests/test_main.py +77 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_mcp.py +107 -0
- voidx-2.0.0/tests/test_npm_package.py +327 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_pure_tui.py +207 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_skills.py +33 -2
- voidx-1.1.0/README.md +0 -37
- voidx-1.1.0/src/voidx/mcp/client.py +0 -458
- voidx-1.1.0/src/voidx/skills/bundled/superpowers/receiving-code-review/SKILL.md +0 -30
- voidx-1.1.0/src/voidx/skills/bundled/superpowers/requesting-code-review/SKILL.md +0 -27
- voidx-1.1.0/src/voidx/skills/bundled/superpowers/systematic-debugging/SKILL.md +0 -36
- voidx-1.1.0/src/voidx/skills/bundled/superpowers/test-driven-development/SKILL.md +0 -33
- voidx-1.1.0/src/voidx/skills/bundled/superpowers/verification-before-completion/SKILL.md +0 -31
- voidx-1.1.0/src/voidx/skills/bundled/superpowers/writing-plans/SKILL.md +0 -27
- voidx-1.1.0/src/voidx/ui/__init__.py +0 -0
- voidx-1.1.0/tests/test_main.py +0 -94
- voidx-1.1.0/tests/test_npm_package.py +0 -101
- {voidx-1.1.0 → voidx-2.0.0}/setup.cfg +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/attachments.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/permissions.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/runtime.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/graph/streaming.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/message_rows.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/slash/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/slash/code_ide.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/slash/lsp.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/slash/skills.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/tool_filters.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/agent/tool_messages.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/llm/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/llm/compaction.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/llm/context.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/llm/provider.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/llm/usage.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/lsp/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/lsp/client.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/lsp/config.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/lsp/detector.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/lsp/errors.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/lsp/manager.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/lsp/schema.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/lsp/service.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/mcp_servers/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/mcp_servers/web.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/permission/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/permission/evaluate.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/permission/schema.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/permission/service.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/permission/wildcard.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/skills/registry.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/skills/schema.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/agent.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/file_ops.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/lsp.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/repomap.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/search.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/task_status.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/task_tracker.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/todo.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/web_content.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/webfetch.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/tools/websearch.py +0 -0
- {voidx-1.1.0/src/voidx/memory → voidx-2.0.0/src/voidx/ui}/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/commands.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/frontend.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/gateway/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/gateway/bootstrap.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/gateway/server.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/gateway/session.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/capture.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/console/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/console/formatting.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/console/streaming.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/diff.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/dock/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/dock/formatting.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/dock/state.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/events/schema.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/output/types.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/protocol/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/protocol/commands.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/protocol/envelope.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/protocol/requests.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/protocol/schema.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/protocol/transcript.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/session.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tools/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tools/attachment_tokens.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tools/code_ide.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tools/file_picker.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/transcript.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tui/__init__.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tui/helpers.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tui/input.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx/ui/tui/panels.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx.egg-info/dependency_links.txt +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx.egg-info/entry_points.txt +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/src/voidx.egg-info/top_level.txt +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_clipboard_image.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_code_ide.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_llm_provider.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_llm_usage.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_lsp.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_scrollback_flush.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_startup.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_tree_smoke.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_ui_diff.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_ui_events.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_ui_frontend_protocol.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_ui_gateway.py +0 -0
- {voidx-1.1.0 → voidx-2.0.0}/tests/test_ui_session_changes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voidx
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A coding agent that quantifies everything and solves with tools, not fuzzy prompts.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -14,7 +14,7 @@ Requires-Dist: typer>=0.15.0
|
|
|
14
14
|
Requires-Dist: rich>=13.9.0
|
|
15
15
|
Requires-Dist: tiktoken>=0.8.0
|
|
16
16
|
Requires-Dist: httpx>=0.28.0
|
|
17
|
-
Requires-Dist: websockets>=
|
|
17
|
+
Requires-Dist: websockets>=14
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: build>=1.2.0; extra == "dev"
|
|
20
20
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
@@ -26,22 +26,46 @@ voidx is a terminal AI coding agent built in Python.
|
|
|
26
26
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
### One-line install (no Python or npm required)
|
|
30
|
+
|
|
31
|
+
macOS / Linux:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
curl -fsSL https://raw.githubusercontent.com/chikhamx/voidx/main/scripts/install.sh | bash
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Windows (PowerShell):
|
|
38
|
+
|
|
39
|
+
```powershell
|
|
40
|
+
irm https://raw.githubusercontent.com/chikhamx/voidx/main/scripts/install.ps1 | iex
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The installer downloads a standalone Python runtime and sets up voidx in an
|
|
44
|
+
isolated environment — nothing else is needed on your machine.
|
|
45
|
+
|
|
46
|
+
### pip
|
|
30
47
|
|
|
31
48
|
```bash
|
|
32
49
|
pip install voidx
|
|
33
50
|
voidx
|
|
34
51
|
```
|
|
35
52
|
|
|
36
|
-
|
|
37
|
-
on the machine and installs the matching Python package into an isolated
|
|
38
|
-
user-local virtual environment on first run:
|
|
53
|
+
### npm
|
|
39
54
|
|
|
40
55
|
```bash
|
|
41
56
|
npm install -g @chikhamx/voidx
|
|
42
57
|
voidx
|
|
43
58
|
```
|
|
44
59
|
|
|
60
|
+
### China / slow network
|
|
61
|
+
|
|
62
|
+
Set mirror environment variables before running any install method:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
export VOIDX_PYTHON_MIRROR=https://npmmirror.com/mirrors/python-standalone
|
|
66
|
+
export VOIDX_PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
|
|
67
|
+
```
|
|
68
|
+
|
|
45
69
|
## Useful Commands
|
|
46
70
|
|
|
47
71
|
```bash
|
voidx-2.0.0/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# voidx
|
|
2
|
+
|
|
3
|
+
voidx is a terminal AI coding agent built in Python.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### One-line install (no Python or npm required)
|
|
8
|
+
|
|
9
|
+
macOS / Linux:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
curl -fsSL https://raw.githubusercontent.com/chikhamx/voidx/main/scripts/install.sh | bash
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Windows (PowerShell):
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
irm https://raw.githubusercontent.com/chikhamx/voidx/main/scripts/install.ps1 | iex
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The installer downloads a standalone Python runtime and sets up voidx in an
|
|
22
|
+
isolated environment — nothing else is needed on your machine.
|
|
23
|
+
|
|
24
|
+
### pip
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install voidx
|
|
28
|
+
voidx
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### npm
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g @chikhamx/voidx
|
|
35
|
+
voidx
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### China / slow network
|
|
39
|
+
|
|
40
|
+
Set mirror environment variables before running any install method:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export VOIDX_PYTHON_MIRROR=https://npmmirror.com/mirrors/python-standalone
|
|
44
|
+
export VOIDX_PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Useful Commands
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
voidx version
|
|
51
|
+
voidx sessions
|
|
52
|
+
voidx -w /path/to/project
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Development
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
.venv/bin/python -m pytest
|
|
59
|
+
.venv/bin/python scripts/package.py --format all --clean
|
|
60
|
+
npm --prefix npm run check
|
|
61
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "voidx"
|
|
3
|
-
version = "
|
|
3
|
+
version = "2.0.0"
|
|
4
4
|
description = "A coding agent that quantifies everything and solves with tools, not fuzzy prompts."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -15,7 +15,7 @@ dependencies = [
|
|
|
15
15
|
"rich>=13.9.0",
|
|
16
16
|
"tiktoken>=0.8.0",
|
|
17
17
|
"httpx>=0.28.0",
|
|
18
|
-
"websockets>=
|
|
18
|
+
"websockets>=14",
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
[project.scripts]
|
|
@@ -43,7 +43,10 @@ build-backend = "setuptools.build_meta"
|
|
|
43
43
|
where = ["src"]
|
|
44
44
|
|
|
45
45
|
[tool.setuptools.package-data]
|
|
46
|
-
"voidx.skills" = [
|
|
46
|
+
"voidx.skills" = [
|
|
47
|
+
"bundled/superpowers/*/SKILL.md",
|
|
48
|
+
"bundled/superpowers/*/templates/*.md",
|
|
49
|
+
]
|
|
47
50
|
|
|
48
51
|
[tool.pytest.ini_options]
|
|
49
52
|
asyncio_mode = "auto"
|
|
@@ -83,8 +83,8 @@ surgical edits directly when that is the shortest safe path.
|
|
|
83
83
|
the user explicitly approves.
|
|
84
84
|
- Fix/implement/modify → edit directly for small scoped changes, or delegate
|
|
85
85
|
broad/isolated work to implement.
|
|
86
|
-
- Ambiguous → continue with read-only investigation when useful.
|
|
87
|
-
|
|
86
|
+
- Ambiguous → continue with read-only investigation when useful. Use clarify
|
|
87
|
+
for one structured question before edits, unsafe bash, or implement delegation.
|
|
88
88
|
|
|
89
89
|
Words like "看看", "分析", "梳理", "有什么建议", "如何设计", "优化方案",
|
|
90
90
|
"look at", "analyze", "suggest", and "proposal" do NOT imply permission
|
|
@@ -92,11 +92,15 @@ surgical edits directly when that is the shortest safe path.
|
|
|
92
92
|
says to modify code.
|
|
93
93
|
|
|
94
94
|
1. **Chat / explain** — just answer. No tools unless you need to look something up.
|
|
95
|
+
If Current Task State says intent is chat or ambiguous, but the user request
|
|
96
|
+
appears to require workspace action, call on_intent before other workspace tools.
|
|
95
97
|
|
|
96
98
|
2. **Simple search** — grab read/glob/grep and find it yourself. Only send explore
|
|
97
99
|
for broad searches across many files.
|
|
98
100
|
|
|
99
|
-
3. **Design / plan** — hand off to plan for architecture questions.
|
|
101
|
+
3. **Design / plan** — hand off to plan for architecture questions. For
|
|
102
|
+
non-trivial implementation plans, call plan_checkpoint before changing files,
|
|
103
|
+
running write-capable commands, or delegating implement.
|
|
100
104
|
|
|
101
105
|
4. **Code changes**
|
|
102
106
|
- Small, local, or mechanical changes → read first, then call write/edit
|
|
@@ -109,9 +113,9 @@ surgical edits directly when that is the shortest safe path.
|
|
|
109
113
|
reporting completion.
|
|
110
114
|
- If review says FAIL or NEEDS_CHANGE → fix, review again.
|
|
111
115
|
|
|
112
|
-
5. **Unclear intent** — ask. One specific clarifying question is
|
|
113
|
-
assumptions. "When you say 'broken', do you mean it crashes,
|
|
114
|
-
or something else?"
|
|
116
|
+
5. **Unclear intent** — ask through clarify. One specific clarifying question is
|
|
117
|
+
better than five assumptions. "When you say 'broken', do you mean it crashes,
|
|
118
|
+
returns wrong data, or something else?"
|
|
115
119
|
|
|
116
120
|
## Rules
|
|
117
121
|
|
|
@@ -119,6 +123,8 @@ surgical edits directly when that is the shortest safe path.
|
|
|
119
123
|
- In plan mode, do not call write/edit/lsp_format, unsafe bash, or implement.
|
|
120
124
|
- Ambiguous implementation intent is not enough for write/edit/lsp_format,
|
|
121
125
|
unsafe bash, or implement delegation.
|
|
126
|
+
- Child agents do not interact with the user. If a child plan result needs user
|
|
127
|
+
approval or clarification, call plan_checkpoint or clarify yourself.
|
|
122
128
|
- Don't tell the user "done" until changes are verified.
|
|
123
129
|
- Child agents have isolated context — give them complete, self-contained briefs.
|
|
124
130
|
|
|
@@ -301,6 +307,7 @@ BUILTIN_AGENTS: dict[str, AgentDef] = {
|
|
|
301
307
|
"delegates broad work to specialists, reviews results.",
|
|
302
308
|
when_to_use="Default agent for all user interactions. Always use first.",
|
|
303
309
|
tools=[
|
|
310
|
+
"on_intent", "clarify", "plan_checkpoint",
|
|
304
311
|
"read", "glob", "grep", "bash", "agent", "task_status", "todo",
|
|
305
312
|
"webfetch", "websearch", "repo_map",
|
|
306
313
|
"lsp_diagnostics", "lsp_symbols", "lsp_definition", "lsp_references",
|
|
@@ -25,7 +25,7 @@ class GraphCompactionMixin:
|
|
|
25
25
|
async def _maybe_compact(
|
|
26
26
|
self: GraphCompactionHost,
|
|
27
27
|
messages: list,
|
|
28
|
-
session_msgs: list,
|
|
28
|
+
session_msgs: list | None = None,
|
|
29
29
|
*,
|
|
30
30
|
force: bool = False,
|
|
31
31
|
ask: bool = True,
|
|
@@ -218,14 +218,23 @@ class GraphCompactionMixin:
|
|
|
218
218
|
|
|
219
219
|
await delete_messages_through(self._session.id, last_message_id)
|
|
220
220
|
|
|
221
|
+
# Sync in-memory cache: drop compacted rows
|
|
222
|
+
cache = getattr(self, "_session_msg_cache", None)
|
|
223
|
+
if cache is not None:
|
|
224
|
+
self._session_msg_cache = [r for r in cache if r.id is not None and r.id > last_message_id]
|
|
225
|
+
|
|
221
226
|
async def _compact_session_history(self: GraphCompactionHost, *, force: bool = True) -> bool:
|
|
222
227
|
if getattr(self, "_session", None) is None:
|
|
223
228
|
ui.print("[dim]No active session to compact.[/dim]")
|
|
224
229
|
return False
|
|
225
230
|
|
|
226
|
-
|
|
231
|
+
cache = getattr(self, "_session_msg_cache", None)
|
|
232
|
+
if cache is not None:
|
|
233
|
+
rows = list(cache)
|
|
234
|
+
else:
|
|
235
|
+
from voidx.memory.session import load_messages
|
|
236
|
+
rows = await load_messages(self._session.id)
|
|
227
237
|
|
|
228
|
-
rows = await load_messages(self._session.id)
|
|
229
238
|
messages = messages_from_rows(rows)
|
|
230
239
|
|
|
231
240
|
head, _tail_id = await self._maybe_compact(messages, rows, force=force, ask=False)
|
|
@@ -53,6 +53,7 @@ class GraphComponentHost(Protocol):
|
|
|
53
53
|
_sub_buffers: dict[str, list[BaseMessage]]
|
|
54
54
|
_pending_summary: str | None
|
|
55
55
|
_compaction_summary: str
|
|
56
|
+
_session_msg_cache: list[Any] | None
|
|
56
57
|
_app: PureTui | None
|
|
57
58
|
_usage_stats: UsageStats
|
|
58
59
|
_compaction: CompactionService
|
|
@@ -64,7 +65,6 @@ class GraphComponentHost(Protocol):
|
|
|
64
65
|
_slash: Any
|
|
65
66
|
|
|
66
67
|
_any_messages_sent: bool
|
|
67
|
-
_current_implementation_allowed: bool
|
|
68
68
|
_needs_failure_check: dict[str, dict]
|
|
69
69
|
|
|
70
70
|
@property
|
|
@@ -85,7 +85,7 @@ class GraphComponentHost(Protocol):
|
|
|
85
85
|
async def _maybe_compact(
|
|
86
86
|
self,
|
|
87
87
|
messages: list[BaseMessage],
|
|
88
|
-
session_msgs: list[Any],
|
|
88
|
+
session_msgs: list[Any] | None = None,
|
|
89
89
|
*,
|
|
90
90
|
force: bool = False,
|
|
91
91
|
ask: bool = True,
|
|
@@ -37,6 +37,7 @@ from voidx.agent.state import AgentState
|
|
|
37
37
|
from voidx.agent.graph.streaming import stream_llm as _stream_llm
|
|
38
38
|
from voidx.agent.graph.subagent import run_subagent as _run_subagent
|
|
39
39
|
from voidx.agent.graph.tool_execution import GraphToolExecutionMixin
|
|
40
|
+
from voidx.agent.intent_refinement import refine_intent
|
|
40
41
|
from voidx.agent.runtime_context import InteractionMode, RuntimeContextBuilder
|
|
41
42
|
from voidx.agent.task_state import TaskRun, TaskState
|
|
42
43
|
from voidx.agent.tool_filters import filter_unavailable_lsp_tools
|
|
@@ -51,11 +52,11 @@ from voidx.llm.usage import (
|
|
|
51
52
|
extract_token_usage,
|
|
52
53
|
)
|
|
53
54
|
from voidx.memory.context_frames import save_context_frame_from_messages
|
|
54
|
-
from voidx.memory.session import SessionInfo
|
|
55
|
-
from voidx.agent.slash import SlashHandler
|
|
55
|
+
from voidx.memory.session import MessageRow, SessionInfo
|
|
56
56
|
from voidx.permission.service import PermissionService
|
|
57
57
|
from voidx.tools.registry import ToolRegistry
|
|
58
58
|
from voidx.tools.agent import AgentTool
|
|
59
|
+
from voidx.tools.on_intent import OnIntentInput, OnIntentTool
|
|
59
60
|
from voidx.tools.task_tracker import TaskTracker
|
|
60
61
|
from voidx.ui.output.console import StreamingRenderer
|
|
61
62
|
from voidx.ui.output.dock import dock
|
|
@@ -109,6 +110,8 @@ class VoidXGraph(
|
|
|
109
110
|
# Build tool registry, wire tracker through registry
|
|
110
111
|
self._tracker = TaskTracker()
|
|
111
112
|
self.tools = ToolRegistry(settings=settings, tracker=self._tracker)
|
|
113
|
+
intent_tool = OnIntentTool(resolver=self._resolve_on_intent)
|
|
114
|
+
self.tools.register("on_intent", intent_tool, intent_tool.description, intent_tool.parameters_schema())
|
|
112
115
|
agent_tool = AgentTool(runner=self._subagent_runner)
|
|
113
116
|
self.tools.register("agent", agent_tool, agent_tool.description, agent_tool.parameters_schema())
|
|
114
117
|
|
|
@@ -137,6 +140,7 @@ class VoidXGraph(
|
|
|
137
140
|
# One-turn summary injection vs. persisted summary restored across turns.
|
|
138
141
|
self._pending_summary: str | None = None
|
|
139
142
|
self._compaction_summary: str = ""
|
|
143
|
+
self._session_msg_cache: list[MessageRow] | None = None
|
|
140
144
|
self._app: PureTui | None = None
|
|
141
145
|
self._next_agent_id: int = 0
|
|
142
146
|
self._task_state = TaskState()
|
|
@@ -153,6 +157,8 @@ class VoidXGraph(
|
|
|
153
157
|
)
|
|
154
158
|
|
|
155
159
|
self._build()
|
|
160
|
+
from voidx.agent.slash import SlashHandler
|
|
161
|
+
|
|
156
162
|
self._slash = SlashHandler(self)
|
|
157
163
|
|
|
158
164
|
# MCP (Model Context Protocol) servers — start on run()
|
|
@@ -187,6 +193,15 @@ class VoidXGraph(
|
|
|
187
193
|
def interaction_mode(self) -> InteractionMode:
|
|
188
194
|
return self._interaction_mode
|
|
189
195
|
|
|
196
|
+
def _resolve_on_intent(self, inp: OnIntentInput, ctx):
|
|
197
|
+
return refine_intent(
|
|
198
|
+
inp,
|
|
199
|
+
ctx,
|
|
200
|
+
config=self.config,
|
|
201
|
+
settings=self._settings,
|
|
202
|
+
registered_tool_ids=self.tools.ids(),
|
|
203
|
+
)
|
|
204
|
+
|
|
190
205
|
async def _subagent_runner(self, agent_def: AgentDef, description: str, model_override: str | None) -> str:
|
|
191
206
|
parent_messages = getattr(self, '_current_messages', None)
|
|
192
207
|
sub_buffer: list[BaseMessage] = []
|
|
@@ -297,6 +312,8 @@ class VoidXGraph(
|
|
|
297
312
|
agent=agent_name,
|
|
298
313
|
task_intent=state.get("task_intent"),
|
|
299
314
|
interaction_mode=interaction_mode,
|
|
315
|
+
scope=_pending_approval_scope(state.get("pending_approval")) or state.get("goal") or latest_user_text,
|
|
316
|
+
turn_count=state.get("goal_turn_count", 0),
|
|
300
317
|
)
|
|
301
318
|
mode_prompt = PLAN_MODE_APPEND if InteractionMode.parse(interaction_mode) == InteractionMode.PLAN else ""
|
|
302
319
|
summary = self._pending_summary or self._compaction_summary
|
|
@@ -313,22 +330,25 @@ class VoidXGraph(
|
|
|
313
330
|
interaction_mode=interaction_mode,
|
|
314
331
|
instructions=instructions,
|
|
315
332
|
skill_instructions=skill_context.instructions,
|
|
333
|
+
skill_runs=skill_context.runs,
|
|
316
334
|
active_skill_summaries=skill_context.active,
|
|
317
335
|
summary=summary,
|
|
318
336
|
current_user_text=latest_user_text,
|
|
319
337
|
task_intent=state.get("task_intent"),
|
|
320
|
-
implementation_allowed=state.get("implementation_allowed"),
|
|
321
338
|
intent_resolution_reason=state.get("intent_resolution_reason", ""),
|
|
322
|
-
|
|
323
|
-
approved_scope=state.get("approved_scope", ""),
|
|
339
|
+
pending_approval=state.get("pending_approval"),
|
|
324
340
|
goal=state.get("goal", ""),
|
|
325
341
|
goal_phase=state.get("goal_phase", ""),
|
|
326
342
|
goal_status=state.get("goal_status", ""),
|
|
327
343
|
goal_turn_count=state.get("goal_turn_count", 0),
|
|
344
|
+
available_tool_ids=state.get("available_tool_ids", []),
|
|
345
|
+
intent_confidence=state.get("intent_confidence"),
|
|
346
|
+
intent_source=state.get("intent_source", ""),
|
|
347
|
+
intent_refined=state.get("intent_refined", False),
|
|
328
348
|
).build()
|
|
329
349
|
context.apply_to_messages(state.get("messages", []))
|
|
330
350
|
|
|
331
|
-
return base
|
|
351
|
+
return {**base, "skill_runs": skill_context.runs}
|
|
332
352
|
|
|
333
353
|
async def _call_llm(self, state: AgentState) -> dict:
|
|
334
354
|
step = state.get("step_count", 0)
|
|
@@ -354,6 +374,9 @@ class VoidXGraph(
|
|
|
354
374
|
tool_defs = [t for t in all_tool_defs if t["function"]["name"] in agent_tool_ids]
|
|
355
375
|
else:
|
|
356
376
|
tool_defs = all_tool_defs
|
|
377
|
+
if "available_tool_ids" in state:
|
|
378
|
+
visible = set(state.get("available_tool_ids") or [])
|
|
379
|
+
tool_defs = [t for t in tool_defs if t["function"]["name"] in visible]
|
|
357
380
|
tool_defs = filter_unavailable_lsp_tools(tool_defs, getattr(self, "_lsp_manager", None))
|
|
358
381
|
|
|
359
382
|
has_tool_budget = step < max_s - 1
|
|
@@ -447,3 +470,11 @@ def _latest_user_text(messages: list[BaseMessage]) -> str:
|
|
|
447
470
|
return "\n".join(parts)
|
|
448
471
|
return str(content)
|
|
449
472
|
return ""
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _pending_approval_scope(value: object | None) -> str:
|
|
476
|
+
if value is None:
|
|
477
|
+
return ""
|
|
478
|
+
if isinstance(value, dict):
|
|
479
|
+
return str(value.get("scope") or "").strip()
|
|
480
|
+
return str(getattr(value, "scope", "") or "").strip()
|
|
@@ -16,8 +16,9 @@ from voidx.agent.attachments import (
|
|
|
16
16
|
)
|
|
17
17
|
from voidx.agent.graph.runtime import ui
|
|
18
18
|
from voidx.agent.message_rows import messages_from_rows
|
|
19
|
+
from voidx.agent.runtime_context import TaskIntent
|
|
19
20
|
from voidx.agent.state import AgentState
|
|
20
|
-
from voidx.agent.task_state import resolve_turn_intent
|
|
21
|
+
from voidx.agent.task_state import IntentResolution, PendingApproval, resolve_turn_intent
|
|
21
22
|
from voidx.llm.provider import get_context_limit
|
|
22
23
|
from voidx.memory.session import (
|
|
23
24
|
MessageRow,
|
|
@@ -179,7 +180,7 @@ class GraphRunLoopMixin:
|
|
|
179
180
|
goal_phase=lambda: getattr(getattr(getattr(self, "_task_run", None), "phase", None), "value", "clarify"),
|
|
180
181
|
goal_status=lambda: getattr(getattr(getattr(self, "_task_run", None), "status", None), "value", "idle"),
|
|
181
182
|
goal_turn_count=lambda: getattr(getattr(self, "_task_run", None), "turn_count", 0),
|
|
182
|
-
goal_awaiting_approval=lambda: getattr(getattr(self, "_task_run", None), "
|
|
183
|
+
goal_awaiting_approval=lambda: bool(getattr(getattr(self, "_task_run", None), "pending_approval", None)),
|
|
183
184
|
mcp_servers=lambda: [
|
|
184
185
|
McpServerStatus(
|
|
185
186
|
name=s.name,
|
|
@@ -228,6 +229,11 @@ class GraphRunLoopMixin:
|
|
|
228
229
|
dock.append_message("\n".join(lsp_lines), markup=True)
|
|
229
230
|
|
|
230
231
|
if hasattr(self, '_mcp_manager'):
|
|
232
|
+
servers = self._settings.list_mcp_servers() if self._settings else []
|
|
233
|
+
enabled = [s for s in servers if not s.disabled]
|
|
234
|
+
if enabled:
|
|
235
|
+
names = ", ".join(s.name for s in enabled)
|
|
236
|
+
dock.append_message(f"[dim]MCP connecting: {names}…[/dim]", markup=True)
|
|
231
237
|
await self._mcp_manager.start_all()
|
|
232
238
|
|
|
233
239
|
async def handle_user_input(user_input: str) -> bool:
|
|
@@ -310,7 +316,13 @@ class GraphRunLoopMixin:
|
|
|
310
316
|
))
|
|
311
317
|
else:
|
|
312
318
|
self._turn_node = dock.start_turn(payload.display_text)
|
|
313
|
-
|
|
319
|
+
# Load session messages — use in-memory cache when available
|
|
320
|
+
if self._session_msg_cache is not None:
|
|
321
|
+
session_msgs = list(self._session_msg_cache)
|
|
322
|
+
else:
|
|
323
|
+
session_msgs = (await load_messages(self._session.id)) if self._session else []
|
|
324
|
+
if self._session:
|
|
325
|
+
self._session_msg_cache = list(session_msgs)
|
|
314
326
|
truncation_notice: str | None = None
|
|
315
327
|
# Safety: if session is huge, only load recent messages
|
|
316
328
|
if len(session_msgs) > 500:
|
|
@@ -349,13 +361,16 @@ class GraphRunLoopMixin:
|
|
|
349
361
|
getattr(self, "_task_state", None),
|
|
350
362
|
)
|
|
351
363
|
task_intent = intent_resolution.intent
|
|
352
|
-
implementation_allowed = intent_resolution.implementation_allowed
|
|
353
|
-
self._current_implementation_allowed = implementation_allowed
|
|
354
364
|
goal_scope = (
|
|
355
365
|
task_run.goal
|
|
356
366
|
if interaction_mode == "goal" and task_run is not None and task_run.goal
|
|
357
367
|
else payload.title_text
|
|
358
368
|
)
|
|
369
|
+
pending_approval = _active_pending_approval(
|
|
370
|
+
getattr(self, "_task_state", None),
|
|
371
|
+
task_run,
|
|
372
|
+
interaction_mode,
|
|
373
|
+
)
|
|
359
374
|
|
|
360
375
|
saved_user_content, user_content_format = serialize_message_content(payload.content)
|
|
361
376
|
user_message_id = await save_message(MessageRow(
|
|
@@ -365,6 +380,15 @@ class GraphRunLoopMixin:
|
|
|
365
380
|
content_format=user_content_format,
|
|
366
381
|
created_at=_now(),
|
|
367
382
|
))
|
|
383
|
+
if self._session_msg_cache is not None:
|
|
384
|
+
self._session_msg_cache.append(MessageRow(
|
|
385
|
+
id=user_message_id,
|
|
386
|
+
session_id=self._session.id,
|
|
387
|
+
role="user",
|
|
388
|
+
content=saved_user_content,
|
|
389
|
+
content_format=user_content_format,
|
|
390
|
+
created_at=_now(),
|
|
391
|
+
))
|
|
368
392
|
self._any_messages_sent = True
|
|
369
393
|
|
|
370
394
|
initial: AgentState = {
|
|
@@ -378,10 +402,8 @@ class GraphRunLoopMixin:
|
|
|
378
402
|
"plan_mode": self._plan_mode,
|
|
379
403
|
"interaction_mode": interaction_mode,
|
|
380
404
|
"task_intent": task_intent.value,
|
|
381
|
-
"implementation_allowed": implementation_allowed,
|
|
382
405
|
"intent_resolution_reason": intent_resolution.reason,
|
|
383
|
-
"
|
|
384
|
-
"approved_scope": intent_resolution.approved_scope,
|
|
406
|
+
"pending_approval": _dump_pending_approval(pending_approval),
|
|
385
407
|
"goal": task_run.goal if task_run is not None else "",
|
|
386
408
|
"goal_phase": task_run.phase.value if task_run is not None else "",
|
|
387
409
|
"goal_status": task_run.status.value if task_run is not None else "",
|
|
@@ -402,43 +424,51 @@ class GraphRunLoopMixin:
|
|
|
402
424
|
await ui_events.emit(StatusFinished(status_id="turn:analyzing"))
|
|
403
425
|
|
|
404
426
|
final = await self.graph.ainvoke(initial, {"recursion_limit": self.config.agent.recursion_limit})
|
|
427
|
+
final_task_intent = TaskIntent(final.get("task_intent", task_intent.value))
|
|
428
|
+
final_intent_resolution_reason = final.get(
|
|
429
|
+
"intent_resolution_reason",
|
|
430
|
+
intent_resolution.reason,
|
|
431
|
+
)
|
|
432
|
+
final_pending_approval = _load_pending_approval(final.get("pending_approval"))
|
|
433
|
+
final_scope = final_pending_approval.scope if final_pending_approval else goal_scope
|
|
434
|
+
final_resolution = IntentResolution(
|
|
435
|
+
intent=final_task_intent,
|
|
436
|
+
reason=final_intent_resolution_reason,
|
|
437
|
+
confirmed_approval=intent_resolution.confirmed_approval,
|
|
438
|
+
)
|
|
405
439
|
if self.model is not None and hasattr(self, "_task_state"):
|
|
406
440
|
self._task_state.update_after_turn(
|
|
407
|
-
|
|
441
|
+
final_resolution,
|
|
408
442
|
payload.title_text,
|
|
409
|
-
scope_text=
|
|
443
|
+
scope_text=final_scope,
|
|
410
444
|
)
|
|
411
445
|
if self.model is not None and interaction_mode == "goal" and task_run is not None:
|
|
412
446
|
task_run.update_after_turn(
|
|
413
|
-
|
|
447
|
+
final_resolution,
|
|
414
448
|
payload.title_text,
|
|
415
|
-
scope_text=
|
|
449
|
+
scope_text=final_scope,
|
|
416
450
|
)
|
|
451
|
+
if self.model is not None and task_run is not None:
|
|
452
|
+
task_run.merge_skill_runs(final.get("skill_runs", []))
|
|
417
453
|
await save_message_runtime_snapshot(MessageRuntimeSnapshot(
|
|
418
454
|
message_id=user_message_id,
|
|
419
455
|
session_id=self._session.id,
|
|
420
456
|
interaction_mode=interaction_mode,
|
|
421
|
-
task_intent=
|
|
422
|
-
|
|
423
|
-
intent_resolution_reason=intent_resolution.reason,
|
|
457
|
+
task_intent=final_task_intent,
|
|
458
|
+
intent_resolution_reason=final_intent_resolution_reason,
|
|
424
459
|
goal=task_run.goal if task_run is not None else "",
|
|
425
460
|
goal_phase=task_run.phase.value if task_run is not None else "",
|
|
426
461
|
goal_status=task_run.status.value if task_run is not None else "",
|
|
427
462
|
goal_turn_count=task_run.turn_count if task_run is not None else 0,
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
getattr(self, "_task_state", None),
|
|
433
|
-
"awaiting_implementation_approval",
|
|
434
|
-
False,
|
|
435
|
-
)
|
|
436
|
-
),
|
|
437
|
-
approved_scope=(
|
|
438
|
-
task_run.approved_scope
|
|
439
|
-
if interaction_mode == "goal" and task_run is not None
|
|
440
|
-
else getattr(getattr(self, "_task_state", None), "approved_scope", "")
|
|
463
|
+
pending_approval=_active_pending_approval(
|
|
464
|
+
getattr(self, "_task_state", None),
|
|
465
|
+
task_run,
|
|
466
|
+
interaction_mode,
|
|
441
467
|
),
|
|
468
|
+
intent_confidence=final.get("intent_confidence"),
|
|
469
|
+
intent_source=final.get("intent_source", ""),
|
|
470
|
+
intent_refined=bool(final.get("intent_refined", False)),
|
|
471
|
+
available_tool_ids=list(final.get("available_tool_ids", []) or []),
|
|
442
472
|
))
|
|
443
473
|
await self._persist_runtime_state()
|
|
444
474
|
|
|
@@ -469,7 +499,7 @@ class GraphRunLoopMixin:
|
|
|
469
499
|
else:
|
|
470
500
|
saved = str(raw_content)
|
|
471
501
|
fmt = "text"
|
|
472
|
-
await save_message(MessageRow(
|
|
502
|
+
row_id = await save_message(MessageRow(
|
|
473
503
|
session_id=self._session.id,
|
|
474
504
|
role="assistant",
|
|
475
505
|
content=saved,
|
|
@@ -477,14 +507,33 @@ class GraphRunLoopMixin:
|
|
|
477
507
|
tool_calls=msg.tool_calls if msg.tool_calls else None,
|
|
478
508
|
created_at=_now(),
|
|
479
509
|
))
|
|
510
|
+
if self._session_msg_cache is not None:
|
|
511
|
+
self._session_msg_cache.append(MessageRow(
|
|
512
|
+
id=row_id,
|
|
513
|
+
session_id=self._session.id,
|
|
514
|
+
role="assistant",
|
|
515
|
+
content=saved,
|
|
516
|
+
content_format=fmt,
|
|
517
|
+
tool_calls=msg.tool_calls if msg.tool_calls else None,
|
|
518
|
+
created_at=_now(),
|
|
519
|
+
))
|
|
480
520
|
elif isinstance(msg, ToolMessage):
|
|
481
|
-
await save_message(MessageRow(
|
|
521
|
+
row_id = await save_message(MessageRow(
|
|
482
522
|
session_id=self._session.id,
|
|
483
523
|
role="tool",
|
|
484
524
|
content=str(msg.content),
|
|
485
525
|
tool_call_id=getattr(msg, "tool_call_id", None),
|
|
486
526
|
created_at=_now(),
|
|
487
527
|
))
|
|
528
|
+
if self._session_msg_cache is not None:
|
|
529
|
+
self._session_msg_cache.append(MessageRow(
|
|
530
|
+
id=row_id,
|
|
531
|
+
session_id=self._session.id,
|
|
532
|
+
role="tool",
|
|
533
|
+
content=str(msg.content),
|
|
534
|
+
tool_call_id=getattr(msg, "tool_call_id", None),
|
|
535
|
+
created_at=_now(),
|
|
536
|
+
))
|
|
488
537
|
await touch_session(self._session.id)
|
|
489
538
|
|
|
490
539
|
# Auto-title on first message
|
|
@@ -517,6 +566,11 @@ class GraphRunLoopMixin:
|
|
|
517
566
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
518
567
|
if self._session is not None and user_message_id is not None:
|
|
519
568
|
await delete_messages_from(self._session.id, user_message_id)
|
|
569
|
+
if self._session_msg_cache is not None:
|
|
570
|
+
self._session_msg_cache = [
|
|
571
|
+
r for r in self._session_msg_cache
|
|
572
|
+
if r.id is None or r.id < user_message_id
|
|
573
|
+
]
|
|
520
574
|
raise
|
|
521
575
|
finally:
|
|
522
576
|
session_tracker.finish_turn()
|
|
@@ -528,7 +582,6 @@ class GraphRunLoopMixin:
|
|
|
528
582
|
await ui_events.drain()
|
|
529
583
|
else:
|
|
530
584
|
dock.set_input("", [])
|
|
531
|
-
self._current_implementation_allowed = True
|
|
532
585
|
|
|
533
586
|
async def _dispatch_slash(self: GraphRunLoopHost, inp: str) -> bool:
|
|
534
587
|
"""Try to dispatch a slash command. Returns True if handled."""
|
|
@@ -594,3 +647,27 @@ class GraphRunLoopMixin:
|
|
|
594
647
|
return False
|
|
595
648
|
active_dock.restore_tree(transcript_rows_to_tree(rows), append=append)
|
|
596
649
|
return True
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _active_pending_approval(task_state, task_run, interaction_mode: str) -> PendingApproval | None:
|
|
653
|
+
if interaction_mode == "goal" and task_run is not None:
|
|
654
|
+
return getattr(task_run, "pending_approval", None)
|
|
655
|
+
if task_state is not None:
|
|
656
|
+
return getattr(task_state, "pending_approval", None)
|
|
657
|
+
return None
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def _dump_pending_approval(value: PendingApproval | dict | None) -> dict | None:
|
|
661
|
+
if value is None:
|
|
662
|
+
return None
|
|
663
|
+
if isinstance(value, dict):
|
|
664
|
+
return value
|
|
665
|
+
return value.model_dump(mode="json")
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def _load_pending_approval(value: PendingApproval | dict | None) -> PendingApproval | None:
|
|
669
|
+
if value is None:
|
|
670
|
+
return None
|
|
671
|
+
if isinstance(value, PendingApproval):
|
|
672
|
+
return value
|
|
673
|
+
return PendingApproval.model_validate(value)
|