xdog-coding 2.4.3__tar.gz → 2.4.4__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 (255) hide show
  1. xdog_coding-2.4.3/README.md → xdog_coding-2.4.4/PKG-INFO +97 -0
  2. xdog_coding-2.4.3/PKG-INFO → xdog_coding-2.4.4/README.md +65 -32
  3. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/pyproject.toml +5 -5
  4. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/__init__.py +1 -1
  5. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/backend.py +52 -30
  6. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/server.py +33 -18
  7. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/config.py +33 -10
  8. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/agent_session.py +241 -157
  9. xdog_coding-2.4.4/src/xdog/coding/core/command_names.py +19 -0
  10. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/goal_adapter.py +19 -15
  11. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/resource_loader.py +46 -26
  12. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sdk.py +35 -57
  13. xdog_coding-2.4.4/src/xdog/coding/core/session_history.py +23 -0
  14. xdog_coding-2.4.4/src/xdog/coding/core/session_lifecycle.py +160 -0
  15. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/session_manager.py +93 -30
  16. xdog_coding-2.4.4/src/xdog/coding/core/session_preparation.py +20 -0
  17. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/session_staging.py +3 -0
  18. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/controller.py +111 -42
  19. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/intake.py +1 -0
  20. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/ownership.py +8 -2
  21. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/runtime.py +49 -59
  22. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/store.py +51 -20
  23. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/slash_commands.py +36 -12
  24. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/main.py +22 -39
  25. xdog_coding-2.4.4/src/xdog/coding/modes/interactive/components/custom_editor.py +21 -0
  26. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/interactive_mode.py +29 -24
  27. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/session_events.py +7 -6
  28. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/goal_installed_consumer.py +7 -3
  29. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_access_scope.py +5 -1
  30. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_artifact_contract.py +4 -6
  31. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_offline_inbox.py +2 -2
  32. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_review_regressions.py +4 -6
  33. xdog_coding-2.4.4/tests/test_admission_navigation_owner.py +201 -0
  34. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_batch_mode_errors.py +6 -3
  35. xdog_coding-2.4.4/tests/test_cli_extension_trust.py +69 -0
  36. xdog_coding-2.4.4/tests/test_effective_configuration.py +88 -0
  37. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_goal_extension_views.py +8 -8
  38. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_goal_lifecycle.py +3 -4
  39. xdog_coding-2.4.4/tests/test_history_commit_owner.py +114 -0
  40. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_inbox_controller.py +2 -2
  41. xdog_coding-2.4.4/tests/test_input_store_capacity.py +106 -0
  42. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_no_coding_rpc.py +2 -3
  43. xdog_coding-2.4.4/tests/test_owner_review_regressions.py +202 -0
  44. xdog_coding-2.4.4/tests/test_prompt_preparation.py +103 -0
  45. xdog_coding-2.4.4/tests/test_public_extension_commands.py +82 -0
  46. xdog_coding-2.4.4/tests/test_resource_loader.py +76 -0
  47. xdog_coding-2.4.4/tests/test_session_close_owner.py +345 -0
  48. xdog_coding-2.4.4/tests/test_session_identity_boundaries.py +153 -0
  49. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_ownership.py +7 -6
  50. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_smoke.py +2 -2
  51. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_slash_skills.py +3 -2
  52. xdog_coding-2.4.3/src/xdog/coding/core/extensions/__init__.py +0 -1
  53. xdog_coding-2.4.3/src/xdog/coding/core/extensions/loader.py +0 -116
  54. xdog_coding-2.4.3/src/xdog/coding/core/extensions/runner.py +0 -79
  55. xdog_coding-2.4.3/src/xdog/coding/core/extensions/types.py +0 -11
  56. xdog_coding-2.4.3/src/xdog/coding/modes/interactive/components/custom_editor.py +0 -30
  57. xdog_coding-2.4.3/tests/test_resource_loader.py +0 -50
  58. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/.gitignore +0 -0
  59. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/LICENSE +0 -0
  60. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/LICENSE-EXCEPTION.md +0 -0
  61. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/__init__.py +0 -0
  62. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/bridge.py +0 -0
  63. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/cli.py +0 -0
  64. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/client.py +0 -0
  65. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/extensions.py +0 -0
  66. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/ingress.py +0 -0
  67. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/ingress_backend.py +0 -0
  68. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/acp/ingress_main.py +0 -0
  69. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/cli/__init__.py +0 -0
  70. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/cli/args.py +0 -0
  71. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/cli/config_selector.py +0 -0
  72. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/cli/enqueue.py +0 -0
  73. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/cli/file_processor.py +0 -0
  74. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/cli/initial_message.py +0 -0
  75. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/cli/list_models.py +0 -0
  76. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/cli/session_picker.py +0 -0
  77. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/__init__.py +0 -0
  78. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/bash_executor.py +0 -0
  79. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/canonical_source.py +0 -0
  80. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/child_agents.py +0 -0
  81. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/compaction/__init__.py +0 -0
  82. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/compaction/compaction.py +0 -0
  83. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/compaction/utils.py +0 -0
  84. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/context_messages.py +0 -0
  85. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/defaults.py +0 -0
  86. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/event_bus.py +0 -0
  87. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/exec_utils.py +0 -0
  88. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/inbox_commands.py +0 -0
  89. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/keybindings.py +0 -0
  90. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/messages.py +0 -0
  91. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/model_catalog.py +0 -0
  92. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/navigation.py +0 -0
  93. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/permissions.py +0 -0
  94. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/prompt_templates.py +0 -0
  95. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/remote_session.py +0 -0
  96. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/__init__.py +0 -0
  97. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/availability.py +0 -0
  98. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/batches.py +0 -0
  99. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/inbox_models.py +0 -0
  100. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/inbox_runtime.py +0 -0
  101. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/inbox_store.py +0 -0
  102. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/inbox_views.py +0 -0
  103. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/models.py +0 -0
  104. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/operations.py +0 -0
  105. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/router.py +0 -0
  106. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/sessions/turns.py +0 -0
  107. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/settings_manager.py +0 -0
  108. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/system_prompt.py +0 -0
  109. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/timings.py +0 -0
  110. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/tools/__init__.py +0 -0
  111. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/core/transcript_source.py +0 -0
  112. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/__init__.py +0 -0
  113. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/__init__.py +0 -0
  114. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/components/__init__.py +0 -0
  115. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/components/assistant_message.py +0 -0
  116. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/components/chat_log.py +0 -0
  117. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/components/editor_layout.py +0 -0
  118. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/components/footer.py +0 -0
  119. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/components/permission_prompt.py +0 -0
  120. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/components/tool_execution.py +0 -0
  121. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/components/user_message.py +0 -0
  122. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/provider_auth.py +0 -0
  123. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/run_status.py +0 -0
  124. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/session_presenter.py +0 -0
  125. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/theme.py +0 -0
  126. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/interactive/tool_renderers.py +0 -0
  127. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/modes/print_mode.py +0 -0
  128. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/py.typed +0 -0
  129. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/utils/__init__.py +0 -0
  130. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/utils/frontmatter.py +0 -0
  131. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/utils/git.py +0 -0
  132. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/utils/mime.py +0 -0
  133. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/utils/shell.py +0 -0
  134. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/src/xdog/coding/utils/sleep.py +0 -0
  135. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/acp_owner_agent.py +0 -0
  136. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/batch_helpers.py +0 -0
  137. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/conftest.py +0 -0
  138. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/extension_session_helpers.py +0 -0
  139. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/fixtures/benchmark_tui.py +0 -0
  140. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/fixtures/terminal_client.py +0 -0
  141. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/fixtures/terminal_review.py +0 -0
  142. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/session_owner_process.py +0 -0
  143. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_backend.py +0 -0
  144. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_batch_intake.py +0 -0
  145. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_bridge.py +0 -0
  146. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_cli.py +0 -0
  147. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_enqueue_cli.py +0 -0
  148. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_ingress.py +0 -0
  149. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_ingress_lifecycle.py +0 -0
  150. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_acp_optional_imports.py +0 -0
  151. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_active_tool_persistence.py +0 -0
  152. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_active_tool_retirement.py +0 -0
  153. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_after_run_compaction.py +0 -0
  154. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_after_run_name.py +0 -0
  155. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_agent_session.py +0 -0
  156. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_batch_config.py +0 -0
  157. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_batch_display.py +0 -0
  158. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_batch_turns.py +0 -0
  159. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_child_active_tools.py +0 -0
  160. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_child_agents.py +0 -0
  161. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_child_image_admission.py +0 -0
  162. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_child_image_submission.py +0 -0
  163. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_child_queue_ui.py +0 -0
  164. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_cli_args.py +0 -0
  165. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_cli_config_selector.py +0 -0
  166. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_cli_file_processor.py +0 -0
  167. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_coding_config.py +0 -0
  168. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_command_artifact_export_ui.py +0 -0
  169. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_compact_layout.py +0 -0
  170. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_compaction_controls_ui.py +0 -0
  171. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_compaction_recovery.py +0 -0
  172. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_context_message_compaction.py +0 -0
  173. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_event_bus.py +0 -0
  174. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_extension_command_results_ui.py +0 -0
  175. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_extension_core_readonly.py +0 -0
  176. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_extension_editor_ui.py +0 -0
  177. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_extension_input_draft_isolation.py +0 -0
  178. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_extension_input_ui.py +0 -0
  179. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_extension_navigation_ui.py +0 -0
  180. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_extension_provider_startup.py +0 -0
  181. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_extension_readonly_ui.py +0 -0
  182. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_extension_session_context_ui.py +0 -0
  183. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_footer_context.py +0 -0
  184. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_goal_adapter.py +0 -0
  185. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_image_composer.py +0 -0
  186. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_image_lifecycle_admission.py +0 -0
  187. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_inbox_activation_navigation.py +0 -0
  188. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_inbox_auto_consume.py +0 -0
  189. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_inbox_commands.py +0 -0
  190. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_inbox_crash.py +0 -0
  191. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_inbox_display.py +0 -0
  192. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_inbox_store.py +0 -0
  193. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_inbox_v2.py +0 -0
  194. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_interactive_commands.py +0 -0
  195. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_interactive_keymap.py +0 -0
  196. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_interactive_mode.py +0 -0
  197. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_live_extensions.py +0 -0
  198. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_auth_projection.py +0 -0
  199. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_browser.py +0 -0
  200. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_catalog_watch_ui.py +0 -0
  201. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_combined_watch_ui.py +0 -0
  202. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_manual_picker_ui.py +0 -0
  203. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_modern_browser_cancel.py +0 -0
  204. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_modern_browser_ui.py +0 -0
  205. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_retired_gateway.py +0 -0
  206. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_roots_review_ui.py +0 -0
  207. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_sampling_ui.py +0 -0
  208. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_mcp_task_terminal.py +0 -0
  209. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_messages.py +0 -0
  210. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_native_session_queue.py +0 -0
  211. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_no_legacy_event_host.py +0 -0
  212. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_oauth_ui.py +0 -0
  213. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_owned_native_model.py +0 -0
  214. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_permissions.py +0 -0
  215. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_prompt_templates.py +0 -0
  216. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_provider_oauth_ui.py +0 -0
  217. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_queue_controls.py +0 -0
  218. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_readonly_ui.py +0 -0
  219. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_removed_child_return_ui.py +0 -0
  220. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_rendered_image_context.py +0 -0
  221. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_renderer_contracts_ui.py +0 -0
  222. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_repository_review.py +0 -0
  223. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_retained_history.py +0 -0
  224. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_run_status.py +0 -0
  225. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_runtime_prompt.py +0 -0
  226. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_saved_child_drafts.py +0 -0
  227. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_saved_source_ui.py +0 -0
  228. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_sdk_compaction_compat.py +0 -0
  229. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_sdk_runtime_compatibility.py +0 -0
  230. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_sdk_session_state.py +0 -0
  231. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_batches.py +0 -0
  232. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_control_fences.py +0 -0
  233. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_controller.py +0 -0
  234. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_history_display.py +0 -0
  235. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_input_store.py +0 -0
  236. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_manager.py +0 -0
  237. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_message_display.py +0 -0
  238. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_mode_routing.py +0 -0
  239. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_name_ui.py +0 -0
  240. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_navigation.py +0 -0
  241. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_picker.py +0 -0
  242. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_router.py +0 -0
  243. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_staging.py +0 -0
  244. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_session_turns.py +0 -0
  245. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_settings_manager.py +0 -0
  246. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_skill_controls_ui.py +0 -0
  247. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_slash_commands.py +0 -0
  248. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_system_prompt.py +0 -0
  249. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_terminal_acceptance.py +0 -0
  250. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_timings.py +0 -0
  251. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_tools_browser.py +0 -0
  252. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_transcript_interaction.py +0 -0
  253. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_transcript_output.py +0 -0
  254. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_tui_usability.py +0 -0
  255. {xdog_coding-2.4.3 → xdog_coding-2.4.4}/tests/test_unified_inbox_review_fixes.py +0 -0
