@godot-mcp/control-plane 0.0.0 → 0.1.0
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/LICENSE +21 -0
- package/README.md +105 -2
- package/dist/audit/jsonlAuditSink.d.ts +33 -0
- package/dist/audit/jsonlAuditSink.d.ts.map +1 -0
- package/dist/audit/jsonlAuditSink.js +57 -0
- package/dist/audit/jsonlAuditSink.js.map +1 -0
- package/dist/audit/redact.d.ts +2 -0
- package/dist/audit/redact.d.ts.map +1 -0
- package/dist/audit/redact.js +44 -0
- package/dist/audit/redact.js.map +1 -0
- package/dist/auth/pairingDescriptor.d.ts +44 -0
- package/dist/auth/pairingDescriptor.d.ts.map +1 -0
- package/dist/auth/pairingDescriptor.js +91 -0
- package/dist/auth/pairingDescriptor.js.map +1 -0
- package/dist/auth/runtimeDirectory.d.ts +3 -0
- package/dist/auth/runtimeDirectory.d.ts.map +1 -0
- package/dist/auth/runtimeDirectory.js +30 -0
- package/dist/auth/runtimeDirectory.js.map +1 -0
- package/dist/auth/sessionCrypto.d.ts +18 -0
- package/dist/auth/sessionCrypto.d.ts.map +1 -0
- package/dist/auth/sessionCrypto.js +118 -0
- package/dist/auth/sessionCrypto.js.map +1 -0
- package/dist/editor/editorMutationService.d.ts +21 -0
- package/dist/editor/editorMutationService.d.ts.map +1 -0
- package/dist/editor/editorMutationService.js +120 -0
- package/dist/editor/editorMutationService.js.map +1 -0
- package/dist/editor/mutationLedger.d.ts +30 -0
- package/dist/editor/mutationLedger.d.ts.map +1 -0
- package/dist/editor/mutationLedger.js +133 -0
- package/dist/editor/mutationLedger.js.map +1 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +23 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence/evidenceStore.d.ts +63 -0
- package/dist/evidence/evidenceStore.d.ts.map +1 -0
- package/dist/evidence/evidenceStore.js +350 -0
- package/dist/evidence/evidenceStore.js.map +1 -0
- package/dist/extensions/extensionRegistry.d.ts +33 -0
- package/dist/extensions/extensionRegistry.d.ts.map +1 -0
- package/dist/extensions/extensionRegistry.js +37 -0
- package/dist/extensions/extensionRegistry.js.map +1 -0
- package/dist/help/coreHelp.d.ts +10 -0
- package/dist/help/coreHelp.d.ts.map +1 -0
- package/dist/help/coreHelp.js +82 -0
- package/dist/help/coreHelp.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/policy/authorize.d.ts +5 -0
- package/dist/policy/authorize.d.ts.map +1 -0
- package/dist/policy/authorize.js +19 -0
- package/dist/policy/authorize.js.map +1 -0
- package/dist/policy/capabilities.d.ts +36 -0
- package/dist/policy/capabilities.d.ts.map +1 -0
- package/dist/policy/capabilities.js +129 -0
- package/dist/policy/capabilities.js.map +1 -0
- package/dist/project/artifactStore.d.ts +32 -0
- package/dist/project/artifactStore.d.ts.map +1 -0
- package/dist/project/artifactStore.js +172 -0
- package/dist/project/artifactStore.js.map +1 -0
- package/dist/project/pathPolicy.d.ts +4 -0
- package/dist/project/pathPolicy.d.ts.map +1 -0
- package/dist/project/pathPolicy.js +74 -0
- package/dist/project/pathPolicy.js.map +1 -0
- package/dist/project/projectConfig.d.ts +10 -0
- package/dist/project/projectConfig.d.ts.map +1 -0
- package/dist/project/projectConfig.js +63 -0
- package/dist/project/projectConfig.js.map +1 -0
- package/dist/project/projectIdentity.d.ts +8 -0
- package/dist/project/projectIdentity.d.ts.map +1 -0
- package/dist/project/projectIdentity.js +58 -0
- package/dist/project/projectIdentity.js.map +1 -0
- package/dist/project/projectJobJournal.d.ts +49 -0
- package/dist/project/projectJobJournal.d.ts.map +1 -0
- package/dist/project/projectJobJournal.js +67 -0
- package/dist/project/projectJobJournal.js.map +1 -0
- package/dist/project/projectJobService.d.ts +52 -0
- package/dist/project/projectJobService.d.ts.map +1 -0
- package/dist/project/projectJobService.js +289 -0
- package/dist/project/projectJobService.js.map +1 -0
- package/dist/project/projectMutationService.d.ts +40 -0
- package/dist/project/projectMutationService.d.ts.map +1 -0
- package/dist/project/projectMutationService.js +145 -0
- package/dist/project/projectMutationService.js.map +1 -0
- package/dist/project/projectPreflight.d.ts +7 -0
- package/dist/project/projectPreflight.d.ts.map +1 -0
- package/dist/project/projectPreflight.js +50 -0
- package/dist/project/projectPreflight.js.map +1 -0
- package/dist/project/projectProcess.d.ts +40 -0
- package/dist/project/projectProcess.d.ts.map +1 -0
- package/dist/project/projectProcess.js +196 -0
- package/dist/project/projectProcess.js.map +1 -0
- package/dist/project/projectService.d.ts +20 -0
- package/dist/project/projectService.d.ts.map +1 -0
- package/dist/project/projectService.js +35 -0
- package/dist/project/projectService.js.map +1 -0
- package/dist/project/secureJournalFile.d.ts +3 -0
- package/dist/project/secureJournalFile.d.ts.map +1 -0
- package/dist/project/secureJournalFile.js +56 -0
- package/dist/project/secureJournalFile.js.map +1 -0
- package/dist/runtime/debugTokenStore.d.ts +41 -0
- package/dist/runtime/debugTokenStore.d.ts.map +1 -0
- package/dist/runtime/debugTokenStore.js +105 -0
- package/dist/runtime/debugTokenStore.js.map +1 -0
- package/dist/runtime/debuggerClient.d.ts +14 -0
- package/dist/runtime/debuggerClient.d.ts.map +1 -0
- package/dist/runtime/debuggerClient.js +21 -0
- package/dist/runtime/debuggerClient.js.map +1 -0
- package/dist/runtime/inputReceipt.d.ts +15 -0
- package/dist/runtime/inputReceipt.d.ts.map +1 -0
- package/dist/runtime/inputReceipt.js +36 -0
- package/dist/runtime/inputReceipt.js.map +1 -0
- package/dist/runtime/runtimeDescriptor.d.ts +50 -0
- package/dist/runtime/runtimeDescriptor.d.ts.map +1 -0
- package/dist/runtime/runtimeDescriptor.js +175 -0
- package/dist/runtime/runtimeDescriptor.js.map +1 -0
- package/dist/runtime/runtimeProcess.d.ts +39 -0
- package/dist/runtime/runtimeProcess.d.ts.map +1 -0
- package/dist/runtime/runtimeProcess.js +180 -0
- package/dist/runtime/runtimeProcess.js.map +1 -0
- package/dist/runtime/runtimeService.d.ts +130 -0
- package/dist/runtime/runtimeService.d.ts.map +1 -0
- package/dist/runtime/runtimeService.js +919 -0
- package/dist/runtime/runtimeService.js.map +1 -0
- package/dist/session/sessionService.d.ts +52 -0
- package/dist/session/sessionService.d.ts.map +1 -0
- package/dist/session/sessionService.js +72 -0
- package/dist/session/sessionService.js.map +1 -0
- package/dist/unsafe/unsafeFixtureAuthority.d.ts +27 -0
- package/dist/unsafe/unsafeFixtureAuthority.d.ts.map +1 -0
- package/dist/unsafe/unsafeFixtureAuthority.js +130 -0
- package/dist/unsafe/unsafeFixtureAuthority.js.map +1 -0
- package/dist/unsafe/unsafeFixtureProcess.d.ts +30 -0
- package/dist/unsafe/unsafeFixtureProcess.d.ts.map +1 -0
- package/dist/unsafe/unsafeFixtureProcess.js +119 -0
- package/dist/unsafe/unsafeFixtureProcess.js.map +1 -0
- package/dist/unsafe/unsafeFixtureService.d.ts +41 -0
- package/dist/unsafe/unsafeFixtureService.d.ts.map +1 -0
- package/dist/unsafe/unsafeFixtureService.js +162 -0
- package/dist/unsafe/unsafeFixtureService.js.map +1 -0
- package/dist/visual/pngComparison.d.ts +12 -0
- package/dist/visual/pngComparison.d.ts.map +1 -0
- package/dist/visual/pngComparison.js +136 -0
- package/dist/visual/pngComparison.js.map +1 -0
- package/dist/visual/scenarioService.d.ts +50 -0
- package/dist/visual/scenarioService.d.ts.map +1 -0
- package/dist/visual/scenarioService.js +421 -0
- package/dist/visual/scenarioService.js.map +1 -0
- package/dist/visual/visualService.d.ts +27 -0
- package/dist/visual/visualService.d.ts.map +1 -0
- package/dist/visual/visualService.js +70 -0
- package/dist/visual/visualService.js.map +1 -0
- package/package.json +26 -6
- package/index.js +0 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { canonicalJson, RuntimeCaptureFrameMetadataSchema, RuntimeLaunchPinsSchema, ScenarioDeclarationSchema, ScenarioJobReceiptSchema, ScenarioReportSchema, VisualComparisonResultSchema, } from "@godot-mcp/protocol";
|
|
3
|
+
import { GodotMcpException } from "../errors.js";
|
|
4
|
+
import { comparePng } from "./pngComparison.js";
|
|
5
|
+
function scenarioError(code, message, evidence = []) {
|
|
6
|
+
const error = new GodotMcpException({
|
|
7
|
+
code,
|
|
8
|
+
message,
|
|
9
|
+
retryable: false,
|
|
10
|
+
correlationId: randomUUID(),
|
|
11
|
+
partialEffects: false,
|
|
12
|
+
rollback: "not_needed",
|
|
13
|
+
});
|
|
14
|
+
return evidence.length === 0 ? error : Object.assign(error, { evidence: [...evidence] });
|
|
15
|
+
}
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function errorCode(error) {
|
|
20
|
+
return isRecord(error) && typeof error.code === "string" ? error.code : "GODOT_RUNTIME_ERROR";
|
|
21
|
+
}
|
|
22
|
+
function errorEvidence(error) {
|
|
23
|
+
if (!isRecord(error) || !Array.isArray(error.evidence))
|
|
24
|
+
return [];
|
|
25
|
+
return error.evidence.filter((entry) => typeof entry === "string" && entry.startsWith("godot-mcp://evidence/"));
|
|
26
|
+
}
|
|
27
|
+
function rethrowIfInterrupted(error) {
|
|
28
|
+
if (errorCode(error) === "CANCELLED" || errorCode(error) === "TIMEOUT")
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
function digest(value) {
|
|
32
|
+
return createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
33
|
+
}
|
|
34
|
+
function wildcardMatches(value, pattern) {
|
|
35
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*").replace(/\\\?/g, ".");
|
|
36
|
+
return new RegExp(`^${escaped}$`, "u").test(value);
|
|
37
|
+
}
|
|
38
|
+
export class ScenarioService {
|
|
39
|
+
dependencies;
|
|
40
|
+
job;
|
|
41
|
+
now;
|
|
42
|
+
constructor(dependencies) {
|
|
43
|
+
this.dependencies = dependencies;
|
|
44
|
+
this.now = dependencies.now ?? (() => Date.now());
|
|
45
|
+
}
|
|
46
|
+
hasActiveJob() {
|
|
47
|
+
return this.job !== undefined && (this.job.state === "queued" || this.job.state === "running");
|
|
48
|
+
}
|
|
49
|
+
start(input) {
|
|
50
|
+
if (this.job && (this.job.state === "queued" || this.job.state === "running")) {
|
|
51
|
+
throw scenarioError("CONFLICT", "A visual scenario job is already active");
|
|
52
|
+
}
|
|
53
|
+
const sessionId = this.dependencies.sessionId();
|
|
54
|
+
if (!sessionId)
|
|
55
|
+
throw scenarioError("NOT_ATTACHED", "Godot editor addon is not attached");
|
|
56
|
+
const scenario = ScenarioDeclarationSchema.parse(input);
|
|
57
|
+
const token = `vsj_${randomBytes(32).toString("base64url")}`;
|
|
58
|
+
const startedAtMs = Math.floor(this.now());
|
|
59
|
+
const job = {
|
|
60
|
+
token,
|
|
61
|
+
sessionId,
|
|
62
|
+
scenario,
|
|
63
|
+
state: "queued",
|
|
64
|
+
completedSteps: 0,
|
|
65
|
+
controller: new AbortController(),
|
|
66
|
+
startedAtMs,
|
|
67
|
+
deadlineAtMs: startedAtMs + scenario.deadlineMs,
|
|
68
|
+
};
|
|
69
|
+
this.job = job;
|
|
70
|
+
queueMicrotask(() => void this.run(job));
|
|
71
|
+
return this.receipt(job);
|
|
72
|
+
}
|
|
73
|
+
status(jobToken) {
|
|
74
|
+
return this.receipt(this.requireJob(jobToken));
|
|
75
|
+
}
|
|
76
|
+
cancel(jobToken) {
|
|
77
|
+
const job = this.requireJob(jobToken);
|
|
78
|
+
if (job.state === "queued" || job.state === "running") {
|
|
79
|
+
job.controller.abort(scenarioError("CANCELLED", "Visual scenario was cancelled"));
|
|
80
|
+
}
|
|
81
|
+
return this.receipt(job);
|
|
82
|
+
}
|
|
83
|
+
result(jobToken) {
|
|
84
|
+
const job = this.requireJob(jobToken);
|
|
85
|
+
if (!job.report)
|
|
86
|
+
throw scenarioError("CONFLICT", "Visual scenario result is not terminal");
|
|
87
|
+
return ScenarioReportSchema.parse(job.report);
|
|
88
|
+
}
|
|
89
|
+
async close() {
|
|
90
|
+
const job = this.job;
|
|
91
|
+
if (!job || !["queued", "running"].includes(job.state))
|
|
92
|
+
return;
|
|
93
|
+
job.controller.abort(scenarioError("CANCELLED", "Visual scenario service closed"));
|
|
94
|
+
for (let attempt = 0; attempt < 200 && !job.report; attempt += 1) {
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
requireJob(jobToken) {
|
|
99
|
+
const job = this.job;
|
|
100
|
+
if (!job || job.token !== jobToken || this.dependencies.sessionId() !== job.sessionId) {
|
|
101
|
+
throw scenarioError("STALE_HANDLE", "Visual scenario job token is stale or belongs to another session");
|
|
102
|
+
}
|
|
103
|
+
return job;
|
|
104
|
+
}
|
|
105
|
+
receipt(job) {
|
|
106
|
+
return ScenarioJobReceiptSchema.parse({
|
|
107
|
+
jobToken: job.token,
|
|
108
|
+
state: job.state,
|
|
109
|
+
completedSteps: job.completedSteps,
|
|
110
|
+
totalSteps: job.scenario.steps.length,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async run(job) {
|
|
114
|
+
if (this.job !== job)
|
|
115
|
+
return;
|
|
116
|
+
job.state = "running";
|
|
117
|
+
let handle;
|
|
118
|
+
let observedGodotVersion;
|
|
119
|
+
let observedPins;
|
|
120
|
+
let paused = false;
|
|
121
|
+
let terminalState = "failed";
|
|
122
|
+
let failedStepIndex = null;
|
|
123
|
+
let cleanup = "not_needed";
|
|
124
|
+
const stepReceipts = [];
|
|
125
|
+
const captures = new Map();
|
|
126
|
+
try {
|
|
127
|
+
this.assertJobActive(job);
|
|
128
|
+
const launchPromise = this.dependencies.runtime.launch({
|
|
129
|
+
scenePath: job.scenario.scenePath,
|
|
130
|
+
startupTimeoutMs: job.scenario.startupTimeoutMs,
|
|
131
|
+
pins: job.scenario.pins,
|
|
132
|
+
});
|
|
133
|
+
const adoptLaunch = (launched) => {
|
|
134
|
+
handle = launched.handle;
|
|
135
|
+
if (isRecord(launched.root) && typeof launched.root.godotVersion === "string")
|
|
136
|
+
observedGodotVersion = launched.root.godotVersion;
|
|
137
|
+
if (isRecord(launched.root)) {
|
|
138
|
+
const parsedObservedPins = RuntimeLaunchPinsSchema.safeParse(launched.root.observedPins);
|
|
139
|
+
if (parsedObservedPins.success)
|
|
140
|
+
observedPins = parsedObservedPins.data;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
adoptLaunch(await this.boundary(job, launchPromise));
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (errorCode(error) === "CANCELLED" || errorCode(error) === "TIMEOUT") {
|
|
148
|
+
try {
|
|
149
|
+
adoptLaunch(await launchPromise);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Runtime launch owns cleanup when it rejects before yielding a handle.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
const activeHandle = handle;
|
|
158
|
+
if (!activeHandle)
|
|
159
|
+
throw scenarioError("GODOT_RUNTIME_ERROR", "Runtime launch did not yield a handle");
|
|
160
|
+
for (const [index, step] of job.scenario.steps.entries()) {
|
|
161
|
+
const startedMonotonicMs = Math.max(0, Math.floor(this.now() - job.startedAtMs));
|
|
162
|
+
try {
|
|
163
|
+
const outcome = await this.runStep(job, activeHandle, step, captures, paused);
|
|
164
|
+
paused = outcome.paused;
|
|
165
|
+
const finishedMonotonicMs = Math.max(startedMonotonicMs, Math.floor(this.now() - job.startedAtMs));
|
|
166
|
+
stepReceipts.push({
|
|
167
|
+
index,
|
|
168
|
+
kind: step.kind,
|
|
169
|
+
state: "completed",
|
|
170
|
+
startedMonotonicMs,
|
|
171
|
+
finishedMonotonicMs,
|
|
172
|
+
summary: outcome.summary,
|
|
173
|
+
evidence: outcome.evidence,
|
|
174
|
+
});
|
|
175
|
+
job.completedSteps += 1;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
failedStepIndex = index;
|
|
179
|
+
const cancelled = errorCode(error) === "CANCELLED";
|
|
180
|
+
const finishedMonotonicMs = Math.max(startedMonotonicMs, Math.floor(this.now() - job.startedAtMs));
|
|
181
|
+
stepReceipts.push({
|
|
182
|
+
index,
|
|
183
|
+
kind: step.kind,
|
|
184
|
+
state: cancelled ? "cancelled" : "failed",
|
|
185
|
+
startedMonotonicMs,
|
|
186
|
+
finishedMonotonicMs,
|
|
187
|
+
summary: { errorCode: errorCode(error) },
|
|
188
|
+
evidence: errorEvidence(error),
|
|
189
|
+
});
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
terminalState = "completed";
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
terminalState = errorCode(error) === "CANCELLED" ? "cancelled" : "failed";
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
if (handle) {
|
|
200
|
+
try {
|
|
201
|
+
await this.dependencies.runtime.execute({ operation: "stop", handle });
|
|
202
|
+
cleanup = "succeeded";
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
cleanup = "failed";
|
|
206
|
+
terminalState = "failed";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const reportWithoutDigest = {
|
|
210
|
+
schemaVersion: 1,
|
|
211
|
+
comparisonContractVersion: 1,
|
|
212
|
+
jobToken: job.token,
|
|
213
|
+
scenarioName: job.scenario.name,
|
|
214
|
+
projectId: this.dependencies.projectId,
|
|
215
|
+
scenePath: job.scenario.scenePath,
|
|
216
|
+
...(handle ? { handle } : {}),
|
|
217
|
+
pins: job.scenario.pins,
|
|
218
|
+
...(observedGodotVersion ? { observedGodotVersion } : {}),
|
|
219
|
+
...(observedPins ? { observedPins } : {}),
|
|
220
|
+
state: terminalState,
|
|
221
|
+
failedStepIndex,
|
|
222
|
+
steps: stepReceipts,
|
|
223
|
+
durationMs: Math.min(job.scenario.deadlineMs, Math.max(0, Math.floor(this.now() - job.startedAtMs))),
|
|
224
|
+
cleanup,
|
|
225
|
+
};
|
|
226
|
+
let report = ScenarioReportSchema.parse({ ...reportWithoutDigest, reportSha256: digest(reportWithoutDigest) });
|
|
227
|
+
try {
|
|
228
|
+
const evidence = await this.dependencies.evidence.putJson(job.sessionId, report, { kind: "scenario_report" });
|
|
229
|
+
report = ScenarioReportSchema.parse({ ...report, reportObservationUri: evidence.observationUri });
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
if (report.state === "completed") {
|
|
233
|
+
const failedReportWithoutDigest = { ...reportWithoutDigest, state: "failed" };
|
|
234
|
+
report = ScenarioReportSchema.parse({
|
|
235
|
+
...failedReportWithoutDigest,
|
|
236
|
+
reportSha256: digest(failedReportWithoutDigest),
|
|
237
|
+
});
|
|
238
|
+
terminalState = "failed";
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
job.report = report;
|
|
242
|
+
job.state = terminalState;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async runStep(job, handle, step, captures, paused) {
|
|
246
|
+
this.assertJobActive(job);
|
|
247
|
+
switch (step.kind) {
|
|
248
|
+
case "wait":
|
|
249
|
+
await this.boundary(job, this.dependencies.runtime.execute({ operation: "wait", handle, timeoutMs: step.timeoutMs, condition: step.condition }));
|
|
250
|
+
return { paused, summary: { satisfied: true }, evidence: [] };
|
|
251
|
+
case "assert":
|
|
252
|
+
await this.assertRuntime(job, handle, step.assertion);
|
|
253
|
+
return { paused, summary: { passed: true, assertion: step.assertion.type }, evidence: [] };
|
|
254
|
+
case "control": {
|
|
255
|
+
const input = step.action === "step"
|
|
256
|
+
? { operation: "step", handle, frames: step.frames }
|
|
257
|
+
: { operation: step.action, handle };
|
|
258
|
+
await this.boundary(job, this.dependencies.runtime.execute(input));
|
|
259
|
+
return { paused: step.action === "pause" || (step.action === "step" && paused), summary: { action: step.action }, evidence: [] };
|
|
260
|
+
}
|
|
261
|
+
case "input": {
|
|
262
|
+
if (step.mode === "deterministic" && !paused)
|
|
263
|
+
throw scenarioError("ASSERTION_FAILED", "Deterministic input requires a paused runtime");
|
|
264
|
+
const input = step.mode === "deterministic"
|
|
265
|
+
? { operation: "replay", handle, mode: "deterministic", timeoutMs: step.timeoutMs, trace: step.trace }
|
|
266
|
+
: { operation: "sequence", handle, mode: "realtime", timeoutMs: step.timeoutMs, events: step.trace.events };
|
|
267
|
+
await this.boundary(job, this.dependencies.runtime.input(input));
|
|
268
|
+
return { paused, summary: { mode: step.mode, eventCount: step.trace.events.length }, evidence: [] };
|
|
269
|
+
}
|
|
270
|
+
case "capture": {
|
|
271
|
+
const captured = await this.boundary(job, this.dependencies.runtime.capture({
|
|
272
|
+
handle,
|
|
273
|
+
maxWidth: step.maxWidth,
|
|
274
|
+
maxHeight: step.maxHeight,
|
|
275
|
+
frameCount: step.frameCount,
|
|
276
|
+
intervalFrames: step.intervalFrames,
|
|
277
|
+
advancePaused: step.advancePaused,
|
|
278
|
+
}));
|
|
279
|
+
if (captured.frames.length !== step.frameCount)
|
|
280
|
+
throw scenarioError("GODOT_RUNTIME_ERROR", "Scenario capture returned an unexpected frame count");
|
|
281
|
+
const frames = [];
|
|
282
|
+
for (const [index, candidate] of captured.frames.entries()) {
|
|
283
|
+
const metadata = RuntimeCaptureFrameMetadataSchema.parse(candidate.metadata);
|
|
284
|
+
const sha256 = createHash("sha256").update(candidate.data).digest("hex");
|
|
285
|
+
if (metadata.frameIndex !== index || metadata.byteLength !== candidate.data.byteLength || metadata.sha256 !== sha256) {
|
|
286
|
+
throw scenarioError("GODOT_RUNTIME_ERROR", "Scenario capture metadata conflicts with PNG evidence");
|
|
287
|
+
}
|
|
288
|
+
const stored = await this.boundary(job, this.dependencies.evidence.putPng(job.sessionId, candidate.data, {
|
|
289
|
+
source: "runtime",
|
|
290
|
+
viewport: "runtime",
|
|
291
|
+
width: metadata.width,
|
|
292
|
+
height: metadata.height,
|
|
293
|
+
runId: handle.runId,
|
|
294
|
+
generation: handle.generation,
|
|
295
|
+
frameIndex: index,
|
|
296
|
+
}));
|
|
297
|
+
frames.push({ data: candidate.data, metadata, observationUri: stored.observationUri });
|
|
298
|
+
}
|
|
299
|
+
captures.set(step.label, frames);
|
|
300
|
+
return { paused, summary: { label: step.label, frameCount: frames.length }, evidence: frames.map((frame) => frame.observationUri) };
|
|
301
|
+
}
|
|
302
|
+
case "compare": {
|
|
303
|
+
const frame = captures.get(step.captureLabel)?.[step.frameIndex];
|
|
304
|
+
if (!frame)
|
|
305
|
+
throw scenarioError("ASSERTION_FAILED", "Scenario comparison capture is unavailable");
|
|
306
|
+
const baseline = await this.boundary(job, this.dependencies.evidence.readPngBaselineData(step.baselineName));
|
|
307
|
+
const compared = comparePng({ baseline: baseline.data, current: frame.data, settings: step.settings });
|
|
308
|
+
const evidence = [frame.observationUri];
|
|
309
|
+
let result = compared.result;
|
|
310
|
+
if (compared.diffPng) {
|
|
311
|
+
const storedDiff = await this.boundary(job, this.dependencies.evidence.putPng(job.sessionId, compared.diffPng, {
|
|
312
|
+
source: "runtime",
|
|
313
|
+
viewport: "runtime",
|
|
314
|
+
width: frame.metadata.width,
|
|
315
|
+
height: frame.metadata.height,
|
|
316
|
+
runId: handle.runId,
|
|
317
|
+
generation: handle.generation,
|
|
318
|
+
frameIndex: step.frameIndex,
|
|
319
|
+
}));
|
|
320
|
+
evidence.push(storedDiff.observationUri);
|
|
321
|
+
result = VisualComparisonResultSchema.parse({ ...result, diffObservationUri: storedDiff.observationUri });
|
|
322
|
+
}
|
|
323
|
+
const report = await this.boundary(job, this.dependencies.evidence.putJson(job.sessionId, result, { kind: "visual_comparison" }));
|
|
324
|
+
evidence.push(report.observationUri);
|
|
325
|
+
result = VisualComparisonResultSchema.parse({ ...result, reportObservationUri: report.observationUri });
|
|
326
|
+
if (!result.passed)
|
|
327
|
+
throw scenarioError("ASSERTION_FAILED", "Visual comparison did not satisfy its declared tolerances", evidence);
|
|
328
|
+
return { paused, summary: { passed: true, resultSha256: result.resultSha256 }, evidence };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async assertRuntime(job, handle, assertion) {
|
|
333
|
+
if (assertion.type === "log_matches") {
|
|
334
|
+
await this.boundary(job, this.dependencies.runtime.execute({
|
|
335
|
+
operation: "wait",
|
|
336
|
+
handle,
|
|
337
|
+
timeoutMs: 1,
|
|
338
|
+
condition: assertion,
|
|
339
|
+
})).catch((error) => {
|
|
340
|
+
rethrowIfInterrupted(error);
|
|
341
|
+
throw scenarioError("ASSERTION_FAILED", "Runtime log assertion failed");
|
|
342
|
+
});
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (assertion.type === "no_error_logs") {
|
|
346
|
+
const response = await this.boundary(job, this.dependencies.runtime.execute({
|
|
347
|
+
operation: "logs",
|
|
348
|
+
handle,
|
|
349
|
+
afterSequence: 0,
|
|
350
|
+
levels: ["error", "script", "shader"],
|
|
351
|
+
limit: 1,
|
|
352
|
+
}));
|
|
353
|
+
if (!isRecord(response) || !Array.isArray(response.records) || response.records.length > 0) {
|
|
354
|
+
throw scenarioError("ASSERTION_FAILED", "Runtime error-log assertion failed");
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
let response;
|
|
359
|
+
try {
|
|
360
|
+
response = await this.boundary(job, this.dependencies.runtime.execute({
|
|
361
|
+
operation: "node",
|
|
362
|
+
handle,
|
|
363
|
+
nodePath: assertion.nodePath,
|
|
364
|
+
includeProperties: assertion.type === "property_equals" || assertion.type === "property_matches",
|
|
365
|
+
includeSignals: false,
|
|
366
|
+
}));
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
rethrowIfInterrupted(error);
|
|
370
|
+
if (assertion.type === "node_missing" && errorCode(error) === "TARGET_NOT_FOUND")
|
|
371
|
+
return;
|
|
372
|
+
throw scenarioError("ASSERTION_FAILED", "Runtime node assertion failed");
|
|
373
|
+
}
|
|
374
|
+
if (assertion.type === "node_missing")
|
|
375
|
+
throw scenarioError("ASSERTION_FAILED", "Runtime node-missing assertion failed");
|
|
376
|
+
if (assertion.type === "node_exists")
|
|
377
|
+
return;
|
|
378
|
+
if (!isRecord(response) || !Array.isArray(response.properties))
|
|
379
|
+
throw scenarioError("ASSERTION_FAILED", "Runtime property assertion failed");
|
|
380
|
+
const property = response.properties.find((entry) => isRecord(entry) && entry.name === assertion.property);
|
|
381
|
+
if (!isRecord(property))
|
|
382
|
+
throw scenarioError("ASSERTION_FAILED", "Runtime property assertion failed");
|
|
383
|
+
if (assertion.type === "property_equals") {
|
|
384
|
+
if (!Object.is(property.value, assertion.value))
|
|
385
|
+
throw scenarioError("ASSERTION_FAILED", "Runtime property equality assertion failed");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (typeof property.value !== "string" || !wildcardMatches(property.value, assertion.pattern)) {
|
|
389
|
+
throw scenarioError("ASSERTION_FAILED", "Runtime property pattern assertion failed");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async boundary(job, promise) {
|
|
393
|
+
this.assertJobActive(job);
|
|
394
|
+
const remaining = job.deadlineAtMs - Math.floor(this.now());
|
|
395
|
+
if (remaining <= 0)
|
|
396
|
+
throw scenarioError("TIMEOUT", "Visual scenario deadline expired");
|
|
397
|
+
let timeout;
|
|
398
|
+
let abortListener;
|
|
399
|
+
const interruption = new Promise((_resolve, reject) => {
|
|
400
|
+
timeout = setTimeout(() => reject(scenarioError("TIMEOUT", "Visual scenario deadline expired")), remaining);
|
|
401
|
+
abortListener = () => reject(job.controller.signal.reason ?? scenarioError("CANCELLED", "Visual scenario was cancelled"));
|
|
402
|
+
job.controller.signal.addEventListener("abort", abortListener, { once: true });
|
|
403
|
+
});
|
|
404
|
+
promise.catch(() => undefined);
|
|
405
|
+
try {
|
|
406
|
+
return await Promise.race([promise, interruption]);
|
|
407
|
+
}
|
|
408
|
+
finally {
|
|
409
|
+
if (timeout)
|
|
410
|
+
clearTimeout(timeout);
|
|
411
|
+
if (abortListener)
|
|
412
|
+
job.controller.signal.removeEventListener("abort", abortListener);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
assertJobActive(job) {
|
|
416
|
+
if (job.controller.signal.aborted) {
|
|
417
|
+
throw job.controller.signal.reason ?? scenarioError("CANCELLED", "Visual scenario was cancelled");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
//# sourceMappingURL=scenarioService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenarioService.js","sourceRoot":"","sources":["../../src/visual/scenarioService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type VisualOperationInput } from "@godot-mcp/protocol";
|
|
2
|
+
import type { EvidenceStore } from "../evidence/evidenceStore.js";
|
|
3
|
+
export interface VisualScenarioController {
|
|
4
|
+
start(input: unknown): unknown;
|
|
5
|
+
status(jobToken: string): unknown;
|
|
6
|
+
cancel(jobToken: string): unknown;
|
|
7
|
+
result(jobToken: string): unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface VisualServiceDependencies {
|
|
10
|
+
sessionId(): string | null;
|
|
11
|
+
evidence: EvidenceStore;
|
|
12
|
+
scenario: VisualScenarioController;
|
|
13
|
+
}
|
|
14
|
+
export interface VisualExecutionResult {
|
|
15
|
+
data: unknown;
|
|
16
|
+
evidence?: string[];
|
|
17
|
+
images?: Array<{
|
|
18
|
+
data: Uint8Array;
|
|
19
|
+
mimeType: "image/png";
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
export declare class VisualService {
|
|
23
|
+
private readonly dependencies;
|
|
24
|
+
constructor(dependencies: VisualServiceDependencies);
|
|
25
|
+
execute(rawInput: VisualOperationInput): Promise<VisualExecutionResult>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=visualService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visualService.d.ts","sourceRoot":"","sources":["../../src/visual/visualService.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAGlE,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAC/B,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,IAAI,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,wBAAwB,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;CAC7D;AAiBD,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAAZ,YAAY,EAAE,yBAAyB;IAE9D,OAAO,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA0C9E"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { VisualComparisonResultSchema, VisualOperationInputSchema, } from "@godot-mcp/protocol";
|
|
3
|
+
import { GodotMcpException } from "../errors.js";
|
|
4
|
+
import { comparePng } from "./pngComparison.js";
|
|
5
|
+
function visualError(code, message) {
|
|
6
|
+
return new GodotMcpException({
|
|
7
|
+
code,
|
|
8
|
+
message,
|
|
9
|
+
retryable: false,
|
|
10
|
+
correlationId: randomUUID(),
|
|
11
|
+
partialEffects: false,
|
|
12
|
+
rollback: "not_needed",
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
export class VisualService {
|
|
19
|
+
dependencies;
|
|
20
|
+
constructor(dependencies) {
|
|
21
|
+
this.dependencies = dependencies;
|
|
22
|
+
}
|
|
23
|
+
async execute(rawInput) {
|
|
24
|
+
const input = VisualOperationInputSchema.parse(rawInput);
|
|
25
|
+
if (input.operation === "scenario_start")
|
|
26
|
+
return { data: this.dependencies.scenario.start(input.scenario) };
|
|
27
|
+
if (input.operation === "scenario_status")
|
|
28
|
+
return { data: this.dependencies.scenario.status(input.jobToken) };
|
|
29
|
+
if (input.operation === "scenario_cancel")
|
|
30
|
+
return { data: this.dependencies.scenario.cancel(input.jobToken) };
|
|
31
|
+
if (input.operation === "scenario_result") {
|
|
32
|
+
const report = this.dependencies.scenario.result(input.jobToken);
|
|
33
|
+
const reportObservationUri = isRecord(report) && typeof report.reportObservationUri === "string"
|
|
34
|
+
? report.reportObservationUri
|
|
35
|
+
: undefined;
|
|
36
|
+
return { data: report, ...(reportObservationUri ? { evidence: [reportObservationUri] } : {}) };
|
|
37
|
+
}
|
|
38
|
+
const sessionId = this.dependencies.sessionId();
|
|
39
|
+
if (!sessionId)
|
|
40
|
+
throw visualError("NOT_ATTACHED", "Godot editor addon is not attached");
|
|
41
|
+
if (input.operation === "baseline_create") {
|
|
42
|
+
return { data: await this.dependencies.evidence.createPngBaseline(sessionId, input.name, input.observationUri) };
|
|
43
|
+
}
|
|
44
|
+
if (input.operation === "baseline_get") {
|
|
45
|
+
return { data: await this.dependencies.evidence.readPngBaseline(input.name) };
|
|
46
|
+
}
|
|
47
|
+
const current = await this.dependencies.evidence.readSessionPngObservation(sessionId, input.observationUri);
|
|
48
|
+
const baseline = await this.dependencies.evidence.readPngBaselineData(input.name);
|
|
49
|
+
const compared = comparePng({ baseline: baseline.data, current: current.data, settings: input.settings });
|
|
50
|
+
const evidence = [current.observationUri];
|
|
51
|
+
let result = compared.result;
|
|
52
|
+
let images;
|
|
53
|
+
if (compared.diffPng) {
|
|
54
|
+
const diff = await this.dependencies.evidence.putPng(sessionId, compared.diffPng, {
|
|
55
|
+
source: "runtime",
|
|
56
|
+
viewport: "runtime",
|
|
57
|
+
width: current.width,
|
|
58
|
+
height: current.height,
|
|
59
|
+
});
|
|
60
|
+
evidence.push(diff.observationUri);
|
|
61
|
+
result = VisualComparisonResultSchema.parse({ ...result, diffObservationUri: diff.observationUri });
|
|
62
|
+
images = [{ data: compared.diffPng, mimeType: "image/png" }];
|
|
63
|
+
}
|
|
64
|
+
const report = await this.dependencies.evidence.putJson(sessionId, result, { kind: "visual_comparison" });
|
|
65
|
+
evidence.push(report.observationUri);
|
|
66
|
+
result = VisualComparisonResultSchema.parse({ ...result, reportObservationUri: report.observationUri });
|
|
67
|
+
return { data: result, evidence, ...(images ? { images } : {}) };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=visualService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visualService.js","sourceRoot":"","sources":["../../src/visual/visualService.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godot-mcp/control-plane",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Namespace bootstrap for the Godot MCP control-plane package.",
|
|
3
|
+
"version": "0.1.0",
|
|
5
4
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@godot-mcp/protocol": "0.1.0",
|
|
20
|
+
"pngjs": "7.0.0",
|
|
21
|
+
"zod": "4.4.3"
|
|
22
|
+
},
|
|
8
23
|
"license": "MIT",
|
|
9
|
-
"
|
|
10
|
-
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22 <23"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/k0nkupa/godot-mcp.git"
|
|
30
|
+
}
|
|
11
31
|
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|