Skip to content

Commit 581f0c4

Browse files
committed
Migrate WM Server protocol and WM clients to identifying actions by source instead of names.
1 parent bc35dcd commit 581f0c4

24 files changed

Lines changed: 773 additions & 315 deletions

File tree

docs/wm-er-protocol.md

Lines changed: 119 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22

33
This document describes the communication protocol between the FineCode Workspace
44
Manager (WM) and Extension Runners (ER). WM is the JSON-RPC client; each ER is a
5-
JSON-RPC server implemented via the LSP stack (`finecode_extension_runner/lsp_server.py`).
6-
The protocol is LSP-shaped with a small set of custom commands.
5+
JSON-RPC server.
6+
7+
The WM-ER protocol uses JSON-RPC 2.0 with LSP-style wire framing. Lifecycle method
8+
names (`initialize`, `initialized`, `shutdown`, `exit`) and text-document notification
9+
names follow LSP conventions; all FineCode-specific commands use direct JSON-RPC
10+
method names.
711

812
## Transport
913

@@ -12,16 +16,20 @@ The protocol is LSP-shaped with a small set of custom commands.
1216
- WM spawns ER processes with:
1317
- `python -m finecode_extension_runner.cli start --project-path=... --env-name=...`
1418
- `--debug` enables a debugpy attach flow before WM connects
15-
- Field names are camelCase for standard LSP params, but command arguments are
16-
passed verbatim (snake_case is common in FineCode payloads).
19+
- All parameter object keys use camelCase.
1720

1821
## Lifecycle
1922

2023
1. WM starts the ER process (per project + env).
2124
2. WM sends `initialize` and waits for the ER response.
2225
3. WM sends `initialized`.
2326
4. WM sends `finecodeRunner/updateConfig` to bootstrap handlers and services.
24-
5. On shutdown: WM sends `shutdown` then `exit`.
27+
- ER processes it and returns `{}`.
28+
5. WM sends `finecodeRunner/resolveActionSources` to get canonical action source paths.
29+
- ER returns a sparse map of `configSource → canonicalSource` for actions whose
30+
declared config path differs from the fully qualified runtime path.
31+
- WM stores these on its `Action` domain objects before the runner is considered ready.
32+
6. On shutdown: WM sends `shutdown` then `exit`.
2533

2634
## Message Catalog
2735

@@ -45,95 +53,104 @@ The protocol is LSP-shaped with a small set of custom commands.
4553
- `shutdown`
4654
- Standard LSP shutdown request.
4755