@@ -1,3 +1,35 @@
1
+ Metadata-Version: 2.5
2
+ Name: xdog-coding
3
+ Version: 2.4.4
4
+ Summary: Interactive coding agent CLI
5
+ Project-URL: Homepage, https://xdog.942295.xyz
6
+ Project-URL: Repository, https://github.com/litterzhang/xdog
7
+ Project-URL: Documentation, https://xdog.942295.xyz
8
+ Project-URL: Issues, https://github.com/litterzhang/xdog/issues
9
+ Author: HugeMan
10
+ License-Expression: AGPL-3.0-or-later
11
+ License-File: LICENSE
12
+ License-File: LICENSE-EXCEPTION.md
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.12
22
+ Requires-Dist: agent-client-protocol<0.13,>=0.12.1; python_version < '3.15'
23
+ Requires-Dist: click>=8.1.0
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: pyyaml>=6.0
26
+ Requires-Dist: xdog-agent>=2.4.4
27
+ Requires-Dist: xdog-ai[openai]>=2.4.4
28
+ Requires-Dist: xdog-tui>=2.4.4
29
+ Provides-Extra: skills
30
+ Requires-Dist: xdog-skills>=2.4.4; extra == 'skills'
31
+ Description-Content-Type: text/markdown
32
+
1
33
  # xdog-coding
