blueprint-extractor-mcp 8.2.6 → 8.2.7
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.
- package/README.md +23 -5
- package/dist/active-editor-session.d.ts +0 -0
- package/dist/active-editor-session.js +0 -0
- package/dist/automation-controller.d.ts +0 -0
- package/dist/automation-controller.js +0 -0
- package/dist/catalogs/example-catalog.d.ts +0 -0
- package/dist/catalogs/example-catalog.js +0 -0
- package/dist/compactor.d.ts +0 -0
- package/dist/compactor.js +0 -0
- package/dist/editor-instance-registry.d.ts +0 -0
- package/dist/editor-instance-registry.js +0 -0
- package/dist/editor-instance-types.d.ts +0 -0
- package/dist/editor-instance-types.js +0 -0
- package/dist/execution/adapters/commandlet-adapter.d.ts +14 -2
- package/dist/execution/adapters/commandlet-adapter.js +145 -54
- package/dist/execution/adapters/editor-adapter.d.ts +0 -0
- package/dist/execution/adapters/editor-adapter.js +0 -0
- package/dist/execution/adapters/lazy-commandlet-adapter.d.ts +0 -0
- package/dist/execution/adapters/lazy-commandlet-adapter.js +0 -0
- package/dist/execution/adaptive-executor.d.ts +9 -0
- package/dist/execution/adaptive-executor.js +74 -9
- package/dist/execution/execution-adapter.d.ts +0 -0
- package/dist/execution/execution-adapter.js +0 -0
- package/dist/execution/execution-mode-detector.d.ts +0 -0
- package/dist/execution/execution-mode-detector.js +0 -0
- package/dist/execution/index.d.ts +0 -0
- package/dist/execution/index.js +0 -0
- package/dist/helpers/active-editor-utils.d.ts +0 -0
- package/dist/helpers/active-editor-utils.js +0 -0
- package/dist/helpers/alias-registration.d.ts +0 -0
- package/dist/helpers/alias-registration.js +0 -0
- package/dist/helpers/blueprint-dsl-parser.d.ts +0 -0
- package/dist/helpers/blueprint-dsl-parser.js +0 -0
- package/dist/helpers/blueprint-validation.d.ts +0 -0
- package/dist/helpers/blueprint-validation.js +0 -0
- package/dist/helpers/capture.d.ts +0 -0
- package/dist/helpers/capture.js +0 -0
- package/dist/helpers/commonui-button-style.d.ts +0 -0
- package/dist/helpers/commonui-button-style.js +0 -0
- package/dist/helpers/composite-patterns.d.ts +3 -0
- package/dist/helpers/composite-patterns.js +0 -0
- package/dist/helpers/formatting.d.ts +0 -0
- package/dist/helpers/formatting.js +0 -0
- package/dist/helpers/live-coding.d.ts +0 -0
- package/dist/helpers/live-coding.js +0 -0
- package/dist/helpers/material-dsl-parser.d.ts +0 -0
- package/dist/helpers/material-dsl-parser.js +0 -0
- package/dist/helpers/mutation-filter.d.ts +0 -0
- package/dist/helpers/mutation-filter.js +0 -0
- package/dist/helpers/next-step-hints.d.ts +0 -0
- package/dist/helpers/next-step-hints.js +0 -0
- package/dist/helpers/operation-deny-list.d.ts +0 -0
- package/dist/helpers/operation-deny-list.js +0 -0
- package/dist/helpers/package-metadata.d.ts +0 -0
- package/dist/helpers/package-metadata.js +0 -0
- package/dist/helpers/phantom-filter.d.ts +0 -0
- package/dist/helpers/phantom-filter.js +0 -0
- package/dist/helpers/project-resolution.d.ts +0 -0
- package/dist/helpers/project-resolution.js +0 -0
- package/dist/helpers/project-utils.d.ts +0 -0
- package/dist/helpers/project-utils.js +0 -0
- package/dist/helpers/property-path-parser.d.ts +0 -0
- package/dist/helpers/property-path-parser.js +0 -0
- package/dist/helpers/property-shorthand.d.ts +0 -0
- package/dist/helpers/property-shorthand.js +0 -0
- package/dist/helpers/response-summarizer.d.ts +0 -0
- package/dist/helpers/response-summarizer.js +0 -0
- package/dist/helpers/slot-presets.d.ts +0 -0
- package/dist/helpers/slot-presets.js +0 -0
- package/dist/helpers/subsystem.d.ts +0 -0
- package/dist/helpers/subsystem.js +0 -0
- package/dist/helpers/token-budget.d.ts +0 -0
- package/dist/helpers/token-budget.js +0 -0
- package/dist/helpers/tool-help.d.ts +0 -0
- package/dist/helpers/tool-help.js +0 -0
- package/dist/helpers/tool-registration.d.ts +0 -0
- package/dist/helpers/tool-registration.js +54 -6
- package/dist/helpers/tool-results.d.ts +0 -0
- package/dist/helpers/tool-results.js +0 -0
- package/dist/helpers/verification.d.ts +0 -0
- package/dist/helpers/verification.js +0 -0
- package/dist/helpers/widget-class-aliases.d.ts +0 -0
- package/dist/helpers/widget-class-aliases.js +0 -0
- package/dist/helpers/widget-diff-parser.d.ts +0 -0
- package/dist/helpers/widget-diff-parser.js +0 -0
- package/dist/helpers/widget-dsl-parser.d.ts +0 -0
- package/dist/helpers/widget-dsl-parser.js +0 -0
- package/dist/helpers/widget-recipe-formatter.d.ts +0 -0
- package/dist/helpers/widget-recipe-formatter.js +0 -0
- package/dist/helpers/widget-recipe-parser.d.ts +0 -0
- package/dist/helpers/widget-recipe-parser.js +0 -0
- package/dist/helpers/widget-utils.d.ts +0 -0
- package/dist/helpers/widget-utils.js +0 -0
- package/dist/helpers/workspace-project.d.ts +0 -0
- package/dist/helpers/workspace-project.js +0 -0
- package/dist/index.d.ts +0 -0
- package/dist/index.js +0 -0
- package/dist/project-controller.d.ts +0 -0
- package/dist/project-controller.js +0 -0
- package/dist/prompts/prompt-catalog.d.ts +0 -0
- package/dist/prompts/prompt-catalog.js +0 -0
- package/dist/register-server-resources.d.ts +0 -0
- package/dist/register-server-resources.js +0 -0
- package/dist/register-server-tools.d.ts +0 -0
- package/dist/register-server-tools.js +2 -0
- package/dist/resources/example-and-capture-resources.d.ts +0 -0
- package/dist/resources/example-and-capture-resources.js +0 -0
- package/dist/resources/static-doc-resources.d.ts +0 -0
- package/dist/resources/static-doc-resources.js +0 -0
- package/dist/schemas/tool-inputs.d.ts +138 -87
- package/dist/schemas/tool-inputs.js +0 -0
- package/dist/schemas/tool-results.d.ts +1098 -687
- package/dist/schemas/tool-results.js +6 -3
- package/dist/server-config.d.ts +9 -1
- package/dist/server-config.js +81 -68
- package/dist/server-factory.d.ts +0 -0
- package/dist/server-factory.js +0 -0
- package/dist/tool-context.d.ts +0 -0
- package/dist/tool-context.js +0 -0
- package/dist/tool-surface-manager.d.ts +0 -0
- package/dist/tool-surface-manager.js +0 -0
- package/dist/tools/analysis-tools.d.ts +0 -0
- package/dist/tools/analysis-tools.js +0 -0
- package/dist/tools/animation-authoring.d.ts +0 -0
- package/dist/tools/animation-authoring.js +0 -0
- package/dist/tools/automation-runs.d.ts +0 -0
- package/dist/tools/automation-runs.js +0 -0
- package/dist/tools/blueprint-authoring.d.ts +0 -0
- package/dist/tools/blueprint-authoring.js +0 -0
- package/dist/tools/commonui-button-style.d.ts +0 -0
- package/dist/tools/commonui-button-style.js +0 -0
- package/dist/tools/composite-tools.d.ts +0 -0
- package/dist/tools/composite-tools.js +0 -0
- package/dist/tools/composite-workflows.d.ts +0 -0
- package/dist/tools/composite-workflows.js +160 -46
- package/dist/tools/data-and-input.d.ts +0 -0
- package/dist/tools/data-and-input.js +0 -0
- package/dist/tools/extraction.d.ts +0 -0
- package/dist/tools/extraction.js +0 -0
- package/dist/tools/import-jobs.d.ts +0 -0
- package/dist/tools/import-jobs.js +0 -0
- package/dist/tools/material-authoring.d.ts +0 -0
- package/dist/tools/material-authoring.js +0 -0
- package/dist/tools/material-instance.d.ts +0 -0
- package/dist/tools/material-instance.js +0 -0
- package/dist/tools/project-control.d.ts +0 -0
- package/dist/tools/project-control.js +0 -0
- package/dist/tools/project-intelligence.d.ts +0 -0
- package/dist/tools/project-intelligence.js +0 -0
- package/dist/tools/recipe-tools.d.ts +0 -0
- package/dist/tools/recipe-tools.js +4 -2
- package/dist/tools/schema-and-ai-authoring.d.ts +0 -0
- package/dist/tools/schema-and-ai-authoring.js +0 -0
- package/dist/tools/tables-and-curves.d.ts +0 -0
- package/dist/tools/tables-and-curves.js +0 -0
- package/dist/tools/utility-tools.d.ts +3 -1
- package/dist/tools/utility-tools.js +2 -1
- package/dist/tools/widget-animation-authoring.d.ts +0 -0
- package/dist/tools/widget-animation-authoring.js +0 -0
- package/dist/tools/widget-extraction.d.ts +0 -0
- package/dist/tools/widget-extraction.js +0 -0
- package/dist/tools/widget-structure.d.ts +0 -0
- package/dist/tools/widget-structure.js +0 -0
- package/dist/tools/widget-verification.d.ts +0 -0
- package/dist/tools/widget-verification.js +0 -0
- package/dist/tools/window-ui.d.ts +0 -0
- package/dist/tools/window-ui.js +0 -0
- package/dist/types.d.ts +0 -0
- package/dist/types.js +0 -0
- package/dist/ue-client.d.ts +0 -0
- package/dist/ue-client.js +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<h1 align="center">Blueprint Extractor MCP</h1>
|
|
3
3
|
<p align="center">
|
|
4
4
|
Give AI assistants full read/write access to Unreal Engine projects<br>
|
|
5
|
-
through a live editor
|
|
5
|
+
through a live editor or headless commandlet lane.
|
|
6
6
|
</p>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
## Overview
|
|
19
19
|
|
|
20
|
-
Blueprint Extractor MCP is a [Model Context Protocol](https://modelcontextprotocol.io) server that bridges AI coding assistants (Claude Code, Codex, OpenCode, etc.) to
|
|
20
|
+
Blueprint Extractor MCP is a [Model Context Protocol](https://modelcontextprotocol.io) server that bridges AI coding assistants (Claude Code, Codex, OpenCode, etc.) to Unreal Engine through two execution lanes:
|
|
21
|
+
|
|
22
|
+
- **Editor lane** via the Remote Control HTTP API for editor-bound and interactive workflows
|
|
23
|
+
- **Commandlet lane** for headless-safe extraction and authoring when no reachable editor session is available
|
|
21
24
|
|
|
22
25
|
For compatible tools, the server can also execute through a commandlet lane when no reachable editor session is available. When an editor is already running, editor execution stays preferred for editor-only flows and for save paths that need to avoid package-lock contention.
|
|
23
26
|
|
|
@@ -49,11 +52,13 @@ For compatible tools, the server can also execute through a commandlet lane when
|
|
|
49
52
|
|
|
50
53
|
### Prerequisites
|
|
51
54
|
|
|
52
|
-
You need
|
|
55
|
+
You need these prerequisites:
|
|
53
56
|
|
|
54
57
|
1. **Node.js 18+**
|
|
55
|
-
2. **
|
|
56
|
-
3.
|
|
58
|
+
2. **[BlueprintExtractor](https://github.com/SunGrow/ue-blueprint-extractor)** plugin installed in your project
|
|
59
|
+
3. One execution lane configured:
|
|
60
|
+
- **Editor lane**: Unreal Editor running with the **Remote Control API** plugin enabled
|
|
61
|
+
- **Commandlet lane**: resolvable `UE_ENGINE_ROOT` and `UE_PROJECT_PATH` so headless-safe tools can launch `UnrealEditor-Cmd`
|
|
57
62
|
|
|
58
63
|
### Run
|
|
59
64
|
|
|
@@ -63,6 +68,8 @@ npx blueprint-extractor-mcp
|
|
|
63
68
|
|
|
64
69
|
Connects to the editor at `127.0.0.1:30010` by default.
|
|
65
70
|
|
|
71
|
+
For headless-safe tools, also set `UE_ENGINE_ROOT` and `UE_PROJECT_PATH` so the MCP server can resolve the commandlet lane when no editor is reachable.
|
|
72
|
+
|
|
66
73
|
### Add to Your AI Client
|
|
67
74
|
|
|
68
75
|
<table>
|
|
@@ -84,6 +91,15 @@ codex mcp add --env UE_REMOTE_CONTROL_PORT=30010 \
|
|
|
84
91
|
blueprint-extractor -- npx -y blueprint-extractor-mcp@latest
|
|
85
92
|
```
|
|
86
93
|
|
|
94
|
+
```bash
|
|
95
|
+
# Optional for headless-safe tools when no editor is running
|
|
96
|
+
codex mcp add \
|
|
97
|
+
--env UE_REMOTE_CONTROL_PORT=30010 \
|
|
98
|
+
--env UE_ENGINE_ROOT="C:\\Program Files\\Epic Games\\UE_5.7" \
|
|
99
|
+
--env UE_PROJECT_PATH="D:\\Development\\V2\\CyberVolleyball6vs6.uproject" \
|
|
100
|
+
blueprint-extractor -- npx -y blueprint-extractor-mcp@latest
|
|
101
|
+
```
|
|
102
|
+
|
|
87
103
|
</td></tr>
|
|
88
104
|
<tr><td><b>OpenCode</b></td></tr>
|
|
89
105
|
<tr><td>
|
|
@@ -168,6 +184,8 @@ See [../docs/CURRENT_STATUS.md](../docs/CURRENT_STATUS.md) for the current valid
|
|
|
168
184
|
| `UE_BUILD_PLATFORM` | — | e.g. `Win64` |
|
|
169
185
|
| `UE_BUILD_CONFIGURATION` | — | e.g. `Development` |
|
|
170
186
|
|
|
187
|
+
`get_tool_help` now reports execution compatibility per tool, including whether the tool is editor-only or headless-safe.
|
|
188
|
+
|
|
171
189
|
`get_project_automation_context` surfaces the coarse editor-derived `engineRoot`, `projectFilePath`, `editorTarget`, and `isPlayingInEditor` state that project-control and verification flows use for fallback or guard logic.
|
|
172
190
|
|
|
173
191
|
`get_editor_context` is the separate read-only editor-state snapshot for selection, open asset editors, active level, and PIE summary. It stays session-bound and intentionally does not open assets, change focus, or switch viewports.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/compactor.d.ts
CHANGED
|
File without changes
|
package/dist/compactor.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -13,11 +13,16 @@ export interface CommandletAdapterOptions {
|
|
|
13
13
|
requestTimeoutMs?: number;
|
|
14
14
|
}
|
|
15
15
|
export declare class CommandletAdapter implements ExecutionAdapter {
|
|
16
|
+
private static readonly MAX_LOG_TAIL_LINES;
|
|
16
17
|
private options;
|
|
17
18
|
private process;
|
|
18
19
|
private requestId;
|
|
19
20
|
private pendingRequests;
|
|
20
|
-
private
|
|
21
|
+
private stdoutBuffer;
|
|
22
|
+
private stderrBuffer;
|
|
23
|
+
private recentLogs;
|
|
24
|
+
private startupPromise;
|
|
25
|
+
private startupState;
|
|
21
26
|
private readonly spawnProcess;
|
|
22
27
|
private readonly platform;
|
|
23
28
|
private readonly startupTimeoutMs;
|
|
@@ -29,5 +34,12 @@ export declare class CommandletAdapter implements ExecutionAdapter {
|
|
|
29
34
|
getMode(): 'commandlet';
|
|
30
35
|
getCapabilities(): ReadonlySet<ToolCapability>;
|
|
31
36
|
shutdown(): Promise<void>;
|
|
32
|
-
private
|
|
37
|
+
private spawnAndWaitForReady;
|
|
38
|
+
private processStdoutBuffer;
|
|
39
|
+
private processStderrBuffer;
|
|
40
|
+
private appendLogLine;
|
|
41
|
+
private withRecentLogs;
|
|
42
|
+
private resolveStartup;
|
|
43
|
+
private rejectStartup;
|
|
44
|
+
private handleProcessTermination;
|
|
33
45
|
}
|
|
@@ -8,11 +8,16 @@ import { resolveEditorExecutable } from '../../project-controller.js';
|
|
|
8
8
|
const STARTUP_TIMEOUT_MS = 30_000;
|
|
9
9
|
const REQUEST_TIMEOUT_MS = 60_000;
|
|
10
10
|
export class CommandletAdapter {
|
|
11
|
+
static MAX_LOG_TAIL_LINES = 100;
|
|
11
12
|
options;
|
|
12
13
|
process = null;
|
|
13
14
|
requestId = 0;
|
|
14
15
|
pendingRequests = new Map();
|
|
15
|
-
|
|
16
|
+
stdoutBuffer = '';
|
|
17
|
+
stderrBuffer = '';
|
|
18
|
+
recentLogs = [];
|
|
19
|
+
startupPromise = null;
|
|
20
|
+
startupState = null;
|
|
16
21
|
spawnProcess;
|
|
17
22
|
platform;
|
|
18
23
|
startupTimeoutMs;
|
|
@@ -25,60 +30,37 @@ export class CommandletAdapter {
|
|
|
25
30
|
this.requestTimeoutMs = options.requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
26
31
|
}
|
|
27
32
|
async initialize() {
|
|
28
|
-
if (this.process)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.process = this.spawnProcess(editorCmd, [
|
|
32
|
-
this.options.projectPath,
|
|
33
|
-
'-run=blueprintextractor',
|
|
34
|
-
'-stdin',
|
|
35
|
-
'-unattended',
|
|
36
|
-
'-nosplash',
|
|
37
|
-
'-nullrhi',
|
|
38
|
-
], {
|
|
39
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
40
|
-
});
|
|
41
|
-
this.process.stdout?.on('data', (data) => {
|
|
42
|
-
this.buffer += data.toString();
|
|
43
|
-
this.processBuffer();
|
|
44
|
-
});
|
|
45
|
-
this.process.on('exit', () => {
|
|
46
|
-
this.process = null;
|
|
47
|
-
// Reject all pending requests
|
|
48
|
-
for (const [, pending] of this.pendingRequests) {
|
|
49
|
-
clearTimeout(pending.timer);
|
|
50
|
-
pending.reject(new Error('Commandlet process exited'));
|
|
33
|
+
if (this.process && !this.process.killed) {
|
|
34
|
+
if (this.startupPromise) {
|
|
35
|
+
await this.startupPromise;
|
|
51
36
|
}
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.process?.stdout?.on('data', onData);
|
|
66
|
-
this.process?.on('error', (err) => {
|
|
67
|
-
clearTimeout(timer);
|
|
68
|
-
reject(err);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (this.startupPromise) {
|
|
40
|
+
await this.startupPromise;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.startupPromise = this.spawnAndWaitForReady();
|
|
44
|
+
try {
|
|
45
|
+
await this.startupPromise;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
this.startupPromise = null;
|
|
49
|
+
}
|
|
71
50
|
}
|
|
72
51
|
async execute(_subsystem, method, params) {
|
|
73
|
-
if (!this.process?.stdin) {
|
|
74
|
-
|
|
52
|
+
if (!this.process?.stdin || this.process.killed) {
|
|
53
|
+
await this.initialize();
|
|
54
|
+
}
|
|
55
|
+
if (!this.process?.stdin || this.process.killed) {
|
|
56
|
+
throw new Error(this.withRecentLogs('Commandlet process not running. Call initialize() first.'));
|
|
75
57
|
}
|
|
76
58
|
const id = ++this.requestId;
|
|
77
59
|
const request = JSON.stringify({ jsonrpc: '2.0', id, method, params });
|
|
78
60
|
return new Promise((resolve, reject) => {
|
|
79
61
|
const timer = setTimeout(() => {
|
|
80
62
|
this.pendingRequests.delete(id);
|
|
81
|
-
reject(new Error(`Commandlet request timed out after ${this.requestTimeoutMs}ms`));
|
|
63
|
+
reject(new Error(this.withRecentLogs(`Commandlet request timed out after ${this.requestTimeoutMs}ms`)));
|
|
82
64
|
}, this.requestTimeoutMs);
|
|
83
65
|
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
84
66
|
this.process.stdin.write(request + '\n');
|
|
@@ -94,21 +76,80 @@ export class CommandletAdapter {
|
|
|
94
76
|
return COMMANDLET_CAPABILITIES;
|
|
95
77
|
}
|
|
96
78
|
async shutdown() {
|
|
97
|
-
if (this.process) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
79
|
+
if (!this.process) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.process.stdin?.end();
|
|
83
|
+
this.process.kill();
|
|
84
|
+
this.process = null;
|
|
85
|
+
}
|
|
86
|
+
async spawnAndWaitForReady() {
|
|
87
|
+
this.stdoutBuffer = '';
|
|
88
|
+
this.stderrBuffer = '';
|
|
89
|
+
this.recentLogs = [];
|
|
90
|
+
const editorCmd = await resolveEditorExecutable(this.options.engineRoot, this.platform, 'commandlet');
|
|
91
|
+
const startupWaiter = new Promise((resolve, reject) => {
|
|
92
|
+
const timer = setTimeout(() => {
|
|
93
|
+
reject(new Error(this.withRecentLogs(`Commandlet startup timed out after ${this.startupTimeoutMs}ms`)));
|
|
94
|
+
}, this.startupTimeoutMs);
|
|
95
|
+
this.startupState = { resolve, reject, timer };
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
this.process = this.spawnProcess(editorCmd, [
|
|
99
|
+
this.options.projectPath,
|
|
100
|
+
'-run=blueprintextractor',
|
|
101
|
+
'-stdin',
|
|
102
|
+
'-unattended',
|
|
103
|
+
'-nosplash',
|
|
104
|
+
'-nullrhi',
|
|
105
|
+
], {
|
|
106
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
107
|
+
});
|
|
108
|
+
this.process.stdout?.on('data', (data) => {
|
|
109
|
+
this.stdoutBuffer += data.toString();
|
|
110
|
+
this.processStdoutBuffer();
|
|
111
|
+
});
|
|
112
|
+
this.process.stderr?.on('data', (data) => {
|
|
113
|
+
this.stderrBuffer += data.toString();
|
|
114
|
+
this.processStderrBuffer();
|
|
115
|
+
});
|
|
116
|
+
this.process.on('exit', (code, signal) => {
|
|
117
|
+
const suffix = code != null
|
|
118
|
+
? ` with code ${code}`
|
|
119
|
+
: signal
|
|
120
|
+
? ` with signal ${signal}`
|
|
121
|
+
: '';
|
|
122
|
+
this.handleProcessTermination(`Commandlet process exited${suffix}`);
|
|
123
|
+
});
|
|
124
|
+
this.process.on('error', (error) => {
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
this.handleProcessTermination(`Commandlet process error: ${message}`);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const startupError = error instanceof Error ? error : new Error(String(error));
|
|
131
|
+
this.rejectStartup(startupError);
|
|
132
|
+
throw startupError;
|
|
101
133
|
}
|
|
134
|
+
await startupWaiter;
|
|
102
135
|
}
|
|
103
|
-
|
|
104
|
-
const lines = this.
|
|
105
|
-
this.
|
|
136
|
+
processStdoutBuffer() {
|
|
137
|
+
const lines = this.stdoutBuffer.split('\n');
|
|
138
|
+
this.stdoutBuffer = lines.pop() ?? '';
|
|
106
139
|
for (const line of lines) {
|
|
107
140
|
const trimmed = line.trim();
|
|
108
141
|
if (!trimmed)
|
|
109
142
|
continue;
|
|
110
143
|
try {
|
|
111
144
|
const response = JSON.parse(trimmed);
|
|
145
|
+
if (response.jsonrpc === '2.0'
|
|
146
|
+
&& response.id === 0
|
|
147
|
+
&& typeof response.result === 'object'
|
|
148
|
+
&& response.result !== null
|
|
149
|
+
&& response.result.ready === true) {
|
|
150
|
+
this.resolveStartup();
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
112
153
|
if (response.id != null && this.pendingRequests.has(response.id)) {
|
|
113
154
|
const pending = this.pendingRequests.get(response.id);
|
|
114
155
|
this.pendingRequests.delete(response.id);
|
|
@@ -123,11 +164,61 @@ export class CommandletAdapter {
|
|
|
123
164
|
? response.result
|
|
124
165
|
: { result: response.result });
|
|
125
166
|
}
|
|
167
|
+
continue;
|
|
126
168
|
}
|
|
127
169
|
}
|
|
128
170
|
catch {
|
|
129
|
-
|
|
171
|
+
this.appendLogLine(trimmed);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
processStderrBuffer() {
|
|
176
|
+
const lines = this.stderrBuffer.split('\n');
|
|
177
|
+
this.stderrBuffer = lines.pop() ?? '';
|
|
178
|
+
for (const line of lines) {
|
|
179
|
+
const trimmed = line.trim();
|
|
180
|
+
if (!trimmed) {
|
|
181
|
+
continue;
|
|
130
182
|
}
|
|
183
|
+
this.appendLogLine(trimmed);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
appendLogLine(line) {
|
|
187
|
+
this.recentLogs.push(line);
|
|
188
|
+
if (this.recentLogs.length > CommandletAdapter.MAX_LOG_TAIL_LINES) {
|
|
189
|
+
this.recentLogs.splice(0, this.recentLogs.length - CommandletAdapter.MAX_LOG_TAIL_LINES);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
withRecentLogs(message) {
|
|
193
|
+
if (this.recentLogs.length === 0) {
|
|
194
|
+
return message;
|
|
195
|
+
}
|
|
196
|
+
return `${message}\nRecent commandlet logs:\n${this.recentLogs.join('\n')}`;
|
|
197
|
+
}
|
|
198
|
+
resolveStartup() {
|
|
199
|
+
if (!this.startupState) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
clearTimeout(this.startupState.timer);
|
|
203
|
+
this.startupState.resolve();
|
|
204
|
+
this.startupState = null;
|
|
205
|
+
}
|
|
206
|
+
rejectStartup(error) {
|
|
207
|
+
if (!this.startupState) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
clearTimeout(this.startupState.timer);
|
|
211
|
+
this.startupState.reject(error);
|
|
212
|
+
this.startupState = null;
|
|
213
|
+
}
|
|
214
|
+
handleProcessTermination(baseMessage) {
|
|
215
|
+
const error = new Error(this.withRecentLogs(baseMessage));
|
|
216
|
+
this.rejectStartup(error);
|
|
217
|
+
this.process = null;
|
|
218
|
+
for (const [, pending] of this.pendingRequests) {
|
|
219
|
+
clearTimeout(pending.timer);
|
|
220
|
+
pending.reject(error);
|
|
131
221
|
}
|
|
222
|
+
this.pendingRequests.clear();
|
|
132
223
|
}
|
|
133
224
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { ExecutionAdapter, ToolCapability, ExecutionMode } from './execution-adapter.js';
|
|
6
6
|
import { ExecutionModeDetector } from './execution-mode-detector.js';
|
|
7
7
|
export type ToolModeAnnotation = 'both' | 'editor_only' | 'read_only';
|
|
8
|
+
export type ExecutionLane = 'editor' | 'commandlet';
|
|
8
9
|
export type EditorFallbackCaller = (method: string, params: Record<string, unknown>, options?: {
|
|
9
10
|
timeoutMs?: number;
|
|
10
11
|
}) => Promise<Record<string, unknown>>;
|
|
@@ -22,13 +23,21 @@ export declare class AdaptiveExecutor {
|
|
|
22
23
|
* parameter to executeRouted).
|
|
23
24
|
*/
|
|
24
25
|
private _activeToolName;
|
|
26
|
+
private _activeToolExecutionMetadata;
|
|
25
27
|
constructor(editorAdapter: ExecutionAdapter, commandletAdapter: ExecutionAdapter | null, detector: ExecutionModeDetector);
|
|
26
28
|
setToolMode(toolName: string, mode: ToolModeAnnotation): void;
|
|
27
29
|
getToolMode(toolName: string): ToolModeAnnotation;
|
|
30
|
+
getSupportedExecutionModes(toolName: string): ExecutionLane[];
|
|
28
31
|
getCurrentMode(): Promise<ExecutionMode>;
|
|
29
32
|
/** Set the active tool name before a handler runs. Cleared after handler completes. */
|
|
30
33
|
setActiveToolName(name: string | null): void;
|
|
31
34
|
getActiveToolName(): string | null;
|
|
35
|
+
getActiveToolExecutionMetadata(): {
|
|
36
|
+
runtime_mode?: ExecutionLane;
|
|
37
|
+
supported_modes: ExecutionLane[];
|
|
38
|
+
fallback_used?: boolean;
|
|
39
|
+
} | null;
|
|
40
|
+
private recordToolExecution;
|
|
32
41
|
/**
|
|
33
42
|
* Route a callSubsystemJson-shaped call through the executor.
|
|
34
43
|
* For editor mode, delegates to the provided editorFallback (the original
|
|
@@ -36,6 +36,7 @@ export class AdaptiveExecutor {
|
|
|
36
36
|
* parameter to executeRouted).
|
|
37
37
|
*/
|
|
38
38
|
_activeToolName = null;
|
|
39
|
+
_activeToolExecutionMetadata = null;
|
|
39
40
|
constructor(editorAdapter, commandletAdapter, detector) {
|
|
40
41
|
this.editorAdapter = editorAdapter;
|
|
41
42
|
this.commandletAdapter = commandletAdapter;
|
|
@@ -47,16 +48,47 @@ export class AdaptiveExecutor {
|
|
|
47
48
|
getToolMode(toolName) {
|
|
48
49
|
return this.toolModes.get(toolName) ?? 'editor_only';
|
|
49
50
|
}
|
|
51
|
+
getSupportedExecutionModes(toolName) {
|
|
52
|
+
return this.getToolMode(toolName) === 'editor_only'
|
|
53
|
+
? ['editor']
|
|
54
|
+
: ['editor', 'commandlet'];
|
|
55
|
+
}
|
|
50
56
|
getCurrentMode() {
|
|
51
57
|
return this.detector.detect().then((r) => r.mode);
|
|
52
58
|
}
|
|
53
59
|
/** Set the active tool name before a handler runs. Cleared after handler completes. */
|
|
54
60
|
setActiveToolName(name) {
|
|
55
61
|
this._activeToolName = name;
|
|
62
|
+
this._activeToolExecutionMetadata = name
|
|
63
|
+
? { supportedModes: this.getSupportedExecutionModes(name) }
|
|
64
|
+
: null;
|
|
56
65
|
}
|
|
57
66
|
getActiveToolName() {
|
|
58
67
|
return this._activeToolName;
|
|
59
68
|
}
|
|
69
|
+
getActiveToolExecutionMetadata() {
|
|
70
|
+
if (!this._activeToolExecutionMetadata) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
runtime_mode: this._activeToolExecutionMetadata.mode,
|
|
75
|
+
supported_modes: [...this._activeToolExecutionMetadata.supportedModes],
|
|
76
|
+
...(typeof this._activeToolExecutionMetadata.fallbackUsed === 'boolean'
|
|
77
|
+
? { fallback_used: this._activeToolExecutionMetadata.fallbackUsed }
|
|
78
|
+
: {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
recordToolExecution(toolName, mode, fallbackUsed) {
|
|
82
|
+
if (this._activeToolName !== toolName) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const current = this._activeToolExecutionMetadata ?? {
|
|
86
|
+
supportedModes: this.getSupportedExecutionModes(toolName),
|
|
87
|
+
};
|
|
88
|
+
current.mode = mode;
|
|
89
|
+
current.fallbackUsed = current.fallbackUsed === true ? true : fallbackUsed;
|
|
90
|
+
this._activeToolExecutionMetadata = current;
|
|
91
|
+
}
|
|
60
92
|
/**
|
|
61
93
|
* Route a callSubsystemJson-shaped call through the executor.
|
|
62
94
|
* For editor mode, delegates to the provided editorFallback (the original
|
|
@@ -89,15 +121,26 @@ export class AdaptiveExecutor {
|
|
|
89
121
|
throw error;
|
|
90
122
|
}
|
|
91
123
|
this.detector.invalidateCache();
|
|
92
|
-
|
|
124
|
+
try {
|
|
125
|
+
const result = await this.commandletAdapter.execute('BlueprintExtractor', method, params);
|
|
126
|
+
this.recordToolExecution(toolName, 'commandlet', true);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
catch (fallbackError) {
|
|
130
|
+
this.recordToolExecution(toolName, 'commandlet', true);
|
|
131
|
+
throw fallbackError;
|
|
132
|
+
}
|
|
93
133
|
};
|
|
94
134
|
// Editor mode keeps the existing direct path, with commandlet fallback for
|
|
95
135
|
// compatible tools when the editor call fails.
|
|
96
136
|
if (detection.mode === 'editor') {
|
|
97
137
|
try {
|
|
98
|
-
|
|
138
|
+
const result = await editorFallback(method, params, options);
|
|
139
|
+
this.recordToolExecution(toolName, 'editor', false);
|
|
140
|
+
return result;
|
|
99
141
|
}
|
|
100
142
|
catch (error) {
|
|
143
|
+
this.recordToolExecution(toolName, 'editor', false);
|
|
101
144
|
if (toolMode === 'editor_only') {
|
|
102
145
|
throw error;
|
|
103
146
|
}
|
|
@@ -118,7 +161,9 @@ export class AdaptiveExecutor {
|
|
|
118
161
|
const editorAvailable = await this.editorAdapter.isAvailable();
|
|
119
162
|
if (editorAvailable) {
|
|
120
163
|
this.detector.invalidateCache();
|
|
121
|
-
|
|
164
|
+
const result = await editorFallback(method, params, options);
|
|
165
|
+
this.recordToolExecution(toolName, 'editor', true);
|
|
166
|
+
return result;
|
|
122
167
|
}
|
|
123
168
|
}
|
|
124
169
|
catch {
|
|
@@ -126,15 +171,20 @@ export class AdaptiveExecutor {
|
|
|
126
171
|
}
|
|
127
172
|
}
|
|
128
173
|
try {
|
|
129
|
-
|
|
174
|
+
const result = await this.commandletAdapter.execute('BlueprintExtractor', method, params);
|
|
175
|
+
this.recordToolExecution(toolName, 'commandlet', false);
|
|
176
|
+
return result;
|
|
130
177
|
}
|
|
131
178
|
catch (error) {
|
|
179
|
+
this.recordToolExecution(toolName, 'commandlet', false);
|
|
132
180
|
if (toolMode !== 'read_only' && shouldFallbackToEditorOnLock(error)) {
|
|
133
181
|
try {
|
|
134
182
|
const editorAvailable = await this.editorAdapter.isAvailable();
|
|
135
183
|
if (editorAvailable) {
|
|
136
184
|
this.detector.invalidateCache();
|
|
137
|
-
|
|
185
|
+
const result = await editorFallback(method, params, options);
|
|
186
|
+
this.recordToolExecution(toolName, 'editor', true);
|
|
187
|
+
return result;
|
|
138
188
|
}
|
|
139
189
|
}
|
|
140
190
|
catch {
|
|
@@ -157,7 +207,15 @@ export class AdaptiveExecutor {
|
|
|
157
207
|
? 'write_simple'
|
|
158
208
|
: 'write_complex';
|
|
159
209
|
if (detection.mode === 'editor') {
|
|
160
|
-
|
|
210
|
+
try {
|
|
211
|
+
const result = await this.editorAdapter.execute(subsystem, method, params);
|
|
212
|
+
this.recordToolExecution(toolName, 'editor', false);
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
this.recordToolExecution(toolName, 'editor', false);
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
161
219
|
}
|
|
162
220
|
// Try commandlet for compatible tools
|
|
163
221
|
if (detection.mode === 'commandlet' && this.commandletAdapter) {
|
|
@@ -173,7 +231,9 @@ export class AdaptiveExecutor {
|
|
|
173
231
|
const editorAvailable = await this.editorAdapter.isAvailable();
|
|
174
232
|
if (editorAvailable) {
|
|
175
233
|
this.detector.invalidateCache();
|
|
176
|
-
|
|
234
|
+
const result = await this.editorAdapter.execute(subsystem, method, params);
|
|
235
|
+
this.recordToolExecution(toolName, 'editor', true);
|
|
236
|
+
return result;
|
|
177
237
|
}
|
|
178
238
|
}
|
|
179
239
|
catch {
|
|
@@ -181,15 +241,20 @@ export class AdaptiveExecutor {
|
|
|
181
241
|
}
|
|
182
242
|
}
|
|
183
243
|
try {
|
|
184
|
-
|
|
244
|
+
const result = await this.commandletAdapter.execute(subsystem, method, params);
|
|
245
|
+
this.recordToolExecution(toolName, 'commandlet', false);
|
|
246
|
+
return result;
|
|
185
247
|
}
|
|
186
248
|
catch (error) {
|
|
249
|
+
this.recordToolExecution(toolName, 'commandlet', false);
|
|
187
250
|
if (toolMode !== 'read_only' && shouldFallbackToEditorOnLock(error)) {
|
|
188
251
|
try {
|
|
189
252
|
const editorAvailable = await this.editorAdapter.isAvailable();
|
|
190
253
|
if (editorAvailable) {
|
|
191
254
|
this.detector.invalidateCache();
|
|
192
|
-
|
|
255
|
+
const result = await this.editorAdapter.execute(subsystem, method, params);
|
|
256
|
+
this.recordToolExecution(toolName, 'editor', true);
|
|
257
|
+
return result;
|
|
193
258
|
}
|
|
194
259
|
}
|
|
195
260
|
catch {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/execution/index.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/helpers/capture.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -24,6 +24,9 @@ export interface CompositeToolResult {
|
|
|
24
24
|
task_support: 'optional' | 'required' | 'forbidden';
|
|
25
25
|
status?: string;
|
|
26
26
|
progress_message?: string;
|
|
27
|
+
runtime_mode?: 'editor' | 'commandlet';
|
|
28
|
+
supported_modes?: Array<'editor' | 'commandlet'>;
|
|
29
|
+
fallback_used?: boolean;
|
|
27
30
|
};
|
|
28
31
|
}
|
|
29
32
|
type Result<T, E> = {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|