windcode 0.1.1__tar.gz → 0.1.3__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.
- {windcode-0.1.1 → windcode-0.1.3}/.gitignore +6 -0
- {windcode-0.1.1 → windcode-0.1.3}/.windcode/config.toml.example +2 -2
- {windcode-0.1.1 → windcode-0.1.3}/AGENTS.md +3 -2
- {windcode-0.1.1 → windcode-0.1.3}/PKG-INFO +6 -5
- {windcode-0.1.1 → windcode-0.1.3}/README.md +5 -4
- {windcode-0.1.1 → windcode-0.1.3}/examples/README.md +1 -1
- {windcode-0.1.1 → windcode-0.1.3}/pyproject.toml +1 -1
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/__init__.py +1 -1
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/cli.py +4 -3
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/config/__init__.py +8 -1
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/config/loader.py +27 -1
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/domain/subagents.py +3 -1
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/mcp/tools.py +7 -2
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/service.py +13 -2
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/prompts.py +4 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/subagents/coordinator.py +35 -5
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/subagents/factory.py +7 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/subagents/roles.py +15 -6
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/sdk.py +20 -43
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/subagents/spawn.py +24 -5
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/app.py +2 -4
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/worktrees/manager.py +54 -20
- {windcode-0.1.1 → windcode-0.1.3}/uv.lock +1 -1
- {windcode-0.1.1 → windcode-0.1.3}/.github/workflows/publish.yml +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/.python-version +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/.windcode/skills/code-review/SKILL.md +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/.windcode/skills/code-review/agents/openai.yaml +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/LICENSE +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/assets/image.png +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/assets/image1.png +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/assets/image2.png +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/assets/image3.png +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/assets/image4.png +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/examples/01_stateful_chat.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/examples/02_custom_tool.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/examples/03_multi_agent.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/examples/04_mcp_tools.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/examples/05_skill.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/examples/06_stream_and_cancel.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/examples/07_bash_approval.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/examples/skills/release_notes/SKILL.md +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/__main__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/auth/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/auth/store.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/config/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/config/writer.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/context/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/context/compactor.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/context/estimator.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/context/truncation.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/domain/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/domain/errors.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/domain/events.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/domain/messages.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/domain/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/domain/tools.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/commands.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/discovery.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/events.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/hooks/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/hooks/dispatcher.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/hooks/executor.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/hooks/loader.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/hooks/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/mcp/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/mcp/adapter.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/mcp/catalog.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/mcp/client.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/mcp/runtime.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/paths.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/plugins/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/plugins/installer.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/plugins/manifest.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/runtime.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/skills/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/skills/loader.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/skills/parser.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/skills/tools.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/snapshot.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/extensions/state.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/instructions/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/instructions/loader.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/memory/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/memory/extraction.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/memory/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/memory/refiner.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/memory/security.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/memory/service.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/memory/store.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/observability/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/observability/redaction.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/observability/trace.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/policy/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/policy/engine.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/policy/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/providers/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/providers/_utils.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/providers/anthropic.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/providers/base.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/providers/catalog.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/providers/errors.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/providers/openai_compat.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/providers/openai_responses.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/providers/registry.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/control.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/event_bus.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/loop.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/report.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/retry.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/scheduler.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/subagents/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/subagents/approvals.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/subagents/budgets.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/runtime/subagents/verification.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/sandbox/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/sandbox/bwrap.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/sessions/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/sessions/artifacts.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/sessions/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/sessions/store.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/sessions/tree.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/apply_patch.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/ask_user.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/builtins.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/edit_file.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/filesystem.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/glob.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/grep.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/memory.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/read_file.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/registry.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/shell.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/subagents/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/subagents/cancel.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/subagents/integrate.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/subagents/list.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/subagents/wait.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tools/write_file.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/commands.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/permission_display.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/styles.tcss +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/approval.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/command_menu.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/extensions.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/input.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/memory.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/messages.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/question.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/sessions.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/status.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/subagents.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/tools.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/tui/widgets/welcome.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/types.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/worktrees/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/worktrees/git.py +0 -0
- {windcode-0.1.1 → windcode-0.1.3}/src/windcode/worktrees/models.py +0 -0
|
@@ -56,8 +56,9 @@ explicit SDK state_root
|
|
|
56
56
|
-> user_storage_root
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
The root contains `memory/`, `sessions/`, `traces/`, `extensions/`, and `worktrees/`.
|
|
60
|
-
`.windcode/config.toml`, `.windcode
|
|
59
|
+
The root contains `skill/`, `memory/`, `sessions/`, `traces/`, `extensions/`, and `worktrees/`.
|
|
60
|
+
`.windcode/config.toml`, runtime directories under `.windcode/`, `tests/`, and `spec/` are local-only
|
|
61
|
+
ignored paths.
|
|
61
62
|
Never remove, rewrite, force-add, or commit them without an explicit user request.
|
|
62
63
|
|
|
63
64
|
For MCP servers, `enable` controls discovery and runtime visibility; `required` only controls startup
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: windcode
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A safe, extensible terminal coding agent
|
|
5
5
|
Project-URL: Homepage, https://github.com/tingfeng347/windcode
|
|
6
6
|
Project-URL: Repository, https://github.com/tingfeng347/windcode
|
|
@@ -149,12 +149,13 @@ Windcode 将记忆、会话、trace、扩展状态和 Worktree 统一存放在
|
|
|
149
149
|
|
|
150
150
|
```toml
|
|
151
151
|
[storage]
|
|
152
|
-
project_state_root = ".windcode
|
|
153
|
-
user_storage_root = "~/.local/state/windcode
|
|
152
|
+
project_state_root = ".windcode"
|
|
153
|
+
user_storage_root = "~/.local/state/windcode"
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
`.windcode
|
|
156
|
+
配置项目状态根时优先使用项目目录;未配置时使用用户状态目录。Skill 会同时扫描两边的
|
|
157
|
+
`skill/`,同名时项目级覆盖用户级。`.windcode/config.toml` 和 `.windcode/` 下的运行状态都
|
|
158
|
+
不应提交到 Git。
|
|
158
159
|
|
|
159
160
|
## 开发
|
|
160
161
|
|
|
@@ -114,12 +114,13 @@ Windcode 将记忆、会话、trace、扩展状态和 Worktree 统一存放在
|
|
|
114
114
|
|
|
115
115
|
```toml
|
|
116
116
|
[storage]
|
|
117
|
-
project_state_root = ".windcode
|
|
118
|
-
user_storage_root = "~/.local/state/windcode
|
|
117
|
+
project_state_root = ".windcode"
|
|
118
|
+
user_storage_root = "~/.local/state/windcode"
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
`.windcode
|
|
121
|
+
配置项目状态根时优先使用项目目录;未配置时使用用户状态目录。Skill 会同时扫描两边的
|
|
122
|
+
`skill/`,同名时项目级覆盖用户级。`.windcode/config.toml` 和 `.windcode/` 下的运行状态都
|
|
123
|
+
不应提交到 Git。
|
|
123
124
|
|
|
124
125
|
## 开发
|
|
125
126
|
|
|
@@ -8,7 +8,7 @@ from collections.abc import Sequence
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
from windcode.config import AppConfig, ConfigError, PermissionMode, load_config
|
|
11
|
+
from windcode.config import AppConfig, ConfigError, PermissionMode, ensure_user_config, load_config
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass(frozen=True, slots=True)
|
|
@@ -156,8 +156,9 @@ def run(argv: Sequence[str] | None = None) -> int:
|
|
|
156
156
|
options = parse_options(arguments)
|
|
157
157
|
if not options.workspace.is_dir():
|
|
158
158
|
raise ConfigError(options.workspace, "workspace is not a directory")
|
|
159
|
+
write_config_file = options.config_file or ensure_user_config()
|
|
159
160
|
config = resolve_config(options)
|
|
160
|
-
except ConfigError as exc:
|
|
161
|
+
except (ConfigError, OSError) as exc:
|
|
161
162
|
print(f"windcode: {exc}", file=sys.stderr)
|
|
162
163
|
return 2
|
|
163
164
|
|
|
@@ -171,7 +172,7 @@ def run(argv: Sequence[str] | None = None) -> int:
|
|
|
171
172
|
permission_mode=(
|
|
172
173
|
options.permission_mode.value if options.permission_mode is not None else None
|
|
173
174
|
),
|
|
174
|
-
config_file=
|
|
175
|
+
config_file=write_config_file,
|
|
175
176
|
)
|
|
176
177
|
app.run()
|
|
177
178
|
return 0
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
from windcode.config.loader import
|
|
1
|
+
from windcode.config.loader import (
|
|
2
|
+
ConfigError,
|
|
3
|
+
default_user_config_path,
|
|
4
|
+
ensure_user_config,
|
|
5
|
+
load_config,
|
|
6
|
+
)
|
|
2
7
|
from windcode.config.models import (
|
|
3
8
|
HARD_MAX_CONCURRENT_SUBAGENTS,
|
|
4
9
|
HARD_MAX_SUBAGENT_TASKS,
|
|
@@ -35,6 +40,8 @@ __all__ = [
|
|
|
35
40
|
"StorageConfig",
|
|
36
41
|
"SubagentConfig",
|
|
37
42
|
"TraceConfig",
|
|
43
|
+
"default_user_config_path",
|
|
44
|
+
"ensure_user_config",
|
|
38
45
|
"load_config",
|
|
39
46
|
"save_memory_config",
|
|
40
47
|
"save_model_config",
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
|
+
import os
|
|
4
5
|
import tomllib
|
|
5
6
|
from collections.abc import Mapping
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, cast
|
|
8
9
|
|
|
10
|
+
import tomli_w
|
|
9
11
|
from platformdirs import user_config_path
|
|
10
12
|
from pydantic import ValidationError
|
|
11
13
|
|
|
@@ -18,6 +20,30 @@ class ConfigError(ValueError):
|
|
|
18
20
|
super().__init__(f"{source}: {message}")
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
def default_user_config_path() -> Path:
|
|
24
|
+
return user_config_path("windcode") / "config.toml"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def ensure_user_config(path: Path | None = None) -> Path:
|
|
28
|
+
"""Create the default user configuration once without overwriting an existing file."""
|
|
29
|
+
target = (path or default_user_config_path()).expanduser().resolve()
|
|
30
|
+
if target.exists():
|
|
31
|
+
return target
|
|
32
|
+
|
|
33
|
+
content = tomli_w.dumps(AppConfig().model_dump(mode="json", exclude_none=True)).encode()
|
|
34
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
try:
|
|
36
|
+
descriptor = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
37
|
+
except FileExistsError:
|
|
38
|
+
return target
|
|
39
|
+
|
|
40
|
+
with os.fdopen(descriptor, "wb") as stream:
|
|
41
|
+
stream.write(content)
|
|
42
|
+
stream.flush()
|
|
43
|
+
os.fsync(stream.fileno())
|
|
44
|
+
return target
|
|
45
|
+
|
|
46
|
+
|
|
21
47
|
def _deep_merge(base: dict[str, Any], overlay: Mapping[str, Any]) -> dict[str, Any]:
|
|
22
48
|
merged = copy.deepcopy(base)
|
|
23
49
|
for key, value in overlay.items():
|
|
@@ -52,7 +78,7 @@ def load_config(
|
|
|
52
78
|
overrides: Mapping[str, Any] | None = None,
|
|
53
79
|
) -> AppConfig:
|
|
54
80
|
workspace = workspace.expanduser().resolve()
|
|
55
|
-
default_user =
|
|
81
|
+
default_user = default_user_config_path()
|
|
56
82
|
default_project = workspace / ".windcode" / "config.toml"
|
|
57
83
|
layers: list[tuple[Path | str, dict[str, Any]]] = [
|
|
58
84
|
(
|
|
@@ -48,7 +48,9 @@ TERMINAL_SUBAGENT_STATUSES = frozenset(
|
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
_ALLOWED_TRANSITIONS: dict[SubagentStatus, frozenset[SubagentStatus]] = {
|
|
51
|
-
SubagentStatus.QUEUED: frozenset(
|
|
51
|
+
SubagentStatus.QUEUED: frozenset(
|
|
52
|
+
{SubagentStatus.RUNNING, SubagentStatus.FAILED, SubagentStatus.CANCELLED}
|
|
53
|
+
),
|
|
52
54
|
SubagentStatus.RUNNING: frozenset(
|
|
53
55
|
{
|
|
54
56
|
SubagentStatus.BLOCKED,
|
|
@@ -439,13 +439,18 @@ class SearchMcpToolsTool:
|
|
|
439
439
|
selected_tools: set[str],
|
|
440
440
|
) -> None:
|
|
441
441
|
self.service = service
|
|
442
|
-
self.
|
|
442
|
+
self.registries = [registry]
|
|
443
443
|
self.selected_tools = selected_tools
|
|
444
444
|
|
|
445
|
+
def add_registry(self, registry: ToolRegistry) -> None:
|
|
446
|
+
if all(item is not registry for item in self.registries):
|
|
447
|
+
self.registries.append(registry)
|
|
448
|
+
|
|
445
449
|
async def _select(self, stable_id: str) -> tuple[McpToolAdapter, bool]:
|
|
446
450
|
already_selected = stable_id in self.selected_tools
|
|
447
451
|
adapter = await self.service.adapter(stable_id)
|
|
448
|
-
self.
|
|
452
|
+
for registry in self.registries:
|
|
453
|
+
registry.register(adapter, replace=True)
|
|
449
454
|
self.selected_tools.add(stable_id)
|
|
450
455
|
return adapter, already_selected
|
|
451
456
|
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from typing import cast
|
|
9
9
|
from uuid import uuid4
|
|
10
10
|
|
|
11
|
+
from platformdirs import user_state_path
|
|
11
12
|
from pydantic import TypeAdapter, ValidationError
|
|
12
13
|
|
|
13
14
|
from windcode.config.models import (
|
|
@@ -57,13 +58,23 @@ class ExtensionService:
|
|
|
57
58
|
workspace: Path,
|
|
58
59
|
state_store: ExtensionStateStore,
|
|
59
60
|
plugins_root: Path,
|
|
61
|
+
*,
|
|
62
|
+
user_skill_root: Path | None = None,
|
|
60
63
|
) -> None:
|
|
61
64
|
self.config = config
|
|
62
65
|
self.workspace = workspace.expanduser().resolve()
|
|
63
66
|
self.state_store = state_store
|
|
64
67
|
self.plugins_root = plugins_root
|
|
65
|
-
|
|
66
|
-
self.
|
|
68
|
+
default_user_skill_root = user_skill_root or user_state_path("windcode") / "skill"
|
|
69
|
+
self._user_skill_roots = tuple(
|
|
70
|
+
dict.fromkeys(
|
|
71
|
+
(
|
|
72
|
+
default_user_skill_root.expanduser().resolve(),
|
|
73
|
+
*(Path(path).expanduser().resolve() for path in config.skill_roots),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
self._project_skill_root = self.workspace / ".windcode" / "skill"
|
|
67
78
|
loaded = state_store.load()
|
|
68
79
|
self._state = loaded.state
|
|
69
80
|
self._state_diagnostics = loaded.diagnostics
|
|
@@ -77,6 +77,8 @@ def build_system_prompt(
|
|
|
77
77
|
"\n\n## 委派策略: explicit\n"
|
|
78
78
|
"仅当用户明确要求委派、并行或使用子智能体时, 才可调用子智能体工具。"
|
|
79
79
|
"创建后调用 wait_subagents 一次等待结果; 禁止循环调用 list_subagents。"
|
|
80
|
+
"写任务使用基于当前 HEAD 的独立 Git Worktree; 不得为创建子智能体而暂存、提交、"
|
|
81
|
+
"还原、移动或删除父工作区的未提交修改。"
|
|
80
82
|
"子智能体可按运行网络策略和权限审批访问外部网络。"
|
|
81
83
|
)
|
|
82
84
|
elif delegation_mode is DelegationMode.PROACTIVE:
|
|
@@ -84,6 +86,8 @@ def build_system_prompt(
|
|
|
84
86
|
"\n\n## 委派策略: proactive\n"
|
|
85
87
|
"可在任务确实独立且适合并行时主动委派; 必须保持任务有界、状态可见并统一汇总。"
|
|
86
88
|
"创建后调用 wait_subagents 一次等待结果; 禁止循环调用 list_subagents。"
|
|
89
|
+
"写任务使用基于当前 HEAD 的独立 Git Worktree; 不得为创建子智能体而暂存、提交、"
|
|
90
|
+
"还原、移动或删除父工作区的未提交修改。"
|
|
87
91
|
"子智能体可按运行网络策略和权限审批访问外部网络。"
|
|
88
92
|
)
|
|
89
93
|
if memory_enabled:
|
|
@@ -46,7 +46,7 @@ from windcode.runtime.subagents.approvals import ApprovalRouter
|
|
|
46
46
|
from windcode.runtime.subagents.budgets import AggregateBudget
|
|
47
47
|
from windcode.runtime.subagents.factory import ChildRuntime, ChildRuntimeFactory
|
|
48
48
|
from windcode.runtime.subagents.verification import VerificationRunner
|
|
49
|
-
from windcode.worktrees import GitBaseline, WorktreeLease, WorktreeManager
|
|
49
|
+
from windcode.worktrees import GitBaseline, WorktreeError, WorktreeLease, WorktreeManager
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class SubagentCoordinatorError(RuntimeError):
|
|
@@ -98,6 +98,7 @@ class SubagentCoordinator:
|
|
|
98
98
|
self._runtimes: dict[str, ChildRuntime] = {}
|
|
99
99
|
self._tasks: dict[str, asyncio.Task[None]] = {}
|
|
100
100
|
self._completion: dict[str, asyncio.Future[SubagentResult]] = {}
|
|
101
|
+
self._startup: dict[str, asyncio.Future[None]] = {}
|
|
101
102
|
self._usage: dict[str, Usage] = {}
|
|
102
103
|
self._queue: deque[str] = deque()
|
|
103
104
|
self._active = 0
|
|
@@ -168,7 +169,10 @@ class SubagentCoordinator:
|
|
|
168
169
|
baseline: GitBaseline | None = None
|
|
169
170
|
if any(spec.kind is SubagentTaskKind.WRITE for spec in specs):
|
|
170
171
|
try:
|
|
171
|
-
baseline = await self.worktrees.validate_parent(
|
|
172
|
+
baseline = await self.worktrees.validate_parent(
|
|
173
|
+
self.workspace,
|
|
174
|
+
require_clean=False,
|
|
175
|
+
)
|
|
172
176
|
except Exception as exc:
|
|
173
177
|
raise SubagentCoordinatorError("write_workspace_blocked", str(exc)) from exc
|
|
174
178
|
|
|
@@ -190,12 +194,22 @@ class SubagentCoordinator:
|
|
|
190
194
|
)
|
|
191
195
|
self._records[record.subagent_id] = record
|
|
192
196
|
self._completion[record.subagent_id] = loop.create_future()
|
|
197
|
+
self._startup[record.subagent_id] = loop.create_future()
|
|
193
198
|
self._queue.append(record.subagent_id)
|
|
194
199
|
await self._persist(record)
|
|
195
200
|
await self._publish_record_event(record, SubagentQueued)
|
|
196
201
|
created.append(record)
|
|
197
202
|
self._schedule_locked()
|
|
198
|
-
|
|
203
|
+
scheduled_ids = tuple(
|
|
204
|
+
record.subagent_id for record in created if record.subagent_id in self._tasks
|
|
205
|
+
)
|
|
206
|
+
# Do not report a successful spawn while every child is merely a queued
|
|
207
|
+
# record. Yield outside the coordinator lock until each scheduled task has
|
|
208
|
+
# either published its running state or reached a terminal failure.
|
|
209
|
+
await asyncio.gather(
|
|
210
|
+
*(asyncio.shield(self._startup[subagent_id]) for subagent_id in scheduled_ids)
|
|
211
|
+
)
|
|
212
|
+
return tuple(self._records[record.subagent_id] for record in created)
|
|
199
213
|
|
|
200
214
|
def _schedule_locked(self) -> None:
|
|
201
215
|
while self._queue and self._active < self.config.max_concurrent:
|
|
@@ -309,7 +323,10 @@ class SubagentCoordinator:
|
|
|
309
323
|
"",
|
|
310
324
|
record.base_commit,
|
|
311
325
|
)
|
|
312
|
-
validated = await self.worktrees.validate_parent(
|
|
326
|
+
validated = await self.worktrees.validate_parent(
|
|
327
|
+
self.workspace,
|
|
328
|
+
require_clean=False,
|
|
329
|
+
)
|
|
313
330
|
baseline = replace(
|
|
314
331
|
baseline, repository=validated.repository, branch=validated.branch
|
|
315
332
|
)
|
|
@@ -339,6 +356,9 @@ class SubagentCoordinator:
|
|
|
339
356
|
summary="subagent started",
|
|
340
357
|
workspace=str(workspace),
|
|
341
358
|
)
|
|
359
|
+
startup = self._startup[subagent_id]
|
|
360
|
+
if not startup.done():
|
|
361
|
+
startup.set_result(None)
|
|
342
362
|
forwarding = asyncio.create_task(self._forward_child_events(runtime))
|
|
343
363
|
try:
|
|
344
364
|
run_result = await runtime.loop.run(runtime.prompt, runtime.workspace)
|
|
@@ -429,6 +449,9 @@ class SubagentCoordinator:
|
|
|
429
449
|
if record.status in {SubagentStatus.QUEUED, SubagentStatus.RUNNING}:
|
|
430
450
|
await self._finish_failed(record, type(exc).__name__, str(exc))
|
|
431
451
|
finally:
|
|
452
|
+
startup = self._startup.get(subagent_id)
|
|
453
|
+
if startup is not None and not startup.done():
|
|
454
|
+
startup.set_result(None)
|
|
432
455
|
self._runtimes.pop(subagent_id, None)
|
|
433
456
|
self.approvals.cancel(subagent_id)
|
|
434
457
|
async with self._lock:
|
|
@@ -566,7 +589,14 @@ class SubagentCoordinator:
|
|
|
566
589
|
raise SubagentCoordinatorError(
|
|
567
590
|
"not_integratable", "subagent must be a completed clean write task"
|
|
568
591
|
)
|
|
569
|
-
|
|
592
|
+
try:
|
|
593
|
+
integration = await self.worktrees.integrate(self._leases[subagent_id], self.workspace)
|
|
594
|
+
except WorktreeError as exc:
|
|
595
|
+
raise SubagentCoordinatorError(
|
|
596
|
+
"integration_workspace_blocked",
|
|
597
|
+
"subagent work is safely committed in its Worktree, but integration is blocked: "
|
|
598
|
+
f"{exc}",
|
|
599
|
+
) from exc
|
|
570
600
|
if not integration.integrated:
|
|
571
601
|
conflict = await self._transition(record, SubagentStatus.CONFLICT)
|
|
572
602
|
result = replace(
|
|
@@ -135,12 +135,19 @@ class ChildRuntime:
|
|
|
135
135
|
def build_child_prompt(record: SubagentRecord) -> str:
|
|
136
136
|
spec = record.spec
|
|
137
137
|
verification = "\n".join(f"- {item}" for item in spec.verification)
|
|
138
|
+
delivery = (
|
|
139
|
+
"This is a read-only task. Return all findings in your final response for the parent "
|
|
140
|
+
"agent to consume. Do not create, edit, or save a report file, including through shell."
|
|
141
|
+
if spec.kind is SubagentTaskKind.READ
|
|
142
|
+
else "Complete file changes in the assigned worktree and commit them before responding."
|
|
143
|
+
)
|
|
138
144
|
return (
|
|
139
145
|
f"Task: {spec.task_name}\n"
|
|
140
146
|
f"Goal: {spec.goal}\n\n"
|
|
141
147
|
f"Context:\n{spec.context}\n\n"
|
|
142
148
|
f"Expected output:\n{spec.expected_output}\n\n"
|
|
143
149
|
f"Verification requirements:\n{verification}\n\n"
|
|
150
|
+
f"Delivery constraint:\n{delivery}\n\n"
|
|
144
151
|
"Complete only this task. Do not delegate and do not ask the user questions."
|
|
145
152
|
)
|
|
146
153
|
|
|
@@ -65,10 +65,19 @@ def resolve_role_tools(
|
|
|
65
65
|
if "__" in name
|
|
66
66
|
or name in {"list_mcp_servers", "search_mcp_tools", "read_mcp_resource", "get_mcp_prompt"}
|
|
67
67
|
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
# Task kind is an independent capability boundary. In particular, a worker
|
|
69
|
+
# may perform either kind of task, but a worker/read task must remain just as
|
|
70
|
+
# read-only as researcher/read and verifier/read.
|
|
71
|
+
kind_tools = (
|
|
72
|
+
policy.default_tools & _READ_TOOLS
|
|
73
|
+
if kind is SubagentTaskKind.READ
|
|
74
|
+
else policy.default_tools
|
|
75
|
+
)
|
|
76
|
+
allowed_tools = kind_tools | network_tools
|
|
77
|
+
# ``requested_tools`` is model-authored input, so treat it as a narrowing
|
|
78
|
+
# hint rather than a second policy boundary. A model can accidentally copy a
|
|
79
|
+
# write tool into a read-only researcher task; denying that tool is enough to
|
|
80
|
+
# preserve the role boundary and should not prevent the remaining research
|
|
81
|
+
# tools from starting.
|
|
82
|
+
selected = allowed_tools if requested_tools is None else requested_tools & allowed_tools
|
|
74
83
|
return selected & parent_tools
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import shutil
|
|
5
4
|
from collections.abc import AsyncIterator, Mapping
|
|
6
5
|
from dataclasses import replace
|
|
7
6
|
from pathlib import Path
|
|
@@ -35,6 +34,7 @@ from windcode.extensions.commands import CommandRoute
|
|
|
35
34
|
from windcode.extensions.hooks.models import HookContext, HookEvent
|
|
36
35
|
from windcode.extensions.mcp.catalog import McpToolDefinition
|
|
37
36
|
from windcode.extensions.mcp.tools import (
|
|
37
|
+
SearchMcpToolsTool,
|
|
38
38
|
register_mcp_management_tools,
|
|
39
39
|
register_mcp_status_tool,
|
|
40
40
|
)
|
|
@@ -226,21 +226,13 @@ class Windcode:
|
|
|
226
226
|
def _resolve_state_root(self, explicit_root: Path | None) -> Path:
|
|
227
227
|
if explicit_root is not None:
|
|
228
228
|
return explicit_root.expanduser().resolve()
|
|
229
|
-
|
|
229
|
+
configured_project_root = self.config.storage.project_state_root
|
|
230
|
+
if configured_project_root is not None:
|
|
231
|
+
return self._configured_state_path(configured_project_root)
|
|
230
232
|
configured_user_root = self.config.storage.user_storage_root
|
|
231
|
-
|
|
232
|
-
self._configured_state_path(configured_user_root)
|
|
233
|
-
|
|
234
|
-
else legacy_root / "state"
|
|
235
|
-
)
|
|
236
|
-
source_root = user_root if user_root.exists() else legacy_root
|
|
237
|
-
configured = self.config.storage.project_state_root
|
|
238
|
-
if configured is None:
|
|
239
|
-
self._migrate_state_root(source_root, user_root)
|
|
240
|
-
return user_root
|
|
241
|
-
project_root = self._configured_state_path(configured)
|
|
242
|
-
self._migrate_state_root(source_root, project_root)
|
|
243
|
-
return project_root
|
|
233
|
+
if configured_user_root is not None:
|
|
234
|
+
return self._configured_state_path(configured_user_root)
|
|
235
|
+
return user_state_path("windcode").expanduser().resolve()
|
|
244
236
|
|
|
245
237
|
def _configured_state_path(self, value: str) -> Path:
|
|
246
238
|
project_root = Path(value).expanduser()
|
|
@@ -248,33 +240,11 @@ class Windcode:
|
|
|
248
240
|
project_root = self.workspace / project_root
|
|
249
241
|
return project_root.resolve()
|
|
250
242
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if path.is_file() and not path.is_symlink()
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
@classmethod
|
|
260
|
-
def _migrate_state_root(cls, source: Path, target: Path) -> None:
|
|
261
|
-
"""Copy the complete legacy state once, validate it, then atomically install it."""
|
|
262
|
-
if source == target or target.exists():
|
|
263
|
-
return
|
|
264
|
-
target.parent.mkdir(parents=True, exist_ok=True)
|
|
265
|
-
temporary_parent = source.parent if target.is_relative_to(source) else target.parent
|
|
266
|
-
temporary = temporary_parent / f".{target.name}.migrate-{uuid4().hex}"
|
|
267
|
-
try:
|
|
268
|
-
if source.exists():
|
|
269
|
-
shutil.copytree(source, temporary, copy_function=shutil.copy2)
|
|
270
|
-
if cls._state_manifest(source) != cls._state_manifest(temporary):
|
|
271
|
-
raise OSError("project state migration validation failed")
|
|
272
|
-
else:
|
|
273
|
-
temporary.mkdir(parents=True)
|
|
274
|
-
temporary.replace(target)
|
|
275
|
-
finally:
|
|
276
|
-
if temporary.exists():
|
|
277
|
-
shutil.rmtree(temporary)
|
|
243
|
+
def _user_storage_root(self) -> Path:
|
|
244
|
+
configured = self.config.storage.user_storage_root
|
|
245
|
+
if configured is not None:
|
|
246
|
+
return self._configured_state_path(configured)
|
|
247
|
+
return user_state_path("windcode").expanduser().resolve()
|
|
278
248
|
|
|
279
249
|
@classmethod
|
|
280
250
|
def open(
|
|
@@ -749,6 +719,10 @@ class Windcode:
|
|
|
749
719
|
continue
|
|
750
720
|
policy.restore_session_approval(tool_name, effects)
|
|
751
721
|
child_tools = run_registry.clone()
|
|
722
|
+
if "search_mcp_tools" in run_registry.names():
|
|
723
|
+
search_mcp_tools = run_registry.get("search_mcp_tools")
|
|
724
|
+
if isinstance(search_mcp_tools, SearchMcpToolsTool):
|
|
725
|
+
search_mcp_tools.add_registry(child_tools)
|
|
752
726
|
instructions = load_instructions(workspace, workspace_root=workspace)
|
|
753
727
|
run_memory = (
|
|
754
728
|
MemoryService(self.state_root, workspace) if self.config.memory.enabled else None
|
|
@@ -830,7 +804,10 @@ class Windcode:
|
|
|
830
804
|
config=self.config.subagents,
|
|
831
805
|
event_bus=bus,
|
|
832
806
|
factory=factory,
|
|
833
|
-
worktrees=WorktreeManager(
|
|
807
|
+
worktrees=WorktreeManager(
|
|
808
|
+
worktrees_root=self.state_root / "worktrees",
|
|
809
|
+
fallback_worktrees_root=self._user_storage_root() / "worktrees",
|
|
810
|
+
),
|
|
834
811
|
verification=VerificationRunner(
|
|
835
812
|
sandbox=sandbox,
|
|
836
813
|
timeout_seconds=self.config.budgets.shell_timeout_seconds,
|
|
@@ -14,13 +14,26 @@ class SubagentTaskInput(BaseModel):
|
|
|
14
14
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
15
15
|
|
|
16
16
|
task_name: str = Field(pattern=r"^[a-z0-9]+(?:_[a-z0-9]+)*$")
|
|
17
|
-
role: SubagentRole
|
|
18
|
-
|
|
17
|
+
role: SubagentRole = Field(
|
|
18
|
+
description=(
|
|
19
|
+
"researcher and verifier are read-only; use worker only when the child itself must "
|
|
20
|
+
"modify files"
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
kind: SubagentTaskKind = Field(
|
|
24
|
+
description="Use read for research/verification and write only for a worker editing files."
|
|
25
|
+
)
|
|
19
26
|
goal: str = Field(min_length=1)
|
|
20
27
|
context: str = Field(min_length=1)
|
|
21
28
|
expected_output: str = Field(min_length=1)
|
|
22
29
|
verification: tuple[str, ...] = Field(min_length=1)
|
|
23
|
-
allowed_tools: frozenset[str] | None =
|
|
30
|
+
allowed_tools: frozenset[str] | None = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description=(
|
|
33
|
+
"Optional tool restriction. Omit it to use role defaults. Never request write_file, "
|
|
34
|
+
"edit_file, or apply_patch for researcher/verifier read tasks."
|
|
35
|
+
),
|
|
36
|
+
)
|
|
24
37
|
model: str | None = None
|
|
25
38
|
requires_network: bool = Field(
|
|
26
39
|
default=False,
|
|
@@ -51,8 +64,14 @@ class SpawnSubagentsInput(BaseModel):
|
|
|
51
64
|
class SpawnSubagentsTool:
|
|
52
65
|
name = "spawn_subagents"
|
|
53
66
|
description = (
|
|
54
|
-
"Create bounded temporary subagents.
|
|
55
|
-
"
|
|
67
|
+
"Create bounded temporary subagents. Researchers and verifiers are read-only and return "
|
|
68
|
+
"their findings to the parent; use a worker/write task only when that child must edit "
|
|
69
|
+
"files. Write tasks use an isolated Git Worktree based on the current parent HEAD, so "
|
|
70
|
+
"never stash, commit, revert, move, or discard parent workspace changes before spawning; "
|
|
71
|
+
"uncommitted parent changes are intentionally not copied into the child Worktree. Omit "
|
|
72
|
+
"allowed_tools unless a narrower tool set is required. Declare "
|
|
73
|
+
"requires_network for network-dependent tasks; network access follows the parent run's "
|
|
74
|
+
"network policy and permission workflow."
|
|
56
75
|
)
|
|
57
76
|
input_model = SpawnSubagentsInput
|
|
58
77
|
effects = frozenset({ToolEffect.PROCESS, ToolEffect.WORKSPACE_WRITE})
|
|
@@ -16,7 +16,7 @@ from textual.theme import Theme
|
|
|
16
16
|
from textual.widgets import Static
|
|
17
17
|
|
|
18
18
|
from windcode.auth import CredentialStore, CredentialStoreError
|
|
19
|
-
from windcode.config import AppConfig, PermissionMode, ProviderConfig
|
|
19
|
+
from windcode.config import AppConfig, PermissionMode, ProviderConfig, default_user_config_path
|
|
20
20
|
from windcode.domain.events import (
|
|
21
21
|
ApprovalRequested,
|
|
22
22
|
ApprovalResponse,
|
|
@@ -86,9 +86,7 @@ class WindcodeApp(App[None]):
|
|
|
86
86
|
super().__init__()
|
|
87
87
|
self.config = config
|
|
88
88
|
self.workspace = workspace
|
|
89
|
-
self.config_file = (
|
|
90
|
-
(config_file or self.workspace / ".windcode" / "config.toml").expanduser().resolve()
|
|
91
|
-
)
|
|
89
|
+
self.config_file = (config_file or default_user_config_path()).expanduser().resolve()
|
|
92
90
|
self.model = model
|
|
93
91
|
self.session_id = session_id
|
|
94
92
|
self.permission_mode = permission_mode or config.permission.mode.value
|