2
34
 
3
35
  > Platform scope: **Linux only**. Non-Linux adapters and native-platform support
@@ -2623,3 +2655,68 @@ and widget layouts are removed. Native approval and recovery reviews remain host
2623
2655
  owned; they expose no plugin layout or editor/session-mutation authority. Core TUI
2624
2656
  overlays, transcript fullscreen, Unicode, clipping, cursor and tool-approval behavior
2625
2657
  remain.
2658
+
2659
+ ## Session ownership and reliable shutdown
2660
+
2661
+ Coding's TUI, print, SDK and ACP ingress share one session controller and Agent.
2662
+ Persistent writer locks live under `<sessions>/.owners`; they do not depend on
2663
+ `TMPDIR`. Short private Linux Unix socket/discovery files are separate, under
2664
+ `/tmp/xdog-coding-<uid>/<store-hash>`. Stop older Coding processes before upgrading:
2665
+ old binaries do not participate in this store-anchored writer-lock protocol. Do
2666
+ not run mixed old/new writers against the same session store.
2667
+
2668
+ SDK applications should `await session.aclose()` in `finally`. Closure is
2669
+ idempotent, waits for actual child/Goal/extension/MCP cleanup, and reports cleanup
2670
+ errors only after attempting the other resources. Cancellation of a waiter does
2671
+ not abandon the underlying close. `session.dispose()` requests the same close
2672
+ and returns a concurrent Future; synchronous applications can wait on `.result()`.
2673
+ Do not block an asynchronous caller loop with `.result()`; await `aclose()` instead.
2674
+ Synchronous construction and controls can be called with `asyncio.to_thread` when
2675
+ used from an async application. A receiver accepting a queued message does not
2676
+ require message approval; existing approval applies to tool execution only.
2677
+
2678
+ Automatic project instructions come **only** from the working directory's
2679
+ `AGENTS.md`, `.coding/INSTRUCTIONS.md` and `.coding/instructions.md`. There is no
2680
+ parent walk. Other harness configuration files are not automatic instruction
2681
+ sources; they remain ordinary files that users can explicitly read or attach under
2682
+ normal file-access and permission rules. There is no filename denylist.
2683
+ Each automatic source must be a regular UTF-8 file of at most 64 KiB; symlinks,
2684
+ special files and unreadable/oversized sources cause an explicit preparation error. Missing files are optional. These
2685
+ instructions are refreshed in owned off-loop preparation before a turn, reach the
2686
+ actual model prompt, and never grant tool permissions. The Agent alone renders
2687
+ skill indexes and session/turn-scoped bodies; loading a skill starts no observer,
2688
+ model, receiver or daemon.
2689
+
2690
+ `CreateSessionOptions.thinking_level` is honored, including explicit `"off"`.
2691
+ Initial `allowed_tools` configuration is a **tool selection**, not authorization:
2692
+ absent/null falls through to a lower layer, `[]` selects no tools, and a nonempty
2693
+ list selects those configured names. Caller options/overrides take precedence
2694
+ over saved explicit choices, then project and global configuration. Choices are
2695
+ not unioned. Unavailable saved names do not create tools or gain authority later;
2696
+ normal permission checks still apply to the selected tools.
2697
+
2698
+ The separate input history ledger has a common **64 MiB** read/write bound.
2699
+ Admission reserves final-state, batch-ID and bounded diagnostic space for every
2700
+ unfinished input; if it cannot fit, it is refused before acknowledgement. Errors
2701
+ retain up to 4096 characters with an explicit truncation marker; batch IDs are
2702
+ limited to 256 UTF-8 bytes. Accepted execution facts are never silently deleted.
2703
+ At capacity, explicitly start another session rather than automatically archiving
2704
+ or replaying requests. Recovery marks unfinished execution interrupted and never
2705
+ replays potentially executed tool effects automatically.
2706
+
2707
+ Ordinary turn history uses one immutable, original-owner disk commit, off the
2708
+ model loop, before publishing saved state and completing producer receipts.
2709
+ File replacement synchronizes the file and parent directory. A failed or
2710
+ uncertain history commit retains the live executed tail and fences further turns;
2711
+ close and explicitly reload/inspect the saved state before continuing. Concurrent
2712
+ metadata revision conflicts fail closed instead of overwriting newer facts.
2713
+ These are local filesystem/process guarantees, not hardware power-loss
2714
+ exactly-once execution or an OS security sandbox. No existing user data is migrated
2715
+ or discarded automatically.
2716
+
2717
+ Enabled extensions may declare a public slash name. The packaged `goal_ui`
2718
+ display exposes `/goals [GOAL_ID | page OFFSET]`; `/goal` remains the skill name.
2719
+ This is generic extension routing, not a Coding-specific Goal command. Public
2720
+ names appear in help/completion and cannot shadow builtin or skill commands;
2721
+ model tool names and permissions do not change. See
2722
+ `docs/extension-public-commands.md` for the shared Coding/Claw contract.
@@ -1,35 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: xdog-coding
3
- Version: 2.4.3
4
- Summary: Interactive coding agent CLI
5
- Project-URL: Homepage, https://xdog.942295.xyz
6
- Project-URL: Repository, https://github.com/litterzhang/xdog
7
- Project-URL: Documentation, https://xdog.942295.xyz
8
- Project-URL: Issues, https://github.com/litterzhang/xdog/issues
9
- Author: HugeMan
10
- License-Expression: AGPL-3.0-or-later
11
- License-File: LICENSE
12
- License-File: LICENSE-EXCEPTION.md
13
- Classifier: Development Status :: 5 - Production/Stable
14
- Classifier: Intended Audience :: Developers
15
- Classifier: Operating System :: POSIX :: Linux
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.12
18
- Classifier: Programming Language :: Python :: 3.13
19
- Classifier: Topic :: Software Development :: Libraries
20
- Classifier: Typing :: Typed
21
- Requires-Python: >=3.12
22
- Requires-Dist: agent-client-protocol<0.13,>=0.12.1; python_version < '3.15'
23
- Requires-Dist: click>=8.1.0
24
- Requires-Dist: pydantic>=2.0
25
- Requires-Dist: pyyaml>=6.0
26
- Requires-Dist: xdog-agent>=2.4.3
27
- Requires-Dist: xdog-ai[openai]>=2.4.3
28
- Requires-Dist: xdog-tui>=2.4.3
29
- Provides-Extra: skills
30
- Requires-Dist: xdog-skills>=2.4.3; extra == 'skills'
31
- Description-Content-Type: text/markdown
32
-
33
1
  # xdog-coding
