xdog-coding 2.1.1__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/.gitignore +1 -1
  2. xdog_coding-2.2.0/PKG-INFO +95 -0
  3. xdog_coding-2.2.0/README.md +67 -0
  4. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/pyproject.toml +6 -6
  5. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/cli/args.py +10 -1
  6. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/cli/config_selector.py +10 -1
  7. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/config.py +16 -7
  8. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/agent_session.py +36 -3
  9. xdog_coding-2.2.0/src/xdog/coding/core/permissions.py +275 -0
  10. xdog_coding-2.2.0/src/xdog/coding/core/remote_session.py +39 -0
  11. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/sdk.py +41 -11
  12. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/slash_commands.py +6 -3
  13. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/main.py +8 -2
  14. xdog_coding-2.2.0/src/xdog/coding/modes/interactive/components/assistant_message.py +63 -0
  15. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/interactive/components/chat_log.py +64 -27
  16. xdog_coding-2.2.0/src/xdog/coding/modes/interactive/components/custom_editor.py +19 -0
  17. xdog_coding-2.2.0/src/xdog/coding/modes/interactive/components/editor_layout.py +5 -0
  18. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/interactive/components/footer.py +29 -2
  19. xdog_coding-2.2.0/src/xdog/coding/modes/interactive/components/permission_prompt.py +91 -0
  20. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/interactive/components/tool_execution.py +119 -64
  21. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/interactive/components/user_message.py +2 -1
  22. xdog_coding-2.2.0/src/xdog/coding/modes/interactive/interactive_mode.py +967 -0
  23. xdog_coding-2.2.0/src/xdog/coding/modes/interactive/tool_renderers.py +24 -0
  24. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/print_mode.py +49 -0
  25. xdog_coding-2.2.0/src/xdog/coding/modes/rpc/rpc_mode.py +231 -0
  26. xdog_coding-2.2.0/tests/fixtures/terminal_client.py +83 -0
  27. xdog_coding-2.2.0/tests/test_cli_args.py +44 -0
  28. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_cli_config_selector.py +3 -2
  29. xdog_coding-2.2.0/tests/test_compact_layout.py +471 -0
  30. xdog_coding-2.2.0/tests/test_footer_context.py +40 -0
  31. xdog_coding-2.2.0/tests/test_interactive_mode.py +875 -0
  32. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_messages.py +27 -5
  33. xdog_coding-2.2.0/tests/test_permissions.py +121 -0
  34. xdog_coding-2.2.0/tests/test_rpc_mode.py +93 -0
  35. xdog_coding-2.2.0/tests/test_sdk_session_state.py +63 -0
  36. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_session_smoke.py +23 -0
  37. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_slash_commands.py +23 -0
  38. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_slash_skills.py +1 -1
  39. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_system_prompt.py +2 -0
  40. xdog_coding-2.2.0/tests/test_terminal_acceptance.py +261 -0
  41. xdog_coding-2.1.1/PKG-INFO +0 -55
  42. xdog_coding-2.1.1/README.md +0 -27
  43. xdog_coding-2.1.1/src/xdog/coding/modes/interactive/components/assistant_message.py +0 -22
  44. xdog_coding-2.1.1/src/xdog/coding/modes/interactive/components/custom_editor.py +0 -260
  45. xdog_coding-2.1.1/src/xdog/coding/modes/interactive/interactive_mode.py +0 -543
  46. xdog_coding-2.1.1/src/xdog/coding/modes/rpc/rpc_mode.py +0 -187
  47. xdog_coding-2.1.1/tests/test_cli_args.py +0 -23
  48. xdog_coding-2.1.1/tests/test_footer_context.py +0 -18
  49. xdog_coding-2.1.1/tests/test_interactive_mode.py +0 -145
  50. xdog_coding-2.1.1/tests/test_rpc_mode.py +0 -10
  51. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/LICENSE +0 -0
  52. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/LICENSE-EXCEPTION.md +0 -0
  53. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/__init__.py +0 -0
  54. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/cli/__init__.py +0 -0
  55. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/cli/file_processor.py +0 -0
  56. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/cli/initial_message.py +0 -0
  57. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/cli/list_models.py +0 -0
  58. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/cli/session_picker.py +0 -0
  59. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/__init__.py +0 -0
  60. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/bash_executor.py +0 -0
  61. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/compaction/__init__.py +0 -0
  62. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/compaction/compaction.py +0 -0
  63. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/compaction/utils.py +0 -0
  64. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/defaults.py +0 -0
  65. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/event_bus.py +0 -0
  66. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/exec_utils.py +0 -0
  67. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/extensions/__init__.py +0 -0
  68. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/extensions/loader.py +0 -0
  69. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/extensions/runner.py +0 -0
  70. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/extensions/types.py +0 -0
  71. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/keybindings.py +0 -0
  72. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/messages.py +0 -0
  73. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/prompt_templates.py +0 -0
  74. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/resource_loader.py +0 -0
  75. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/session_manager.py +0 -0
  76. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/settings_manager.py +0 -0
  77. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/system_prompt.py +0 -0
  78. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/timings.py +0 -0
  79. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/core/tools/__init__.py +0 -0
  80. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/__init__.py +0 -0
  81. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/interactive/__init__.py +0 -0
  82. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/interactive/components/__init__.py +0 -0
  83. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/interactive/theme.py +0 -0
  84. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/modes/rpc/__init__.py +0 -0
  85. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/py.typed +0 -0
  86. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/utils/__init__.py +0 -0
  87. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/utils/frontmatter.py +0 -0
  88. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/utils/git.py +0 -0
  89. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/utils/mime.py +0 -0
  90. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/utils/shell.py +0 -0
  91. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/src/xdog/coding/utils/sleep.py +0 -0
  92. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_agent_session.py +0 -0
  93. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_cli_file_processor.py +0 -0
  94. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_coding_config.py +0 -0
  95. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_event_bus.py +0 -0
  96. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_prompt_templates.py +0 -0
  97. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_resource_loader.py +0 -0
  98. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_session_manager.py +0 -0
  99. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_settings_manager.py +0 -0
  100. {xdog_coding-2.1.1 → xdog_coding-2.2.0}/tests/test_timings.py +0 -0
