xdog-coding 2.3.0__tar.gz → 2.4.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.
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/PKG-INFO +4 -4
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/pyproject.toml +4 -4
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/__init__.py +1 -1
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/config.py +1 -1
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/agent_session.py +6 -35
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/compaction/compaction.py +14 -51
- xdog_coding-2.4.1/src/xdog/coding/core/compaction/utils.py +13 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/events.py +51 -3
- xdog_coding-2.4.1/tests/test_mcp_task_ci_ordering.py +69 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_task_hosts.py +4 -1
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_task_terminal.py +17 -3
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_terminal.py +9 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_events.py +102 -1
- xdog_coding-2.4.1/tests/test_sdk_compaction_compat.py +55 -0
- xdog_coding-2.4.1/tests/test_sdk_runtime_compatibility.py +225 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_skill_controls_ui.py +8 -3
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_slash_skills.py +4 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_terminal_acceptance.py +21 -17
- xdog_coding-2.3.0/src/xdog/coding/core/compaction/utils.py +0 -80
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/.gitignore +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/LICENSE +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/LICENSE-EXCEPTION.md +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/README.md +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/args.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/config_selector.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/file_processor.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/initial_message.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/list_models.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/session_picker.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/bash_executor.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/canonical_source.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/child_agents.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/compaction/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/context_messages.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/defaults.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/event_bus.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/exec_utils.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/extensions/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/extensions/loader.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/extensions/runner.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/extensions/types.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/keybindings.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/messages.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/model_catalog.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/navigation.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/permissions.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/prompt_templates.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/remote_session.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/resource_loader.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/sdk.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/session_manager.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/session_staging.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/settings_manager.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/slash_commands.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/system_prompt.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/timings.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/tools/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/transcript_source.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/main.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/after_run_compaction.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/after_run_name.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/after_run_tools.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/assistant_message.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/chat_log.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/custom_editor.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/editor_layout.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/footer.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/permission_prompt.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/tool_execution.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/user_message.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/interactive_mode.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/message_inputs.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/provider_auth.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/run_status.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/theme.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/tool_renderers.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/print_mode.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/after_run_compaction.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/after_run_name.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/after_run_tools.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/children.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/context_messages.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/custom_delivery.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/editor_write.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/extensions.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/images.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/input.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/navigation.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/output.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/parent_drafts.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/provider_auth.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/queued_inputs.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/rpc_mode.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/terminal_data.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/transcripts.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/py.typed +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/__init__.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/frontmatter.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/git.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/mime.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/shell.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/sleep.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/benchmark_tui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_client.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_menu_client.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_parent_draft_client.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_terminal_client.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_terminal_edit_client.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/terminal_client.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_persistence.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_persistence_ownership.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_retirement.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_selection_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_selection_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tools_after_run_hosts.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tools_after_run_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_after_run_compaction.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_after_run_compaction_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_after_run_name.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_after_run_name_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_agent_session.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_argument_completion_session.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_argument_completion_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_callback_inspection.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_active_tools.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_agents.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_image_admission.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_image_submission.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_queue_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_claw_active_tools_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_claw_elicitation_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_claw_sampling_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_cli_args.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_cli_config_selector.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_cli_file_processor.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_coding_config.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_command_artifact_export_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_command_help_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_command_lifecycle_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_compact_layout.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_compaction_controls_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_compaction_recovery.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_context_message_compaction.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_context_message_persistence.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_custom_delivery_modes.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_custom_delivery_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_draft_durability_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_editor_write_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_editor_write_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_event_bus.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_exact_command_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_catalog_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_command_catalog_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_command_results_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_compaction_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_compaction_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_context_message_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_context_message_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_custom_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_dialogs.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_editor_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_entries.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_failure_projection.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_input_draft_isolation.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_input_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_message_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_message_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_navigation_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_nested_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_auth.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_auth_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_capabilities.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_dynamic.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_startup.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_queued_input_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_queued_input_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_results_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_session_context_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_session_name_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_shortcuts_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_footer_context.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_hook_lifecycle_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_idle_message_images.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_idle_message_images_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_image_composer.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_image_lifecycle_admission.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_interactive_commands.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_interactive_keymap.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_interactive_mode.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_live_extensions.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_browser.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_catalog_watch_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_combined_watch_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_context_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_elicitation_recovery.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_elicitation_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_elicitation_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_modern_browser_cancel.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_modern_browser_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_modern_browser_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_resource_watch_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_root_hosts.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_roots_review_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_roots_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_sampling_catalog.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_sampling_context_hosts.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_sampling_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_sampling_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_session.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_structured_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_menu_scope_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_messages.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_multi_forms_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_oauth_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_owned_native_model.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_permissions.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_planning.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_prompt_templates.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_provider_oauth_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_provider_oauth_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_queue_controls.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_remote_parent_hash_changes.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_remote_parent_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rendered_image_context.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_renderer_contracts_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_repository_review.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_resource_loader.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_active_tools.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_canonical.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_capability_atomicity.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_canonical.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_contributions.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_custom.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_editor_write.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_events.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_extension_queued_inputs.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_overlay.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_terminal_lifecycle.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_children.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_children_cli.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_command_catalog.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_compaction.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_contributions.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_backpressure.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_contribution_integration.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_delivery.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_keyboard.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_refresh.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_views.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_deferred_delivery.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_editor.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_editor_write.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_browser.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_catalog.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_compaction.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_context_messages.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_message.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_name.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_navigation.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_queued_inputs.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extensions.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_host_generation.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_image_uploads.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_images.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_menu_scope.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_mode.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_navigation.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_output.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_overlay.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_parent_draft_interlocks.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_parent_draft_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_parent_drafts.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_parent_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_physical_editing.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_physical_terminal.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_session_lifecycle.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_shutdown_delivery.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_structured_layout.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_terminal_approval_revision.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_terminal_keymap.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_transcript_cli.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_transcripts.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_transient_backpressure.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_run_status.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_saved_child_drafts.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_saved_source_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_sdk_session_state.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_manager.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_name_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_navigation.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_picker.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_smoke.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_staging.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_settings_manager.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_slash_commands.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_system_prompt.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_terminal_input_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_terminal_release_physical.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_timings.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_tool_inventory_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_tool_lifecycle_ui.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_tools_browser.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_transcript_interaction.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_transcript_output.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_tui_usability.py +0 -0
- {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_ui_clear.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: xdog-coding
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: Interactive coding agent CLI
|
|
5
5
|
Project-URL: Homepage, https://xdog.942295.xyz
|
|
6
6
|
Project-URL: Repository, https://github.com/litterzhang/xdog
|
|
@@ -21,9 +21,9 @@ Requires-Python: >=3.12
|
|
|
21
21
|
Requires-Dist: click>=8.1.0
|
|
22
22
|
Requires-Dist: pydantic>=2.0
|
|
23
23
|
Requires-Dist: pyyaml>=6.0
|
|
24
|
-
Requires-Dist: xdog-agent>=2.
|
|
25
|
-
Requires-Dist: xdog-ai[openai]>=2.
|
|
26
|
-
Requires-Dist: xdog-tui>=2.
|
|
24
|
+
Requires-Dist: xdog-agent>=2.4.1
|
|
25
|
+
Requires-Dist: xdog-ai[openai]>=2.4.1
|
|
26
|
+
Requires-Dist: xdog-tui>=2.4.1
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
# xdog-coding
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "xdog-coding"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.4.1"
|
|
8
8
|
description = "Interactive coding agent CLI"
|
|
9
9
|
requires-python = ">=3.12"
|
|
10
10
|
classifiers = [
|
|
@@ -21,9 +21,9 @@ readme = "README.md"
|
|
|
21
21
|
license = "AGPL-3.0-or-later"
|
|
22
22
|
license-files = ["LICENSE", "LICENSE-EXCEPTION.md"]
|
|
23
23
|
dependencies = [
|
|
24
|
-
"xdog-ai[openai]>=2.
|
|
25
|
-
"xdog-agent>=2.
|
|
26
|
-
"xdog-tui>=2.
|
|
24
|
+
"xdog-ai[openai]>=2.4.1",
|
|
25
|
+
"xdog-agent>=2.4.1",
|
|
26
|
+
"xdog-tui>=2.4.1",
|
|
27
27
|
"pyyaml>=6.0",
|
|
28
28
|
"click>=8.1.0",
|
|
29
29
|
"pydantic>=2.0",
|
|
@@ -274,15 +274,8 @@ class AgentSession:
|
|
|
274
274
|
try:
|
|
275
275
|
if self._base_context_transform is not None:
|
|
276
276
|
messages = await self._base_context_transform(messages)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def transient_messages() -> list[UserMessage]:
|
|
280
|
-
manager = skill_manager()
|
|
281
|
-
return [UserMessage(content='## Active skill: ' + skill.name + '\n\n' + render_skill_body(skill))
|
|
282
|
-
for slug in sorted(self._active_skills)
|
|
283
|
-
if (skill := manager.load_skill(slug)) is not None and skill.expires_after_turn]
|
|
284
|
-
transient = await asyncio.to_thread(transient_messages) if self._active_skills else []
|
|
285
|
-
result = await self.extensions.transform([*transient, *messages])
|
|
277
|
+
# The SDK supplies run-owned turn skills before this transform.
|
|
278
|
+
result = await self.extensions.transform(messages)
|
|
286
279
|
return result
|
|
287
280
|
finally:
|
|
288
281
|
await self._sync_extension_tools()
|
|
@@ -626,33 +619,11 @@ class AgentSession:
|
|
|
626
619
|
self._expire_turn_scoped_skills()
|
|
627
620
|
|
|
628
621
|
def _expire_turn_scoped_skills(self) -> None:
|
|
629
|
-
"""
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
way to observe whether the model followed an instruction. Only the
|
|
634
|
-
author knows which kind they wrote, so only the author gets to say.
|
|
635
|
-
"""
|
|
636
|
-
if not self._active_skills:
|
|
637
|
-
return
|
|
638
|
-
try:
|
|
639
|
-
from xdog.coding.core.slash_commands import skill_manager
|
|
640
|
-
|
|
641
|
-
manager = skill_manager()
|
|
642
|
-
expired = {
|
|
643
|
-
slug
|
|
644
|
-
for slug in self._active_skills
|
|
645
|
-
if (skill := manager.load_skill(slug)) is not None and skill.expires_after_turn
|
|
646
|
-
}
|
|
647
|
-
except Exception:
|
|
648
|
-
logger.debug("could not expire turn-scoped skills", exc_info=True)
|
|
649
|
-
return
|
|
650
|
-
|
|
651
|
-
if expired:
|
|
652
|
-
self._active_skills = self._active_skills - expired
|
|
653
|
-
self._push_skills()
|
|
622
|
+
"""Mirror SDK retirement; preparation failures do not consume pending skills."""
|
|
623
|
+
retained = self._active_skills & frozenset(self.agent.active_skills)
|
|
624
|
+
if retained != self._active_skills:
|
|
625
|
+
self._active_skills = retained
|
|
654
626
|
self._rebuild_system_prompt()
|
|
655
|
-
logger.debug("expired turn-scoped skills: %s", sorted(expired))
|
|
656
627
|
|
|
657
628
|
def steer(self, message: str) -> None:
|
|
658
629
|
"""Queue a steering interrupt for the current turn."""
|
|
@@ -6,23 +6,19 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
from xdog.agent import AgentMessage
|
|
8
8
|
from xdog.agent.agent import Agent
|
|
9
|
-
from xdog.
|
|
9
|
+
from xdog.agent.compaction import (
|
|
10
|
+
SUMMARIZE_SYSTEM_PROMPT as SUMMARIZE_SYSTEM_PROMPT,
|
|
11
|
+
)
|
|
12
|
+
from xdog.agent.compaction import (
|
|
13
|
+
conversation_text,
|
|
14
|
+
extract_text,
|
|
15
|
+
summary_context,
|
|
16
|
+
)
|
|
17
|
+
from xdog.ai.types import UserMessage
|
|
10
18
|
from xdog.coding.core.compaction.utils import split_messages
|
|
11
19
|
from xdog.coding.core.defaults import COMPACTION_TARGET_RATIO
|
|
12
20
|
from xdog.coding.core.prompt_templates import COMPACTION_SUMMARY_TEMPLATE
|
|
13
21
|
|
|
14
|
-
SUMMARIZE_SYSTEM_PROMPT = """\
|
|
15
|
-
You are a conversation summarizer. You will receive a portion of a conversation \
|
|
16
|
-
between a user and a coding assistant. Produce a concise summary that captures:
|
|
17
|
-
|
|
18
|
-
1. What tasks were discussed and their current status.
|
|
19
|
-
2. Key decisions made.
|
|
20
|
-
3. Important code changes or file paths mentioned.
|
|
21
|
-
4. Any outstanding issues or next steps.
|
|
22
|
-
|
|
23
|
-
Be concise but ensure no critical context is lost. Output only the summary text.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
22
|
|
|
27
23
|
def prepare_index(messages: list[AgentMessage], agent: Agent, *, force: bool = False,
|
|
28
24
|
context_limit: int | None = None) -> int:
|
|
@@ -64,31 +60,11 @@ async def compact_messages(
|
|
|
64
60
|
if not old_messages:
|
|
65
61
|
return list(messages)
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
text_parts: list[str] = []
|
|
69
|
-
for msg in old_messages:
|
|
70
|
-
if isinstance(msg, UserMessage):
|
|
71
|
-
text = msg.content if isinstance(msg.content, str) else _extract_text(msg)
|
|
72
|
-
if text:
|
|
73
|
-
text_parts.append(f"[USER]: {text}")
|
|
74
|
-
elif isinstance(msg, AssistantMessage):
|
|
75
|
-
text = _extract_text(msg)
|
|
76
|
-
calls = [f"[TOOL CALL {part.name}]: {part.arguments}" for part in msg.content
|
|
77
|
-
if isinstance(part, ToolCall)]
|
|
78
|
-
if text or calls:
|
|
79
|
-
text_parts.append(f"[ASSISTANT]: {text}" + "\n" + "\n".join(calls))
|
|
80
|
-
elif isinstance(msg, ToolResultMessage):
|
|
81
|
-
text_parts.append(f"[TOOL RESULT {msg.tool_name}, error={msg.is_error}]: {_extract_text(msg)}")
|
|
82
|
-
else:
|
|
83
|
-
from xdog.agent.extensions.context_message import summary_text as context_summary_text
|
|
84
|
-
if custom_text := context_summary_text(msg):
|
|
85
|
-
text_parts.append('[EXTENSION CONTEXT]: ' + custom_text)
|
|
86
|
-
|
|
87
|
-
conversation_text = "\n\n".join(text_parts)
|
|
63
|
+
text = conversation_text(old_messages)
|
|
88
64
|
|
|
89
65
|
# Use the agent's stream_fn to summarize
|
|
90
66
|
summary = (summary_override if summary_override is not None
|
|
91
|
-
else await _summarize(
|
|
67
|
+
else await _summarize(text, agent, instructions))
|
|
92
68
|
|
|
93
69
|
# Build the compacted history
|
|
94
70
|
summary_text = COMPACTION_SUMMARY_TEMPLATE.format(summary=summary)
|
|
@@ -98,25 +74,12 @@ async def compact_messages(
|
|
|
98
74
|
|
|
99
75
|
|
|
100
76
|
def _extract_text(msg: Any) -> str:
|
|
101
|
-
"""
|
|
102
|
-
|
|
103
|
-
content = getattr(msg, "content", None)
|
|
104
|
-
if isinstance(content, str):
|
|
105
|
-
return content
|
|
106
|
-
if content is None:
|
|
107
|
-
return ""
|
|
108
|
-
for part in content:
|
|
109
|
-
if isinstance(part, TextContent):
|
|
110
|
-
parts.append(part.text)
|
|
111
|
-
return "\n".join(parts)
|
|
77
|
+
"""Compatibility wrapper for the shared text projection."""
|
|
78
|
+
return extract_text(msg)
|
|
112
79
|
|
|
113
80
|
|
|
114
81
|
async def _summarize(conversation_text: str, agent: Agent, instructions: str = "") -> str:
|
|
115
82
|
"""Use the selected session adapter, including extension/custom providers."""
|
|
116
83
|
from xdog.agent.summarization import stream_summary
|
|
117
|
-
request = conversation_text
|
|
118
|
-
if instructions:
|
|
119
|
-
request += "\n\nUser's compaction focus (not a request to execute tools):\n" + instructions
|
|
120
84
|
return await stream_summary(agent.stream_fn, agent.state.model,
|
|
121
|
-
|
|
122
|
-
agent.options)
|
|
85
|
+
summary_context(conversation_text, instructions), agent.options)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Compatibility exports of Coding's original history estimates and splitter."""
|
|
2
|
+
from xdog.agent.compaction import (
|
|
3
|
+
estimate_history_tokens as estimate_history_tokens,
|
|
4
|
+
)
|
|
5
|
+
from xdog.agent.compaction import (
|
|
6
|
+
estimate_message_tokens as estimate_message_tokens,
|
|
7
|
+
)
|
|
8
|
+
from xdog.agent.compaction import (
|
|
9
|
+
estimate_tokens as estimate_tokens,
|
|
10
|
+
)
|
|
11
|
+
from xdog.agent.compaction import (
|
|
12
|
+
split_messages as split_messages,
|
|
13
|
+
)
|
|
@@ -12,6 +12,9 @@ from xdog.agent import AgentEvent, AgentMessage
|
|
|
12
12
|
from xdog.agent.events import (
|
|
13
13
|
AgentEndEvent,
|
|
14
14
|
AgentStartEvent,
|
|
15
|
+
CompactionEndEvent,
|
|
16
|
+
CompactionFailedEvent,
|
|
17
|
+
CompactionStartEvent,
|
|
15
18
|
InputConsumedEvent,
|
|
16
19
|
InputHandledEvent,
|
|
17
20
|
InputRejectedEvent,
|
|
@@ -19,11 +22,13 @@ from xdog.agent.events import (
|
|
|
19
22
|
MessageEndEvent,
|
|
20
23
|
MessageStartEvent,
|
|
21
24
|
MessageUpdateEvent,
|
|
25
|
+
SnapshotEvent,
|
|
22
26
|
ToolExecutionEndEvent,
|
|
23
27
|
ToolExecutionStartEvent,
|
|
24
28
|
ToolExecutionUpdateEvent,
|
|
25
29
|
TurnEndEvent,
|
|
26
30
|
TurnStartEvent,
|
|
31
|
+
UsageEvent,
|
|
27
32
|
)
|
|
28
33
|
from xdog.ai.types import ImageContent, TextContent
|
|
29
34
|
from xdog.coding.core.agent_session import AgentSession
|
|
@@ -80,7 +85,7 @@ class RpcEvents:
|
|
|
80
85
|
payload = {'event': event.type, 'data': {}, 'limited': True}
|
|
81
86
|
if payload is not None:
|
|
82
87
|
if len(json.dumps(payload, ensure_ascii=True, allow_nan=False).encode()) > MAX_EVENT_BYTES // 2:
|
|
83
|
-
payload = {
|
|
88
|
+
payload = {**payload, 'data': {}, 'limited': True}
|
|
84
89
|
with self._lock:
|
|
85
90
|
if self._pending >= 32:
|
|
86
91
|
self._limited = True
|
|
@@ -134,9 +139,52 @@ def _message(message: AgentMessage | None) -> dict[str, Any] | None:
|
|
|
134
139
|
return value
|
|
135
140
|
|
|
136
141
|
|
|
142
|
+
def _sdk_metadata(event: AgentEvent) -> dict[str, Any]:
|
|
143
|
+
"""SDK identity is separate from the RPC session lease/delivery sequence.
|
|
144
|
+
|
|
145
|
+
Bound identity strings independently so payload-size fallback can retain
|
|
146
|
+
correlation without retaining oversized data or unbounded identifiers.
|
|
147
|
+
"""
|
|
148
|
+
if (any(not isinstance(value, str) or len(value) > 256 for value in (event.session_id, event.run_id))
|
|
149
|
+
or type(event.sequence) is not int or event.sequence < 0):
|
|
150
|
+
raise ValueError('Invalid SDK event identity')
|
|
151
|
+
return {'session_id': safe_json(event.session_id), 'run_id': safe_json(event.run_id), 'sequence': event.sequence}
|
|
152
|
+
|
|
153
|
+
|
|
137
154
|
def _payload(event: AgentEvent) -> dict[str, Any] | None:
|
|
138
155
|
data: dict[str, Any]
|
|
139
|
-
if isinstance(event,
|
|
156
|
+
if isinstance(event, (CompactionStartEvent, CompactionEndEvent, CompactionFailedEvent)):
|
|
157
|
+
if type(event.automatic) is not bool:
|
|
158
|
+
raise ValueError('Invalid SDK compaction metadata')
|
|
159
|
+
if isinstance(event, CompactionEndEvent) and (type(event.generation) is not int or event.generation < 0):
|
|
160
|
+
raise ValueError('Invalid SDK compaction generation')
|
|
161
|
+
if isinstance(event, UsageEvent):
|
|
162
|
+
# Do not recursively serialize SDK dataclasses or apply key-based token
|
|
163
|
+
# redaction to numeric token counters. Explicit numeric fields only.
|
|
164
|
+
counters = {key: getattr(event.usage, key) for key in
|
|
165
|
+
('input', 'output', 'cache_read', 'cache_write', 'total_tokens')}
|
|
166
|
+
cost = {key: getattr(event.usage.cost, key)
|
|
167
|
+
for key in ('input', 'output', 'cache_read', 'cache_write', 'total')}
|
|
168
|
+
if (any(type(value) is not int or value < 0 for value in counters.values())
|
|
169
|
+
or any(type(value) not in (int, float) or value < 0 for value in cost.values())
|
|
170
|
+
or event.source not in ('model', 'compaction') or not isinstance(event.model, str)):
|
|
171
|
+
raise ValueError('Invalid SDK usage event')
|
|
172
|
+
json.dumps(cost, allow_nan=False)
|
|
173
|
+
data = {'source': event.source, 'model': safe_json(event.model), 'usage': {**counters, 'cost': cost}}
|
|
174
|
+
elif isinstance(event, CompactionStartEvent):
|
|
175
|
+
data = {'automatic': event.automatic}
|
|
176
|
+
elif isinstance(event, CompactionEndEvent):
|
|
177
|
+
data = {'automatic': event.automatic, 'generation': event.generation,
|
|
178
|
+
'removed_message_count': len(event.removed_messages), 'summary_omitted': True}
|
|
179
|
+
elif isinstance(event, CompactionFailedEvent):
|
|
180
|
+
# Summary/error prose can contain arbitrary sensitive history, not merely
|
|
181
|
+
# recognizable credential patterns. Keep observations metadata-only.
|
|
182
|
+
data = {'automatic': event.automatic, 'error_omitted': True}
|
|
183
|
+
elif isinstance(event, SnapshotEvent):
|
|
184
|
+
# Do not even parse snapshot_json/access snapshot: opt-in transient
|
|
185
|
+
# events grant neither canonical history nor persistence/credential data.
|
|
186
|
+
data = {'snapshot_omitted': True}
|
|
187
|
+
elif isinstance(event, ToolExecutionStartEvent):
|
|
140
188
|
data = {'call_id': event.tool_call_id, 'tool': event.tool_name, 'arguments': safe_json(event.args)}
|
|
141
189
|
elif isinstance(event, ToolExecutionUpdateEvent):
|
|
142
190
|
result = event.partial_result
|
|
@@ -178,4 +226,4 @@ def _payload(event: AgentEvent) -> dict[str, Any] | None:
|
|
|
178
226
|
data = {} # Never copy the whole canonical history into an agent-end event.
|
|
179
227
|
else:
|
|
180
228
|
return None
|
|
181
|
-
return {'event': event.type, 'data': data}
|
|
229
|
+
return {'event': event.type, 'data': data, 'sdk': _sdk_metadata(event)}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Controlled CI schedules: host acknowledgement is not UI settlement."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from xdog.claw.channels.tui.tui_app import ChatApp
|
|
8
|
+
from xdog.claw.core.runtime.sampling_protocol import SamplingConnection
|
|
9
|
+
|
|
10
|
+
from packages.coding.tests.test_mcp_task_hosts import (
|
|
11
|
+
test_native_required_tools_not_selected_refuses_before_model_or_output_review as run_required_tools,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def test_required_tool_refusal_waits_for_delayed_socket_close_event(tmp_path, monkeypatch):
|
|
16
|
+
"""Drive the old GitHub failure without sleeps or changing runtime policy.
|
|
17
|
+
|
|
18
|
+
Deliver the terminal error to the peer but withhold only the UI close event.
|
|
19
|
+
The input form is already gone; busy must stay true until the actual close
|
|
20
|
+
observation is delivered. The host integration test must wait for that fact.
|
|
21
|
+
"""
|
|
22
|
+
held = []
|
|
23
|
+
panels = []
|
|
24
|
+
published = SamplingConnection._publish
|
|
25
|
+
opened = ChatApp._open_mcp
|
|
26
|
+
|
|
27
|
+
def defer_closed(connection, binding, view, event, models):
|
|
28
|
+
if event['type'] == 'sampling_closed':
|
|
29
|
+
held.append((connection, binding, view, event, models))
|
|
30
|
+
else:
|
|
31
|
+
published(connection, binding, view, event, models)
|
|
32
|
+
|
|
33
|
+
def capture_panel(ui):
|
|
34
|
+
opened(ui)
|
|
35
|
+
panels.append(ui._mcp_panel)
|
|
36
|
+
|
|
37
|
+
monkeypatch.setattr(SamplingConnection, '_publish', defer_closed)
|
|
38
|
+
monkeypatch.setattr(ChatApp, '_open_mcp', capture_panel)
|
|
39
|
+
operation = asyncio.create_task(run_required_tools(tmp_path, 'claw'))
|
|
40
|
+
|
|
41
|
+
def release():
|
|
42
|
+
while held:
|
|
43
|
+
published(*held.pop(0))
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
async with asyncio.timeout(6):
|
|
47
|
+
while not held:
|
|
48
|
+
if operation.done():
|
|
49
|
+
operation.result()
|
|
50
|
+
raise AssertionError('No terminal close event was produced')
|
|
51
|
+
await asyncio.sleep(0)
|
|
52
|
+
# This is the original test's failure window: the MCP result is
|
|
53
|
+
# available while the socket UI still has not received its close.
|
|
54
|
+
log = tmp_path / 'task-frames'
|
|
55
|
+
while not log.exists() or not any(
|
|
56
|
+
row.get('id') == 'retrieve' for row in (json.loads(line) for line in log.read_text().splitlines())
|
|
57
|
+
):
|
|
58
|
+
await asyncio.sleep(0)
|
|
59
|
+
assert panels[-1].sampling_review is None
|
|
60
|
+
assert panels[-1]._busy
|
|
61
|
+
assert not operation.done(), 'The host test mistook form removal for a settled request'
|
|
62
|
+
release()
|
|
63
|
+
await asyncio.wait_for(operation, 6)
|
|
64
|
+
assert not panels[-1]._busy
|
|
65
|
+
finally:
|
|
66
|
+
release()
|
|
67
|
+
if not operation.done():
|
|
68
|
+
operation.cancel()
|
|
69
|
+
await asyncio.gather(operation, return_exceptions=True)
|
|
@@ -310,7 +310,10 @@ async def test_native_required_tools_not_selected_refuses_before_model_or_output
|
|
|
310
310
|
first.handle_input(KeyEvent('enter'))
|
|
311
311
|
await wait(lambda: any(row.get('id') == 'retrieve' for row in frames()))
|
|
312
312
|
assert next(row for row in frames() if row.get('id') == 'retrieve')['error']['code'] == -32000
|
|
313
|
-
|
|
313
|
+
# Answer admission removes the input form immediately, before the task
|
|
314
|
+
# worker and socket delivery have settled. Only the terminal close event
|
|
315
|
+
# clears busy; a transport error reply is not a UI settlement receipt.
|
|
316
|
+
await wait(lambda: panel.sampling_review is None and not panel._busy)
|
|
314
317
|
assert not adapter.calls and not session.agent.messages and not panel._busy
|
|
315
318
|
assert ui._editor.get_text() == 'PARENT REQUIRED TOOL DRAFT'
|
|
316
319
|
if kind == 'coding':
|
|
@@ -26,12 +26,13 @@ def test_physical_native_task_full_review_result_transport_resize_and_termios(tm
|
|
|
26
26
|
owned, errors = {}, []
|
|
27
27
|
thread = None
|
|
28
28
|
terminal = Terminal(tmp_path)
|
|
29
|
+
cancel_gate = tmp_path / "allow-peer-cancel"
|
|
29
30
|
if kind == 'coding':
|
|
30
31
|
configs, log = task_config(tmp_path, family)
|
|
31
32
|
if end == 'expire':
|
|
32
33
|
configs['owned']['args'].append('2000')
|
|
33
34
|
elif end == 'peer_cancel':
|
|
34
|
-
configs['owned']['args'].extend(['60000', 'cancel'])
|
|
35
|
+
configs['owned']['args'].extend(['60000', 'cancel', str(cancel_gate)])
|
|
35
36
|
home = tmp_path / 'home'
|
|
36
37
|
home.mkdir(mode=0o700)
|
|
37
38
|
(home / 'settings.json').write_text(json.dumps({'mcp_servers': configs}))
|
|
@@ -50,7 +51,7 @@ def test_physical_native_task_full_review_result_transport_resize_and_termios(tm
|
|
|
50
51
|
from dataclasses import replace
|
|
51
52
|
original = runtime.mcp.configs['owned']
|
|
52
53
|
runtime.mcp.configs['owned'] = replace(original,
|
|
53
|
-
command=(*original.command, '60000', 'cancel'))
|
|
54
|
+
command=(*original.command, '60000', 'cancel', str(cancel_gate)))
|
|
54
55
|
owned.update(config=config, runtime=runtime, session=session, adapter=adapter, log=log)
|
|
55
56
|
ready.set()
|
|
56
57
|
while not stop.is_set():
|
|
@@ -92,7 +93,13 @@ def test_physical_native_task_full_review_result_transport_resize_and_termios(tm
|
|
|
92
93
|
while not any(row.get('id') == 'create' for row in frames()) and time.monotonic() < deadline:
|
|
93
94
|
time.sleep(.03)
|
|
94
95
|
created = next(row for row in frames() if row.get('id') == 'create')['result']['task']
|
|
95
|
-
assert 'PRIVATE' not in str(created)
|
|
96
|
+
assert 'PRIVATE' not in str(created)
|
|
97
|
+
assert not any(row.get('id') == 'retrieve' for row in frames())
|
|
98
|
+
if end == 'peer_cancel':
|
|
99
|
+
# Keep the pre-cancel withholding assertion above, then explicitly
|
|
100
|
+
# admit cancellation. A terminal error response is now legitimate;
|
|
101
|
+
# successful/private result delivery is never legitimate.
|
|
102
|
+
cancel_gate.touch()
|
|
96
103
|
initial = next(row for row in frames() if row.get('method') == 'initialize')
|
|
97
104
|
assert initial['params']['capabilities']['tasks']['requests'] == {
|
|
98
105
|
family: {'createMessage' if family == 'sampling' else 'create': {}}}
|
|
@@ -112,6 +119,12 @@ def test_physical_native_task_full_review_result_transport_resize_and_termios(tm
|
|
|
112
119
|
cancelled = next(row for row in frames() if row.get('id') == 'peer-cancel')
|
|
113
120
|
assert cancelled['result']['status'] == 'cancelled'
|
|
114
121
|
assert cancelled['result']['taskId'] == created['taskId']
|
|
122
|
+
deadline = time.monotonic() + 5
|
|
123
|
+
while not any(row.get('id') == 'retrieve' for row in frames()) and time.monotonic() < deadline:
|
|
124
|
+
time.sleep(.03)
|
|
125
|
+
denied = next(row for row in frames() if row.get('id') == 'retrieve')
|
|
126
|
+
assert denied['error']['code'] == -32000 and 'result' not in denied
|
|
127
|
+
assert 'PRIVATE' not in json.dumps(frames())
|
|
115
128
|
terminal.keys('Escape')
|
|
116
129
|
terminal.wait_hidden('MCP servers')
|
|
117
130
|
terminal.wait_cursor(True)
|
|
@@ -173,6 +186,7 @@ def test_physical_native_task_full_review_result_transport_resize_and_termios(tm
|
|
|
173
186
|
else:
|
|
174
187
|
no_model()
|
|
175
188
|
finally:
|
|
189
|
+
cancel_gate.touch() # Release our own peer if an earlier assertion failed.
|
|
176
190
|
terminal.call('kill-server')
|
|
177
191
|
stop.set()
|
|
178
192
|
if thread is not None:
|
|
@@ -159,6 +159,15 @@ def test_actual_cli_child_terminal_transform_receipts_never_submit_or_authorize_
|
|
|
159
159
|
else:
|
|
160
160
|
raise AssertionError('Child terminal registry never became active')
|
|
161
161
|
peer.caps = caps
|
|
162
|
+
# Registry activity admits terminal hooks, not the model generator. Quit
|
|
163
|
+
# can still cancel input/context/checkpoint work before its first step;
|
|
164
|
+
# only an entered model owns the fixture's producer cleanup marker.
|
|
165
|
+
deadline = time.monotonic() + 6
|
|
166
|
+
while not peer.marker.exists() and time.monotonic() < deadline:
|
|
167
|
+
assert peer.process.poll() is None, 'CLI exited before child model admission'
|
|
168
|
+
time.sleep(0.01)
|
|
169
|
+
assert peer.marker.exists(), 'Child model generator never entered'
|
|
170
|
+
assert not peer.marker.with_suffix('.settled').exists()
|
|
162
171
|
for key, kind, data, expected in (('keys', 'key', 'ab', '[a][b]'),
|
|
163
172
|
('paste', 'paste', '开发🙂\nline', '[开发🙂\nline]'),
|
|
164
173
|
('consume', 'paste', 'drop', None)):
|
|
@@ -8,10 +8,15 @@ import pytest
|
|
|
8
8
|
from xdog.agent.core import AgentToolResult
|
|
9
9
|
from xdog.agent.events import (
|
|
10
10
|
AgentEndEvent,
|
|
11
|
+
CompactionEndEvent,
|
|
12
|
+
CompactionFailedEvent,
|
|
13
|
+
CompactionStartEvent,
|
|
11
14
|
MessageUpdateEvent,
|
|
15
|
+
SnapshotEvent,
|
|
12
16
|
ToolExecutionEndEvent,
|
|
13
17
|
ToolExecutionStartEvent,
|
|
14
18
|
ToolExecutionUpdateEvent,
|
|
19
|
+
UsageEvent,
|
|
15
20
|
)
|
|
16
21
|
from xdog.ai.types import AssistantMessage, ImageContent, TextContent, ThinkingContent
|
|
17
22
|
from xdog.coding.modes.rpc.events import MAX_EVENT_BYTES, RpcEvents
|
|
@@ -212,7 +217,7 @@ def test_every_declared_agent_event_has_a_named_transient_projection_with_safe_m
|
|
|
212
217
|
payload = _payload(event)
|
|
213
218
|
assert payload is not None and payload['event'] == event.type
|
|
214
219
|
projected.append(payload)
|
|
215
|
-
assert len(projected) ==
|
|
220
|
+
assert len(projected) == len(get_args(AgentEvent))
|
|
216
221
|
encoded = json.dumps(projected)
|
|
217
222
|
assert 'PRIVATE' not in encoded and 'PUBLIC' in encoded and '123' in encoded
|
|
218
223
|
update = next(row for row in projected if row['event'] == 'message_update')
|
|
@@ -224,3 +229,99 @@ def test_every_declared_agent_event_has_a_named_transient_projection_with_safe_m
|
|
|
224
229
|
assert 'PRIVATE' not in json.dumps(final)
|
|
225
230
|
tool_message = ToolResultMessage(tool_call_id='call', tool_name='tool', content=(picture,))
|
|
226
231
|
assert 'PRIVATE-BINARY' not in json.dumps(_payload(MessageEndEvent(message=tool_message)))
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_sdk_observation_projections_have_exact_safe_fields_and_separate_identity():
|
|
235
|
+
from xdog.ai.types import CostBreakdown, Usage, UserMessage
|
|
236
|
+
from xdog.coding.modes.rpc.events import _payload
|
|
237
|
+
|
|
238
|
+
metadata = {'session_id': 'sdk-session', 'run_id': 'sdk-run', 'sequence': 41}
|
|
239
|
+
usage = Usage(input=10, output=3, cache_read=2, cache_write=1, total_tokens=16,
|
|
240
|
+
cost=CostBreakdown(input=.1, output=.2, cache_read=.03, cache_write=.04, total=.37))
|
|
241
|
+
for source in ('model', 'compaction'):
|
|
242
|
+
assert _payload(UsageEvent(usage=usage, model='offline/model', source=source, **metadata)) == {
|
|
243
|
+
'event': 'usage', 'sdk': metadata,
|
|
244
|
+
'data': {'source': source, 'model': 'offline/model', 'usage': {
|
|
245
|
+
'input': 10, 'output': 3, 'cache_read': 2, 'cache_write': 1, 'total_tokens': 16,
|
|
246
|
+
'cost': {'input': .1, 'output': .2, 'cache_read': .03, 'cache_write': .04, 'total': .37},
|
|
247
|
+
}},
|
|
248
|
+
}
|
|
249
|
+
secret = 'ARBITRARY-PRIVATE-CONTENT without a recognizable credential prefix'
|
|
250
|
+
cases = (
|
|
251
|
+
(CompactionStartEvent(automatic=True, **metadata), {'automatic': True}),
|
|
252
|
+
(CompactionEndEvent(automatic=True, generation=2, summary=secret,
|
|
253
|
+
removed_messages=(UserMessage(content=secret),), **metadata),
|
|
254
|
+
{'automatic': True, 'generation': 2, 'removed_message_count': 1, 'summary_omitted': True}),
|
|
255
|
+
(CompactionFailedEvent(error=secret, **metadata), {'automatic': False, 'error_omitted': True}),
|
|
256
|
+
(SnapshotEvent(snapshot_json=json.dumps({'messages': secret, 'api_key': secret}), **metadata),
|
|
257
|
+
{'snapshot_omitted': True}),
|
|
258
|
+
(SnapshotEvent(snapshot_json='NOT JSON ' + secret, **metadata), {'snapshot_omitted': True}),
|
|
259
|
+
)
|
|
260
|
+
for event, expected in cases:
|
|
261
|
+
payload = _payload(event)
|
|
262
|
+
assert payload == {'event': event.type, 'sdk': metadata, 'data': expected}
|
|
263
|
+
assert 'PRIVATE' not in json.dumps(payload)
|
|
264
|
+
assert all(key not in payload['data'] for key in ('removed_messages', 'snapshot_json', 'summary', 'error'))
|
|
265
|
+
redacted = _payload(UsageEvent(model='token=PRIVATE-MODEL', session_id='api_key=PRIVATE-SESSION',
|
|
266
|
+
run_id='password=PRIVATE-RUN', sequence=1))
|
|
267
|
+
assert 'PRIVATE' not in json.dumps(redacted)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
async def test_sdk_metadata_preserves_rpc_lease_sequence_and_snapshot_privacy(tmp_path):
|
|
271
|
+
from xdog.ai.types import Usage, UserMessage
|
|
272
|
+
|
|
273
|
+
session, _, calls, nav, sources, events = setup(tmp_path)
|
|
274
|
+
stream = RpcEvents(session, nav, events.append)
|
|
275
|
+
try:
|
|
276
|
+
# An observation before negotiation advances only the SDK counter.
|
|
277
|
+
session.agent._emit(AgentEndEvent())
|
|
278
|
+
stream.negotiate({'transient_events': 1})
|
|
279
|
+
session.agent._emit(AgentEndEvent())
|
|
280
|
+
await asyncio.sleep(0)
|
|
281
|
+
sdk_events = [
|
|
282
|
+
session.agent._emit(UsageEvent(usage=Usage(input=17), model='offline/model')),
|
|
283
|
+
session.agent._emit(CompactionEndEvent(summary='PRIVATE-SUMMARY',
|
|
284
|
+
removed_messages=(UserMessage(content='PRIVATE-HISTORY'),), generation=3)),
|
|
285
|
+
session.agent._emit(SnapshotEvent(snapshot_json='PRIVATE-INVALID-JSON')),
|
|
286
|
+
]
|
|
287
|
+
await asyncio.sleep(0)
|
|
288
|
+
rows = [row for row in events if row['type'] == 'transient_event'][1:]
|
|
289
|
+
assert len(rows) == len(sdk_events) == 3
|
|
290
|
+
for row, sdk_event in zip(rows, sdk_events, strict=True):
|
|
291
|
+
assert row['session_id'] == session.session_id
|
|
292
|
+
assert row['event_owner'] == stream.lease and row['retained'] is False
|
|
293
|
+
assert row['sdk'] == {'session_id': sdk_event.session_id, 'run_id': sdk_event.run_id,
|
|
294
|
+
'sequence': sdk_event.sequence}
|
|
295
|
+
assert [row['sequence'] for row in rows] == [2, 3, 4]
|
|
296
|
+
assert [row['sdk']['sequence'] for row in rows] == [3, 4, 5]
|
|
297
|
+
assert 'PRIVATE' not in json.dumps(rows)
|
|
298
|
+
assert not calls and session.permissions.mode == 'deny'
|
|
299
|
+
finally:
|
|
300
|
+
stream.close()
|
|
301
|
+
await close(session, nav, sources)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
async def test_oversized_usage_keeps_sdk_correlation_when_data_is_limited(tmp_path):
|
|
305
|
+
session, _, _, nav, sources, events = setup(tmp_path)
|
|
306
|
+
stream = RpcEvents(session, nav, events.append)
|
|
307
|
+
try:
|
|
308
|
+
stream.negotiate({'transient_events': 1})
|
|
309
|
+
sdk_event = session.agent._emit(UsageEvent(model='x' * MAX_EVENT_BYTES))
|
|
310
|
+
await asyncio.sleep(0)
|
|
311
|
+
row = events[-1]
|
|
312
|
+
assert row['event'] == 'usage' and row['limited'] is True and row['data'] == {}
|
|
313
|
+
assert row['sdk'] == {'session_id': sdk_event.session_id, 'run_id': sdk_event.run_id,
|
|
314
|
+
'sequence': sdk_event.sequence}
|
|
315
|
+
assert len(json.dumps(row).encode()) <= MAX_EVENT_BYTES
|
|
316
|
+
finally:
|
|
317
|
+
stream.close()
|
|
318
|
+
await close(session, nav, sources)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@pytest.mark.parametrize('value', ['PRIVATE-INVALID-COUNTER', True, -1])
|
|
322
|
+
def test_usage_projection_rejects_non_numeric_or_negative_counters(value):
|
|
323
|
+
from xdog.ai.types import Usage
|
|
324
|
+
from xdog.coding.modes.rpc.events import _payload
|
|
325
|
+
|
|
326
|
+
with pytest.raises(ValueError, match='Invalid SDK usage event'):
|
|
327
|
+
_payload(UsageEvent(usage=Usage(input=value)))
|