34
2
 
35
3
  > Platform scope: **Linux only**. Non-Linux adapters and native-platform support
@@ -2655,3 +2623,68 @@ and widget layouts are removed. Native approval and recovery reviews remain host
2655
2623
  owned; they expose no plugin layout or editor/session-mutation authority. Core TUI
2656
2624
  overlays, transcript fullscreen, Unicode, clipping, cursor and tool-approval behavior
2657
2625
  remain.
2626
+
2627
+ ## Session ownership and reliable shutdown
2628
+
2629
+ Coding's TUI, print, SDK and ACP ingress share one session controller and Agent.
2630
+ Persistent writer locks live under `<sessions>/.owners`; they do not depend on
2631
+ `TMPDIR`. Short private Linux Unix socket/discovery files are separate, under
2632
+ `/tmp/xdog-coding-<uid>/<store-hash>`. Stop older Coding processes before upgrading:
2633
+ old binaries do not participate in this store-anchored writer-lock protocol. Do
2634
+ not run mixed old/new writers against the same session store.
2635
+
2636
+ SDK applications should `await session.aclose()` in `finally`. Closure is
2637
+ idempotent, waits for actual child/Goal/extension/MCP cleanup, and reports cleanup
2638
+ errors only after attempting the other resources. Cancellation of a waiter does
2639
+ not abandon the underlying close. `session.dispose()` requests the same close
2640
+ and returns a concurrent Future; synchronous applications can wait on `.result()`.
2641
+ Do not block an asynchronous caller loop with `.result()`; await `aclose()` instead.
2642
+ Synchronous construction and controls can be called with `asyncio.to_thread` when
2643
+ used from an async application. A receiver accepting a queued message does not
2644
+ require message approval; existing approval applies to tool execution only.
2645
+
2646
+ Automatic project instructions come **only** from the working directory's
2647
+ `AGENTS.md`, `.coding/INSTRUCTIONS.md` and `.coding/instructions.md`. There is no
2648
+ parent walk. Other harness configuration files are not automatic instruction
2649
+ sources; they remain ordinary files that users can explicitly read or attach under
2650
+ normal file-access and permission rules. There is no filename denylist.
2651
+ Each automatic source must be a regular UTF-8 file of at most 64 KiB; symlinks,
2652
+ special files and unreadable/oversized sources cause an explicit preparation error. Missing files are optional. These
2653
+ instructions are refreshed in owned off-loop preparation before a turn, reach the
2654
+ actual model prompt, and never grant tool permissions. The Agent alone renders
2655
+ skill indexes and session/turn-scoped bodies; loading a skill starts no observer,
2656
+ model, receiver or daemon.
2657
+
2658
+ `CreateSessionOptions.thinking_level` is honored, including explicit `"off"`.
2659
+ Initial `allowed_tools` configuration is a **tool selection**, not authorization:
2660
+ absent/null falls through to a lower layer, `[]` selects no tools, and a nonempty
2661
+ list selects those configured names. Caller options/overrides take precedence
2662
+ over saved explicit choices, then project and global configuration. Choices are
2663
+ not unioned. Unavailable saved names do not create tools or gain authority later;
2664
+ normal permission checks still apply to the selected tools.
2665
+
2666
+ The separate input history ledger has a common **64 MiB** read/write bound.
2667
+ Admission reserves final-state, batch-ID and bounded diagnostic space for every
2668
+ unfinished input; if it cannot fit, it is refused before acknowledgement. Errors
2669
+ retain up to 4096 characters with an explicit truncation marker; batch IDs are
2670
+ limited to 256 UTF-8 bytes. Accepted execution facts are never silently deleted.
2671
+ At capacity, explicitly start another session rather than automatically archiving
2672
+ or replaying requests. Recovery marks unfinished execution interrupted and never
2673
+ replays potentially executed tool effects automatically.
2674
+
2675
+ Ordinary turn history uses one immutable, original-owner disk commit, off the
2676
+ model loop, before publishing saved state and completing producer receipts.
2677
+ File replacement synchronizes the file and parent directory. A failed or
2678
+ uncertain history commit retains the live executed tail and fences further turns;
2679
+ close and explicitly reload/inspect the saved state before continuing. Concurrent
2680
+ metadata revision conflicts fail closed instead of overwriting newer facts.
2681
+ These are local filesystem/process guarantees, not hardware power-loss
2682
+ exactly-once execution or an OS security sandbox. No existing user data is migrated
2683
+ or discarded automatically.
2684
+
2685
+ Enabled extensions may declare a public slash name. The packaged `goal_ui`
2686
+ display exposes `/goals [GOAL_ID | page OFFSET]`; `/goal` remains the skill name.
2687
+ This is generic extension routing, not a Coding-specific Goal command. Public
2688
+ names appear in help/completion and cannot shadow builtin or skill commands;
2689
+ model tool names and permissions do not change. See
2690
+ `docs/extension-public-commands.md` for the shared Coding/Claw contract.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "xdog-coding"
7
- version = "2.4.3"
7
+ version = "2.4.4"
8
8
  description = "Interactive coding agent CLI"
