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.
Files changed (314) hide show
  1. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/PKG-INFO +4 -4
  2. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/pyproject.toml +4 -4
  3. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/__init__.py +1 -1
  4. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/config.py +1 -1
  5. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/agent_session.py +6 -35
  6. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/compaction/compaction.py +14 -51
  7. xdog_coding-2.4.1/src/xdog/coding/core/compaction/utils.py +13 -0
  8. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/events.py +51 -3
  9. xdog_coding-2.4.1/tests/test_mcp_task_ci_ordering.py +69 -0
  10. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_task_hosts.py +4 -1
  11. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_task_terminal.py +17 -3
  12. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_terminal.py +9 -0
  13. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_events.py +102 -1
  14. xdog_coding-2.4.1/tests/test_sdk_compaction_compat.py +55 -0
  15. xdog_coding-2.4.1/tests/test_sdk_runtime_compatibility.py +225 -0
  16. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_skill_controls_ui.py +8 -3
  17. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_slash_skills.py +4 -0
  18. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_terminal_acceptance.py +21 -17
  19. xdog_coding-2.3.0/src/xdog/coding/core/compaction/utils.py +0 -80
  20. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/.gitignore +0 -0
  21. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/LICENSE +0 -0
  22. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/LICENSE-EXCEPTION.md +0 -0
  23. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/README.md +0 -0
  24. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/__init__.py +0 -0
  25. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/args.py +0 -0
  26. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/config_selector.py +0 -0
  27. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/file_processor.py +0 -0
  28. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/initial_message.py +0 -0
  29. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/list_models.py +0 -0
  30. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/cli/session_picker.py +0 -0
  31. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/__init__.py +0 -0
  32. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/bash_executor.py +0 -0
  33. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/canonical_source.py +0 -0
  34. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/child_agents.py +0 -0
  35. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/compaction/__init__.py +0 -0
  36. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/context_messages.py +0 -0
  37. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/defaults.py +0 -0
  38. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/event_bus.py +0 -0
  39. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/exec_utils.py +0 -0
  40. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/extensions/__init__.py +0 -0
  41. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/extensions/loader.py +0 -0
  42. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/extensions/runner.py +0 -0
  43. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/extensions/types.py +0 -0
  44. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/keybindings.py +0 -0
  45. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/messages.py +0 -0
  46. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/model_catalog.py +0 -0
  47. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/navigation.py +0 -0
  48. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/permissions.py +0 -0
  49. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/prompt_templates.py +0 -0
  50. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/remote_session.py +0 -0
  51. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/resource_loader.py +0 -0
  52. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/sdk.py +0 -0
  53. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/session_manager.py +0 -0
  54. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/session_staging.py +0 -0
  55. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/settings_manager.py +0 -0
  56. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/slash_commands.py +0 -0
  57. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/system_prompt.py +0 -0
  58. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/timings.py +0 -0
  59. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/tools/__init__.py +0 -0
  60. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/core/transcript_source.py +0 -0
  61. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/main.py +0 -0
  62. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/__init__.py +0 -0
  63. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/__init__.py +0 -0
  64. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/after_run_compaction.py +0 -0
  65. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/after_run_name.py +0 -0
  66. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/after_run_tools.py +0 -0
  67. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/__init__.py +0 -0
  68. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/assistant_message.py +0 -0
  69. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/chat_log.py +0 -0
  70. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/custom_editor.py +0 -0
  71. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/editor_layout.py +0 -0
  72. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/footer.py +0 -0
  73. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/permission_prompt.py +0 -0
  74. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/tool_execution.py +0 -0
  75. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/components/user_message.py +0 -0
  76. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/interactive_mode.py +0 -0
  77. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/message_inputs.py +0 -0
  78. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/provider_auth.py +0 -0
  79. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/run_status.py +0 -0
  80. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/theme.py +0 -0
  81. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/interactive/tool_renderers.py +0 -0
  82. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/print_mode.py +0 -0
  83. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/__init__.py +0 -0
  84. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/after_run_compaction.py +0 -0
  85. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/after_run_name.py +0 -0
  86. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/after_run_tools.py +0 -0
  87. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/children.py +0 -0
  88. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/context_messages.py +0 -0
  89. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/custom_delivery.py +0 -0
  90. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/editor_write.py +0 -0
  91. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/extensions.py +0 -0
  92. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/images.py +0 -0
  93. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/input.py +0 -0
  94. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/navigation.py +0 -0
  95. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/output.py +0 -0
  96. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/parent_drafts.py +0 -0
  97. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/provider_auth.py +0 -0
  98. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/queued_inputs.py +0 -0
  99. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/rpc_mode.py +0 -0
  100. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/terminal.py +0 -0
  101. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/terminal_data.py +0 -0
  102. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/modes/rpc/transcripts.py +0 -0
  103. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/py.typed +0 -0
  104. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/__init__.py +0 -0
  105. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/frontmatter.py +0 -0
  106. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/git.py +0 -0
  107. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/mime.py +0 -0
  108. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/shell.py +0 -0
  109. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/src/xdog/coding/utils/sleep.py +0 -0
  110. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/benchmark_tui.py +0 -0
  111. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_client.py +0 -0
  112. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_menu_client.py +0 -0
  113. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_parent_draft_client.py +0 -0
  114. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_terminal_client.py +0 -0
  115. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/rpc_terminal_edit_client.py +0 -0
  116. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/fixtures/terminal_client.py +0 -0
  117. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_persistence.py +0 -0
  118. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_persistence_ownership.py +0 -0
  119. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_retirement.py +0 -0
  120. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_selection_terminal.py +0 -0
  121. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tool_selection_ui.py +0 -0
  122. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tools_after_run_hosts.py +0 -0
  123. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_active_tools_after_run_terminal.py +0 -0
  124. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_after_run_compaction.py +0 -0
  125. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_after_run_compaction_terminal.py +0 -0
  126. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_after_run_name.py +0 -0
  127. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_after_run_name_terminal.py +0 -0
  128. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_agent_session.py +0 -0
  129. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_argument_completion_session.py +0 -0
  130. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_argument_completion_ui.py +0 -0
  131. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_callback_inspection.py +0 -0
  132. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_active_tools.py +0 -0
  133. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_agents.py +0 -0
  134. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_image_admission.py +0 -0
  135. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_image_submission.py +0 -0
  136. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_child_queue_ui.py +0 -0
  137. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_claw_active_tools_terminal.py +0 -0
  138. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_claw_elicitation_terminal.py +0 -0
  139. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_claw_sampling_terminal.py +0 -0
  140. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_cli_args.py +0 -0
  141. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_cli_config_selector.py +0 -0
  142. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_cli_file_processor.py +0 -0
  143. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_coding_config.py +0 -0
  144. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_command_artifact_export_ui.py +0 -0
  145. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_command_help_ui.py +0 -0
  146. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_command_lifecycle_ui.py +0 -0
  147. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_compact_layout.py +0 -0
  148. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_compaction_controls_ui.py +0 -0
  149. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_compaction_recovery.py +0 -0
  150. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_context_message_compaction.py +0 -0
  151. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_context_message_persistence.py +0 -0
  152. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_custom_delivery_modes.py +0 -0
  153. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_custom_delivery_terminal.py +0 -0
  154. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_draft_durability_terminal.py +0 -0
  155. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_editor_write_terminal.py +0 -0
  156. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_editor_write_ui.py +0 -0
  157. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_event_bus.py +0 -0
  158. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_exact_command_ui.py +0 -0
  159. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_catalog_ui.py +0 -0
  160. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_command_catalog_ui.py +0 -0
  161. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_command_results_ui.py +0 -0
  162. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_compaction_terminal.py +0 -0
  163. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_compaction_ui.py +0 -0
  164. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_context_message_terminal.py +0 -0
  165. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_context_message_ui.py +0 -0
  166. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_custom_ui.py +0 -0
  167. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_dialogs.py +0 -0
  168. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_editor_ui.py +0 -0
  169. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_entries.py +0 -0
  170. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_failure_projection.py +0 -0
  171. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_input_draft_isolation.py +0 -0
  172. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_input_ui.py +0 -0
  173. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_message_terminal.py +0 -0
  174. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_message_ui.py +0 -0
  175. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_navigation_ui.py +0 -0
  176. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_nested_ui.py +0 -0
  177. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_auth.py +0 -0
  178. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_auth_terminal.py +0 -0
  179. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_capabilities.py +0 -0
  180. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_dynamic.py +0 -0
  181. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_startup.py +0 -0
  182. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_provider_ui.py +0 -0
  183. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_queued_input_terminal.py +0 -0
  184. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_queued_input_ui.py +0 -0
  185. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_results_ui.py +0 -0
  186. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_session_context_ui.py +0 -0
  187. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_session_name_ui.py +0 -0
  188. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_extension_shortcuts_ui.py +0 -0
  189. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_footer_context.py +0 -0
  190. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_hook_lifecycle_ui.py +0 -0
  191. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_idle_message_images.py +0 -0
  192. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_idle_message_images_terminal.py +0 -0
  193. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_image_composer.py +0 -0
  194. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_image_lifecycle_admission.py +0 -0
  195. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_interactive_commands.py +0 -0
  196. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_interactive_keymap.py +0 -0
  197. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_interactive_mode.py +0 -0
  198. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_live_extensions.py +0 -0
  199. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_browser.py +0 -0
  200. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_catalog_watch_ui.py +0 -0
  201. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_combined_watch_ui.py +0 -0
  202. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_context_terminal.py +0 -0
  203. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_elicitation_recovery.py +0 -0
  204. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_elicitation_terminal.py +0 -0
  205. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_elicitation_ui.py +0 -0
  206. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_modern_browser_cancel.py +0 -0
  207. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_modern_browser_terminal.py +0 -0
  208. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_modern_browser_ui.py +0 -0
  209. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_resource_watch_ui.py +0 -0
  210. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_root_hosts.py +0 -0
  211. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_roots_review_ui.py +0 -0
  212. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_roots_terminal.py +0 -0
  213. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_sampling_catalog.py +0 -0
  214. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_sampling_context_hosts.py +0 -0
  215. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_sampling_terminal.py +0 -0
  216. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_sampling_ui.py +0 -0
  217. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_session.py +0 -0
  218. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_mcp_structured_ui.py +0 -0
  219. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_menu_scope_ui.py +0 -0
  220. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_messages.py +0 -0
  221. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_multi_forms_ui.py +0 -0
  222. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_oauth_ui.py +0 -0
  223. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_owned_native_model.py +0 -0
  224. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_permissions.py +0 -0
  225. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_planning.py +0 -0
  226. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_prompt_templates.py +0 -0
  227. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_provider_oauth_terminal.py +0 -0
  228. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_provider_oauth_ui.py +0 -0
  229. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_queue_controls.py +0 -0
  230. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_remote_parent_hash_changes.py +0 -0
  231. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_remote_parent_terminal.py +0 -0
  232. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rendered_image_context.py +0 -0
  233. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_renderer_contracts_ui.py +0 -0
  234. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_repository_review.py +0 -0
  235. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_resource_loader.py +0 -0
  236. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_active_tools.py +0 -0
  237. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_canonical.py +0 -0
  238. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_capability_atomicity.py +0 -0
  239. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_canonical.py +0 -0
  240. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_contributions.py +0 -0
  241. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_custom.py +0 -0
  242. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_editor_write.py +0 -0
  243. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_events.py +0 -0
  244. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_extension_queued_inputs.py +0 -0
  245. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_overlay.py +0 -0
  246. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_child_terminal_lifecycle.py +0 -0
  247. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_children.py +0 -0
  248. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_children_cli.py +0 -0
  249. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_command_catalog.py +0 -0
  250. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_compaction.py +0 -0
  251. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_contributions.py +0 -0
  252. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_backpressure.py +0 -0
  253. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_contribution_integration.py +0 -0
  254. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_delivery.py +0 -0
  255. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_keyboard.py +0 -0
  256. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_refresh.py +0 -0
  257. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_custom_views.py +0 -0
  258. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_deferred_delivery.py +0 -0
  259. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_editor.py +0 -0
  260. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_editor_write.py +0 -0
  261. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_browser.py +0 -0
  262. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_catalog.py +0 -0
  263. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_compaction.py +0 -0
  264. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_context_messages.py +0 -0
  265. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_message.py +0 -0
  266. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_name.py +0 -0
  267. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_navigation.py +0 -0
  268. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extension_queued_inputs.py +0 -0
  269. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_extensions.py +0 -0
  270. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_host_generation.py +0 -0
  271. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_image_uploads.py +0 -0
  272. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_images.py +0 -0
  273. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_menu_scope.py +0 -0
  274. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_mode.py +0 -0
  275. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_navigation.py +0 -0
  276. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_output.py +0 -0
  277. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_overlay.py +0 -0
  278. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_parent_draft_interlocks.py +0 -0
  279. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_parent_draft_terminal.py +0 -0
  280. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_parent_drafts.py +0 -0
  281. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_parent_terminal.py +0 -0
  282. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_physical_editing.py +0 -0
  283. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_physical_terminal.py +0 -0
  284. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_session_lifecycle.py +0 -0
  285. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_shutdown_delivery.py +0 -0
  286. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_structured_layout.py +0 -0
  287. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_terminal_approval_revision.py +0 -0
  288. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_terminal_keymap.py +0 -0
  289. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_transcript_cli.py +0 -0
  290. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_transcripts.py +0 -0
  291. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_rpc_transient_backpressure.py +0 -0
  292. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_run_status.py +0 -0
  293. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_saved_child_drafts.py +0 -0
  294. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_saved_source_ui.py +0 -0
  295. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_sdk_session_state.py +0 -0
  296. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_manager.py +0 -0
  297. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_name_ui.py +0 -0
  298. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_navigation.py +0 -0
  299. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_picker.py +0 -0
  300. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_smoke.py +0 -0
  301. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_session_staging.py +0 -0
  302. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_settings_manager.py +0 -0
  303. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_slash_commands.py +0 -0
  304. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_system_prompt.py +0 -0
  305. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_terminal_input_ui.py +0 -0
  306. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_terminal_release_physical.py +0 -0
  307. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_timings.py +0 -0
  308. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_tool_inventory_ui.py +0 -0
  309. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_tool_lifecycle_ui.py +0 -0
  310. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_tools_browser.py +0 -0
  311. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_transcript_interaction.py +0 -0
  312. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_transcript_output.py +0 -0
  313. {xdog_coding-2.3.0 → xdog_coding-2.4.1}/tests/test_tui_usability.py +0 -0
  314. {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.0
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.3.0
25
- Requires-Dist: xdog-ai[openai]>=2.3.0
26
- Requires-Dist: xdog-tui>=2.3.0
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.3.0"
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.3.0",
25
- "xdog-agent>=2.3.0",
26
- "xdog-tui>=2.3.0",
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",
@@ -1,3 +1,3 @@
1
1
  """coding - Interactive coding agent CLI."""
2
2
 
3
- __version__ = "2.3.0"
3
+ __version__ = "2.4.1"
@@ -26,7 +26,7 @@ from pydantic import BaseModel, Field
26
26
 
27
27
  MONOREPO_NAME = "xdog"
28
28
  APP_NAME = "coding"
29
- VERSION = "2.3.0"
29
+ VERSION = "2.4.1"
30
30
  PROJECT_DIR_NAME = ".coding"
31
31
  ENV_OVERRIDE = "CODING_DIR"
32
32
 
@@ -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
- from xdog.agent.skills import render_skill_body
278
- from xdog.coding.core.slash_commands import skill_manager
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
- """Retire skills their author declared as lasting one turn.
630
-
631
- Declared, not inferred. A timer cannot tell a one-shot procedure from a
632
- guardrail, and "unused for N turns" is unmeasurable anyway — there is no
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.ai.types import AssistantMessage, Context, TextContent, ToolCall, ToolResultMessage, UserMessage
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
- # Build text representation of old messages for summarization
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(conversation_text, agent, instructions))
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
- """Extract concatenated text from a message's content parts."""
102
- parts: list[str] = []
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
- Context(system_prompt=SUMMARIZE_SYSTEM_PROMPT, messages=(UserMessage(content=request),), tools=()),
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 = {'event': event.type, 'data': {}, 'limited': True}
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, ToolExecutionStartEvent):
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
- await wait(lambda: panel.sampling_review is None)
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) and not any(row.get('id') == 'retrieve' for row in frames())
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) == 14
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)))