Skip to content

Commit b0eaa9c

Browse files
committed
Start only required runners in CLI if projects are provided
1 parent 889b393 commit b0eaa9c

6 files changed

Lines changed: 69 additions & 23 deletions

File tree

docs/wm-protocol.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,26 @@ and optionally starts extension runners.
114114
**Params:**
115115

116116
```json
117-
{"dir_path": "/path/to/workspace", "start_runners": true}
117+
{"dir_path": "/path/to/workspace", "start_runners": true, "projects": ["my_project"]}
118118
```
119119

120120
`start_runners` is optional (default: `true`). When `false`, the server reads
121121
configs and collects actions without starting any extension runners. Use this
122122
when runner environments may not exist yet (e.g. before running `prepare-envs`).
123123
Actions are still available in the result so clients can validate the workspace.
124124

125+
`projects` is optional. When provided, only the listed projects (by name) will
126+
be config-initialized and have their runners started. All other projects in the
127+
directory are still discovered (added to workspace state) but skipped for
128+
initialization. This avoids the cost of reading configs and spawning runner
129+
processes for projects that are not needed.
130+
131+
Calling `workspace/addDir` again for the same `dir_path` with a different
132+
`projects` filter (or with `projects` omitted) will initialize the previously
133+
skipped projects — the call is **incremental**, not idempotent. Only projects
134+
that have not yet been config-initialized are processed on each call. This makes
135+
it safe to issue a filtered call followed by an unfiltered one.
136+
125137
**Result:**
126138

127139
```json
@@ -132,6 +144,9 @@ Actions are still available in the result so clients can validate the workspace.
132144
}
133145
```
134146

147+
The `projects` list contains only the projects initialized during **this call**,
148+
not all projects in the workspace.
149+
135150
`status` values: `"CONFIG_VALID"`, `"CONFIG_INVALID"`
136151

137152
---

mkdocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,5 @@ nav:
8080
- LSP and MCP Architecture: reference/lsp-mcp-architecture.md
8181
- Development:
8282
- Overview: development.md
83-
- API Protocol: api-protocol.md
83+
- WM Protocol: wm-protocol.md
8484
- Developing FineCode: guides/developing-finecode.md