9
9
  requires-python = ">=3.12"
10
10
  classifiers = [
@@ -22,17 +22,17 @@ readme = "README.md"
22
22
  license = "AGPL-3.0-or-later"
23
23
  license-files = ["LICENSE", "LICENSE-EXCEPTION.md"]
24
24
  dependencies = [
25
- "xdog-ai[openai]>=2.4.3",
26
- "xdog-agent>=2.4.3",
25
+ "xdog-ai[openai]>=2.4.4",
26
+ "xdog-agent>=2.4.4",
27
27
  "agent-client-protocol>=0.12.1,<0.13; python_version < '3.15'",
28
- "xdog-tui>=2.4.3",
28
+ "xdog-tui>=2.4.4",
29
29
  "pyyaml>=6.0",
30
30
  "click>=8.1.0",
31
31
  "pydantic>=2.0",
32
32
  ]
33
33
 
34
34
  [project.optional-dependencies]
35
- skills = ["xdog-skills>=2.4.3"]
35
+ skills = ["xdog-skills>=2.4.4"]
36
36
 
37
37
  [project.urls]
38
38
  Homepage = "https://xdog.942295.xyz"
@@ -1,3 +1,3 @@
1
1
  """coding - Interactive coding agent CLI."""
2
2
 
3
- __version__ = "2.4.3"
3
+ __version__ = "2.4.4"
@@ -89,17 +89,19 @@ class CodingBackend:
89
89
  try:
90
90
  session = await asyncio.shield(task)
91
91
  except asyncio.CancelledError:
92
- session = await task
93
- session.dispose()
92
+ from xdog.agent.async_work import wait_owned
93
+ async def settle_construction() -> None:
94
+ late_session = await task
95
+ await late_session.aclose()
96
+ await wait_owned(asyncio.create_task(settle_construction(), name='coding-acp-canceled-construction'))
94
97
  raise
95
- runtime = SessionRuntime(session, bound=False, router=self.router)
96
- runtime.start()
97
98
  try:
99
+ runtime = SessionRuntime(session, bound=False, router=self.router)
100
+ runtime.start()
98
101
  from xdog.coding.acp.server import serve_local
99
-
100
102
  server = await serve_local(runtime)
101
103
  except BaseException:
102
- await asyncio.wrap_future(runtime.close())
104
+ await session.aclose()
103
105
  raise
104
106
  self._owned[session.session_id] = peer.id, runtime, server
105
107
  ref = SessionRef(session.session_id, session.working_dir, runtime.controller.generation)
@@ -300,31 +302,51 @@ class CodingBackend:
300
302
  await asyncio.wrap_future(running.controller.cancel(running.ticket.id, peer_id=peer.id))
301
303
 
302
304
  async def disconnect(self, peer: Peer) -> None:
303
- await self._inbox.disconnect(peer)
305
+ from functools import partial
306
+
307
+ from xdog.agent.async_work import settle_cleanup
304
308
  pending = tuple(row for key, row in self._running.items() if key[0] == peer.id)
305
- for row in pending:
306
- if row.ticket is None and not row.task.cancelling():
307
- row.task.cancel()
308
- for controller in {row.controller for row in pending}:
309
- await asyncio.wrap_future(controller.disconnect(peer.id))
310
- tasks = [row.task for row in pending if row.task is not asyncio.current_task()]
311
- if tasks:
312
- for task in tasks:
313
- if not task.done() and not task.cancelling():
314
- task.cancel()
315
- await asyncio.gather(*tasks, return_exceptions=True)
316
- for key, remote in tuple(self._remote.items()):
317
- if key[0] == peer.id:
318
- await remote.close()
319
- del self._remote[key]
320
- for identifier, (owner, runtime, server) in tuple(self._owned.items()):
321
- if owner == peer.id:
322
- await server.close()
323
- await asyncio.wrap_future(runtime.close())
324
- del self._owned[identifier]
325
- for key in tuple(self._refs):
326
- if key[0] == peer.id:
327
- del self._refs[key]
309
+ async def ingress() -> None:
310
+ await self._inbox.disconnect(peer)
311
+ async def requests() -> None:
312
+ for row in pending:
313
+ if row.ticket is None and not row.task.cancelling():
314
+ row.task.cancel()
315
+ async def disconnect(controller: SessionController) -> None:
316
+ await asyncio.wrap_future(controller.disconnect(peer.id))
317
+ async def join() -> None:
318
+ tasks = [row.task for row in pending if row.task is not asyncio.current_task()]
319
+ for task in tasks:
320
+ if not task.done() and not task.cancelling():
321
+ task.cancel()
322
+ if tasks:
323
+ await asyncio.gather(*tasks, return_exceptions=True)
324
+ controllers = {row.controller for row in pending}
325
+ await settle_cleanup(*(partial(disconnect, controller) for controller in controllers),
326
+ join, message='ACP request retirement failed')
327
+ async def remote(key: tuple[str, str], attachment: AcpAttachment) -> None:
328
+ try:
329
+ await attachment.close()
330
+ finally:
331
+ self._remote.pop(key, None)
332
+ async def owned(identifier: str, runtime: SessionRuntime) -> None:
333
+ try:
334
+ # The session owns its CURRENT endpoint too. Closing the saved
335
+ # old endpoint first would bypass all remaining resource cleanup.
336
+ await runtime.session.aclose()
337
+ finally:
338
+ if runtime.session._lease is None:
339
+ self._owned.pop(identifier, None)
340
+ async def references() -> None:
341
+ for key in tuple(self._refs):
342
+ if key[0] == peer.id:
343
+ self._refs.pop(key, None)
344
+ owned_sessions = tuple(self._owned.items())
345
+ await settle_cleanup(ingress, requests,
346
+ *(partial(remote, key, attachment) for key, attachment in tuple(self._remote.items()) if key[0] == peer.id),
347
+ *(partial(owned, identifier, runtime) for identifier, (owner, runtime, _server) in owned_sessions
348
+ if owner == peer.id), references, message='ACP peer cleanup failed')
349
+
328
350
  async def extension(self, peer: Peer, method: str, params: dict[str, Any]) -> dict[str, Any]:
329
351
  if method in {'xdog/enqueue', 'xdog/inbox_status'}:
330
352
  target = params.get('session_id')
@@ -22,6 +22,7 @@ class LocalServer:
22
22
  self.identity: tuple[int, int] | None = None
23
23
  self.server: asyncio.AbstractServer | None = None
24
24
  self.closing = False
25
+ self._closing: asyncio.Task[None] | None = None
25
26
  self.writers: set[asyncio.StreamWriter] = set()
26
27
  self.tasks: set[asyncio.Task[None]] = set()
27
28
 
@@ -47,24 +48,38 @@ class LocalServer:
47
48
  await writer.wait_closed()
48
49
 
49
50
  async def close(self) -> None:
50
- self.closing = True
51
- if self.server is not None:
52
- self.server.close()
53
- for writer in tuple(self.writers):
54
- writer.close()
55
- if self.server is not None:
56
- # Python >=3.12 waits for client transports too; close them first.
57
- await self.server.wait_closed()
58
- self.server = None
59
- if self.tasks:
60
- await asyncio.gather(*tuple(self.tasks), return_exceptions=True)
61
- path = self.record.address
62
- if path.exists():
63
- info = path.lstat()
64
- if (stat.S_ISSOCK(info.st_mode) and info.st_uid == os.getuid()
65
- and (info.st_dev, info.st_ino) == self.identity):
66
- path.unlink()
67
- self.runtime.registry.remove(self.record)
51
+ from xdog.agent.async_work import wait_owned
52
+ if self._closing is None:
53
+ self.closing = True
54
+ self._closing = asyncio.create_task(self._close(), name='coding-endpoint-close')
55
+ await wait_owned(self._closing)
56
+
57
+ async def _close(self) -> None:
58
+ from xdog.agent.async_work import settle_cleanup
59
+ from xdog.coding.core.session_lifecycle import settle_sync
60
+ async def transports() -> None:
61
+ steps = [writer.close for writer in tuple(self.writers)]
62
+ if self.server is not None:
63
+ steps.insert(0, self.server.close)
64
+ settle_sync(*steps, message='ACP transport close failed')
65
+ async def server() -> None:
66
+ if self.server is not None:
67
+ await self.server.wait_closed()
68
+ self.server = None
69
+ async def peers() -> None:
70
+ if self.tasks:
71
+ # Disconnect/request errors are peer outcomes, not cleanup errors.
72
+ await asyncio.gather(*tuple(self.tasks), return_exceptions=True)
73
+ async def unlink() -> None:
74
+ path = self.record.address
75
+ if path.exists():
76
+ info = path.lstat()
77
+ if (stat.S_ISSOCK(info.st_mode) and info.st_uid == os.getuid()
78
+ and (info.st_dev, info.st_ino) == self.identity):
79
+ path.unlink()
80
+ async def registry() -> None:
81
+ self.runtime.registry.remove(self.record)
82
+ await settle_cleanup(transports, server, peers, unlink, registry, message='ACP endpoint cleanup failed')
68
83
 
69
84
 
70
85
  async def serve_local(runtime: SessionRuntime) -> LocalServer:
@@ -21,6 +21,20 @@ from typing import Any
21
21
  from pydantic import BaseModel, Field, field_validator
22
22
 
23
23
 
24
+ class InvalidToolSelection(ValueError):
25
+ """Explicit invalid tool selection must not silently become all tools."""
26
+
27
+
28
+ def validate_tool_selection(value: object) -> tuple[str, ...] | None:
29
+ if value is None:
30
+ return None
31
+ from xdog.agent.extensions.active_tools import names
32
+ try:
33
+ return names(value)
34
+ except (TypeError, ValueError) as exc:
35
+ raise InvalidToolSelection('allowed_tools must be a list of distinct tool names or null') from exc
36
+
37
+
24
38
  class InvalidBatchLimit(ValueError):
25
39
  """An explicit malformed owner setting must not fall back silently."""
26
40
 
@@ -37,7 +51,7 @@ def validate_max_messages_per_turn(value: object) -> int:
37
51
 
38
52
  MONOREPO_NAME = "xdog"
39
53
  APP_NAME = "coding"
40
- VERSION = "2.4.3"
54
+ VERSION = "2.4.4"
41
55
  PROJECT_DIR_NAME = ".coding"
42
56
  ENV_OVERRIDE = "CODING_DIR"
43
57
 
@@ -202,7 +216,7 @@ class GlobalConfig(BaseModel):
202
216
  child_presets: Any = Field(default_factory=dict)
203
217
  mcp_servers: dict[str, Any] = Field(default_factory=dict)
204
218
  mcp_credential_backend: Any = 'file' # Validated at the host boundary; never defaults on an invalid native choice.
205
- allowed_tools: list[str] = Field(default_factory=list)
219
+ allowed_tools: list[str] | None = None
206
220
  custom_instructions: str = ""
207
221
 
208
222
  @classmethod
@@ -215,8 +229,10 @@ class GlobalConfig(BaseModel):
215
229
  raw = json.loads(target.read_text(encoding="utf-8"))
216
230
  if isinstance(raw, dict) and 'max_messages_per_turn' in raw:
217
231
  validate_max_messages_per_turn(raw['max_messages_per_turn'])
232
+ if isinstance(raw, dict) and 'allowed_tools' in raw:
233
+ validate_tool_selection(raw['allowed_tools'])
218
234
  return cls.model_validate(raw)
219
- except InvalidBatchLimit:
235
+ except (InvalidBatchLimit, InvalidToolSelection):
220
236
  raise
221
237
  except Exception:
222
238
  return cls()
@@ -248,7 +264,7 @@ class ProjectConfig(BaseModel):
248
264
  model: str | None = None
249
265
  thinking_level: str | None = None
250
266
  permission_mode: str | None = None
251
- allowed_tools: list[str] = Field(default_factory=list)
267
+ allowed_tools: list[str] | None = None
252
268
  custom_instructions: str = ""
253
269
  extensions: list[str] = Field(default_factory=list)
254
270
  mcp_servers: dict[str, Any] = Field(default_factory=dict)
@@ -262,8 +278,10 @@ class ProjectConfig(BaseModel):
262
278
  raw = json.loads(target.read_text(encoding="utf-8"))
263
279
  if isinstance(raw, dict) and 'max_messages_per_turn' in raw:
264
280
  validate_max_messages_per_turn(raw['max_messages_per_turn'])
281
+ if isinstance(raw, dict) and 'allowed_tools' in raw:
282
+ validate_tool_selection(raw['allowed_tools'])
265
283
  return cls.model_validate(raw)
266
- except InvalidBatchLimit:
284
+ except (InvalidBatchLimit, InvalidToolSelection):
267
285
  raise
268
286
  except Exception:
269
287
  return cls()
@@ -280,7 +298,7 @@ class RuntimeConfig:
280
298
  model: str
281
299
  thinking_level: str
282
300
  permission_mode: str
283
- allowed_tools: tuple[str, ...]
301
+ allowed_tools: tuple[str, ...] | None
284
302
  custom_instructions: str
285
303
  extensions: tuple[str, ...]
286
304
  working_dir: str
@@ -295,19 +313,23 @@ class RuntimeConfig:
295
313
  project_cfg: ProjectConfig | None = None,
296
314
  working_dir: Path | None = None,
297
315
  overrides: dict[str, Any] | None = None,
316
+ session_settings: dict[str, Any] | None = None,
298
317
  ) -> RuntimeConfig:
