voidx 3.0.0__tar.gz → 3.1.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-3.0.0 → voidx-3.1.0}/PKG-INFO +4 -1
- {voidx-3.0.0 → voidx-3.1.0}/pyproject.toml +3 -2
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/__init__.py +1 -1
- voidx-3.1.0/src/voidx/agent/agents.py +77 -0
- voidx-3.1.0/src/voidx/agent/goal_resolver.py +439 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/compaction.py +10 -2
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/compaction_coordinator.py +8 -5
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/contracts.py +2 -1
- voidx-3.1.0/src/voidx/agent/graph/convergence.py +87 -0
- voidx-3.1.0/src/voidx/agent/graph/core/__init__.py +9 -0
- voidx-3.1.0/src/voidx/agent/graph/core/_voidx_graph.py +461 -0
- voidx-3.1.0/src/voidx/agent/graph/core/helpers.py +126 -0
- voidx-3.1.0/src/voidx/agent/graph/core/llm.py +335 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/permissions.py +2 -14
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/run_loop.py +14 -17
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/runtime_guards.py +20 -23
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/session_runtime.py +15 -5
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/streaming.py +152 -2
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/subagent.py +41 -96
- voidx-3.1.0/src/voidx/agent/graph/tool_executor/__init__.py +8 -0
- voidx-3.1.0/src/voidx/agent/graph/tool_executor/executor.py +353 -0
- voidx-3.1.0/src/voidx/agent/graph/tool_executor/guards.py +150 -0
- voidx-3.1.0/src/voidx/agent/graph/tool_executor/helpers.py +346 -0
- voidx-3.1.0/src/voidx/agent/graph/tool_executor/types.py +86 -0
- voidx-3.1.0/src/voidx/agent/graph/tool_executor/ui.py +143 -0
- voidx-3.1.0/src/voidx/agent/graph/tool_executor/workflow.py +314 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/turn_runner.py +95 -19
- voidx-3.1.0/src/voidx/agent/graph/workflow_utils.py +33 -0
- voidx-3.1.0/src/voidx/agent/prompts.py +177 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/runtime_context.py +111 -275
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/handler.py +46 -6
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/task_state.py +5 -7
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/todo_state.py +1 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/models.py +8 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/catalog.py +6 -2
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/instruction.py +2 -14
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/provider.py +66 -3
- voidx-3.1.0/src/voidx/logging/__init__.py +6 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/logging/request_log.py +31 -7
- voidx-3.1.0/src/voidx/logging/tool_log.py +75 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/base.py +4 -2
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/stdio_transport.py +3 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/jsonl_store.py +7 -5
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/runtime_state.py +2 -21
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/store.py +6 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/engine.py +4 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/evaluate.py +1 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/rules.py +19 -15
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/service.py +1 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/__init__.py +6 -9
- voidx-3.1.0/src/voidx/runtime/intent.py +46 -0
- voidx-3.1.0/src/voidx/runtime/task_state.py +229 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/ui.py +2 -2
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/selfupdate.py +2 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/context.py +0 -52
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/service.py +0 -2
- voidx-3.1.0/src/voidx/tools/agent.py +304 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/base.py +37 -6
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/bash.py +59 -16
- voidx-3.1.0/src/voidx/tools/bash_router.py +1222 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/clarify.py +11 -55
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/compact_context.py +1 -1
- voidx-3.1.0/src/voidx/tools/file_ops/__init__.py +19 -0
- voidx-3.1.0/src/voidx/tools/file_ops/edit_execute.py +538 -0
- voidx-3.1.0/src/voidx/tools/file_ops/edit_resolve.py +257 -0
- voidx-3.1.0/src/voidx/tools/file_ops/file.py +177 -0
- voidx-3.1.0/src/voidx/tools/file_ops/line.py +150 -0
- voidx-3.1.0/src/voidx/tools/file_ops/read.py +232 -0
- voidx-3.1.0/src/voidx/tools/file_ops/types.py +79 -0
- voidx-3.1.0/src/voidx/tools/file_ops/write.py +89 -0
- voidx-3.1.0/src/voidx/tools/file_state.py +353 -0
- voidx-3.1.0/src/voidx/tools/git.py +1335 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/load_doc_template.py +6 -5
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/load_skills.py +4 -3
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/lsp.py +6 -3
- voidx-3.1.0/src/voidx/tools/plan_checkpoint.py +254 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/registry.py +4 -4
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/repomap.py +25 -6
- voidx-3.1.0/src/voidx/tools/search.py +299 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/task_status.py +1 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/task_tracker.py +3 -6
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/todo.py +1 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/webfetch.py +111 -15
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/websearch.py +3 -2
- voidx-3.1.0/src/voidx/tools/workflow.py +553 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/commands.py +3 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/capture.py +2 -5
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/console/app.py +10 -7
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/console/formatting.py +1 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/display_policy.py +13 -9
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes.py +6 -6
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/events/consumers.py +1 -3
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/events/schema.py +0 -4
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/types.py +1 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/session.py +1 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/clipboard_image.py +1 -6
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/file_picker.py +10 -6
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/choice_mixin.py +15 -4
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/input.py +2 -2
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/overlays.py +4 -2
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/panels.py +1 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_activity.py +12 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_status.py +7 -10
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/__init__.py +0 -4
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/auto_advance.py +2 -2
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/context.py +1 -4
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/dag.py +4 -14
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/nodes.py +71 -70
- voidx-3.1.0/src/voidx/workflow/policy.py +82 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/reconcile.py +47 -5
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/runtime.py +2 -6
- voidx-3.1.0/src/voidx/workflow/service.py +143 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/types.py +1 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/PKG-INFO +4 -1
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/SOURCES.txt +25 -49
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/requires.txt +4 -0
- voidx-3.0.0/src/voidx/agent/agents.py +0 -266
- voidx-3.0.0/src/voidx/agent/goal_resolver.py +0 -228
- voidx-3.0.0/src/voidx/agent/graph/convergence.py +0 -170
- voidx-3.0.0/src/voidx/agent/graph/core.py +0 -954
- voidx-3.0.0/src/voidx/agent/graph/tool_executor.py +0 -1226
- voidx-3.0.0/src/voidx/config/cli.py +0 -1
- voidx-3.0.0/src/voidx/data/intent_classifier.json +0 -1
- voidx-3.0.0/src/voidx/logging/__init__.py +0 -1
- voidx-3.0.0/src/voidx/runtime/intent.py +0 -163
- voidx-3.0.0/src/voidx/runtime/intent_classifier.py +0 -262
- voidx-3.0.0/src/voidx/runtime/task_state.py +0 -338
- voidx-3.0.0/src/voidx/skills/policy.py +0 -46
- voidx-3.0.0/src/voidx/skills/runtime.py +0 -25
- voidx-3.0.0/src/voidx/tools/advance_workflow.py +0 -253
- voidx-3.0.0/src/voidx/tools/agent.py +0 -187
- voidx-3.0.0/src/voidx/tools/file_ops.py +0 -208
- voidx-3.0.0/src/voidx/tools/file_state.py +0 -134
- voidx-3.0.0/src/voidx/tools/git.py +0 -645
- voidx-3.0.0/src/voidx/tools/plan_checkpoint.py +0 -157
- voidx-3.0.0/src/voidx/tools/search.py +0 -157
- voidx-3.0.0/src/voidx/workflow/policy.py +0 -205
- voidx-3.0.0/src/voidx/workflow/service.py +0 -391
- voidx-3.0.0/tests/test_auto_advance.py +0 -410
- voidx-3.0.0/tests/test_clipboard_image.py +0 -100
- voidx-3.0.0/tests/test_clipboard_text.py +0 -22
- voidx-3.0.0/tests/test_code_ide.py +0 -75
- voidx-3.0.0/tests/test_compaction.py +0 -892
- voidx-3.0.0/tests/test_config.py +0 -695
- voidx-3.0.0/tests/test_dock_formatting.py +0 -48
- voidx-3.0.0/tests/test_goal_resolution_refactor.py +0 -128
- voidx-3.0.0/tests/test_instruction_cache.py +0 -88
- voidx-3.0.0/tests/test_intent_classifier_phase_a.py +0 -158
- voidx-3.0.0/tests/test_llm_catalog.py +0 -46
- voidx-3.0.0/tests/test_llm_provider.py +0 -632
- voidx-3.0.0/tests/test_llm_usage.py +0 -212
- voidx-3.0.0/tests/test_lsp.py +0 -559
- voidx-3.0.0/tests/test_main.py +0 -77
- voidx-3.0.0/tests/test_main_startup.py +0 -129
- voidx-3.0.0/tests/test_mcp.py +0 -377
- voidx-3.0.0/tests/test_module_boundaries.py +0 -242
- voidx-3.0.0/tests/test_output_browse.py +0 -23
- voidx-3.0.0/tests/test_runtime_intent_classifier.py +0 -131
- voidx-3.0.0/tests/test_runtime_ui.py +0 -71
- voidx-3.0.0/tests/test_scrollback_flush.py +0 -151
- voidx-3.0.0/tests/test_selfupdate.py +0 -103
- voidx-3.0.0/tests/test_skills.py +0 -1236
- voidx-3.0.0/tests/test_startup.py +0 -59
- voidx-3.0.0/tests/test_streaming_sanitize.py +0 -103
- voidx-3.0.0/tests/test_tree_smoke.py +0 -270
- voidx-3.0.0/tests/test_tui_frame_rendering.py +0 -555
- voidx-3.0.0/tests/test_tui_input_handling.py +0 -865
- voidx-3.0.0/tests/test_tui_output_tree.py +0 -750
- voidx-3.0.0/tests/test_tui_paste_handling.py +0 -280
- voidx-3.0.0/tests/test_tui_status_activity.py +0 -778
- voidx-3.0.0/tests/test_tui_terminal_panels.py +0 -776
- voidx-3.0.0/tests/test_ui_diff.py +0 -68
- voidx-3.0.0/tests/test_ui_events.py +0 -1118
- voidx-3.0.0/tests/test_ui_frontend_protocol.py +0 -150
- voidx-3.0.0/tests/test_ui_gateway.py +0 -366
- voidx-3.0.0/tests/test_ui_session_changes.py +0 -131
- voidx-3.0.0/tests/test_workflow_reconcile.py +0 -309
- {voidx-3.0.0 → voidx-3.1.0}/README.md +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/setup.cfg +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/attachments.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/runtime.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/session_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/title_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/todo_events.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/tool_execution.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/topology.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/transcript_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/turn_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/wiring.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/message_rows.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/code_ide.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/guide.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/host.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/init.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/lsp.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/mcp.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/model.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/profile.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/runtime.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/session.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/skills.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/upgrade.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/state.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/tool_filters.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/tool_messages.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/tool_result_storage.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/enums.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/permissions.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_agent.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_api_keys.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_code_ide.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_custom.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_mcp.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_permissions.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_skills.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_update.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_utils.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_web.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/api-doc.md +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/prd.md +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/readme.md +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/rfc.md +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/tech-design.md +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/diffing.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/compaction.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/context.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/message_markers.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/service.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/usage.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/client.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/config.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/detector.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/detector_data.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/errors.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/manager.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/service.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/main.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/errors.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/http_transport.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/sse_transport.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/manager.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/tool.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp_servers/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp_servers/web.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/cleanup.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/context_frames.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/model_profiles.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/service.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/session.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/subagents.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/transcript.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/context.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/sandbox.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/wildcard.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/attachments.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/reference_tokens.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/todo.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/ui_port.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/references.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/registry.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/service.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/web_content.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/web_mcp.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/frontend.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/gateway/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/gateway/bootstrap.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/gateway/server.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/gateway/session.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/agent_display.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/browse.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/console/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/console/streaming.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/diff.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/agent_placeholder.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/app.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/formatting.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes_permission.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes_startup.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes_status.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/state.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/status.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/stream.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/todo.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/events/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/events/bus.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/tree.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/commands.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/envelope.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/requests.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/transcript.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/attachment_tokens.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/clipboard_text.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/code_ide.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/skill_picker.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/transcript.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/activity.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/app.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/clipboard_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/helpers.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/parser.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_frame.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_input.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_todo.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/renderer.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/state.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/terminal_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/text_prompt_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/render.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/route.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/dependency_links.txt +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/entry_points.txt +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/top_level.txt +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/tests/test_install_sh.py +0 -0
- {voidx-3.0.0 → voidx-3.1.0}/tests/test_npm_package.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voidx
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.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
|
|
@@ -15,6 +15,9 @@ Requires-Dist: rich>=13.9.0
|
|
|
15
15
|
Requires-Dist: tiktoken>=0.8.0
|
|
16
16
|
Requires-Dist: httpx>=0.28.0
|
|
17
17
|
Requires-Dist: websockets>=14
|
|
18
|
+
Requires-Dist: pathspec>=0.12
|
|
19
|
+
Provides-Extra: gemini
|
|
20
|
+
Requires-Dist: langchain-google-genai>=4.0.0; extra == "gemini"
|
|
18
21
|
Provides-Extra: dev
|
|
19
22
|
Requires-Dist: build>=1.2.0; extra == "dev"
|
|
20
23
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "voidx"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.1.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"
|
|
@@ -16,12 +16,14 @@ dependencies = [
|
|
|
16
16
|
"tiktoken>=0.8.0",
|
|
17
17
|
"httpx>=0.28.0",
|
|
18
18
|
"websockets>=14",
|
|
19
|
+
"pathspec>=0.12",
|
|
19
20
|
]
|
|
20
21
|
|
|
21
22
|
[project.scripts]
|
|
22
23
|
voidx = "voidx.main:cli"
|
|
23
24
|
|
|
24
25
|
[project.optional-dependencies]
|
|
26
|
+
gemini = ["langchain-google-genai>=4.0.0"]
|
|
25
27
|
dev = [
|
|
26
28
|
"build>=1.2.0",
|
|
27
29
|
"pytest>=8.0.0",
|
|
@@ -47,7 +49,6 @@ where = ["src"]
|
|
|
47
49
|
"bundled/*/SKILL.md",
|
|
48
50
|
]
|
|
49
51
|
"voidx.data" = [
|
|
50
|
-
"intent_classifier.json",
|
|
51
52
|
"templates/*.md",
|
|
52
53
|
]
|
|
53
54
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Agent definitions — typed config and whenToUse descriptions.
|
|
2
|
+
|
|
3
|
+
voidx uses one agent identity:
|
|
4
|
+
voidx — primary identity, also used for isolated child runs
|
|
5
|
+
|
|
6
|
+
Runtime personas (coordinate/explore/plan/implement/review) are thinking-mode
|
|
7
|
+
labels, not AgentDef ids.
|
|
8
|
+
|
|
9
|
+
Tool visibility is controlled by the ToolRegistry and permission layer,
|
|
10
|
+
not by a static whitelist on AgentDef.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
|
|
18
|
+
# ── agent definitions ─────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
class AgentDef(BaseModel):
|
|
21
|
+
"""An agent's complete definition — typed, no loose config."""
|
|
22
|
+
name: str
|
|
23
|
+
description: str
|
|
24
|
+
when_to_use: str
|
|
25
|
+
can_write: bool
|
|
26
|
+
can_delegate: bool # can it start child agents via the agent tool?
|
|
27
|
+
hidden: bool = False # hidden from user-facing lists?
|
|
28
|
+
model: str | None = None # None = inherit from parent
|
|
29
|
+
|
|
30
|
+
# ── built-in agents ────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
BUILTIN_AGENTS: dict[str, AgentDef] = {
|
|
33
|
+
"voidx": AgentDef(
|
|
34
|
+
name="voidx",
|
|
35
|
+
description="Primary agent. Understands intent, edits small scoped changes directly, "
|
|
36
|
+
"delegates broad work to specialists, reviews results.",
|
|
37
|
+
when_to_use="Default agent for all user interactions. Always use first.",
|
|
38
|
+
can_write=True,
|
|
39
|
+
can_delegate=True,
|
|
40
|
+
hidden=False,
|
|
41
|
+
),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_agent(name: str) -> AgentDef | None:
|
|
46
|
+
return BUILTIN_AGENTS.get(name)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_visible_agents() -> list[AgentDef]:
|
|
50
|
+
return [a for a in BUILTIN_AGENTS.values() if not a.hidden]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_subagents() -> list[AgentDef]:
|
|
54
|
+
"""Child-run identities voidx can delegate to."""
|
|
55
|
+
agent = get_agent("voidx")
|
|
56
|
+
return [child_run_agent_def(agent)] if agent is not None else []
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def child_run_agent_def(agent: AgentDef) -> AgentDef:
|
|
60
|
+
"""Return the child-run view of the public voidx identity."""
|
|
61
|
+
return agent.model_copy(update={
|
|
62
|
+
"name": "voidx",
|
|
63
|
+
"description": "Isolated child run of voidx that follows the supplied workflow route.",
|
|
64
|
+
"when_to_use": "Use for delegated child work that benefits from isolated context.",
|
|
65
|
+
"can_delegate": False,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def child_agent_descriptions_for_llm() -> str:
|
|
70
|
+
"""Generate child-agent descriptions for the agent tool."""
|
|
71
|
+
lines = ["Available child agents:"]
|
|
72
|
+
for agent in get_subagents():
|
|
73
|
+
lines.append(
|
|
74
|
+
f"- {agent.name}: {agent.description}\n"
|
|
75
|
+
f" Write access: {agent.can_write}"
|
|
76
|
+
)
|
|
77
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""Runtime-owned structured goal resolution for top-level turns."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
|
|
10
|
+
from pydantic import BaseModel, model_validator
|
|
11
|
+
|
|
12
|
+
from voidx.logging.request_log import log_llm_diagnostic, serialize_llm_message
|
|
13
|
+
from voidx.runtime.intent import InteractionMode, TaskIntent, _contains_any
|
|
14
|
+
from voidx.runtime.task_state import (
|
|
15
|
+
GoalResolution,
|
|
16
|
+
GoalSpec,
|
|
17
|
+
IntentResolution,
|
|
18
|
+
PlanResolution,
|
|
19
|
+
TaskState,
|
|
20
|
+
goal_type_from_join,
|
|
21
|
+
)
|
|
22
|
+
from voidx.workflow.dag import DEFAULT_WORKFLOW_DAG
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
GOAL_RESOLVER_TIMEOUT_SECONDS = 20
|
|
26
|
+
WorkflowName = Literal["brainstorm", "debug", "design", "feedback", "plan", "review", "tdd", "verify"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ResolverGoal(BaseModel):
|
|
30
|
+
intent: Literal["coding", "general"] = "general"
|
|
31
|
+
goal: str | None = None
|
|
32
|
+
workflow: WorkflowName | None = None
|
|
33
|
+
kind_hint: str | None = None
|
|
34
|
+
|
|
35
|
+
@model_validator(mode="after")
|
|
36
|
+
def _goal_and_workflow_are_bound(self) -> "ResolverGoal":
|
|
37
|
+
has_goal = bool(self.goal and self.goal.strip())
|
|
38
|
+
has_workflow = self.workflow is not None
|
|
39
|
+
if has_goal != has_workflow:
|
|
40
|
+
raise ValueError("goal and workflow must be set together")
|
|
41
|
+
if not has_goal:
|
|
42
|
+
self.goal = None
|
|
43
|
+
self.workflow = None
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_plan_mode(user_text: str, task_state: TaskState) -> GoalResolution:
|
|
48
|
+
"""PLAN mode: construct result directly without LLM call."""
|
|
49
|
+
desc = (
|
|
50
|
+
task_state.current_goal.desc
|
|
51
|
+
if task_state.current_goal and task_state.current_goal.desc.strip()
|
|
52
|
+
else user_text
|
|
53
|
+
)
|
|
54
|
+
return GoalResolution(
|
|
55
|
+
intent=IntentResolution(type=TaskIntent.CODING),
|
|
56
|
+
goal=GoalSpec(desc=desc),
|
|
57
|
+
plan=PlanResolution(join="brainstorm", leave="brainstorm"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def resolve_goal_mode(user_text: str, task_state: TaskState) -> GoalResolution:
|
|
62
|
+
"""GOAL mode: construct result directly without LLM call.
|
|
63
|
+
|
|
64
|
+
The user must specify a goal to enter goal mode. Fixed entry from plan node;
|
|
65
|
+
plan will clarify via questions before planning.
|
|
66
|
+
"""
|
|
67
|
+
goal = task_state.current_goal or GoalSpec(desc=user_text)
|
|
68
|
+
return GoalResolution(
|
|
69
|
+
intent=IntentResolution(type=TaskIntent.CODING),
|
|
70
|
+
goal=goal,
|
|
71
|
+
plan=PlanResolution(join="plan", leave=None),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def resolve_goal_for_turn(
|
|
76
|
+
*,
|
|
77
|
+
model: Any | None,
|
|
78
|
+
user_text: str,
|
|
79
|
+
interaction_mode: str | InteractionMode | None,
|
|
80
|
+
task_state: TaskState,
|
|
81
|
+
log_diagnostic: bool = True,
|
|
82
|
+
) -> GoalResolution:
|
|
83
|
+
del interaction_mode
|
|
84
|
+
fallback = GoalResolution(
|
|
85
|
+
intent=IntentResolution(type=TaskIntent.GENERAL),
|
|
86
|
+
goal=None,
|
|
87
|
+
plan=None,
|
|
88
|
+
)
|
|
89
|
+
fallback_reason = ""
|
|
90
|
+
fallback_error_type = ""
|
|
91
|
+
fallback_error = ""
|
|
92
|
+
if model is None:
|
|
93
|
+
fallback_reason = "model_unavailable"
|
|
94
|
+
normalized = _normalize_resolution(fallback, user_text, task_state)
|
|
95
|
+
_log_goal_resolver_decision(normalized, user_text, task_state, fallback_reason, fallback_error_type, fallback_error, enabled=log_diagnostic)
|
|
96
|
+
return normalized
|
|
97
|
+
|
|
98
|
+
structured = getattr(model, "with_structured_output", None)
|
|
99
|
+
if not callable(structured):
|
|
100
|
+
fallback_reason = "structured_output_unsupported"
|
|
101
|
+
normalized = _normalize_resolution(fallback, user_text, task_state)
|
|
102
|
+
_log_goal_resolver_decision(normalized, user_text, task_state, fallback_reason, fallback_error_type, fallback_error, enabled=log_diagnostic)
|
|
103
|
+
return normalized
|
|
104
|
+
|
|
105
|
+
resolver_goal: ResolverGoal | None
|
|
106
|
+
try:
|
|
107
|
+
runnable = structured(ResolverGoal)
|
|
108
|
+
resolver_messages = _resolver_messages_from_exchanges(user_text, task_state)
|
|
109
|
+
raw = await asyncio.wait_for(
|
|
110
|
+
runnable.ainvoke(resolver_messages),
|
|
111
|
+
timeout=GOAL_RESOLVER_TIMEOUT_SECONDS,
|
|
112
|
+
)
|
|
113
|
+
_log_goal_resolver_exchange(resolver_messages, raw=raw, enabled=log_diagnostic)
|
|
114
|
+
resolver_goal = _coerce_resolution(raw)
|
|
115
|
+
except Exception as exc:
|
|
116
|
+
fallback_reason = "structured_output_error"
|
|
117
|
+
fallback_error_type = type(exc).__name__
|
|
118
|
+
fallback_error = _truncate_error_text(str(exc))
|
|
119
|
+
if "resolver_messages" in locals():
|
|
120
|
+
_log_goal_resolver_exchange(
|
|
121
|
+
resolver_messages,
|
|
122
|
+
error_type=fallback_error_type,
|
|
123
|
+
error=fallback_error,
|
|
124
|
+
enabled=log_diagnostic,
|
|
125
|
+
)
|
|
126
|
+
resolver_goal = None
|
|
127
|
+
|
|
128
|
+
if resolver_goal is None:
|
|
129
|
+
fallback_reason = fallback_reason or "invalid_structured_output"
|
|
130
|
+
normalized = _normalize_resolution(fallback, user_text, task_state)
|
|
131
|
+
resolver_kind_hint = ""
|
|
132
|
+
else:
|
|
133
|
+
resolution = _to_goal_resolution(resolver_goal, task_state)
|
|
134
|
+
normalized = _normalize_resolution(resolution, user_text, task_state)
|
|
135
|
+
resolver_kind_hint = resolver_goal.kind_hint or ""
|
|
136
|
+
_log_goal_resolver_decision(
|
|
137
|
+
normalized,
|
|
138
|
+
user_text,
|
|
139
|
+
task_state,
|
|
140
|
+
fallback_reason,
|
|
141
|
+
fallback_error_type,
|
|
142
|
+
fallback_error,
|
|
143
|
+
resolver_kind_hint=resolver_kind_hint,
|
|
144
|
+
enabled=log_diagnostic,
|
|
145
|
+
)
|
|
146
|
+
return normalized
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _resolver_messages_from_exchanges(user_text: str, task_state: TaskState) -> list[BaseMessage]:
|
|
150
|
+
return [
|
|
151
|
+
SystemMessage(content=_resolver_system_prompt()),
|
|
152
|
+
HumanMessage(content=_resolver_request_markdown(user_text, task_state)),
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _log_goal_resolver_decision(
|
|
157
|
+
resolution: GoalResolution,
|
|
158
|
+
user_text: str,
|
|
159
|
+
task_state: TaskState,
|
|
160
|
+
fallback_reason: str,
|
|
161
|
+
fallback_error_type: str,
|
|
162
|
+
fallback_error: str,
|
|
163
|
+
*,
|
|
164
|
+
resolver_kind_hint: str = "",
|
|
165
|
+
enabled: bool = True,
|
|
166
|
+
) -> None:
|
|
167
|
+
goal = resolution.goal
|
|
168
|
+
plan = resolution.plan
|
|
169
|
+
log_llm_diagnostic(
|
|
170
|
+
"goal_resolver_decision",
|
|
171
|
+
enabled=enabled,
|
|
172
|
+
intent=resolution.intent.type.value,
|
|
173
|
+
goal_type=goal_type_from_join(plan.join if plan is not None else None),
|
|
174
|
+
goal_desc=goal.desc if goal is not None else "",
|
|
175
|
+
plan_join=plan.join if plan is not None else "",
|
|
176
|
+
plan_leave=plan.leave if plan is not None and plan.leave is not None else "",
|
|
177
|
+
resolver_kind_hint=resolver_kind_hint,
|
|
178
|
+
fallback_reason=fallback_reason,
|
|
179
|
+
fallback_error_type=fallback_error_type,
|
|
180
|
+
fallback_error=fallback_error,
|
|
181
|
+
active_workflows=_active_workflow_names(task_state),
|
|
182
|
+
user_text=user_text,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _log_goal_resolver_exchange(
|
|
187
|
+
messages: list[BaseMessage],
|
|
188
|
+
*,
|
|
189
|
+
raw: object | None = None,
|
|
190
|
+
error_type: str = "",
|
|
191
|
+
error: str = "",
|
|
192
|
+
enabled: bool = True,
|
|
193
|
+
) -> None:
|
|
194
|
+
response: dict[str, Any] = {}
|
|
195
|
+
if error_type or error:
|
|
196
|
+
response["error_type"] = error_type
|
|
197
|
+
response["error"] = error
|
|
198
|
+
else:
|
|
199
|
+
response["raw"] = _raw_response_for_log(raw)
|
|
200
|
+
log_llm_diagnostic(
|
|
201
|
+
"goal_resolver_exchange",
|
|
202
|
+
enabled=enabled,
|
|
203
|
+
request={"messages": [serialize_llm_message(message) for message in messages]},
|
|
204
|
+
response=response,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _raw_response_for_log(value: object) -> object:
|
|
209
|
+
if isinstance(value, BaseModel):
|
|
210
|
+
return value.model_dump(mode="json")
|
|
211
|
+
if isinstance(value, AIMessage):
|
|
212
|
+
return {
|
|
213
|
+
"content": value.content,
|
|
214
|
+
"tool_calls": getattr(value, "tool_calls", None) or [],
|
|
215
|
+
"usage_metadata": getattr(value, "usage_metadata", None) or {},
|
|
216
|
+
}
|
|
217
|
+
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
218
|
+
return value
|
|
219
|
+
if isinstance(value, (dict, list, tuple)):
|
|
220
|
+
return value
|
|
221
|
+
return repr(value)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _truncate_error_text(value: str, limit: int = 2000) -> str:
|
|
225
|
+
text = " ".join(value.split())
|
|
226
|
+
if len(text) <= limit:
|
|
227
|
+
return text
|
|
228
|
+
return text[:limit].rstrip() + "..."
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _resolver_system_prompt() -> str:
|
|
232
|
+
return (
|
|
233
|
+
"Resolve this turn into intent, goal, workflow, and kind_hint.\n"
|
|
234
|
+
"Read the Markdown request. Return only structured data matching the schema at the end."
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _resolver_request_markdown(user_text: str, task_state: TaskState) -> str:
|
|
239
|
+
recent_content = _recent_exchanges_content(task_state)
|
|
240
|
+
active = ", ".join(_active_workflow_names(task_state)) or "none"
|
|
241
|
+
goal = task_state.current_goal.label if task_state.current_goal is not None else "none"
|
|
242
|
+
return "\n".join([
|
|
243
|
+
"# Goal Resolver Request",
|
|
244
|
+
"",
|
|
245
|
+
"## Current State",
|
|
246
|
+
"",
|
|
247
|
+
f"- intent: {task_state.current_intent.value}",
|
|
248
|
+
f"- goal: {goal}",
|
|
249
|
+
f"- active workflows: {active}",
|
|
250
|
+
"",
|
|
251
|
+
"## Recent Conversation Content",
|
|
252
|
+
"",
|
|
253
|
+
"```text",
|
|
254
|
+
recent_content,
|
|
255
|
+
"```",
|
|
256
|
+
"",
|
|
257
|
+
"## Current User Content",
|
|
258
|
+
"",
|
|
259
|
+
"```text",
|
|
260
|
+
user_text,
|
|
261
|
+
"```",
|
|
262
|
+
"",
|
|
263
|
+
"## Available Workflows",
|
|
264
|
+
"",
|
|
265
|
+
"- brainstorm: Confirm requirements and design, get user approval",
|
|
266
|
+
"- debug: Locate root cause and confirm fix direction",
|
|
267
|
+
"- design: Produce a structured document that passes the reader test",
|
|
268
|
+
"- feedback: Verify and implement valid review feedback",
|
|
269
|
+
"- plan: Produce an executable implementation plan, get user approval",
|
|
270
|
+
"- review: Initiate structured code review request and collect verdict",
|
|
271
|
+
"- tdd: Complete implementation via TDD cycle, all tests green",
|
|
272
|
+
"- verify: Prove changes reach expected state with reproducible evidence",
|
|
273
|
+
"",
|
|
274
|
+
"## Return Fields",
|
|
275
|
+
"",
|
|
276
|
+
'- intent: "coding" for codebase/workspace work; "general" for non-code conversation.',
|
|
277
|
+
"- goal: short user-language summary, or null when no workflow should start.",
|
|
278
|
+
"- workflow: workflow to start, or null. Must be set exactly when goal is set.",
|
|
279
|
+
"- kind_hint: optional semantic hint such as review/debug/feature/inspect. Advisory only; never overrides workflow.",
|
|
280
|
+
"",
|
|
281
|
+
"## ResolverGoal Schema",
|
|
282
|
+
"",
|
|
283
|
+
"- intent: 'coding' | 'general'",
|
|
284
|
+
"- goal: null or string (short summary of the user's request in their language, 1-2 sentences)",
|
|
285
|
+
"- workflow: null or one of [brainstorm, debug, design, feedback, plan, review, tdd, verify]",
|
|
286
|
+
"- kind_hint: null or string (non-authoritative semantic hint; not used for routing)",
|
|
287
|
+
])
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
_ALLOWED_JOIN_NODES = {"debug", "brainstorm", "design", "plan", "tdd", "review", "feedback", "verify"}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _coerce_resolution(value: object) -> ResolverGoal | None:
|
|
294
|
+
if isinstance(value, ResolverGoal):
|
|
295
|
+
return value
|
|
296
|
+
if isinstance(value, GoalResolution):
|
|
297
|
+
return _resolver_goal_from_goal_resolution(value)
|
|
298
|
+
if isinstance(value, AIMessage):
|
|
299
|
+
value = value.content
|
|
300
|
+
if isinstance(value, str):
|
|
301
|
+
try:
|
|
302
|
+
value = json.loads(value)
|
|
303
|
+
except json.JSONDecodeError:
|
|
304
|
+
return None
|
|
305
|
+
if isinstance(value, dict) and "parsed" in value:
|
|
306
|
+
return _coerce_resolution(value.get("parsed"))
|
|
307
|
+
if isinstance(value, dict):
|
|
308
|
+
if "workflow" not in value and ("plan" in value or isinstance(value.get("goal"), dict) or isinstance(value.get("intent"), dict)):
|
|
309
|
+
value = _legacy_dict_to_resolver_dict(value)
|
|
310
|
+
try:
|
|
311
|
+
return ResolverGoal.model_validate(value)
|
|
312
|
+
except ValueError:
|
|
313
|
+
return None
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _to_goal_resolution(resolver: ResolverGoal, task_state: TaskState) -> GoalResolution:
|
|
318
|
+
del task_state
|
|
319
|
+
intent_type = TaskIntent(resolver.intent)
|
|
320
|
+
if resolver.goal is None or resolver.workflow is None:
|
|
321
|
+
return GoalResolution(intent=IntentResolution(type=intent_type), goal=None, plan=None)
|
|
322
|
+
return GoalResolution(
|
|
323
|
+
intent=IntentResolution(type=intent_type),
|
|
324
|
+
goal=GoalSpec(desc=resolver.goal),
|
|
325
|
+
plan=PlanResolution(join=resolver.workflow, leave=None),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _resolver_goal_from_goal_resolution(resolution: GoalResolution) -> ResolverGoal | None:
|
|
330
|
+
goal = resolution.goal
|
|
331
|
+
plan = resolution.plan
|
|
332
|
+
return ResolverGoal(
|
|
333
|
+
intent=resolution.intent.type.value,
|
|
334
|
+
goal=goal.desc if goal is not None else None,
|
|
335
|
+
workflow=plan.join if plan is not None else None,
|
|
336
|
+
kind_hint=None,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _legacy_dict_to_resolver_dict(value: dict) -> dict:
|
|
341
|
+
intent_value = value.get("intent")
|
|
342
|
+
intent = intent_value.get("type") if isinstance(intent_value, dict) else intent_value
|
|
343
|
+
goal_value = value.get("goal")
|
|
344
|
+
if isinstance(goal_value, dict):
|
|
345
|
+
goal = goal_value.get("desc")
|
|
346
|
+
kind_hint = goal_value.get("type")
|
|
347
|
+
else:
|
|
348
|
+
goal = goal_value
|
|
349
|
+
kind_hint = value.get("kind_hint")
|
|
350
|
+
plan_value = value.get("plan")
|
|
351
|
+
workflow = plan_value.get("join") if isinstance(plan_value, dict) else value.get("workflow")
|
|
352
|
+
return {
|
|
353
|
+
"intent": intent or "general",
|
|
354
|
+
"goal": goal,
|
|
355
|
+
"workflow": workflow,
|
|
356
|
+
"kind_hint": kind_hint,
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _normalize_resolution(
|
|
361
|
+
resolution: GoalResolution,
|
|
362
|
+
user_text: str,
|
|
363
|
+
task_state: TaskState,
|
|
364
|
+
) -> GoalResolution:
|
|
365
|
+
plan = resolution.plan
|
|
366
|
+
if plan is not None:
|
|
367
|
+
if plan.join and plan.join not in _ALLOWED_JOIN_NODES:
|
|
368
|
+
plan = None
|
|
369
|
+
elif plan.leave and plan.leave not in DEFAULT_WORKFLOW_DAG.nodes:
|
|
370
|
+
plan = PlanResolution(join=plan.join, leave=None)
|
|
371
|
+
|
|
372
|
+
if resolution.intent.type == TaskIntent.GENERAL:
|
|
373
|
+
current_join = _current_active_join(task_state)
|
|
374
|
+
if current_join and task_state.current_goal is not None:
|
|
375
|
+
return GoalResolution(
|
|
376
|
+
intent=IntentResolution(type=TaskIntent.CODING),
|
|
377
|
+
goal=task_state.current_goal,
|
|
378
|
+
plan=PlanResolution(join=current_join, leave=None),
|
|
379
|
+
)
|
|
380
|
+
return GoalResolution(
|
|
381
|
+
intent=resolution.intent,
|
|
382
|
+
goal=None,
|
|
383
|
+
plan=None,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
goal = resolution.goal
|
|
387
|
+
if goal is not None and (plan is None or not plan.join):
|
|
388
|
+
current_join = _current_active_join(task_state)
|
|
389
|
+
if current_join and task_state.current_goal is not None and _is_short_continuation(user_text):
|
|
390
|
+
return GoalResolution(
|
|
391
|
+
intent=resolution.intent,
|
|
392
|
+
goal=task_state.current_goal,
|
|
393
|
+
plan=PlanResolution(join=current_join, leave=None),
|
|
394
|
+
)
|
|
395
|
+
goal = None
|
|
396
|
+
plan = None
|
|
397
|
+
|
|
398
|
+
return GoalResolution(
|
|
399
|
+
intent=resolution.intent,
|
|
400
|
+
goal=goal,
|
|
401
|
+
plan=plan,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _recent_exchanges_content(task_state: TaskState) -> str:
|
|
406
|
+
blocks: list[str] = []
|
|
407
|
+
for index, exchange in enumerate(task_state.recent_exchanges, start=1):
|
|
408
|
+
parts = [part for part in (exchange.user_text.strip(), exchange.assistant_text.strip()) if part]
|
|
409
|
+
if not parts:
|
|
410
|
+
continue
|
|
411
|
+
blocks.append(f"### Content {index}\n\n" + "\n\n".join(parts))
|
|
412
|
+
return "\n\n".join(blocks)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
_SHORT_CONTINUATION_TEXTS = {"ok", "okay", "continue", "go on", "yes", "y", "改", "继续", "继续改", "好", "好的"}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _is_short_continuation(user_text: str) -> bool:
|
|
419
|
+
text = user_text.strip().lower()
|
|
420
|
+
if text in _SHORT_CONTINUATION_TEXTS:
|
|
421
|
+
return True
|
|
422
|
+
return len(text) <= 8 and _contains_any(text, ("继续", "改", "ok"))
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _current_active_join(task_state: TaskState) -> str:
|
|
426
|
+
if task_state.workflow_route is not None and task_state.workflow_route.join:
|
|
427
|
+
return task_state.workflow_route.join
|
|
428
|
+
for name, run in task_state.workflow_runs.items():
|
|
429
|
+
if getattr(run.status, "value", run.status) == "active":
|
|
430
|
+
return name
|
|
431
|
+
return ""
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _active_workflow_names(task_state: TaskState) -> list[str]:
|
|
435
|
+
return [
|
|
436
|
+
name
|
|
437
|
+
for name, run in task_state.workflow_runs.items()
|
|
438
|
+
if getattr(run.status, "value", run.status) == "active"
|
|
439
|
+
]
|
|
@@ -48,7 +48,7 @@ class GraphCompactionMixin:
|
|
|
48
48
|
self._in_turn_compaction_count = count
|
|
49
49
|
if count > 2:
|
|
50
50
|
return None
|
|
51
|
-
|
|
51
|
+
result = await _compaction_component_for(self).compact_for_live_state(
|
|
52
52
|
messages,
|
|
53
53
|
force=True,
|
|
54
54
|
ask=False,
|
|
@@ -56,6 +56,10 @@ class GraphCompactionMixin:
|
|
|
56
56
|
run_compaction_agent=self._run_compaction_agent,
|
|
57
57
|
persist_compaction=self._persist_compaction,
|
|
58
58
|
)
|
|
59
|
+
if result is not None:
|
|
60
|
+
self._file_read_coverage.clear()
|
|
61
|
+
self._file_mtimes.clear()
|
|
62
|
+
return result
|
|
59
63
|
|
|
60
64
|
async def _ask_compact(self: GraphCompactionHost, total_tokens: int) -> bool:
|
|
61
65
|
return await _compaction_component_for(self).ask_compact(total_tokens)
|
|
@@ -64,11 +68,15 @@ class GraphCompactionMixin:
|
|
|
64
68
|
await _compaction_component_for(self).persist_compaction(head_messages)
|
|
65
69
|
|
|
66
70
|
async def _compact_session_history(self: GraphCompactionHost, *, force: bool = True) -> bool:
|
|
67
|
-
|
|
71
|
+
result = await _compaction_component_for(self).compact_session_history(
|
|
68
72
|
force=force,
|
|
69
73
|
run_compaction_agent=self._run_compaction_agent,
|
|
70
74
|
persist_compaction=self._persist_compaction,
|
|
71
75
|
)
|
|
76
|
+
if result:
|
|
77
|
+
self._file_read_coverage.clear()
|
|
78
|
+
self._file_mtimes.clear()
|
|
79
|
+
return result
|
|
72
80
|
|
|
73
81
|
async def _run_compaction_agent(
|
|
74
82
|
self: GraphCompactionHost,
|
|
@@ -20,10 +20,10 @@ from voidx.llm.compaction import (
|
|
|
20
20
|
CompactionService,
|
|
21
21
|
)
|
|
22
22
|
from voidx.llm.service import resolve_protocol
|
|
23
|
+
from voidx.logging.tool_log import log_tool_event
|
|
23
24
|
from voidx.llm.usage import estimate_context_tokens, estimate_message_tokens, extract_token_usage
|
|
24
25
|
from voidx.memory.service import save_context_frame_from_messages
|
|
25
26
|
from voidx.runtime.ui import StatusFinished, StatusUpdated, StreamingRenderer
|
|
26
|
-
from voidx.skills.service import is_skill_context_content
|
|
27
27
|
from voidx.workflow.service import is_workflow_context_content
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
@@ -437,6 +437,10 @@ class GraphCompactionCoordinator:
|
|
|
437
437
|
type(assistant_msg).__name__,
|
|
438
438
|
_content_type_summary(getattr(assistant_msg, "content", None)),
|
|
439
439
|
)
|
|
440
|
+
log_tool_event(
|
|
441
|
+
"compaction_empty_result",
|
|
442
|
+
message=f"Compaction agent returned empty text: message_type={type(assistant_msg).__name__} content_type={_content_type_summary(getattr(assistant_msg, 'content', None))}",
|
|
443
|
+
)
|
|
440
444
|
return None
|
|
441
445
|
|
|
442
446
|
|
|
@@ -468,10 +472,9 @@ def _runtime_prefix(messages: list[BaseMessage]) -> list[BaseMessage]:
|
|
|
468
472
|
continue
|
|
469
473
|
prefix.append(message)
|
|
470
474
|
continue
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
):
|
|
475
|
+
# Back-compat only: sessions created before workflow runtime moved into
|
|
476
|
+
# the stable SystemMessage may still have persisted workflow prefixes.
|
|
477
|
+
if isinstance(message, HumanMessage) and is_workflow_context_content(message.content):
|
|
475
478
|
prefix.append(message)
|
|
476
479
|
continue
|
|
477
480
|
break
|
|
@@ -78,7 +78,8 @@ class GraphToolExecutionHost(Protocol):
|
|
|
78
78
|
_workspace: str
|
|
79
79
|
_app: Any | None
|
|
80
80
|
_debug: bool
|
|
81
|
-
_file_mtimes: dict[str,
|
|
81
|
+
_file_mtimes: dict[str, dict[str, int]]
|
|
82
|
+
_file_read_coverage: dict[str, dict]
|
|
82
83
|
_turn_node: Any | None
|
|
83
84
|
_current_messages: list[BaseMessage] | None
|
|
84
85
|
_permission: PermissionService
|