48-
- `workspace/executeCommand`
49-
- Used for all FineCode WM → ER commands. The `arguments` array is passed to
50-
the handler verbatim.
51-
52-
**Commands**
53-
54-
- `finecodeRunner/updateConfig`
55-
- Arguments:
56-
1. `working_dir` (string path)
57-
2. `project_name` (string)
58-
3. `project_def_path` (string path)
59-
4. `config` (object)
60-
- Config shape (top-level):
61-
- `actions`: list of action objects (`name`, `handlers`, `source`, `config`)
62-
- `action_handler_configs`: map of handler source → config
63-
- `services`: list of service declarations (optional)
64-
- `handlers_to_initialize`: map of action name → handler names (optional)
65-
- Result: `{}` (empty object)
66-
67-
- `finecodeRunner/getInfo`
68-
- Arguments: none
69-
- Result: `{ "logFilePath": "/abs/path/to/runner.log" | null }`
70-
- Returns runtime information about the runner. Currently reports the path
71-
to the runner's log file, or `null` if logging to a file is not configured.
72-
73-
- `actions/run`
74-
- Arguments:
75-
1. `action_name` (string)
76-
2. `params` (object)
77-
3. `options` (object, optional)
78-
- Options (snake_case keys are expected):
79-
- `meta`: `{ "trigger": "user|system|unknown", "dev_env": "ide|cli|ai|precommit|ci", "orchestration_depth": int }`
80-
- `orchestration_depth`: cross-boundary hop counter, defaults to `0`. The ER propagates it unchanged via `RunActionMeta.orchestration_depth`.
81-
- `partial_result_token`: `int | string` (used to correlate `$/progress`)
82-
- `result_formats`: `["json", "string"]` (defaults to `["json"]`)
83-
- Result (success):
84-
```json
85-
{
86-
"status": "success",
87-
"result_by_format": "{\"json\": {\"...\": \"...\"}}",
88-
"return_code": 0
89-
}
90-
```
91-
- Result (streamed): used when `partial_result_token` was provided and all
92-
results were delivered via `$/progress` notifications. Following LSP convention,
93-
the final response is an explicit completion signal — `result_by_format` is
94-
intentionally empty. The WM treats this as a valid completion; an empty
95-
`result_by_format` with any other status is a protocol error.
96-
```json
97-
{
98-
"status": "streamed",
99-
"result_by_format": "{}",
100-
"return_code": 0
101-
}
102-
```
103-
- Result (stopped):
104-
```json
105-
{
106-
"status": "stopped",
107-
"result_by_format": "{\"json\": {\"...\": \"...\"}}",
108-
"return_code": 1
109-
}
110-
```
111-
- Result (error):
112-
```json
113-
{"error": "message"}
114-
```
115-
- Note: `result_by_format` is a JSON string (not a JSON object) due to
116-
LSP serialization constraints in the runner.
117-
118-
- `actions/getPayloadSchemas`
119-
- Arguments: none
120-
- Result: `{ action_name: JSON Schema fragment | null }`
121-
- Returns a payload schema for every action currently known to the runner.
122-
Each schema has `properties` (field name → JSON Schema type object) and
123-
`required` (list of field names without defaults).
124-
`null` means the action class could not be imported.
125-
126-
- `actions/mergeResults`
127-
- Arguments: `[action_name, results]`
128-
- Result: `{ "merged": ... }` or `{ "error": "..." }`
129-
130-
- `actions/reload`
131-
- Arguments: `[action_name]`
132-
- Result: `{}`
133-
134-
- `packages/resolvePath`
135-
- Arguments: `[package_name]`
136-
- Result: `{ "packagePath": "/abs/path/to/package" }`
56+
- `finecodeRunner/updateConfig`
57+
- Params: `{ "workingDir": string, "projectName": string, "projectDefPath": string, "config": object }`
58+
- Config shape (top-level):
59+
- `actions`: list of action objects (`name`, `handlers`, `source`, `config`)
60+
- `action_handler_configs`: map of handler source → config
61+
- `services`: list of service declarations (optional)
62+
- `handlers_to_initialize`: map of action name → handler names (optional)
63+
- Result: `{}` (empty object)
64+
65+
- `finecodeRunner/getInfo`
66+
- Params: `{}`
67+
- Result: `{ "logFilePath": "/abs/path/to/runner.log" | null }`
68+
- Returns runtime information about the runner. Currently reports the path
69+
to the runner's log file, or `null` if logging to a file is not configured.
70+
71+
- `actions/run`
72+
- Params: `{ "actionName": string, "params": object, "options": object | null }`
73+
- Options keys (camelCase):
74+
- `meta`: `{ "trigger": "user|system|unknown", "devEnv": "ide|cli|ai|git_hook|ci", "orchestrationDepth": int }`
75+
- `orchestrationDepth`: cross-boundary hop counter, defaults to `0`. The ER propagates it unchanged via `RunActionMeta.orchestration_depth`.
76+
- `partialResultToken`: `int | string` (used to correlate `$/progress`)
77+
- `resultFormats`: `["json", "string"]` (defaults to `["json"]`)
78+
- Result (success):
79+
```json
80+
{
81+
"status": "success",
82+
"result_by_format": "{\"json\": {\"...\": \"...\"}}",
83+
"return_code": 0
84+
}
85+
```
86+
- Result (streamed): used when `partialResultToken` was provided and all
87+
results were delivered via `$/progress` notifications. The final response
88+
is an explicit completion signal — `result_by_format` is intentionally empty.
89+
The WM treats this as a valid completion; an empty `result_by_format` with
90+
any other status is a protocol error.
91+
```json
92+
{
93+
"status": "streamed",
94+
"result_by_format": "{}",
95+
"return_code": 0
96+
}
97+
```
98+
- Result (stopped):
99+
```json
100+
{
101+
"status": "stopped",
102+
"result_by_format": "{\"json\": {\"...\": \"...\"}}",
103+
"return_code": 1
104+
}
105+
```
106+
- Result (error):
107+
```json
108+
{"error": "message"}
109+
```
110+
- Note: `result_by_format` is a JSON-encoded string (not a nested object) —
111+
the WM decodes it with `json.loads` after receiving the response.
112+
113+
- `actions/getPayloadSchemas`
114+
- Params: `{}`
115+
- Result: `{ action_name: JSON Schema fragment | null }`
116+
- Returns a payload schema for every action currently known to the runner.
117+
Each schema has `properties` (field name → JSON Schema type object) and
118+
`required` (list of field names without defaults).
119+
`null` means the action class could not be imported.
120+
121+
- `actions/mergeResults`
122+
- Params: `{ "actionName": string, "results": list }`
123+
- Result: `{ "merged": ... }` or `{ "error": "..." }`
124+
125+
- `actions/reload`
126+
- Params: `{ "actionName": string }`
127+
- Result: `{}`
128+
129+
- `finecodeRunner/resolveActionSources`
130+
- Params: `{}` (no params)
131+
- Result: sparse map of `{ "<configSource>": "<canonicalSource>", ... }` for actions
132+
whose declared config path differs from the fully qualified runtime path.
133+
Only entries where the two differ are included.
134+
Example: `{ "myext.LintAction": "myext.actions.lint.LintAction" }`
135+
- Called by the WM after `finecodeRunner/updateConfig` completes to store canonical
136+
sources on its `Action` domain objects before the runner is considered ready.
137+
The WM uses `canonical_source` as the primary identifier in all subsequent action
138+
lookups; `source` (from config) is the fallback for actions whose class could not
139+
be imported in this env.
140+
- Actions where `cls.__module__ + "." + cls.__qualname__ == config source` are
141+
omitted (no mapping needed — the config source is already canonical).
142+
143+
- `actions/resolveSource`
144+
- Params: `{ "source": string }` — an arbitrary import-path alias to resolve.
145+
- Result: `{ "canonicalSource": string }` — the fully qualified class path
146+
(`cls.__module__ + "." + cls.__qualname__`).
147+
- Raises a JSON-RPC error if the alias cannot be imported or resolved.
148+
- Used during action lookup when a caller provides an alias not already known
149+
from `finecodeRunner/resolveActionSources` (full ADR-0019 support).
150+
151+
- `packages/resolvePath`
152+
- Params: `{ "packageName": string }`
153+
- Result: `{ "packagePath": "/abs/path/to/package" }`
137154