src/finecode/cli_app/commands/dump_config_cmd.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async def dump_config(
3030
client = ApiClient()
3131
await client.connect("127.0.0.1", port)
3232
try:
33-
result = await client.add_dir(workdir_path)
33+
result = await client.add_dir(workdir_path, projects=[project_name])
3434
projects = result.get("projects", [])
3535
project = next(
3636
(p for p in projects if p["name"] == project_name), None

src/finecode/cli_app/commands/run_cmd.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ async def run_actions(
5353
"Warning: --config overrides are ignored in --shared-server mode. ",
5454
err=True,
5555
)
56-
# TODO: could it be optimized: if projects are provided, parse only them?
57-
# the same also in other CLI commands
58-
await client.add_dir(workdir_path)
56+
await client.add_dir(
57+
workdir_path,
58+
projects=projects_names if own_server else None,
59+
)
5960

6061
params_by_project: dict[str, dict[str, typing.Any]] = {}
6162
if map_payload_fields:

src/finecode/wm_client.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,17 +236,27 @@ async def run_action(
236236
body["params"] = params
237237
return await self.request("actions/run", body)
238238

239-
async def add_dir(self, dir_path: pathlib.Path, start_runners: bool = True) -> dict:
239+
async def add_dir(
240+
self,
241+
dir_path: pathlib.Path,
242+
start_runners: bool = True,
243+
projects: list[str] | None = None,
244+
) -> dict:
240245
"""Add a workspace directory. Returns {projects: [...]}.
241246
242247
When ``start_runners=False`` the server reads configs and collects
243248
actions without starting any extension runners. Use this when runner
244249
environments may not exist yet (e.g. before ``prepare-envs``).
250+
251+
When ``projects`` is provided, only those projects (by name) will have
252+
their configs read and runners started — the rest are still discovered
253+
but not initialised. Only use this in own-server mode where the server
254+
lifetime matches a single CLI invocation.
245255
"""
246-
return await self.request(
247-
"workspace/addDir",
248-
{"dir_path": str(dir_path), "start_runners": start_runners},
249-
)
256+
body: dict = {"dir_path": str(dir_path), "start_runners": start_runners}
257+
if projects is not None:
258+
body["projects"] = projects
259+
return await self.request("workspace/addDir", body)
250260

251261
async def start_runners(self, projects: list[str] | None = None) -> None:
252262
"""Start extension runners for all (or specified) projects.

src/finecode/wm_server/wm_server.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,11 @@ async def _handle_add_dir(
249249
When false, configs are read and actions collected without starting any
250250
runners. Useful when runner environments may not exist yet (e.g. before
251251
running prepare-envs).
252+
projects: list[str] | null - optional list of project names to initialize.
253+
Projects not in this list are discovered but not config-initialized or
254+
started. Omit (or pass null) to initialize all projects.
255+
Calling add_dir again for the same dir with a different filter (or no
256+
filter) will initialize the previously skipped projects.
252257
"""
253258
from finecode.wm_server.config import collect_actions, read_configs
254259
from finecode.wm_server.runner import runner_manager
@@ -257,23 +262,38 @@ async def _handle_add_dir(
257262
params = params or {}
258263
dir_path = pathlib.Path(params["dir_path"])
259264
start_runners: bool = params.get("start_runners", True)
265+
projects_filter: set[str] | None = (
266+
set(params["projects"]) if params.get("projects") else None
267+
)
260268
logger.trace(f"Add ws dir: {dir_path}")
261269

262-
if dir_path in ws_context.ws_dirs_paths:
263-
return {"projects": []}
270+
if dir_path not in ws_context.ws_dirs_paths:
271+
ws_context.ws_dirs_paths.append(dir_path)
272+
273+
# Discover new projects in this dir (idempotent — skips already-known ones).
274+
await read_configs.read_projects_in_dir(dir_path, ws_context)
275+
276+
# Collect all projects in this dir that haven't been config-initialized yet.
277+
# This covers both newly discovered projects and ones that were filtered out
278+
# by a previous add_dir call with a projects filter.
279+
projects_to_init = [
280+
p for p in ws_context.ws_projects.values()
281+
if p.dir_path.is_relative_to(dir_path)
282+
and p.dir_path not in ws_context.ws_projects_raw_configs
283+
]
264284

265-
ws_context.ws_dirs_paths.append(dir_path)
266-
new_projects = await read_configs.read_projects_in_dir(dir_path, ws_context)
285+
if projects_filter is not None:
286+
projects_to_init = [p for p in projects_to_init if p.name in projects_filter]
267287

268-
for project in new_projects:
288+
for project in projects_to_init:
269289
await read_configs.read_project_config(
270290
project=project, ws_context=ws_context, resolve_presets=False
271291
)
272292

273293
if not start_runners:
274294
# Collect actions directly from raw config without needing runners.
275295
from finecode.wm_server.config import config_models
276-
for project in new_projects:
296+
for project in projects_to_init:
277297
if project.status == domain.ProjectStatus.CONFIG_VALID:
278298
try:
279299
collect_actions.collect_actions(
@@ -283,11 +303,11 @@ async def _handle_add_dir(
283303
logger.warning(
284304
f"Failed to collect actions for {project.name}: {exc.message}"
285305
)
286-
return {"projects": [_project_to_dict(p) for p in new_projects]}
306+
return {"projects": [_project_to_dict(p) for p in projects_to_init]}
287307

288308
try:
289309
await runner_manager.start_runners_with_presets(
290-
projects=new_projects,
310+
projects=projects_to_init,
291311
ws_context=ws_context,
292312
initialize_all_handlers=True,
293313
)
@@ -300,12 +320,12 @@ async def _handle_add_dir(
300320

301321
# If config overrides were set before this addDir call (e.g. standalone CLI mode),
302322
# apply them to the newly discovered projects and push to their running runners.
303-
if ws_context.handler_config_overrides and new_projects:
323+
if ws_context.handler_config_overrides and projects_to_init:
304324
action_names = list(ws_context.handler_config_overrides.keys())
305-
_apply_config_overrides_to_projects(new_projects, action_names, ws_context.handler_config_overrides)
325+
_apply_config_overrides_to_projects(projects_to_init, action_names, ws_context.handler_config_overrides)
306326
try:
307327
async with asyncio.TaskGroup() as tg:
308-
for project in new_projects:
328+
for project in projects_to_init:
309329
runners = ws_context.ws_projects_extension_runners.get(project.dir_path, {})
310330
for runner in runners.values():
311331
if runner.status == RunnerStatus.RUNNING:
@@ -320,7 +340,7 @@ async def _handle_add_dir(
320340
for exc in eg.exceptions:
321341
logger.warning(f"Failed to push config update to runner: {exc}")
322342

323-
return {"projects": [_project_to_dict(p) for p in new_projects]}
343+
return {"projects": [_project_to_dict(p) for p in projects_to_init]}
324344

325345

326346
async def _handle_remove_dir(

0 commit comments

Comments
 (0)