299
318
  """Merge global, project and CLI overrides into a single config."""
300
319
  ov = overrides or {}
301
320
  pcfg = project_cfg or ProjectConfig()
302
- model = ov.get("model") or pcfg.model or global_cfg.default_model
303
- thinking = ov.get("thinking_level") or pcfg.thinking_level or global_cfg.thinking_level
321
+ saved = session_settings or {}
322
+ model = ov.get('model') or saved.get('model') or pcfg.model or global_cfg.default_model
323
+ thinking = (ov.get('thinking_level') or saved.get('thinking_level')
324
+ or pcfg.thinking_level or global_cfg.thinking_level)
304
325
  permission_mode = (
305
326
  ov.get("permission_mode")
306
327
  or pcfg.permission_mode
307
328
  or global_cfg.permission_mode
308
329
  )
309
330
 
310
- merged_tools = list(global_cfg.allowed_tools) + list(pcfg.allowed_tools)
331
+ selection = next((value for value in (ov.get('allowed_tools'), saved.get('allowed_tools'),
332
+ pcfg.allowed_tools, global_cfg.allowed_tools) if value is not None), None)
311
333
  merged_ext = list(global_cfg.extensions) + list(pcfg.extensions)
312
334
 
313
335
  instructions_parts: list[str] = []
@@ -320,13 +342,14 @@ class RuntimeConfig:
320
342
  model=model,
321
343
  thinking_level=thinking,
322
344
  permission_mode=permission_mode,
323
- allowed_tools=tuple(merged_tools),
345
+ allowed_tools=validate_tool_selection(selection),
324
346
  custom_instructions="\n\n".join(instructions_parts),
325
347
  extensions=tuple(merged_ext),
326
348
  working_dir=str(working_dir or Path.cwd()),
327
349
  platform_info=PlatformInfo.detect(),
328
350
  max_messages_per_turn=validate_max_messages_per_turn(
329
351
  ov['max_messages_per_turn'] if 'max_messages_per_turn' in ov else
352
+ saved['max_messages_per_turn'] if 'max_messages_per_turn' in saved else
330
353
  pcfg.max_messages_per_turn if pcfg.max_messages_per_turn is not None else
331
354
  global_cfg.max_messages_per_turn),
332
355
  )