voidx 3.0.0__tar.gz → 3.1.1__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.1}/PKG-INFO +4 -1
- {voidx-3.0.0 → voidx-3.1.1}/pyproject.toml +3 -2
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/__init__.py +1 -1
- voidx-3.1.1/src/voidx/agent/agents.py +77 -0
- voidx-3.1.1/src/voidx/agent/goal_resolver.py +443 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/compaction.py +28 -12
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/compaction_coordinator.py +202 -25
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/contracts.py +15 -5
- voidx-3.1.1/src/voidx/agent/graph/convergence.py +87 -0
- voidx-3.1.1/src/voidx/agent/graph/core/__init__.py +9 -0
- voidx-3.1.1/src/voidx/agent/graph/core/_voidx_graph.py +460 -0
- voidx-3.1.1/src/voidx/agent/graph/core/helpers.py +126 -0
- voidx-3.1.1/src/voidx/agent/graph/core/llm.py +439 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/permissions.py +2 -14
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/run_loop.py +14 -17
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/runtime_guards.py +20 -23
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/session_runtime.py +15 -5
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/streaming.py +217 -28
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/subagent.py +55 -107
- voidx-3.1.1/src/voidx/agent/graph/tool_executor/__init__.py +8 -0
- voidx-3.1.1/src/voidx/agent/graph/tool_executor/executor.py +355 -0
- voidx-3.1.1/src/voidx/agent/graph/tool_executor/guards.py +150 -0
- voidx-3.1.1/src/voidx/agent/graph/tool_executor/helpers.py +347 -0
- voidx-3.1.1/src/voidx/agent/graph/tool_executor/types.py +86 -0
- voidx-3.1.1/src/voidx/agent/graph/tool_executor/ui.py +143 -0
- voidx-3.1.1/src/voidx/agent/graph/tool_executor/workflow.py +314 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/turn_runner.py +105 -21
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/wiring.py +2 -0
- voidx-3.1.1/src/voidx/agent/graph/workflow_utils.py +33 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/message_rows.py +3 -0
- voidx-3.1.1/src/voidx/agent/prompts.py +177 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/runtime_context.py +111 -275
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/handler.py +46 -6
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/task_state.py +5 -7
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/todo_state.py +1 -0
- voidx-3.1.1/src/voidx/agent/tool_call_ids.py +33 -0
- voidx-3.1.1/src/voidx/agent/tool_exchange_sanitizer.py +158 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/models.py +11 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/catalog.py +6 -2
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/compaction.py +144 -8
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/instruction.py +2 -14
- voidx-3.1.1/src/voidx/llm/message_status.py +12 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/provider.py +66 -3
- voidx-3.1.1/src/voidx/logging/__init__.py +6 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/logging/request_log.py +31 -7
- voidx-3.1.1/src/voidx/logging/tool_log.py +75 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/base.py +4 -2
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/stdio_transport.py +3 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/jsonl_store.py +7 -5
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/runtime_state.py +2 -21
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/session.py +5 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/store.py +6 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/engine.py +4 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/evaluate.py +1 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/rules.py +19 -15
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/service.py +1 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/__init__.py +6 -9
- voidx-3.1.1/src/voidx/runtime/intent.py +46 -0
- voidx-3.1.1/src/voidx/runtime/task_state.py +229 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/ui.py +2 -2
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/selfupdate.py +2 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/context.py +0 -52
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/service.py +0 -2
- voidx-3.1.1/src/voidx/tools/agent.py +304 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/base.py +79 -11
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/bash.py +59 -16
- voidx-3.1.1/src/voidx/tools/bash_router.py +1222 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/clarify.py +11 -55
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/compact_context.py +1 -1
- voidx-3.1.1/src/voidx/tools/file_ops/__init__.py +19 -0
- voidx-3.1.1/src/voidx/tools/file_ops/edit_execute.py +559 -0
- voidx-3.1.1/src/voidx/tools/file_ops/edit_resolve.py +325 -0
- voidx-3.1.1/src/voidx/tools/file_ops/file.py +184 -0
- voidx-3.1.1/src/voidx/tools/file_ops/line.py +150 -0
- voidx-3.1.1/src/voidx/tools/file_ops/read.py +232 -0
- voidx-3.1.1/src/voidx/tools/file_ops/types.py +79 -0
- voidx-3.1.1/src/voidx/tools/file_ops/write.py +89 -0
- voidx-3.1.1/src/voidx/tools/file_state.py +353 -0
- voidx-3.1.1/src/voidx/tools/git.py +1335 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/load_doc_template.py +6 -5
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/load_skills.py +4 -3
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/lsp.py +6 -3
- voidx-3.1.1/src/voidx/tools/plan_checkpoint.py +254 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/registry.py +4 -4
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/repomap.py +25 -6
- voidx-3.1.1/src/voidx/tools/search.py +299 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/task_status.py +1 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/task_tracker.py +3 -6
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/todo.py +1 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/webfetch.py +111 -15
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/websearch.py +3 -2
- voidx-3.1.1/src/voidx/tools/workflow.py +553 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/commands.py +3 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/capture.py +2 -5
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/console/app.py +10 -7
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/console/formatting.py +1 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/display_policy.py +13 -9
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/nodes.py +6 -6
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/events/consumers.py +1 -3
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/events/schema.py +0 -4
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/types.py +1 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/session.py +1 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/clipboard_image.py +1 -6
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/file_picker.py +10 -6
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/choice_mixin.py +15 -4
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/input.py +2 -2
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/overlays.py +4 -2
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/panels.py +1 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_activity.py +12 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_status.py +7 -10
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/__init__.py +0 -4
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/auto_advance.py +2 -2
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/context.py +1 -4
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/dag.py +4 -14
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/nodes.py +71 -70
- voidx-3.1.1/src/voidx/workflow/policy.py +82 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/reconcile.py +47 -5
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/render.py +0 -23
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/runtime.py +2 -6
- voidx-3.1.1/src/voidx/workflow/service.py +143 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/types.py +1 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/PKG-INFO +4 -1
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/SOURCES.txt +28 -49
- {voidx-3.0.0 → voidx-3.1.1}/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.1}/README.md +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/setup.cfg +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/attachments.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/runtime.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/session_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/title_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/todo_events.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/tool_execution.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/topology.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/transcript_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/turn_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/code_ide.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/guide.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/host.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/init.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/lsp.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/mcp.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/model.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/profile.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/runtime.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/session.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/skills.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/upgrade.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/state.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/tool_filters.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/tool_messages.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/tool_result_storage.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/enums.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/permissions.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_agent.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_api_keys.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_code_ide.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_custom.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_mcp.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_permissions.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_skills.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_update.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_utils.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_web.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/api-doc.md +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/prd.md +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/readme.md +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/rfc.md +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/tech-design.md +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/diffing.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/context.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/message_markers.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/service.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/usage.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/client.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/config.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/detector.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/detector_data.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/errors.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/manager.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/service.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/main.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/errors.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/http_transport.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/sse_transport.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/manager.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/tool.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp_servers/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp_servers/web.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/cleanup.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/context_frames.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/model_profiles.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/service.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/subagents.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/transcript.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/context.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/sandbox.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/wildcard.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/attachments.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/reference_tokens.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/todo.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/ui_port.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/references.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/registry.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/service.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/web_content.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/web_mcp.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/frontend.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/gateway/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/gateway/bootstrap.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/gateway/server.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/gateway/session.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/agent_display.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/browse.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/console/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/console/streaming.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/diff.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/agent_placeholder.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/app.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/formatting.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/nodes_permission.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/nodes_startup.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/nodes_status.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/state.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/status.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/stream.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/todo.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/events/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/events/bus.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/tree.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/commands.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/envelope.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/requests.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/transcript.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/attachment_tokens.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/clipboard_text.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/code_ide.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/skill_picker.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/transcript.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/__init__.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/activity.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/app.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/clipboard_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/helpers.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/parser.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_frame.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_input.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_todo.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/renderer.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/state.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/terminal_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/text_prompt_mixin.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/route.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/schema.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/dependency_links.txt +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/entry_points.txt +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/top_level.txt +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/tests/test_install_sh.py +0 -0
- {voidx-3.0.0 → voidx-3.1.1}/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.1
|
|
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.1"
|
|
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,443 @@
|
|
|
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
|
+
"You are a goal resolver. Classify the user's current turn into intent, goal, workflow, and kind_hint.\n"
|
|
234
|
+
"\n"
|
|
235
|
+
"## Output Schema\n"
|
|
236
|
+
"\n"
|
|
237
|
+
"Return a JSON object matching this template:\n"
|
|
238
|
+
"\n"
|
|
239
|
+
"{\n"
|
|
240
|
+
' "intent": "coding" or "general",\n'
|
|
241
|
+
' "goal": null or "<short summary of the user\'s request in their language, 1-2 sentences>",\n'
|
|
242
|
+
' "workflow": null or "<one of the workflows listed below>",\n'
|
|
243
|
+
' "kind_hint": null or "<semantic hint: review | debug | feature | inspect | refactor | test | docs>"\n'
|
|
244
|
+
"}\n"
|
|
245
|
+
"\n"
|
|
246
|
+
"## Field Rules\n"
|
|
247
|
+
"\n"
|
|
248
|
+
'- **intent**: "coding" for codebase/workspace work; "general" for non-code conversation.\n'
|
|
249
|
+
"- **goal**: Short user-language summary when a workflow should start; null otherwise. Must be set exactly when workflow is set, and null exactly when workflow is null.\n"
|
|
250
|
+
"- **workflow**: The workflow to start, or null. Must be set exactly when goal is set.\n"
|
|
251
|
+
"- **kind_hint**: Optional semantic hint. Advisory only; never overrides workflow selection.\n"
|
|
252
|
+
"\n"
|
|
253
|
+
"## Available Workflows\n"
|
|
254
|
+
"\n"
|
|
255
|
+
"- brainstorm: Confirm requirements and design, get user approval\n"
|
|
256
|
+
"- debug: Locate root cause and confirm fix direction\n"
|
|
257
|
+
"- design: Produce a structured document that passes the reader test\n"
|
|
258
|
+
"- feedback: Verify and implement valid review feedback\n"
|
|
259
|
+
"- plan: Produce an executable implementation plan, get user approval\n"
|
|
260
|
+
"- review: Initiate structured code review request and collect verdict\n"
|
|
261
|
+
"- tdd: Complete implementation via TDD cycle, all tests green\n"
|
|
262
|
+
"- verify: Prove changes reach expected state with reproducible evidence\n"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _resolver_request_markdown(user_text: str, task_state: TaskState) -> str:
|
|
267
|
+
recent_content = _recent_exchanges_content(task_state)
|
|
268
|
+
active = ", ".join(_active_workflow_names(task_state)) or "none"
|
|
269
|
+
goal = task_state.current_goal.label if task_state.current_goal is not None else "none"
|
|
270
|
+
sections = [
|
|
271
|
+
"# Context",
|
|
272
|
+
"",
|
|
273
|
+
f"- intent: {task_state.current_intent.value}",
|
|
274
|
+
f"- goal: {goal}",
|
|
275
|
+
f"- active workflows: {active}",
|
|
276
|
+
"",
|
|
277
|
+
"# Recent Conversation",
|
|
278
|
+
"",
|
|
279
|
+
recent_content,
|
|
280
|
+
"",
|
|
281
|
+
"# Current User Question",
|
|
282
|
+
"",
|
|
283
|
+
user_text,
|
|
284
|
+
]
|
|
285
|
+
return "\n".join(sections)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
_ALLOWED_JOIN_NODES = {"debug", "brainstorm", "design", "plan", "tdd", "review", "feedback", "verify"}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _coerce_resolution(value: object) -> ResolverGoal | None:
|
|
292
|
+
if isinstance(value, ResolverGoal):
|
|
293
|
+
return value
|
|
294
|
+
if isinstance(value, GoalResolution):
|
|
295
|
+
return _resolver_goal_from_goal_resolution(value)
|
|
296
|
+
if isinstance(value, AIMessage):
|
|
297
|
+
value = value.content
|
|
298
|
+
if isinstance(value, str):
|
|
299
|
+
try:
|
|
300
|
+
value = json.loads(value)
|
|
301
|
+
except json.JSONDecodeError:
|
|
302
|
+
return None
|
|
303
|
+
if isinstance(value, dict) and "parsed" in value:
|
|
304
|
+
return _coerce_resolution(value.get("parsed"))
|
|
305
|
+
if isinstance(value, dict):
|
|
306
|
+
if "workflow" not in value and ("plan" in value or isinstance(value.get("goal"), dict) or isinstance(value.get("intent"), dict)):
|
|
307
|
+
value = _legacy_dict_to_resolver_dict(value)
|
|
308
|
+
try:
|
|
309
|
+
return ResolverGoal.model_validate(value)
|
|
310
|
+
except ValueError:
|
|
311
|
+
return None
|
|
312
|
+
return None
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _to_goal_resolution(resolver: ResolverGoal, task_state: TaskState) -> GoalResolution:
|
|
316
|
+
del task_state
|
|
317
|
+
intent_type = TaskIntent(resolver.intent)
|
|
318
|
+
if resolver.goal is None or resolver.workflow is None:
|
|
319
|
+
return GoalResolution(intent=IntentResolution(type=intent_type), goal=None, plan=None)
|
|
320
|
+
return GoalResolution(
|
|
321
|
+
intent=IntentResolution(type=intent_type),
|
|
322
|
+
goal=GoalSpec(desc=resolver.goal),
|
|
323
|
+
plan=PlanResolution(join=resolver.workflow, leave=None),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _resolver_goal_from_goal_resolution(resolution: GoalResolution) -> ResolverGoal | None:
|
|
328
|
+
goal = resolution.goal
|
|
329
|
+
plan = resolution.plan
|
|
330
|
+
return ResolverGoal(
|
|
331
|
+
intent=resolution.intent.type.value,
|
|
332
|
+
goal=goal.desc if goal is not None else None,
|
|
333
|
+
workflow=plan.join if plan is not None else None,
|
|
334
|
+
kind_hint=None,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _legacy_dict_to_resolver_dict(value: dict) -> dict:
|
|
339
|
+
intent_value = value.get("intent")
|
|
340
|
+
intent = intent_value.get("type") if isinstance(intent_value, dict) else intent_value
|
|
341
|
+
goal_value = value.get("goal")
|
|
342
|
+
if isinstance(goal_value, dict):
|
|
343
|
+
goal = goal_value.get("desc")
|
|
344
|
+
kind_hint = goal_value.get("type")
|
|
345
|
+
else:
|
|
346
|
+
goal = goal_value
|
|
347
|
+
kind_hint = value.get("kind_hint")
|
|
348
|
+
plan_value = value.get("plan")
|
|
349
|
+
workflow = plan_value.get("join") if isinstance(plan_value, dict) else value.get("workflow")
|
|
350
|
+
return {
|
|
351
|
+
"intent": intent or "general",
|
|
352
|
+
"goal": goal,
|
|
353
|
+
"workflow": workflow,
|
|
354
|
+
"kind_hint": kind_hint,
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _normalize_resolution(
|
|
359
|
+
resolution: GoalResolution,
|
|
360
|
+
user_text: str,
|
|
361
|
+
task_state: TaskState,
|
|
362
|
+
) -> GoalResolution:
|
|
363
|
+
plan = resolution.plan
|
|
364
|
+
if plan is not None:
|
|
365
|
+
if plan.join and plan.join not in _ALLOWED_JOIN_NODES:
|
|
366
|
+
plan = None
|
|
367
|
+
elif plan.leave and plan.leave not in DEFAULT_WORKFLOW_DAG.nodes:
|
|
368
|
+
plan = PlanResolution(join=plan.join, leave=None)
|
|
369
|
+
|
|
370
|
+
if resolution.intent.type == TaskIntent.GENERAL:
|
|
371
|
+
current_join = _current_active_join(task_state)
|
|
372
|
+
if current_join and task_state.current_goal is not None:
|
|
373
|
+
return GoalResolution(
|
|
374
|
+
intent=IntentResolution(type=TaskIntent.CODING),
|
|
375
|
+
goal=task_state.current_goal,
|
|
376
|
+
plan=PlanResolution(join=current_join, leave=None),
|
|
377
|
+
)
|
|
378
|
+
return GoalResolution(
|
|
379
|
+
intent=resolution.intent,
|
|
380
|
+
goal=None,
|
|
381
|
+
plan=None,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
goal = resolution.goal
|
|
385
|
+
if goal is not None and (plan is None or not plan.join):
|
|
386
|
+
current_join = _current_active_join(task_state)
|
|
387
|
+
if current_join and task_state.current_goal is not None and _is_short_continuation(user_text):
|
|
388
|
+
return GoalResolution(
|
|
389
|
+
intent=resolution.intent,
|
|
390
|
+
goal=task_state.current_goal,
|
|
391
|
+
plan=PlanResolution(join=current_join, leave=None),
|
|
392
|
+
)
|
|
393
|
+
goal = None
|
|
394
|
+
plan = None
|
|
395
|
+
|
|
396
|
+
return GoalResolution(
|
|
397
|
+
intent=resolution.intent,
|
|
398
|
+
goal=goal,
|
|
399
|
+
plan=plan,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _recent_exchanges_content(task_state: TaskState) -> str:
|
|
404
|
+
blocks: list[str] = []
|
|
405
|
+
for index, exchange in enumerate(task_state.recent_exchanges, start=1):
|
|
406
|
+
lines: list[str] = [f"## Turn {index}", ""]
|
|
407
|
+
user_text = exchange.user_text.strip()
|
|
408
|
+
assistant_text = exchange.assistant_text.strip()
|
|
409
|
+
if user_text:
|
|
410
|
+
lines.append(f"**Human**: {user_text}")
|
|
411
|
+
if assistant_text:
|
|
412
|
+
lines.append(f"**Assistant**: {assistant_text}")
|
|
413
|
+
if len(lines) <= 2:
|
|
414
|
+
continue
|
|
415
|
+
blocks.append("\n".join(lines))
|
|
416
|
+
return "\n\n".join(blocks)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
_SHORT_CONTINUATION_TEXTS = {"ok", "okay", "continue", "go on", "yes", "y", "改", "继续", "继续改", "好", "好的"}
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _is_short_continuation(user_text: str) -> bool:
|
|
423
|
+
text = user_text.strip().lower()
|
|
424
|
+
if text in _SHORT_CONTINUATION_TEXTS:
|
|
425
|
+
return True
|
|
426
|
+
return len(text) <= 8 and _contains_any(text, ("继续", "改", "ok"))
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _current_active_join(task_state: TaskState) -> str:
|
|
430
|
+
if task_state.workflow_route is not None and task_state.workflow_route.join:
|
|
431
|
+
return task_state.workflow_route.join
|
|
432
|
+
for name, run in task_state.workflow_runs.items():
|
|
433
|
+
if getattr(run.status, "value", run.status) == "active":
|
|
434
|
+
return name
|
|
435
|
+
return ""
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _active_workflow_names(task_state: TaskState) -> list[str]:
|
|
439
|
+
return [
|
|
440
|
+
name
|
|
441
|
+
for name, run in task_state.workflow_runs.items()
|
|
442
|
+
if getattr(run.status, "value", run.status) == "active"
|
|
443
|
+
]
|
|
@@ -4,7 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from voidx.agent.graph.compaction_coordinator import
|
|
7
|
+
from voidx.agent.graph.compaction_coordinator import (
|
|
8
|
+
CompactionResult,
|
|
9
|
+
GraphCompactionCoordinator,
|
|
10
|
+
PreflightCompactionResult,
|
|
11
|
+
)
|
|
8
12
|
|
|
9
13
|
if TYPE_CHECKING:
|
|
10
14
|
from voidx.agent.graph.contracts import GraphCompactionHost
|
|
@@ -30,32 +34,40 @@ class GraphCompactionMixin:
|
|
|
30
34
|
*,
|
|
31
35
|
force: bool = False,
|
|
32
36
|
ask: bool = True,
|
|
37
|
+
preflight: bool = False,
|
|
33
38
|
) -> tuple[list | None, str | None]:
|
|
34
39
|
return await _compaction_component_for(self).maybe_compact(
|
|
35
40
|
messages,
|
|
36
41
|
session_msgs,
|
|
37
42
|
force=force,
|
|
38
43
|
ask=ask,
|
|
44
|
+
preflight=preflight,
|
|
39
45
|
run_compaction_agent=self._run_compaction_agent,
|
|
40
46
|
persist_compaction=self._persist_compaction,
|
|
41
47
|
)
|
|
42
48
|
|
|
43
|
-
async def
|
|
49
|
+
async def _preflight_compact_if_needed(
|
|
44
50
|
self: GraphCompactionHost,
|
|
45
51
|
messages: list,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
session_msgs: list | None = None,
|
|
53
|
+
*,
|
|
54
|
+
force: bool = False,
|
|
55
|
+
reason: str = "threshold",
|
|
56
|
+
ask: bool = False,
|
|
57
|
+
) -> tuple[CompactionResult | None, PreflightCompactionResult]:
|
|
58
|
+
result, preflight_result = await _compaction_component_for(self).preflight_compact_if_needed(
|
|
52
59
|
messages,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
session_msgs,
|
|
61
|
+
force=force,
|
|
62
|
+
reason=reason,
|
|
63
|
+
ask=ask,
|
|
56
64
|
run_compaction_agent=self._run_compaction_agent,
|
|
57
65
|
persist_compaction=self._persist_compaction,
|
|
58
66
|
)
|
|
67
|
+
if result is not None:
|
|
68
|
+
self._file_read_coverage.clear()
|
|
69
|
+
self._file_mtimes.clear()
|
|
70
|
+
return result, preflight_result
|
|
59
71
|
|
|
60
72
|
async def _ask_compact(self: GraphCompactionHost, total_tokens: int) -> bool:
|
|
61
73
|
return await _compaction_component_for(self).ask_compact(total_tokens)
|
|
@@ -64,11 +76,15 @@ class GraphCompactionMixin:
|
|
|
64
76
|
await _compaction_component_for(self).persist_compaction(head_messages)
|
|
65
77
|
|
|
66
78
|
async def _compact_session_history(self: GraphCompactionHost, *, force: bool = True) -> bool:
|
|
67
|
-
|
|
79
|
+
result = await _compaction_component_for(self).compact_session_history(
|
|
68
80
|
force=force,
|
|
69
81
|
run_compaction_agent=self._run_compaction_agent,
|
|
70
82
|
persist_compaction=self._persist_compaction,
|
|
71
83
|
)
|
|
84
|
+
if result:
|
|
85
|
+
self._file_read_coverage.clear()
|
|
86
|
+
self._file_mtimes.clear()
|
|
87
|
+
return result
|
|
72
88
|
|
|
73
89
|
async def _run_compaction_agent(
|
|
74
90
|
self: GraphCompactionHost,
|