22
33This document describes the communication protocol between the FineCode Workspace
44Manager (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
20231 . WM starts the ER process (per project + env).
21242 . WM sends ` initialize ` and waits for the ER response.
22253 . WM sends ` initialized ` .
23264 . 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