138155
**Notifications**
139156

@@ -159,15 +176,21 @@ The protocol is LSP-shaped with a small set of custom commands.
159176

160177
- `finecode/runActionInProject`
161178
- Params:
162-
- `actionSource` (string): import path of the action class (e.g. `"myext.actions.lint.LintAction"`)
179+
- `actionSource` (string): **fully qualified** import path of the action class —
180+
`f"{cls.__module__}.{cls.__qualname__}"` (e.g. `"myext.actions.lint.LintAction"`).
181+
Must not be a re-exported alias such as `"myext.LintAction"`. The WM resolves
182+
the action name by matching against the canonical source reported by
183+
`finecodeRunner/resolveActionSources`; a re-exported path will not match and the
184+
request will fail.
163185
- `payload` (object): serialized action payload (`dataclasses.asdict`)
164186
- `meta` (object): `{ "trigger": string, "devEnv": string, "orchestrationDepth": int }`
165187
- Result: `{ "result": <json result object>, "returnCode": 0|1 }`
166188
- Runs the action at project scope (all env-runners of the ER's own project). WM enforces `OrchestrationPolicy.max_recursion_depth` before dispatching.
167189

168190
- `finecode/runActionInWorkspace`
169191
- Params:
170-
- `actionSource` (string): import path of the action class
192+
- `actionSource` (string): **fully qualified** import path of the action class —
193+
same constraint as `finecode/runActionInProject` above.
171194
- `payload` (object): serialized action payload
172195
- `meta` (object): `{ "trigger": string, "devEnv": string, "orchestrationDepth": int }`
173196
- `projectPaths` (list[string] | null): explicit POSIX project paths, or `null` for all projects that declare the action

0 commit comments

Comments
 (0)