nonoka-cli 0.2.5__tar.gz → 0.2.6__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.
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/.gitignore +4 -1
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/PKG-INFO +1 -1
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/pyproject.toml +1 -1
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/server.py +1 -3
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/opencode_cmd.py +3 -2
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/config/models.py +3 -9
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/agent_factory.py +6 -22
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/orchestrator.py +1 -26
- nonoka_cli-0.2.6/src/nonoka_cli/core/planning_service.py +89 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/prompt_builder.py +4 -13
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/builtins/agent_tools.py +3 -3
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/commands/test_opencode_cmd.py +0 -25
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/config/test_models.py +0 -27
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/core/test_agent_factory.py +0 -46
- nonoka_cli-0.2.6/tests/unit/core/test_orchestrator.py +98 -0
- nonoka_cli-0.2.6/tests/unit/core/test_planning_service.py +40 -0
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/SKILL.md +0 -413
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/references/checklist.md +0 -48
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/scripts/analyze-timeline.py +0 -100
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/scripts/capture-screenshots.py +0 -191
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/scripts/setup-sandbox.sh +0 -237
- nonoka_cli-0.2.5/.agents/skills/tui-testing/SKILL.md +0 -60
- nonoka_cli-0.2.5/.agents/skills/tui-testing/scripts/opencode_tui.py +0 -288
- nonoka_cli-0.2.5/.nonoka/repo_map.jsonl +0 -85
- nonoka_cli-0.2.5/docs/external-mcp-skill-design.md +0 -542
- nonoka_cli-0.2.5/docs/nonoka-cli-complex-task-evaluation.md +0 -254
- nonoka_cli-0.2.5/docs/nonoka-cli-refactor-design.md +0 -735
- nonoka_cli-0.2.5/docs/subagent-refactor-design.md +0 -162
- nonoka_cli-0.2.5/docs/subagent-refactor-plan.md +0 -134
- nonoka_cli-0.2.5/src/nonoka_cli/core/planning_service.py +0 -192
- nonoka_cli-0.2.5/src/nonoka_cli/core/review_service.py +0 -293
- nonoka_cli-0.2.5/src/nonoka_cli/core/subagent_service.py +0 -184
- nonoka_cli-0.2.5/tests/unit/core/test_orchestrator.py +0 -205
- nonoka_cli-0.2.5/tests/unit/core/test_planning_service.py +0 -174
- nonoka_cli-0.2.5/tests/unit/core/test_review_service.py +0 -251
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/.nonoka/plugin.json.example +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/LICENSE +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/NOTICE +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/README.md +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/TODO.md +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/install.sh +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/nonoka.yaml +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/nonoka.yaml.example +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/.gitignore +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/LICENSE +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/README.md +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/bun.lock +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/package.json +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/src/index.ts +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/src/nonoka-language-model.ts +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/src/protocol.ts +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/src/stream.ts +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/tests/language-model.test.ts +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/tests/protocol.test.ts +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/tests/stream.test.ts +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/tsconfig.json +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/scripts/quick-install.sh +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/__main__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/events.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/handler.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/protocol.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/cli.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/config_cmd.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/doctor_cmd.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/plugin_cmd.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/run_cmd.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/config/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/config/loader.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/config/manager.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/context.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/context_trimmer.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/git_service.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/mcp_service.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/namespaces.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/plugin_manifest.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/plugin_manifest_converter.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/repo_map_service.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/runner_service.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/session_service.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/task_state.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/tool_output_policy.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/core/tool_service.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/mcp/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/mcp/manager.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/mcp/models.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/sessions/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/sessions/manager.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/sessions/models.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/skills/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/builtins/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/builtins/file_tools.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/loader.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/utils/__init__.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/utils/errors.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/utils/logging.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/src/nonoka_cli/utils/trace_logger.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/conftest.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/integration/test_server_external_capabilities.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/bridge/test_events.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/bridge/test_handler.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/bridge/test_protocol.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/commands/test_config_cmd.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/commands/test_doctor_cmd.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/commands/test_run_cmd.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/config/test_loader.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/core/test_context_trimmer.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/core/test_git_service.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/core/test_plugin_manifest.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/core/test_plugin_manifest_converter.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/core/test_repo_map_service.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/core/test_task_state.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/core/test_tool_output_policy.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/sessions/test_manager.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/tests/unit/utils/test_trace_logger.py +0 -0
- {nonoka_cli-0.2.5 → nonoka_cli-0.2.6}/uv.lock +0 -0
|
@@ -138,9 +138,7 @@ async def _async_main(
|
|
|
138
138
|
) -> int:
|
|
139
139
|
"""Set up async stdin/stdout and run the bridge server."""
|
|
140
140
|
loop = asyncio.get_event_loop()
|
|
141
|
-
|
|
142
|
-
# well above the default 64 KiB StreamReader limit. Bump it to 10 MiB.
|
|
143
|
-
stdin = asyncio.StreamReader(limit=10 * 1024 * 1024)
|
|
141
|
+
stdin = asyncio.StreamReader()
|
|
144
142
|
await loop.connect_read_pipe(lambda: asyncio.StreamReaderProtocol(stdin), sys.stdin)
|
|
145
143
|
|
|
146
144
|
w_transport, w_protocol = await loop.connect_write_pipe(
|
|
@@ -54,8 +54,9 @@ def _build_opencode_permission(config: CLIConfig) -> dict[str, str]:
|
|
|
54
54
|
permission[tool] = action
|
|
55
55
|
|
|
56
56
|
# nonoka.yaml permissions take precedence over auto_approve defaults.
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
user_permissions = getattr(config, "permissions", None)
|
|
58
|
+
if user_permissions:
|
|
59
|
+
permission.update(user_permissions)
|
|
59
60
|
return permission
|
|
60
61
|
|
|
61
62
|
|
|
@@ -111,20 +111,16 @@ class RepoMapConfig(BaseModel):
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
class AgentRoleConfig(BaseModel):
|
|
114
|
-
"""Configuration for a single agent role (planner /
|
|
114
|
+
"""Configuration for a single agent role (planner / executor)."""
|
|
115
115
|
model: str = ""
|
|
116
116
|
max_turns: int = 5
|
|
117
117
|
system_prompt: str = ""
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
class AgentsConfig(BaseModel):
|
|
121
|
-
"""Multi-agent role configuration.
|
|
122
|
-
|
|
123
|
-
The ``executor`` role has been removed: the main agent is the executor, and
|
|
124
|
-
its ``max_turns`` is configured at the top level of ``CLIConfig``.
|
|
125
|
-
"""
|
|
121
|
+
"""Multi-agent role configuration."""
|
|
126
122
|
planner: AgentRoleConfig = Field(default_factory=AgentRoleConfig)
|
|
127
|
-
|
|
123
|
+
executor: AgentRoleConfig = Field(default_factory=AgentRoleConfig)
|
|
128
124
|
|
|
129
125
|
|
|
130
126
|
class PluginConfig(BaseModel):
|
|
@@ -146,11 +142,9 @@ class CLIConfig(BaseModel):
|
|
|
146
142
|
Matches the expected structure of ~/.config/nonoka/config.yaml.
|
|
147
143
|
"""
|
|
148
144
|
model: str = "gpt-4o"
|
|
149
|
-
max_turns: int = 20
|
|
150
145
|
system_prompt: str = ""
|
|
151
146
|
api_key: str = ""
|
|
152
147
|
base_url: str = ""
|
|
153
|
-
permissions: dict[str, str] = Field(default_factory=dict)
|
|
154
148
|
mcp_servers: dict[str, MCPServerConfigModel] = Field(default_factory=dict)
|
|
155
149
|
tool_paths: list[Path] = Field(default_factory=list)
|
|
156
150
|
skills: list[str] = Field(default_factory=list)
|
|
@@ -26,6 +26,7 @@ from nonoka_cli.config.models import CLIConfig
|
|
|
26
26
|
from nonoka_cli.core.namespaces import (
|
|
27
27
|
PrefixedCapability,
|
|
28
28
|
mcp_tool_name,
|
|
29
|
+
sanitize_tool_name,
|
|
29
30
|
skill_tool_name,
|
|
30
31
|
)
|
|
31
32
|
from nonoka_cli.core.prompt_builder import SystemPromptBuilder
|
|
@@ -143,8 +144,11 @@ class AgentFactory:
|
|
|
143
144
|
return self._skill_registry
|
|
144
145
|
|
|
145
146
|
def _executor_max_turns(self) -> int:
|
|
146
|
-
"""Return the configured
|
|
147
|
-
|
|
147
|
+
"""Return the configured executor max turns, falling back to 20."""
|
|
148
|
+
configured = getattr(
|
|
149
|
+
self._config.agents.executor, "max_turns", None
|
|
150
|
+
)
|
|
151
|
+
return configured if configured else 20
|
|
148
152
|
|
|
149
153
|
def build(
|
|
150
154
|
self,
|
|
@@ -153,7 +157,6 @@ class AgentFactory:
|
|
|
153
157
|
git_summary: str | None = None,
|
|
154
158
|
plugin_summary: str | None = None,
|
|
155
159
|
execution_plan: str | None = None,
|
|
156
|
-
extra_tools: list[Capability] | None = None,
|
|
157
160
|
) -> Agent:
|
|
158
161
|
"""Build (or rebuild) an Agent from the current configuration.
|
|
159
162
|
|
|
@@ -164,9 +167,6 @@ class AgentFactory:
|
|
|
164
167
|
repo_map: Optional repo-map block to inject into the system prompt.
|
|
165
168
|
git_summary: Optional git-checkpoint status summary.
|
|
166
169
|
plugin_summary: Optional loaded plugin manifest summary.
|
|
167
|
-
execution_plan: Optional structured plan produced by the planner agent.
|
|
168
|
-
extra_tools: Optional additional tools (e.g. sub-agent AgentTools) to
|
|
169
|
-
register with the Agent.
|
|
170
170
|
|
|
171
171
|
Returns:
|
|
172
172
|
A nonoka Agent instance.
|
|
@@ -233,12 +233,6 @@ class AgentFactory:
|
|
|
233
233
|
builder = builder.tool(capability)
|
|
234
234
|
logger.debug("agent_factory_mcp_tools", count=len(mcp_tools))
|
|
235
235
|
|
|
236
|
-
# Extra tools (e.g. planner/reviewer sub-agents) injected by Orchestrator.
|
|
237
|
-
if extra_tools:
|
|
238
|
-
for tool in extra_tools:
|
|
239
|
-
builder = builder.tool(tool)
|
|
240
|
-
logger.debug("agent_factory_extra_tools", count=len(extra_tools))
|
|
241
|
-
|
|
242
236
|
self._agent = builder.build()
|
|
243
237
|
return self._agent
|
|
244
238
|
|
|
@@ -274,7 +268,6 @@ class AgentFactory:
|
|
|
274
268
|
git_summary: str | None = None,
|
|
275
269
|
plugin_summary: str | None = None,
|
|
276
270
|
execution_plan: str | None = None,
|
|
277
|
-
extra_tools: list[Capability] | None = None,
|
|
278
271
|
) -> Agent:
|
|
279
272
|
"""Build an Agent for host-managed (e.g. OpenCode) mode.
|
|
280
273
|
|
|
@@ -296,9 +289,6 @@ class AgentFactory:
|
|
|
296
289
|
in nonoka.yaml.
|
|
297
290
|
external_mcp_servers: Optional host-managed MCP server definitions.
|
|
298
291
|
external_skills: Optional host-managed skill definitions.
|
|
299
|
-
execution_plan: Optional structured plan produced by the planner agent.
|
|
300
|
-
extra_tools: Optional additional tools (e.g. sub-agent AgentTools) to
|
|
301
|
-
register with the Agent.
|
|
302
292
|
"""
|
|
303
293
|
if not self._config.model:
|
|
304
294
|
raise AgentBuildError("No model configured. Set 'model' in config.yaml.")
|
|
@@ -454,12 +444,6 @@ class AgentFactory:
|
|
|
454
444
|
for server, cap in mcp_tools:
|
|
455
445
|
builder = builder.tool(PrefixedCapability(cap, f"mcp__{server}__"))
|
|
456
446
|
|
|
457
|
-
# Extra tools (e.g. planner/reviewer sub-agents) injected by Orchestrator.
|
|
458
|
-
if extra_tools:
|
|
459
|
-
for tool in extra_tools:
|
|
460
|
-
builder = builder.tool(tool)
|
|
461
|
-
logger.debug("agent_factory_extra_tools", count=len(extra_tools))
|
|
462
|
-
|
|
463
447
|
return builder.build()
|
|
464
448
|
|
|
465
449
|
def _skill_registry_for_build(
|
|
@@ -26,7 +26,6 @@ from nonoka_cli.core.context_trimmer import TurnBasedContextTrimmer
|
|
|
26
26
|
from nonoka_cli.core.git_service import GitService, build_git_service
|
|
27
27
|
from nonoka_cli.core.mcp_service import MCPService
|
|
28
28
|
from nonoka_cli.core.planning_service import PlanningService, build_planning_service
|
|
29
|
-
from nonoka_cli.core.review_service import ReviewService, build_review_service
|
|
30
29
|
from nonoka_cli.core.plugin_manifest import (
|
|
31
30
|
PluginManifestLoader,
|
|
32
31
|
format_manifest_summary,
|
|
@@ -99,7 +98,6 @@ class Orchestrator:
|
|
|
99
98
|
self._git_service: GitService | None = None
|
|
100
99
|
self._repo_map_service: RepoMapService | None = None
|
|
101
100
|
self._planning_service: PlanningService | None = None
|
|
102
|
-
self._review_service: ReviewService | None = None
|
|
103
101
|
self._plugin_manifests: list[Any] = []
|
|
104
102
|
self._initialized = False
|
|
105
103
|
|
|
@@ -162,10 +160,6 @@ class Orchestrator:
|
|
|
162
160
|
working_dir=Path.cwd(),
|
|
163
161
|
config=self._config,
|
|
164
162
|
)
|
|
165
|
-
self._review_service = build_review_service(
|
|
166
|
-
working_dir=Path.cwd(),
|
|
167
|
-
config=self._config,
|
|
168
|
-
)
|
|
169
163
|
|
|
170
164
|
# Load project plugin manifests before building the Agent so that
|
|
171
165
|
# allowed_tools / plugin summary can be taken into account.
|
|
@@ -191,7 +185,6 @@ class Orchestrator:
|
|
|
191
185
|
repo_map=repo_map_block,
|
|
192
186
|
git_summary=git_summary,
|
|
193
187
|
plugin_summary=plugin_summary,
|
|
194
|
-
extra_tools=self._subagent_tools(),
|
|
195
188
|
)
|
|
196
189
|
self._tool_service = ToolService(self._agent_factory, self._tool_loader)
|
|
197
190
|
self._mcp_service = MCPService(self._mcp_manager, self._agent_factory)
|
|
@@ -354,19 +347,6 @@ class Orchestrator:
|
|
|
354
347
|
merged = merge_manifests(self._plugin_manifests)
|
|
355
348
|
return merged.allowed_tools
|
|
356
349
|
|
|
357
|
-
def _subagent_tools(self) -> list[Any]:
|
|
358
|
-
"""Return enabled sub-agent AgentTools for injection into the executor.
|
|
359
|
-
|
|
360
|
-
Only planner/reviewer tools whose model is configured are returned, so the
|
|
361
|
-
default executor tool list stays small.
|
|
362
|
-
"""
|
|
363
|
-
tools: list[Any] = []
|
|
364
|
-
if self._planning_service is not None and self._planning_service.enabled:
|
|
365
|
-
tools.append(self._planning_service.agent_tool)
|
|
366
|
-
if self._review_service is not None and self._review_service.enabled:
|
|
367
|
-
tools.append(self._review_service.agent_tool)
|
|
368
|
-
return tools
|
|
369
|
-
|
|
370
350
|
def _on_config_changed(self, config: CLIConfig) -> None:
|
|
371
351
|
"""ConfigManager hot-reload listener: update local reference only."""
|
|
372
352
|
self._config = config
|
|
@@ -450,10 +430,7 @@ class Orchestrator:
|
|
|
450
430
|
logger.info("execute_plan_result", plan_preview=plan[:200] if plan else None)
|
|
451
431
|
if plan and not plan.startswith(("Error", "Planning is disabled")):
|
|
452
432
|
logger.info("execution_plan_generated", plan_length=len(plan))
|
|
453
|
-
agent = self._agent_factory.build(
|
|
454
|
-
execution_plan=plan,
|
|
455
|
-
extra_tools=self._subagent_tools(),
|
|
456
|
-
)
|
|
433
|
+
agent = self._agent_factory.build(execution_plan=plan)
|
|
457
434
|
except Exception as exc:
|
|
458
435
|
logger.warning("execution_plan_generation_failed", error=str(exc))
|
|
459
436
|
|
|
@@ -560,7 +537,6 @@ class Orchestrator:
|
|
|
560
537
|
git_summary=git_summary,
|
|
561
538
|
plugin_summary=plugin_summary,
|
|
562
539
|
execution_plan=execution_plan,
|
|
563
|
-
extra_tools=self._subagent_tools(),
|
|
564
540
|
)
|
|
565
541
|
|
|
566
542
|
logger.info(
|
|
@@ -618,7 +594,6 @@ class Orchestrator:
|
|
|
618
594
|
repo_map=repo_map_block,
|
|
619
595
|
git_summary=git_summary,
|
|
620
596
|
plugin_summary=plugin_summary,
|
|
621
|
-
extra_tools=self._subagent_tools(),
|
|
622
597
|
)
|
|
623
598
|
|
|
624
599
|
logger.info(
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Planning service for nonoka-cli.
|
|
2
|
+
|
|
3
|
+
Wires planner/executor configuration (``agents.planner``) to the orchestrator.
|
|
4
|
+
The actual planner implementation has been removed from nonoka-agent in 1.3.4;
|
|
5
|
+
nonoka-cli will provide its own LLM-based planner here in a future release.
|
|
6
|
+
For now the service reports itself as disabled when invoked so the orchestrator
|
|
7
|
+
falls back to single-agent execution.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from nonoka_cli.config.models import AgentsConfig, CLIConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PlanningService:
|
|
18
|
+
"""Adapts the nonoka-agent planner tool to CLI config."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
working_dir: Path,
|
|
23
|
+
config: AgentsConfig | None = None,
|
|
24
|
+
default_model: str | None = None,
|
|
25
|
+
):
|
|
26
|
+
self._working_dir = working_dir
|
|
27
|
+
self._config = config or AgentsConfig()
|
|
28
|
+
self._default_model = default_model
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def enabled(self) -> bool:
|
|
32
|
+
return bool(self.planner_model)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def planner_model(self) -> str:
|
|
36
|
+
"""Return the effective planner model identifier."""
|
|
37
|
+
configured = self._config.planner.model
|
|
38
|
+
if configured:
|
|
39
|
+
return configured
|
|
40
|
+
if self._default_model:
|
|
41
|
+
return self._default_model
|
|
42
|
+
return ""
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def executor_model(self) -> str:
|
|
46
|
+
"""Return the effective executor model identifier."""
|
|
47
|
+
configured = self._config.executor.model
|
|
48
|
+
if configured:
|
|
49
|
+
return configured
|
|
50
|
+
if self._default_model:
|
|
51
|
+
return self._default_model
|
|
52
|
+
return ""
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def planner_max_turns(self) -> int:
|
|
56
|
+
return self._config.planner.max_turns
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def executor_max_turns(self) -> int:
|
|
60
|
+
return self._config.executor.max_turns
|
|
61
|
+
|
|
62
|
+
async def plan(self, task: str, max_steps: int = 10) -> str:
|
|
63
|
+
"""Generate a structured plan for *task*.
|
|
64
|
+
|
|
65
|
+
Returns a human-readable plan or an error message. The planner tool has
|
|
66
|
+
been removed from nonoka-agent in 1.3.4; a CLI-native planner will replace
|
|
67
|
+
this stub in a future release.
|
|
68
|
+
"""
|
|
69
|
+
if not self.enabled:
|
|
70
|
+
return "Planning is disabled: no planner model configured."
|
|
71
|
+
return (
|
|
72
|
+
"Planning is disabled: the built-in planner tool was removed from "
|
|
73
|
+
"nonoka-agent 1.3.4. Configure a planner model to opt into the future "
|
|
74
|
+
"CLI-native planner implementation."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def build_planning_service(
|
|
79
|
+
working_dir: Path,
|
|
80
|
+
config: CLIConfig | None = None,
|
|
81
|
+
) -> PlanningService:
|
|
82
|
+
"""Factory for creating a PlanningService from CLI configuration."""
|
|
83
|
+
agents_config = config.agents if config is not None else AgentsConfig()
|
|
84
|
+
default_model = config.model if config is not None else None
|
|
85
|
+
return PlanningService(
|
|
86
|
+
working_dir=working_dir,
|
|
87
|
+
config=agents_config,
|
|
88
|
+
default_model=default_model,
|
|
89
|
+
)
|
|
@@ -181,20 +181,11 @@ class SystemPromptBuilder:
|
|
|
181
181
|
def _build_planner_block(self) -> str:
|
|
182
182
|
"""Return guidance for the planner/executor workflow.
|
|
183
183
|
|
|
184
|
-
The ``plan_task``
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
UI via ``todowrite``.
|
|
184
|
+
The built-in ``plan_task`` tool was removed from nonoka-agent in 1.3.4; the
|
|
185
|
+
planner block is currently suppressed until nonoka-cli provides its own
|
|
186
|
+
LLM-based planner implementation.
|
|
188
187
|
"""
|
|
189
|
-
|
|
190
|
-
return ""
|
|
191
|
-
return (
|
|
192
|
-
"## Planner / Executor Workflow\n"
|
|
193
|
-
"For complex multi-file or multi-step tasks, first call the ``plan_task`` "
|
|
194
|
-
"tool to produce a structured plan. Then execute the steps using the "
|
|
195
|
-
"file, search, and run-command tools. After each step, call ``todowrite`` "
|
|
196
|
-
"to update the TODO list so the user can follow progress in the OpenCode TUI."
|
|
197
|
-
)
|
|
188
|
+
return ""
|
|
198
189
|
|
|
199
190
|
def _build_execution_plan_block(self) -> str:
|
|
200
191
|
"""Return the concrete execution plan produced by the planner agent."""
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Agent-level tools re-exported from nonoka-agent.
|
|
2
2
|
|
|
3
3
|
These tools are implemented in nonoka-agent (the core agent framework) and are
|
|
4
|
-
made available to every nonoka-cli Agent.
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
made available to every nonoka-cli Agent. Keeping the implementation in
|
|
5
|
+
nonoka-agent lets other consumers reuse them, while this module declares the
|
|
6
|
+
subset the CLI exposes by default.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
@@ -151,28 +151,3 @@ def test_opencode_init_disables_native_skill_tool(tmp_path: Path):
|
|
|
151
151
|
|
|
152
152
|
data = json.loads((tmp_path / "opencode.json").read_text())
|
|
153
153
|
assert data.get("tools", {}).get("skill") is False
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def test_opencode_init_permissions_override(tmp_path: Path):
|
|
157
|
-
config_path = tmp_path / "nonoka.yaml"
|
|
158
|
-
ConfigLoader.save(
|
|
159
|
-
CLIConfig(
|
|
160
|
-
model="deepseek-chat",
|
|
161
|
-
permissions={"read": "allow", "bash": "ask"},
|
|
162
|
-
),
|
|
163
|
-
config_path,
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
args = argparse.Namespace(
|
|
167
|
-
config=str(config_path),
|
|
168
|
-
cwd=str(tmp_path),
|
|
169
|
-
global_=False,
|
|
170
|
-
)
|
|
171
|
-
assert cmd_init(args) == 0
|
|
172
|
-
|
|
173
|
-
data = json.loads((tmp_path / "opencode.json").read_text())
|
|
174
|
-
assert data["permission"]["read"] == "allow"
|
|
175
|
-
assert data["permission"]["bash"] == "ask"
|
|
176
|
-
assert data["permission"]["write"] == "ask"
|
|
177
|
-
assert data["agent"]["build"]["permission"]["read"] == "allow"
|
|
178
|
-
assert data["agent"]["build"]["permission"]["bash"] == "ask"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from nonoka_cli.config.models import (
|
|
4
|
-
AgentsConfig,
|
|
5
4
|
CLIBehaviorConfig,
|
|
6
5
|
CLIConfig,
|
|
7
6
|
HITLConfigModel,
|
|
@@ -21,9 +20,7 @@ def test_cli_behavior_defaults():
|
|
|
21
20
|
def test_cli_config_defaults():
|
|
22
21
|
cfg = CLIConfig()
|
|
23
22
|
assert cfg.model == "gpt-4o"
|
|
24
|
-
assert cfg.max_turns == 20
|
|
25
23
|
assert cfg.system_prompt == ""
|
|
26
|
-
assert cfg.permissions == {}
|
|
27
24
|
assert cfg.mcp_servers == {}
|
|
28
25
|
assert cfg.tool_paths == []
|
|
29
26
|
assert cfg.skills == []
|
|
@@ -31,30 +28,6 @@ def test_cli_config_defaults():
|
|
|
31
28
|
assert isinstance(cfg.hitl, HITLConfigModel)
|
|
32
29
|
|
|
33
30
|
|
|
34
|
-
def test_agents_config_has_no_executor():
|
|
35
|
-
cfg = AgentsConfig()
|
|
36
|
-
assert hasattr(cfg, "planner")
|
|
37
|
-
assert hasattr(cfg, "reviewer")
|
|
38
|
-
assert not hasattr(cfg, "executor")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_cli_config_ignores_legacy_executor():
|
|
42
|
-
# Existing nonoka.yaml files may still contain agents.executor; Pydantic
|
|
43
|
-
# should ignore it rather than crash.
|
|
44
|
-
data = {
|
|
45
|
-
"model": "deepseek-chat",
|
|
46
|
-
"agents": {
|
|
47
|
-
"planner": {"model": "gpt-4o-mini"},
|
|
48
|
-
"executor": {"model": "gpt-4o", "max_turns": 10},
|
|
49
|
-
"reviewer": {"model": "gpt-4o-mini"},
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
cfg = CLIConfig.model_validate(data)
|
|
53
|
-
assert cfg.max_turns == 20
|
|
54
|
-
assert cfg.agents.planner.model == "gpt-4o-mini"
|
|
55
|
-
assert not hasattr(cfg.agents, "executor")
|
|
56
|
-
|
|
57
|
-
|
|
58
31
|
def test_cli_config_tool_paths_expanded():
|
|
59
32
|
cfg = CLIConfig(tool_paths=["~/tools", "./local_tools"])
|
|
60
33
|
assert cfg.tool_paths[0] == Path.home() / "tools"
|
|
@@ -415,49 +415,3 @@ async def test_build_with_external_tools_no_warning_when_native_skill_disabled(t
|
|
|
415
415
|
agent = factory.build_with_external_tools(tools, cwd=str(tmp_path))
|
|
416
416
|
prompt = agent.system_prompt
|
|
417
417
|
assert "OpenCode's native skill tool is enabled" not in prompt
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
class _FakeExtraTool:
|
|
421
|
-
"""Minimal capability stand-in for extra_tools injection tests."""
|
|
422
|
-
|
|
423
|
-
name = "extra_tool"
|
|
424
|
-
description = "An extra tool"
|
|
425
|
-
|
|
426
|
-
@property
|
|
427
|
-
def parameters(self) -> dict:
|
|
428
|
-
return {"type": "object", "properties": {}}
|
|
429
|
-
|
|
430
|
-
async def invoke(self, ctx, arguments):
|
|
431
|
-
return "ok"
|
|
432
|
-
|
|
433
|
-
def to_json_schema(self):
|
|
434
|
-
return {
|
|
435
|
-
"type": "function",
|
|
436
|
-
"function": {"name": self.name, "description": self.description, "parameters": {}},
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
@pytest.mark.asyncio
|
|
441
|
-
async def test_build_injects_extra_tools():
|
|
442
|
-
config = CLIConfig(model="gpt-4o")
|
|
443
|
-
factory = AgentFactory(config)
|
|
444
|
-
agent = factory.build(extra_tools=[_FakeExtraTool()])
|
|
445
|
-
tool_names = {t.name for t in agent.tools}
|
|
446
|
-
assert "extra_tool" in tool_names
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
@pytest.mark.asyncio
|
|
450
|
-
async def test_build_with_external_tools_injects_extra_tools():
|
|
451
|
-
config = CLIConfig(model="gpt-4o")
|
|
452
|
-
factory = AgentFactory(config)
|
|
453
|
-
tools = [
|
|
454
|
-
AgentFactory.create_external_tool_capability(
|
|
455
|
-
name="bash",
|
|
456
|
-
description="Run shell commands",
|
|
457
|
-
parameters={"type": "object", "properties": {}},
|
|
458
|
-
)
|
|
459
|
-
]
|
|
460
|
-
agent = factory.build_with_external_tools(tools, extra_tools=[_FakeExtraTool()])
|
|
461
|
-
tool_names = {t.name for t in agent.tools}
|
|
462
|
-
assert "bash" in tool_names
|
|
463
|
-
assert "extra_tool" in tool_names
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Tests for Orchestrator two-stage planning integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from types import SimpleNamespace
|
|
7
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from nonoka_cli.config.models import CLIConfig
|
|
12
|
+
from nonoka_cli.core.orchestrator import Orchestrator
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def base_orchestrator(tmp_path: Path) -> Orchestrator:
|
|
17
|
+
"""Return a minimally initialized orchestrator with mocked services."""
|
|
18
|
+
config = CLIConfig(
|
|
19
|
+
model="gpt-4o",
|
|
20
|
+
agents={
|
|
21
|
+
"planner": {"model": "gpt-4o-mini", "max_turns": 3, "system_prompt": ""},
|
|
22
|
+
"executor": {"model": "gpt-4o", "max_turns": 10, "system_prompt": ""},
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
orch = Orchestrator(config=config)
|
|
26
|
+
orch._initialized = True
|
|
27
|
+
orch._session_service = MagicMock()
|
|
28
|
+
orch._session_service.current_id = "test-session"
|
|
29
|
+
orch._session_service.manager = SimpleNamespace(db_path=tmp_path / "nonoka.db")
|
|
30
|
+
orch._runner_service = MagicMock()
|
|
31
|
+
orch._agent_factory = MagicMock()
|
|
32
|
+
orch._agent_factory.get_agent.return_value = MagicMock()
|
|
33
|
+
orch._config = config
|
|
34
|
+
return orch
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.asyncio
|
|
38
|
+
async def test_execute_with_external_tools_injects_execution_plan(
|
|
39
|
+
base_orchestrator: Orchestrator,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""When planning is enabled, the generated plan is passed to the agent factory."""
|
|
42
|
+
orch = base_orchestrator
|
|
43
|
+
|
|
44
|
+
planning_service = MagicMock()
|
|
45
|
+
planning_service.enabled = True
|
|
46
|
+
planning_service.planner_model = "gpt-4o-mini"
|
|
47
|
+
planning_service.plan = AsyncMock(return_value="1. Do X\n2. Do Y")
|
|
48
|
+
orch._planning_service = planning_service
|
|
49
|
+
|
|
50
|
+
async def _fake_stream(*args, **kwargs):
|
|
51
|
+
yield MagicMock(type="final", data={"success": True})
|
|
52
|
+
|
|
53
|
+
orch._runner_service.run = MagicMock(side_effect=_fake_stream)
|
|
54
|
+
|
|
55
|
+
events = []
|
|
56
|
+
async for event in orch.execute_with_external_tools(
|
|
57
|
+
prompt="Create a test file",
|
|
58
|
+
tools=[],
|
|
59
|
+
working_dir=Path.cwd(),
|
|
60
|
+
host_system_prompt="Host prompt",
|
|
61
|
+
):
|
|
62
|
+
events.append(event)
|
|
63
|
+
|
|
64
|
+
planning_service.plan.assert_awaited_once_with("Create a test file")
|
|
65
|
+
call_kwargs = orch._agent_factory.build_with_external_tools.call_args.kwargs
|
|
66
|
+
assert call_kwargs.get("execution_plan") == "1. Do X\n2. Do Y"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.mark.asyncio
|
|
70
|
+
async def test_execute_with_external_tools_skips_planning_when_disabled(
|
|
71
|
+
base_orchestrator: Orchestrator,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""When no planner model is configured, no plan is generated."""
|
|
74
|
+
orch = base_orchestrator
|
|
75
|
+
|
|
76
|
+
planning_service = MagicMock()
|
|
77
|
+
planning_service.enabled = False
|
|
78
|
+
planning_service.planner_model = ""
|
|
79
|
+
planning_service.plan = AsyncMock(return_value="should not be used")
|
|
80
|
+
orch._planning_service = planning_service
|
|
81
|
+
|
|
82
|
+
async def _fake_stream(*args, **kwargs):
|
|
83
|
+
yield MagicMock(type="final", data={"success": True})
|
|
84
|
+
|
|
85
|
+
orch._runner_service.run = MagicMock(side_effect=_fake_stream)
|
|
86
|
+
|
|
87
|
+
events = []
|
|
88
|
+
async for event in orch.execute_with_external_tools(
|
|
89
|
+
prompt="Create a test file",
|
|
90
|
+
tools=[],
|
|
91
|
+
working_dir=Path.cwd(),
|
|
92
|
+
host_system_prompt="Host prompt",
|
|
93
|
+
):
|
|
94
|
+
events.append(event)
|
|
95
|
+
|
|
96
|
+
planning_service.plan.assert_not_awaited()
|
|
97
|
+
call_kwargs = orch._agent_factory.build_with_external_tools.call_args.kwargs
|
|
98
|
+
assert call_kwargs.get("execution_plan") is None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Tests for PlanningService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from nonoka_cli.config.models import AgentsConfig, AgentRoleConfig, CLIConfig
|
|
10
|
+
from nonoka_cli.core.planning_service import PlanningService, build_planning_service
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_planner_model_prefers_configured() -> None:
|
|
14
|
+
config = AgentsConfig(
|
|
15
|
+
planner=AgentRoleConfig(model="gpt-4o-mini"),
|
|
16
|
+
executor=AgentRoleConfig(model="gpt-4o"),
|
|
17
|
+
)
|
|
18
|
+
service = PlanningService(working_dir=Path("."), config=config, default_model="fallback")
|
|
19
|
+
assert service.planner_model == "gpt-4o-mini"
|
|
20
|
+
assert service.executor_model == "gpt-4o"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_planner_model_falls_back_to_default() -> None:
|
|
24
|
+
config = AgentsConfig()
|
|
25
|
+
service = PlanningService(working_dir=Path("."), config=config, default_model="fallback")
|
|
26
|
+
assert service.planner_model == "fallback"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_disabled_when_no_model() -> None:
|
|
30
|
+
service = PlanningService(working_dir=Path("."), config=AgentsConfig())
|
|
31
|
+
assert not service.enabled
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_build_from_config() -> None:
|
|
35
|
+
config = CLIConfig(model="base-model", agents=AgentsConfig(
|
|
36
|
+
planner=AgentRoleConfig(model="planner-model"),
|
|
37
|
+
))
|
|
38
|
+
service = build_planning_service(working_dir=Path("."), config=config)
|
|
39
|
+
assert service.planner_model == "planner-model"
|
|
40
|
+
assert service.executor_model == "base-model"
|