@@ -45,7 +45,7 @@ env/
45
45
  *credential*
46
46
  *secret*
47
47
  # --- OS / editor ---
48
- # --- x-dog ---
48
+ # --- xdog ---
49
49
  data/
50
50
  result.txt
51
51
  plan.txt
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.5
2
+ Name: xdog-coding
3
+ Version: 2.2.0
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: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: click>=8.1.0
22
+ Requires-Dist: pydantic>=2.0
23
+ Requires-Dist: pyyaml>=6.0
24
+ Requires-Dist: xdog-agent>=2.2.0
25
+ Requires-Dist: xdog-ai[openai]>=2.2.0
26
+ Requires-Dist: xdog-tui>=2.2.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # xdog-coding
30
+
31
+ **Interactive coding-agent CLI.**
32
+
33
+ A terminal coding agent with session management, built on
34
+ [`xdog-agent`](https://pypi.org/project/xdog-agent/) and the
35
+ [`xdog-tui`](https://pypi.org/project/xdog-tui/) rendering layer.
36
+
37
+ ```bash
38
+ uv run xdog-coding
39
+ ```
40
+
41
+ ## Interactive terminal
42
+
43
+ The interface uses native scrollback, one status row, and a shared multiline
44
+ editor. Enter submits or queues; Shift/Alt+Enter inserts a newline. Ctrl+O opens
45
+ recent tool/reasoning details: Left/Right chooses an entry and Page Up/Page Down
46
+ scrolls. Escape closes autocomplete/details first, then cancels active work and
47
+ restores queued text ahead of the current draft. Ctrl+Z suspends on POSIX.
48
+
49
+ Permission prompts take priority. Up/Down chooses an action, Enter confirms,
50
+ Escape denies that call, and Page Up/Page Down scrolls the command summary.
51
+ The editor draft stays intact when panels open or the terminal resizes.
52
+ See [terminal verification](../../docs/tui-pi-parity.md); remote coding and
53
+ fullscreen application modes are excluded.
54
+
55
+ ## Tool permissions
56
+
57
+ Potentially mutating tool calls are gated immediately before execution. By
58
+ default, read-only filesystem operations and `current_time` run automatically;
59
+ `bash`, filesystem writes/edits/deletes, and unknown extension tools ask first.
60
+
61
+ ```bash
62
+ xdog-coding --permission-mode ask # default: ask for dangerous calls
63
+ xdog-coding --permission-mode ask-all # ask before every tool
64
+ xdog-coding --permission-mode allow-all # trusted unattended automation
65
+ xdog-coding --permission-mode deny # allow read-only calls only
66
+ ```
67
+
68
+ Interactive approvals offer **Allow once**, **Allow for this session** (the exact
69
+ same call only), and **Deny**. Non-interactive runs fail closed when no approver
70
+ is available unless `allow-all` was explicitly selected.
71
+
72
+ The same setting can be stored in `~/.config/xdog/coding/settings.json`:
73
+
74
+ ```json
75
+ {"permission_mode": "ask"}
76
+ ```
77
+
78
+ Permission prompts are an execution gate, not an OS sandbox: an approved shell
79
+ command still has the permissions of the `xdog-coding` process.
80
+
81
+ ## Part of xdog
82
+
83
+ This package is one piece of [xdog](https://github.com/litterzhang/xdog), a
84
+ local-first toolkit for building, running and scheduling LLM workflows. The
85
+ centrepiece is [`xdog-flow`](https://pypi.org/project/xdog-flow/) — a typed
86
+ workflow format and compiler.
87
+
88
+ Documentation: **https://xdog.942295.xyz**
89
+
90
+ ## Licence
91
+
92
+ Copyright (c) 2026 HugeMan <942295.xyz>
93
+
94
+ GNU Affero General Public License v3.0 or later — see
95
+ [LICENSE](https://github.com/litterzhang/xdog/blob/main/LICENSE).
@@ -0,0 +1,67 @@
1
+ # xdog-coding
2
+
3
+ **Interactive coding-agent CLI.**
4
+
5
+ A terminal coding agent with session management, built on
6
+ [`xdog-agent`](https://pypi.org/project/xdog-agent/) and the
7
+ [`xdog-tui`](https://pypi.org/project/xdog-tui/) rendering layer.
8
+
9
+ ```bash
10
+ uv run xdog-coding
11
+ ```
12
+
13
+ ## Interactive terminal
14
+
15
+ The interface uses native scrollback, one status row, and a shared multiline
16
+ editor. Enter submits or queues; Shift/Alt+Enter inserts a newline. Ctrl+O opens
17
+ recent tool/reasoning details: Left/Right chooses an entry and Page Up/Page Down
18
+ scrolls. Escape closes autocomplete/details first, then cancels active work and
19
+ restores queued text ahead of the current draft. Ctrl+Z suspends on POSIX.
20
+
21
+ Permission prompts take priority. Up/Down chooses an action, Enter confirms,
22
+ Escape denies that call, and Page Up/Page Down scrolls the command summary.
23
+ The editor draft stays intact when panels open or the terminal resizes.
24
+ See [terminal verification](../../docs/tui-pi-parity.md); remote coding and
25
+ fullscreen application modes are excluded.
26
+
27
+ ## Tool permissions
28
+
29
+ Potentially mutating tool calls are gated immediately before execution. By
30
+ default, read-only filesystem operations and `current_time` run automatically;
31
+ `bash`, filesystem writes/edits/deletes, and unknown extension tools ask first.
32
+
33
+ ```bash
34
+ xdog-coding --permission-mode ask # default: ask for dangerous calls
35
+ xdog-coding --permission-mode ask-all # ask before every tool
36
+ xdog-coding --permission-mode allow-all # trusted unattended automation
37
+ xdog-coding --permission-mode deny # allow read-only calls only
38
+ ```
39
+
40
+ Interactive approvals offer **Allow once**, **Allow for this session** (the exact
41
+ same call only), and **Deny**. Non-interactive runs fail closed when no approver
42
+ is available unless `allow-all` was explicitly selected.
43
+
44
+ The same setting can be stored in `~/.config/xdog/coding/settings.json`:
45
+
46
+ ```json
47
+ {"permission_mode": "ask"}
48
+ ```
49
+
50
+ Permission prompts are an execution gate, not an OS sandbox: an approved shell
51
+ command still has the permissions of the `xdog-coding` process.
52
+
53
+ ## Part of xdog
54
+
55
+ This package is one piece of [xdog](https://github.com/litterzhang/xdog), a
56
+ local-first toolkit for building, running and scheduling LLM workflows. The
57
+ centrepiece is [`xdog-flow`](https://pypi.org/project/xdog-flow/) — a typed
58
+ workflow format and compiler.
59
+
60
+ Documentation: **https://xdog.942295.xyz**
61
+
62
+ ## Licence
63
+
64
+ Copyright (c) 2026 HugeMan <942295.xyz>
65
+
66
+ GNU Affero General Public License v3.0 or later — see
67
+ [LICENSE](https://github.com/litterzhang/xdog/blob/main/LICENSE).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "xdog-coding"
7
- version = "2.1.1"
7
+ version = "2.2.0"
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>=2.1.1",
25
- "xdog-agent>=2.1.1",
26
- "xdog-tui>=2.1.1",
24
+ "xdog-ai[openai]>=2.2.0",
25
+ "xdog-agent>=2.2.0",
26
+ "xdog-tui>=2.2.0",
27
27
  "pyyaml>=6.0",
28
28
  "click>=8.1.0",
29
29
  "pydantic>=2.0",
@@ -31,9 +31,9 @@ dependencies = [
31
31
 
32
32
  [project.urls]
33
33
  Homepage = "https://xdog.942295.xyz"
34
- Repository = "https://github.com/litterzhang/x-dog"
34
+ Repository = "https://github.com/litterzhang/xdog"
35
35
  Documentation = "https://xdog.942295.xyz"
36
- Issues = "https://github.com/litterzhang/x-dog/issues"
36
+ Issues = "https://github.com/litterzhang/xdog/issues"
37
37
 
38
38
  [project.scripts]
39
39
  xdog-coding = "xdog.coding.main:main"
@@ -60,10 +60,16 @@ from xdog.coding.cli.session_picker import pick_session_command
60
60
  )
61
61
  @click.option(
62
62
  "--thinking-level",
63
- type=click.Choice(["none", "normal", "deep", "ultrathink"]),
63
+ type=click.Choice(["off", "minimal", "low", "medium", "high", "xhigh"]),
64
64
  default=None,
65
65
  help="Thinking / reasoning level.",
66
66
  )
67
+ @click.option(
68
+ "--permission-mode",
69
+ type=click.Choice(["ask", "ask-all", "allow-all", "deny"]),
70
+ default=None,
71
+ help="Tool permission policy.",
72
+ )
67
73
  @click.option(
68
74
  "--list-models",
69
75
  is_flag=True,
@@ -99,6 +105,7 @@ def cli(
99
105
  working_dir: Path | None,
100
106
  config_path: Path | None,
101
107
  thinking_level: str | None,
108
+ permission_mode: str | None,
102
109
  list_models: bool,
103
110
  pick_session: bool,
104
111
  rpc: bool,
@@ -124,6 +131,8 @@ def cli(
124
131
  overrides["model"] = model
125
132
  if thinking_level is not None:
126
133
  overrides["thinking_level"] = thinking_level
134
+ if permission_mode is not None:
135
+ overrides["permission_mode"] = permission_mode
127
136
 
128
137
  # Defer to the main entry-point for actual execution
129
138
  from xdog.coding.main import run_agent
@@ -57,10 +57,19 @@ def config_selector(config_path: Path | None = None) -> GlobalConfig:
57
57
  cfg = cfg.model_copy(update={"default_model": new_model})
58
58
 
59
59
  # Thinking level
60
- new_thinking = input(f"Thinking level [{cfg.thinking_level}] (off/normal/high): ").strip()
60
+ new_thinking = input(
61
+ f"Thinking level [{cfg.thinking_level}] (off/minimal/low/medium/high/xhigh): "
62
+ ).strip()
61
63
  if new_thinking:
62
64
  cfg = cfg.model_copy(update={"thinking_level": new_thinking})
63
65
 
66
+ # Tool permissions
67
+ new_permissions = input(
68
+ f"Permission mode [{cfg.permission_mode}] (ask/ask-all/allow-all/deny): "
69
+ ).strip()
70
+ if new_permissions:
71
+ cfg = cfg.model_copy(update={"permission_mode": new_permissions})
72
+
64
73
  # Custom instructions
65
74
  if cfg.custom_instructions:
66
75
  print(f"\nCurrent custom instructions: {cfg.custom_instructions[:80]}...")
@@ -1,9 +1,9 @@
1
1
  """Global configuration and paths for the coding agent.
2
2
 
3
3
  Follows XDG Base Directory Specification:
4
- - Config: $XDG_CONFIG_HOME/x-dog/coding/ (settings, models, keybindings)
5
- - Data: $XDG_DATA_HOME/x-dog/coding/ (sessions, auth, extensions)
6
- - State: $XDG_STATE_HOME/x-dog/coding/ (logs, cache)
4
+ - Config: $XDG_CONFIG_HOME/xdog/coding/ (settings, models, keybindings)
5
+ - Data: $XDG_DATA_HOME/xdog/coding/ (sessions, auth, extensions)
6
+ - State: $XDG_STATE_HOME/xdog/coding/ (logs, cache)
7
7
  - Project: <cwd>/.coding/ (project-local config)
8
8
 
9
9
  Override all XDG paths with a single directory via CODING_DIR env var.
@@ -24,7 +24,7 @@ from pydantic import BaseModel, Field
24
24
  # Constants
25
25
  # ---------------------------------------------------------------------------
26
26
 
27
- MONOREPO_NAME = "x-dog"
27
+ MONOREPO_NAME = "xdog"
28
28
  APP_NAME = "coding"
29
29
  VERSION = "0.57.1"
30
30
  PROJECT_DIR_NAME = ".coding"
@@ -45,7 +45,7 @@ def _env_override_dir() -> Path | None:
45
45
 
46
46
 
47
47
  def get_config_dir() -> Path:
48
- """$XDG_CONFIG_HOME/x-dog/coding/"""
48
+ """$XDG_CONFIG_HOME/xdog/coding/"""
49
49
  override = _env_override_dir()
50
50
  if override is not None:
51
51
  return override
@@ -54,7 +54,7 @@ def get_config_dir() -> Path:
54
54
 
55
55
 
56
56
  def get_data_dir() -> Path:
57
- """$XDG_DATA_HOME/x-dog/coding/"""
57
+ """$XDG_DATA_HOME/xdog/coding/"""
58
58
  override = _env_override_dir()
59
59
  if override is not None:
60
60
  return override
@@ -63,7 +63,7 @@ def get_data_dir() -> Path:
63
63
 
64
64
 
65
65
  def get_state_dir() -> Path:
66
- """$XDG_STATE_HOME/x-dog/coding/"""
66
+ """$XDG_STATE_HOME/xdog/coding/"""
67
67
  override = _env_override_dir()
68
68
  if override is not None:
69
69
  return override
@@ -172,6 +172,7 @@ class GlobalConfig(BaseModel):
172
172
 
173
173
  default_model: str = ""
174
174
  thinking_level: str = "normal"
175
+ permission_mode: str = "ask"
175
176
  extensions: list[str] = Field(default_factory=list)
176
177
  allowed_tools: list[str] = Field(default_factory=list)
177
178
  custom_instructions: str = ""
@@ -207,6 +208,7 @@ class ProjectConfig(BaseModel):
207
208
 
208
209
  model: str | None = None
209
210
  thinking_level: str | None = None
211
+ permission_mode: str | None = None
210
212
  allowed_tools: list[str] = Field(default_factory=list)
211
213
  custom_instructions: str = ""
212
214
  extensions: list[str] = Field(default_factory=list)
@@ -233,6 +235,7 @@ class RuntimeConfig:
233
235
 
234
236
  model: str
235
237
  thinking_level: str
238
+ permission_mode: str
236
239
  allowed_tools: tuple[str, ...]
237
240
  custom_instructions: str
238
241
  extensions: tuple[str, ...]
@@ -253,6 +256,11 @@ class RuntimeConfig:
253
256
  pcfg = project_cfg or ProjectConfig()
254
257
  model = ov.get("model") or pcfg.model or global_cfg.default_model
255
258
  thinking = ov.get("thinking_level") or pcfg.thinking_level or global_cfg.thinking_level
259
+ permission_mode = (
260
+ ov.get("permission_mode")
261
+ or pcfg.permission_mode
262
+ or global_cfg.permission_mode
263
+ )
256
264
 
257
265
  merged_tools = list(global_cfg.allowed_tools) + list(pcfg.allowed_tools)
258
266
  merged_ext = list(global_cfg.extensions) + list(pcfg.extensions)
@@ -266,6 +274,7 @@ class RuntimeConfig:
266
274
  return cls(
267
275
  model=model,
268
276
  thinking_level=thinking,
277
+ permission_mode=permission_mode,
269
278
  allowed_tools=tuple(merged_tools),
270
279
  custom_instructions="\n\n".join(instructions_parts),
271
280
  extensions=tuple(merged_ext),
@@ -24,6 +24,7 @@ from xdog.ai.types import (
24
24
  from xdog.coding.core.defaults import COMPACTION_THRESHOLD_RATIO, MAX_CONTEXT_TOKENS
25
25
  from xdog.coding.core.event_bus import EventBus, get_event_bus
26
26
  from xdog.coding.core.messages import dicts_to_messages, messages_to_dicts
27
+ from xdog.coding.core.permissions import PermissionManager
27
28
  from xdog.coding.core.session_manager import SessionData, SessionManager
28
29
  from xdog.coding.core.settings_manager import SettingsManager
29
30
  from xdog.coding.core.system_prompt import build_system_prompt
@@ -47,6 +48,9 @@ class AgentSession:
47
48
  tool_registry: Any # kept for backward compat, may be None
48
49
  bash: Any # kept for backward compat, may be None
49
50
  working_dir: Path
51
+ context_window: int = MAX_CONTEXT_TOKENS
52
+ max_prompt_tokens: int = 0
53
+ permissions: PermissionManager = field(default_factory=PermissionManager)
50
54
  event_bus: EventBus = field(default_factory=get_event_bus)
51
55
  _unsubscribe: Any = field(default=None, init=False, repr=False)
52
56
  #: Slugs of skills whose instructions are currently in the system prompt.
@@ -59,6 +63,9 @@ class AgentSession:
59
63
  if self.session_data.settings:
60
64
  self.settings.load_session_settings(self.session_data.settings)
61
65
 
66
+ # Sessions constructed outside the SDK still receive enforcement.
67
+ self.agent.set_before_tool_call(self.permissions.before_tool_call)
68
+
62
69
  # Subscribe to agent events for persistence and event bus forwarding
63
70
  self._unsubscribe = self.agent.subscribe(self._on_agent_event)
64
71
 
@@ -80,6 +87,11 @@ class AgentSession:
80
87
  def is_streaming(self) -> bool:
81
88
  return self.agent.state.is_streaming
82
89
 
90
+ @property
91
+ def context_limit(self) -> int:
92
+ """Prompt limit used for compaction; falls back to the model window."""
93
+ return self.max_prompt_tokens or self.context_window or MAX_CONTEXT_TOKENS
94
+
83
95
  # -- Public API --
84
96
 
85
97
  @property
@@ -176,17 +188,35 @@ class AgentSession:
176
188
  """Cancel the current agent turn."""
177
189
  self.agent.abort()
178
190
 
191
+ def cancel(self) -> None:
192
+ """Cancel active work and discard agent-level queued messages."""
193
+ self.agent.clear_all_queues()
194
+ self.permissions.deny_all()
195
+ self.agent.abort()
196
+
179
197
  # -- Model / thinking --
180
198
 
181
199
  def set_model(self, model: str) -> None:
182
- """Switch the active model."""
200
+ """Switch the active model and refresh its provider-reported limits."""
183
201
  self.agent.set_model(model)
202
+ try:
203
+ import xdog.ai as ai
204
+
205
+ model_info = ai.provider("copilot").model(model)
206
+ if model_info is not None:
207
+ self.context_window = model_info.context_window or MAX_CONTEXT_TOKENS
208
+ self.max_prompt_tokens = model_info.max_prompt_tokens
209
+ except Exception:
210
+ logger.debug("could not refresh model limits for %s", model, exc_info=True)
211
+ self.settings.set_session_model(model)
184
212
  self._persist()
185
213
 
186
214
  def set_thinking_level(self, level: ThinkingLevel | None) -> None:
187
- """Switch the thinking/reasoning level."""
215
+ """Switch and persist the thinking/reasoning level."""
188
216
  from dataclasses import replace
217
+
189
218
  self.agent.set_options(replace(self.agent.options, thinking=level))
219
+ self.settings.set_session_thinking(level or "off")
190
220
  self._persist()
191
221
 
192
222
  # -- Compaction --
@@ -222,7 +252,7 @@ class AgentSession:
222
252
  char_count += 200
223
253
 
224
254
  estimated_tokens = char_count / 4
225
- threshold = MAX_CONTEXT_TOKENS * COMPACTION_THRESHOLD_RATIO
255
+ threshold = self.context_limit * COMPACTION_THRESHOLD_RATIO
226
256
 
227
257
  if estimated_tokens > threshold:
228
258
  await self._run_compaction()
@@ -257,6 +287,7 @@ class AgentSession:
257
287
  return False
258
288
 
259
289
  self.session_data = loaded
290
+ self.permissions.clear_session_rules()
260
291
  restored_messages = list(self.session_data.messages)
261
292
  self.agent.replace_messages(restored_messages)
262
293
 
@@ -335,6 +366,7 @@ class AgentSession:
335
366
  RuntimeConfig(
336
367
  model=self.agent.state.model or "unknown",
337
368
  thinking_level=str(self.agent.options.thinking or "normal"),
369
+ permission_mode=self.permissions.mode,
338
370
  allowed_tools=tuple(t.name for t in self.agent.state.tools),
339
371
  custom_instructions=self.settings.custom_instructions,
340
372
  extensions=(),
@@ -372,6 +404,7 @@ class AgentSession:
372
404
 
373
405
  def dispose(self) -> None:
374
406
  """Clean up resources."""
407
+ self.permissions.close()
375
408
  if self._unsubscribe:
376
409
  self._unsubscribe()
377
410
  self._unsubscribe = None