glasspane-mcp 1.22.0 → 1.24.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/dist/dispatch.d.ts +1 -1
- package/dist/engine-client.d.ts +1 -0
- package/dist/http-gateway-cli.js +103 -29
- package/dist/http-gateway.d.ts +12 -0
- package/dist/http-gateway.js +104 -29
- package/dist/index.js +26 -8
- package/dist/stdio-session.js +26 -8
- package/package.json +1 -1
package/dist/dispatch.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare const MCP_PROTOCOL_VERSION: "2025-06-18";
|
|
|
26
26
|
export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
|
|
27
27
|
export declare const SERVER_INFO: {
|
|
28
28
|
readonly name: "glasspane-mcp";
|
|
29
|
-
readonly version: "1.
|
|
29
|
+
readonly version: "1.24.0";
|
|
30
30
|
};
|
|
31
31
|
export declare const PARSE_ERROR = -32700;
|
|
32
32
|
export declare const INVALID_REQUEST = -32600;
|
package/dist/engine-client.d.ts
CHANGED
|
@@ -590,6 +590,7 @@ export declare class EngineJsonRpcClient {
|
|
|
590
590
|
private verifyIdentity;
|
|
591
591
|
}
|
|
592
592
|
export declare function truncate(text: string): string;
|
|
593
|
+
export declare function lateReplyBodyNote(serializedBody: string, operationId?: unknown): string;
|
|
593
594
|
/** How a transport stream is opened; injectable so tests need no socket. */
|
|
594
595
|
export type EngineTransportOpener = (socketPath: string) => Duplex;
|
|
595
596
|
/**
|
package/dist/http-gateway-cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import net from "node:net";
|
|
|
8
8
|
import os from "node:os";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import fs2 from "node:fs";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
13
|
|
|
13
14
|
// src/io.ts
|
|
@@ -800,9 +801,9 @@ var EngineJsonRpcClient = class {
|
|
|
800
801
|
);
|
|
801
802
|
return;
|
|
802
803
|
}
|
|
803
|
-
const
|
|
804
|
+
const bodyJson = jsonOrString(frame.result);
|
|
804
805
|
this.report(
|
|
805
|
-
`late engine reply for '${entry.method}' (id ${entry.id}), ${entry.deadlineMs}ms deadline already reported: the engine did answer,
|
|
806
|
+
`late engine reply for '${entry.method}' (id ${entry.id}), ${entry.deadlineMs}ms deadline already reported: the engine did answer, ${lateReplyBodyNote(bodyJson, operationIdOf(frame.result))}${byOrder}`
|
|
806
807
|
);
|
|
807
808
|
if (frame.result === void 0) {
|
|
808
809
|
return;
|
|
@@ -957,6 +958,13 @@ function attribute(error, entry, inferred = true) {
|
|
|
957
958
|
function truncate(text) {
|
|
958
959
|
return text.length <= LOG_BODY_CHARS ? text : `${text.slice(0, LOG_BODY_CHARS)}\u2026 [+${text.length - LOG_BODY_CHARS} chars not logged]`;
|
|
959
960
|
}
|
|
961
|
+
var ARCHIVE_OPERATION_ID = /^op_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
962
|
+
function lateReplyBodyNote(serializedBody, operationId) {
|
|
963
|
+
const bytes = Buffer.byteLength(serializedBody, "utf8");
|
|
964
|
+
const digest = createHash("sha256").update(serializedBody, "utf8").digest("hex").slice(0, 12);
|
|
965
|
+
const named2 = typeof operationId === "string" && ARCHIVE_OPERATION_ID.test(operationId) ? `, for ${operationId}` : "";
|
|
966
|
+
return `with a ${bytes}-byte body (sha256 ${digest})${named2} that is on-screen accessibility content \u2014 element titles and values, sometimes a document's text \u2014 so its text is not written to this log; the reply itself is in the daemon's evidence archive under the state root, read it back with gp_last_evidence {operationId} or gp_export_evidence {operationId}`;
|
|
967
|
+
}
|
|
960
968
|
function nonBlankOr(value, fallback) {
|
|
961
969
|
if (value === void 0 || value === null) {
|
|
962
970
|
return fallback;
|
|
@@ -967,6 +975,9 @@ function nonBlankOr(value, fallback) {
|
|
|
967
975
|
function jsonOrString(value) {
|
|
968
976
|
return String(JSON.stringify(value));
|
|
969
977
|
}
|
|
978
|
+
function operationIdOf(result) {
|
|
979
|
+
return typeof result === "object" && result !== null ? result.operationId : void 0;
|
|
980
|
+
}
|
|
970
981
|
var ReconnectingSocketIo = class {
|
|
971
982
|
constructor(socketPath, open) {
|
|
972
983
|
this.socketPath = socketPath;
|
|
@@ -1050,7 +1061,7 @@ function unixSocketEngineClient(socketPath, options = {}) {
|
|
|
1050
1061
|
|
|
1051
1062
|
// src/http-gateway.ts
|
|
1052
1063
|
import http from "node:http";
|
|
1053
|
-
import { createHash as
|
|
1064
|
+
import { createHash as createHash3, timingSafeEqual } from "node:crypto";
|
|
1054
1065
|
import { isIP } from "node:net";
|
|
1055
1066
|
import { URL } from "node:url";
|
|
1056
1067
|
|
|
@@ -1140,6 +1151,9 @@ function engineBody(params) {
|
|
|
1140
1151
|
const { dimension: _dimension, ...rest } = params;
|
|
1141
1152
|
return rest;
|
|
1142
1153
|
}
|
|
1154
|
+
function dimensionAttributionLine(dimension) {
|
|
1155
|
+
return `dimension: ${dimension} | declared by this call; this shell records no decision, so the ledger gains an attribution only where a decision is written for this operation`;
|
|
1156
|
+
}
|
|
1143
1157
|
|
|
1144
1158
|
// src/evidence-report.ts
|
|
1145
1159
|
var PLACEHOLDER = "\u2014";
|
|
@@ -2007,14 +2021,21 @@ function attributeCapability(engineMethod, query = {}) {
|
|
|
2007
2021
|
};
|
|
2008
2022
|
} catch (error) {
|
|
2009
2023
|
if (error instanceof KernelProviderError) {
|
|
2010
|
-
return {
|
|
2024
|
+
return {
|
|
2025
|
+
status: "refused",
|
|
2026
|
+
error: {
|
|
2027
|
+
code: GP_E_INTERNAL,
|
|
2028
|
+
message: `provider registration refused (${error.code}): ${error.message}`,
|
|
2029
|
+
remedy: error.remedy
|
|
2030
|
+
}
|
|
2031
|
+
};
|
|
2011
2032
|
}
|
|
2012
2033
|
if (error instanceof KernelSchemaError) {
|
|
2013
2034
|
return {
|
|
2014
2035
|
status: "refused",
|
|
2015
2036
|
error: {
|
|
2016
|
-
code:
|
|
2017
|
-
message:
|
|
2037
|
+
code: GP_E_INTERNAL,
|
|
2038
|
+
message: `the kernel refused a registration this product writes itself (${error.code}): ${error.message} \u2014 built with the version from ${registrationVersionSource(measured, usable(own))}`,
|
|
2018
2039
|
remedy: "fix shellProviderDrafts in mcp-shell/src/providers.ts; it is the contract both harnesses read, so it cannot be relaxed at a call site \u2014 run node scripts/check-kernel-corpus.mjs and compare against kernel/fixtures/provider-registration.ok-01.json"
|
|
2019
2040
|
}
|
|
2020
2041
|
};
|
|
@@ -2065,7 +2086,7 @@ var TRAIL_PROVENANCE_NOTES = {
|
|
|
2065
2086
|
import fs4 from "node:fs";
|
|
2066
2087
|
import os2 from "node:os";
|
|
2067
2088
|
import path3 from "node:path";
|
|
2068
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2089
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
2069
2090
|
import { z as z9 } from "zod";
|
|
2070
2091
|
var CROCKFORD_BODY_RE = "[0-9A-HJKMNP-TV-Z]{26}";
|
|
2071
2092
|
var PROJECT_ID_RE = new RegExp(`^prj_${CROCKFORD_BODY_RE}$`);
|
|
@@ -2369,7 +2390,7 @@ function registryDigest(filePath) {
|
|
|
2369
2390
|
return registryBytesDigest(bytes);
|
|
2370
2391
|
}
|
|
2371
2392
|
function registryBytesDigest(bytes) {
|
|
2372
|
-
return
|
|
2393
|
+
return createHash2("sha256").update(bytes).digest("hex");
|
|
2373
2394
|
}
|
|
2374
2395
|
function registryDriftReason(filePath, openedAs, now) {
|
|
2375
2396
|
if (now === openedAs) {
|
|
@@ -3848,6 +3869,7 @@ var HTTP_NOT_FOUND = "GP_HTTP_NOT_FOUND";
|
|
|
3848
3869
|
var HTTP_BAD_REQUEST = "GP_HTTP_BAD_REQUEST";
|
|
3849
3870
|
var HTTP_PAYLOAD_TOO_LARGE = "GP_HTTP_PAYLOAD_TOO_LARGE";
|
|
3850
3871
|
var HTTP_NOT_PROVIDED = "GP_HTTP_NOT_PROVIDED";
|
|
3872
|
+
var HTTP_BODY_UNREAD = "GP_HTTP_BODY_UNREAD";
|
|
3851
3873
|
var FORWARDABLE_TOOLS = new Map(
|
|
3852
3874
|
TOOL_SPECS.filter((spec) => spec.execute === void 0).map((spec) => [spec.engineMethod, spec])
|
|
3853
3875
|
);
|
|
@@ -3895,11 +3917,12 @@ function createHttpGateway(options) {
|
|
|
3895
3917
|
const client = options.connectController !== void 0 ? options.connectController(options.socketPath) : unixSocketEngineClient(options.socketPath, { surface: "http" });
|
|
3896
3918
|
client.onEngineNote?.((note) => report(`engine: ${note}`));
|
|
3897
3919
|
client.onLateReply?.(({ method, result }) => {
|
|
3898
|
-
report(`late engine reply to '${method}'
|
|
3920
|
+
report(`late engine reply to '${method}': the engine did answer, ${lateReplyBodyNote(stringifyReply(result), result?.operationId)}`);
|
|
3899
3921
|
});
|
|
3900
3922
|
const expectedToken = Buffer.from(options.token, "utf8");
|
|
3923
|
+
const bodyReadTimeoutMs = options.bodyReadTimeoutMs ?? DEFAULT_BODY_READ_TIMEOUT_MS;
|
|
3901
3924
|
const server = http.createServer((req, res) => {
|
|
3902
|
-
handleHttp(req, res, client, expectedToken).catch((error) => {
|
|
3925
|
+
handleHttp(req, res, client, expectedToken, bodyReadTimeoutMs).catch((error) => {
|
|
3903
3926
|
fail(res, 500, GP_E_INTERNAL, `internal gateway error: ${String(error)}`, gatewayFaultAdvice());
|
|
3904
3927
|
});
|
|
3905
3928
|
});
|
|
@@ -3979,7 +4002,7 @@ function stringifyReply(result) {
|
|
|
3979
4002
|
return String(result);
|
|
3980
4003
|
}
|
|
3981
4004
|
}
|
|
3982
|
-
async function handleHttp(req, res, client, expectedToken) {
|
|
4005
|
+
async function handleHttp(req, res, client, expectedToken, bodyReadTimeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
3983
4006
|
if (!authorized(req, expectedToken)) {
|
|
3984
4007
|
fail(
|
|
3985
4008
|
res,
|
|
@@ -3996,7 +4019,7 @@ async function handleHttp(req, res, client, expectedToken) {
|
|
|
3996
4019
|
return;
|
|
3997
4020
|
}
|
|
3998
4021
|
const segments = url.pathname.split("/").filter((part) => part !== "");
|
|
3999
|
-
await dispatch(req, res, url, segments, client);
|
|
4022
|
+
await dispatch(req, res, url, segments, client, bodyReadTimeoutMs);
|
|
4000
4023
|
}
|
|
4001
4024
|
function parseRequestUrl(raw) {
|
|
4002
4025
|
if (typeof raw !== "string" || raw === "") {
|
|
@@ -4008,7 +4031,7 @@ function parseRequestUrl(raw) {
|
|
|
4008
4031
|
return null;
|
|
4009
4032
|
}
|
|
4010
4033
|
}
|
|
4011
|
-
async function dispatch(req, res, url, segments, client) {
|
|
4034
|
+
async function dispatch(req, res, url, segments, client, bodyReadTimeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
4012
4035
|
const [v1, resource, id] = segments;
|
|
4013
4036
|
if (v1 !== "v1") {
|
|
4014
4037
|
NotFound(res, url.pathname);
|
|
@@ -4033,11 +4056,11 @@ async function dispatch(req, res, url, segments, client) {
|
|
|
4033
4056
|
}
|
|
4034
4057
|
} else if (req.method === "POST") {
|
|
4035
4058
|
if (resource === "tools" && segments.length === 3 && id !== void 0) {
|
|
4036
|
-
await handleToolForward(id, req, client, res);
|
|
4059
|
+
await handleToolForward(id, req, client, res, bodyReadTimeoutMs);
|
|
4037
4060
|
return;
|
|
4038
4061
|
}
|
|
4039
4062
|
if (resource === "recipes" && id === "validate" && segments.length === 3) {
|
|
4040
|
-
await handleRecipeValidate(req, res);
|
|
4063
|
+
await handleRecipeValidate(req, res, bodyReadTimeoutMs);
|
|
4041
4064
|
return;
|
|
4042
4065
|
}
|
|
4043
4066
|
}
|
|
@@ -4254,7 +4277,7 @@ async function handleEvidenceAggregate(url, client, res) {
|
|
|
4254
4277
|
report: sections2.join("\n")
|
|
4255
4278
|
});
|
|
4256
4279
|
}
|
|
4257
|
-
async function handleToolForward(name, req, client, res) {
|
|
4280
|
+
async function handleToolForward(name, req, client, res, bodyReadTimeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
4258
4281
|
const spec = FORWARDABLE_TOOLS.get(name);
|
|
4259
4282
|
if (spec === void 0) {
|
|
4260
4283
|
fail(
|
|
@@ -4268,7 +4291,7 @@ async function handleToolForward(name, req, client, res) {
|
|
|
4268
4291
|
}
|
|
4269
4292
|
let body;
|
|
4270
4293
|
try {
|
|
4271
|
-
const text = await readBody(req, MAX_REQUEST_BODY_BYTES);
|
|
4294
|
+
const text = await readBody(req, MAX_REQUEST_BODY_BYTES, bodyReadTimeoutMs);
|
|
4272
4295
|
body = JSON.parse(text);
|
|
4273
4296
|
} catch (error) {
|
|
4274
4297
|
if (error instanceof HTTPPayloadTooLargeError) {
|
|
@@ -4281,6 +4304,16 @@ async function handleToolForward(name, req, client, res) {
|
|
|
4281
4304
|
);
|
|
4282
4305
|
return;
|
|
4283
4306
|
}
|
|
4307
|
+
if (error instanceof HTTPBodyUnreadError) {
|
|
4308
|
+
fail(
|
|
4309
|
+
res,
|
|
4310
|
+
408,
|
|
4311
|
+
HTTP_BODY_UNREAD,
|
|
4312
|
+
`request body never completed: ${error.message}`,
|
|
4313
|
+
`send the whole body in one request (Content-Length must match what you write) and retry; this gateway waits ${bodyReadTimeoutMs}ms for one body before it answers this way`
|
|
4314
|
+
);
|
|
4315
|
+
return;
|
|
4316
|
+
}
|
|
4284
4317
|
fail(
|
|
4285
4318
|
res,
|
|
4286
4319
|
400,
|
|
@@ -4334,7 +4367,7 @@ async function handleToolForward(name, req, client, res) {
|
|
|
4334
4367
|
return;
|
|
4335
4368
|
}
|
|
4336
4369
|
}
|
|
4337
|
-
ok(res, result, attribution);
|
|
4370
|
+
ok(res, result, attribution, declared.dimension);
|
|
4338
4371
|
} catch (error) {
|
|
4339
4372
|
await failEngine(res, error, spec, engineBody(checked.value));
|
|
4340
4373
|
}
|
|
@@ -4348,9 +4381,9 @@ function handleEvidenceStats(res) {
|
|
|
4348
4381
|
"run `glasspaned --evidence-stats` against the same --state-dir this socket serves to get count/totalBytes/listFailure/dir"
|
|
4349
4382
|
);
|
|
4350
4383
|
}
|
|
4351
|
-
async function handleRecipeValidate(req, res) {
|
|
4384
|
+
async function handleRecipeValidate(req, res, bodyReadTimeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
4352
4385
|
try {
|
|
4353
|
-
await readBody(req, MAX_REQUEST_BODY_BYTES);
|
|
4386
|
+
await readBody(req, MAX_REQUEST_BODY_BYTES, bodyReadTimeoutMs);
|
|
4354
4387
|
} catch (error) {
|
|
4355
4388
|
if (error instanceof HTTPPayloadTooLargeError) {
|
|
4356
4389
|
fail(
|
|
@@ -4362,6 +4395,16 @@ async function handleRecipeValidate(req, res) {
|
|
|
4362
4395
|
);
|
|
4363
4396
|
return;
|
|
4364
4397
|
}
|
|
4398
|
+
if (error instanceof HTTPBodyUnreadError) {
|
|
4399
|
+
fail(
|
|
4400
|
+
res,
|
|
4401
|
+
408,
|
|
4402
|
+
HTTP_BODY_UNREAD,
|
|
4403
|
+
`request body never completed: ${error.message}`,
|
|
4404
|
+
`send the whole body in one request and retry; this gateway waits ${bodyReadTimeoutMs}ms for one body before it answers this way`
|
|
4405
|
+
);
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4365
4408
|
fail(res, 400, HTTP_BAD_REQUEST, "could not read the request body", "resend the request");
|
|
4366
4409
|
return;
|
|
4367
4410
|
}
|
|
@@ -4394,7 +4437,7 @@ function tokenMatches(expected, received) {
|
|
|
4394
4437
|
return timingSafeEqual(expectedDigest, receivedDigest);
|
|
4395
4438
|
}
|
|
4396
4439
|
function sha256(data) {
|
|
4397
|
-
return
|
|
4440
|
+
return createHash3("sha256").update(data).digest();
|
|
4398
4441
|
}
|
|
4399
4442
|
function parseOperationId(value) {
|
|
4400
4443
|
if (typeof value !== "string") {
|
|
@@ -4468,8 +4511,11 @@ function makeHardFailureNote(failure, format) {
|
|
|
4468
4511
|
const sentence = `incomplete: ${failure.operationId} failed with ${failure.line} \u2014 the fan-out stopped there, so the packs above are what this request read, not the whole list`;
|
|
4469
4512
|
return format === "html" ? `<p class="gp-incomplete">${escapeHTML(sentence)}</p>` : `> ${sentence}`;
|
|
4470
4513
|
}
|
|
4471
|
-
function ok(res, result, attribution) {
|
|
4472
|
-
|
|
4514
|
+
function ok(res, result, attribution, dimension) {
|
|
4515
|
+
const body = { ok: true, result };
|
|
4516
|
+
if (attribution !== void 0) body.attribution = attribution;
|
|
4517
|
+
if (dimension !== void 0) body.dimension = dimensionAttributionLine(dimension);
|
|
4518
|
+
writeJson(res, 200, body);
|
|
4473
4519
|
}
|
|
4474
4520
|
function fail(res, status, code, message, remedy) {
|
|
4475
4521
|
writeJson(res, status, { ok: false, error: { code, message: forHttpCaller(message), remedy: forHttpCaller(remedy) } });
|
|
@@ -4601,22 +4647,50 @@ var HTTPPayloadTooLargeError = class extends Error {
|
|
|
4601
4647
|
this.name = "HTTPPayloadTooLargeError";
|
|
4602
4648
|
}
|
|
4603
4649
|
};
|
|
4604
|
-
|
|
4650
|
+
var HTTPBodyUnreadError = class extends Error {
|
|
4651
|
+
constructor(reason, timeoutMs) {
|
|
4652
|
+
super(`request body did not arrive (${reason}; ceiling ${timeoutMs}ms)`);
|
|
4653
|
+
this.name = "HTTPBodyUnreadError";
|
|
4654
|
+
}
|
|
4655
|
+
};
|
|
4656
|
+
var DEFAULT_BODY_READ_TIMEOUT_MS = 15e3;
|
|
4657
|
+
function readBody(req, limit, timeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
4605
4658
|
return new Promise((resolve, reject) => {
|
|
4606
4659
|
let size = 0;
|
|
4607
4660
|
const chunks = [];
|
|
4661
|
+
let settled = false;
|
|
4662
|
+
const finish = (action) => {
|
|
4663
|
+
if (settled) return;
|
|
4664
|
+
settled = true;
|
|
4665
|
+
clearTimeout(timer);
|
|
4666
|
+
action();
|
|
4667
|
+
};
|
|
4668
|
+
const timer = setTimeout(() => {
|
|
4669
|
+
finish(() => {
|
|
4670
|
+
chunks.length = 0;
|
|
4671
|
+
req.pause();
|
|
4672
|
+
reject(new HTTPBodyUnreadError("no complete body within the read ceiling", timeoutMs));
|
|
4673
|
+
});
|
|
4674
|
+
}, timeoutMs);
|
|
4675
|
+
timer.unref?.();
|
|
4608
4676
|
req.on("data", (chunk) => {
|
|
4609
4677
|
size += chunk.length;
|
|
4610
4678
|
if (size > limit) {
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4679
|
+
finish(() => {
|
|
4680
|
+
chunks.length = 0;
|
|
4681
|
+
req.pause();
|
|
4682
|
+
reject(new HTTPPayloadTooLargeError(limit));
|
|
4683
|
+
});
|
|
4614
4684
|
return;
|
|
4615
4685
|
}
|
|
4616
4686
|
chunks.push(chunk);
|
|
4617
4687
|
});
|
|
4618
|
-
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
4619
|
-
req.on("
|
|
4688
|
+
req.on("end", () => finish(() => resolve(Buffer.concat(chunks).toString("utf8"))));
|
|
4689
|
+
req.on("close", () => finish(() => {
|
|
4690
|
+
chunks.length = 0;
|
|
4691
|
+
reject(new HTTPBodyUnreadError("the connection closed before the body ended", timeoutMs));
|
|
4692
|
+
}));
|
|
4693
|
+
req.on("error", (error) => finish(() => reject(error)));
|
|
4620
4694
|
});
|
|
4621
4695
|
}
|
|
4622
4696
|
|
package/dist/http-gateway.d.ts
CHANGED
|
@@ -109,6 +109,16 @@ export interface HttpGatewayOptions {
|
|
|
109
109
|
* stderr* — a promise that has to have a writer behind it (R8-高3).
|
|
110
110
|
*/
|
|
111
111
|
report?: (note: string) => void;
|
|
112
|
+
/**
|
|
113
|
+
* Ceiling on how long one request body may take to arrive (finding B1,
|
|
114
|
+
* 2026-10-27). `readBody` had a byte cap and no *time* cap, and the server set
|
|
115
|
+
* no `requestTimeout`, so a caller that sent headers and then stalled the body
|
|
116
|
+
* held a handler open until Node's own default fired and destroyed the socket —
|
|
117
|
+
* which answers the caller with an unstructured `ECONNRESET`, the exact shape
|
|
118
|
+
* this file already refuses for oversized bodies. Tests may shorten it; it is
|
|
119
|
+
* never allowed to be absent.
|
|
120
|
+
*/
|
|
121
|
+
bodyReadTimeoutMs?: number;
|
|
112
122
|
}
|
|
113
123
|
export interface HttpGateway {
|
|
114
124
|
/**
|
|
@@ -152,4 +162,6 @@ export declare function isLoopbackHost(value: string): boolean;
|
|
|
152
162
|
export declare function assertLoopbackHost(host: string): string;
|
|
153
163
|
/** Refuse an empty token, and one too short or too uniform to be a credential. */
|
|
154
164
|
export declare function assertTokenPolicy(token: string): void;
|
|
165
|
+
/** How long one request body may take, when the caller did not choose (see {@link HttpGatewayOptions.bodyReadTimeoutMs}). */
|
|
166
|
+
export declare const DEFAULT_BODY_READ_TIMEOUT_MS = 15000;
|
|
155
167
|
//# sourceMappingURL=http-gateway.d.ts.map
|
package/dist/http-gateway.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/http-gateway.ts
|
|
2
2
|
import http from "node:http";
|
|
3
|
-
import { createHash as
|
|
3
|
+
import { createHash as createHash3, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { isIP } from "node:net";
|
|
5
5
|
import { URL } from "node:url";
|
|
6
6
|
|
|
@@ -8,6 +8,7 @@ import { URL } from "node:url";
|
|
|
8
8
|
import net from "node:net";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import fs from "node:fs";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
13
|
|
|
13
14
|
// src/io.ts
|
|
@@ -752,9 +753,9 @@ var EngineJsonRpcClient = class {
|
|
|
752
753
|
);
|
|
753
754
|
return;
|
|
754
755
|
}
|
|
755
|
-
const
|
|
756
|
+
const bodyJson = jsonOrString(frame.result);
|
|
756
757
|
this.report(
|
|
757
|
-
`late engine reply for '${entry.method}' (id ${entry.id}), ${entry.deadlineMs}ms deadline already reported: the engine did answer,
|
|
758
|
+
`late engine reply for '${entry.method}' (id ${entry.id}), ${entry.deadlineMs}ms deadline already reported: the engine did answer, ${lateReplyBodyNote(bodyJson, operationIdOf(frame.result))}${byOrder}`
|
|
758
759
|
);
|
|
759
760
|
if (frame.result === void 0) {
|
|
760
761
|
return;
|
|
@@ -909,6 +910,13 @@ function attribute(error, entry, inferred = true) {
|
|
|
909
910
|
function truncate(text) {
|
|
910
911
|
return text.length <= LOG_BODY_CHARS ? text : `${text.slice(0, LOG_BODY_CHARS)}\u2026 [+${text.length - LOG_BODY_CHARS} chars not logged]`;
|
|
911
912
|
}
|
|
913
|
+
var ARCHIVE_OPERATION_ID = /^op_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
914
|
+
function lateReplyBodyNote(serializedBody, operationId) {
|
|
915
|
+
const bytes = Buffer.byteLength(serializedBody, "utf8");
|
|
916
|
+
const digest = createHash("sha256").update(serializedBody, "utf8").digest("hex").slice(0, 12);
|
|
917
|
+
const named2 = typeof operationId === "string" && ARCHIVE_OPERATION_ID.test(operationId) ? `, for ${operationId}` : "";
|
|
918
|
+
return `with a ${bytes}-byte body (sha256 ${digest})${named2} that is on-screen accessibility content \u2014 element titles and values, sometimes a document's text \u2014 so its text is not written to this log; the reply itself is in the daemon's evidence archive under the state root, read it back with gp_last_evidence {operationId} or gp_export_evidence {operationId}`;
|
|
919
|
+
}
|
|
912
920
|
function nonBlankOr(value, fallback) {
|
|
913
921
|
if (value === void 0 || value === null) {
|
|
914
922
|
return fallback;
|
|
@@ -919,6 +927,9 @@ function nonBlankOr(value, fallback) {
|
|
|
919
927
|
function jsonOrString(value) {
|
|
920
928
|
return String(JSON.stringify(value));
|
|
921
929
|
}
|
|
930
|
+
function operationIdOf(result) {
|
|
931
|
+
return typeof result === "object" && result !== null ? result.operationId : void 0;
|
|
932
|
+
}
|
|
922
933
|
var ReconnectingSocketIo = class {
|
|
923
934
|
constructor(socketPath, open) {
|
|
924
935
|
this.socketPath = socketPath;
|
|
@@ -1086,6 +1097,9 @@ function engineBody(params) {
|
|
|
1086
1097
|
const { dimension: _dimension, ...rest } = params;
|
|
1087
1098
|
return rest;
|
|
1088
1099
|
}
|
|
1100
|
+
function dimensionAttributionLine(dimension) {
|
|
1101
|
+
return `dimension: ${dimension} | declared by this call; this shell records no decision, so the ledger gains an attribution only where a decision is written for this operation`;
|
|
1102
|
+
}
|
|
1089
1103
|
|
|
1090
1104
|
// src/evidence-report.ts
|
|
1091
1105
|
var PLACEHOLDER = "\u2014";
|
|
@@ -1953,14 +1967,21 @@ function attributeCapability(engineMethod, query = {}) {
|
|
|
1953
1967
|
};
|
|
1954
1968
|
} catch (error) {
|
|
1955
1969
|
if (error instanceof KernelProviderError) {
|
|
1956
|
-
return {
|
|
1970
|
+
return {
|
|
1971
|
+
status: "refused",
|
|
1972
|
+
error: {
|
|
1973
|
+
code: GP_E_INTERNAL,
|
|
1974
|
+
message: `provider registration refused (${error.code}): ${error.message}`,
|
|
1975
|
+
remedy: error.remedy
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1957
1978
|
}
|
|
1958
1979
|
if (error instanceof KernelSchemaError) {
|
|
1959
1980
|
return {
|
|
1960
1981
|
status: "refused",
|
|
1961
1982
|
error: {
|
|
1962
|
-
code:
|
|
1963
|
-
message:
|
|
1983
|
+
code: GP_E_INTERNAL,
|
|
1984
|
+
message: `the kernel refused a registration this product writes itself (${error.code}): ${error.message} \u2014 built with the version from ${registrationVersionSource(measured, usable(own))}`,
|
|
1964
1985
|
remedy: "fix shellProviderDrafts in mcp-shell/src/providers.ts; it is the contract both harnesses read, so it cannot be relaxed at a call site \u2014 run node scripts/check-kernel-corpus.mjs and compare against kernel/fixtures/provider-registration.ok-01.json"
|
|
1965
1986
|
}
|
|
1966
1987
|
};
|
|
@@ -2011,7 +2032,7 @@ var TRAIL_PROVENANCE_NOTES = {
|
|
|
2011
2032
|
import fs3 from "node:fs";
|
|
2012
2033
|
import os from "node:os";
|
|
2013
2034
|
import path3 from "node:path";
|
|
2014
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2035
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
2015
2036
|
import { z as z9 } from "zod";
|
|
2016
2037
|
var CROCKFORD_BODY_RE = "[0-9A-HJKMNP-TV-Z]{26}";
|
|
2017
2038
|
var PROJECT_ID_RE = new RegExp(`^prj_${CROCKFORD_BODY_RE}$`);
|
|
@@ -2315,7 +2336,7 @@ function registryDigest(filePath) {
|
|
|
2315
2336
|
return registryBytesDigest(bytes);
|
|
2316
2337
|
}
|
|
2317
2338
|
function registryBytesDigest(bytes) {
|
|
2318
|
-
return
|
|
2339
|
+
return createHash2("sha256").update(bytes).digest("hex");
|
|
2319
2340
|
}
|
|
2320
2341
|
function registryDriftReason(filePath, openedAs, now) {
|
|
2321
2342
|
if (now === openedAs) {
|
|
@@ -3794,6 +3815,7 @@ var HTTP_NOT_FOUND = "GP_HTTP_NOT_FOUND";
|
|
|
3794
3815
|
var HTTP_BAD_REQUEST = "GP_HTTP_BAD_REQUEST";
|
|
3795
3816
|
var HTTP_PAYLOAD_TOO_LARGE = "GP_HTTP_PAYLOAD_TOO_LARGE";
|
|
3796
3817
|
var HTTP_NOT_PROVIDED = "GP_HTTP_NOT_PROVIDED";
|
|
3818
|
+
var HTTP_BODY_UNREAD = "GP_HTTP_BODY_UNREAD";
|
|
3797
3819
|
var FORWARDABLE_TOOLS = new Map(
|
|
3798
3820
|
TOOL_SPECS.filter((spec) => spec.execute === void 0).map((spec) => [spec.engineMethod, spec])
|
|
3799
3821
|
);
|
|
@@ -3841,11 +3863,12 @@ function createHttpGateway(options) {
|
|
|
3841
3863
|
const client = options.connectController !== void 0 ? options.connectController(options.socketPath) : unixSocketEngineClient(options.socketPath, { surface: "http" });
|
|
3842
3864
|
client.onEngineNote?.((note) => report(`engine: ${note}`));
|
|
3843
3865
|
client.onLateReply?.(({ method, result }) => {
|
|
3844
|
-
report(`late engine reply to '${method}'
|
|
3866
|
+
report(`late engine reply to '${method}': the engine did answer, ${lateReplyBodyNote(stringifyReply(result), result?.operationId)}`);
|
|
3845
3867
|
});
|
|
3846
3868
|
const expectedToken = Buffer.from(options.token, "utf8");
|
|
3869
|
+
const bodyReadTimeoutMs = options.bodyReadTimeoutMs ?? DEFAULT_BODY_READ_TIMEOUT_MS;
|
|
3847
3870
|
const server = http.createServer((req, res) => {
|
|
3848
|
-
handleHttp(req, res, client, expectedToken).catch((error) => {
|
|
3871
|
+
handleHttp(req, res, client, expectedToken, bodyReadTimeoutMs).catch((error) => {
|
|
3849
3872
|
fail(res, 500, GP_E_INTERNAL, `internal gateway error: ${String(error)}`, gatewayFaultAdvice());
|
|
3850
3873
|
});
|
|
3851
3874
|
});
|
|
@@ -3925,7 +3948,7 @@ function stringifyReply(result) {
|
|
|
3925
3948
|
return String(result);
|
|
3926
3949
|
}
|
|
3927
3950
|
}
|
|
3928
|
-
async function handleHttp(req, res, client, expectedToken) {
|
|
3951
|
+
async function handleHttp(req, res, client, expectedToken, bodyReadTimeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
3929
3952
|
if (!authorized(req, expectedToken)) {
|
|
3930
3953
|
fail(
|
|
3931
3954
|
res,
|
|
@@ -3942,7 +3965,7 @@ async function handleHttp(req, res, client, expectedToken) {
|
|
|
3942
3965
|
return;
|
|
3943
3966
|
}
|
|
3944
3967
|
const segments = url.pathname.split("/").filter((part) => part !== "");
|
|
3945
|
-
await dispatch(req, res, url, segments, client);
|
|
3968
|
+
await dispatch(req, res, url, segments, client, bodyReadTimeoutMs);
|
|
3946
3969
|
}
|
|
3947
3970
|
function parseRequestUrl(raw) {
|
|
3948
3971
|
if (typeof raw !== "string" || raw === "") {
|
|
@@ -3954,7 +3977,7 @@ function parseRequestUrl(raw) {
|
|
|
3954
3977
|
return null;
|
|
3955
3978
|
}
|
|
3956
3979
|
}
|
|
3957
|
-
async function dispatch(req, res, url, segments, client) {
|
|
3980
|
+
async function dispatch(req, res, url, segments, client, bodyReadTimeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
3958
3981
|
const [v1, resource, id] = segments;
|
|
3959
3982
|
if (v1 !== "v1") {
|
|
3960
3983
|
NotFound(res, url.pathname);
|
|
@@ -3979,11 +4002,11 @@ async function dispatch(req, res, url, segments, client) {
|
|
|
3979
4002
|
}
|
|
3980
4003
|
} else if (req.method === "POST") {
|
|
3981
4004
|
if (resource === "tools" && segments.length === 3 && id !== void 0) {
|
|
3982
|
-
await handleToolForward(id, req, client, res);
|
|
4005
|
+
await handleToolForward(id, req, client, res, bodyReadTimeoutMs);
|
|
3983
4006
|
return;
|
|
3984
4007
|
}
|
|
3985
4008
|
if (resource === "recipes" && id === "validate" && segments.length === 3) {
|
|
3986
|
-
await handleRecipeValidate(req, res);
|
|
4009
|
+
await handleRecipeValidate(req, res, bodyReadTimeoutMs);
|
|
3987
4010
|
return;
|
|
3988
4011
|
}
|
|
3989
4012
|
}
|
|
@@ -4200,7 +4223,7 @@ async function handleEvidenceAggregate(url, client, res) {
|
|
|
4200
4223
|
report: sections2.join("\n")
|
|
4201
4224
|
});
|
|
4202
4225
|
}
|
|
4203
|
-
async function handleToolForward(name, req, client, res) {
|
|
4226
|
+
async function handleToolForward(name, req, client, res, bodyReadTimeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
4204
4227
|
const spec = FORWARDABLE_TOOLS.get(name);
|
|
4205
4228
|
if (spec === void 0) {
|
|
4206
4229
|
fail(
|
|
@@ -4214,7 +4237,7 @@ async function handleToolForward(name, req, client, res) {
|
|
|
4214
4237
|
}
|
|
4215
4238
|
let body;
|
|
4216
4239
|
try {
|
|
4217
|
-
const text = await readBody(req, MAX_REQUEST_BODY_BYTES);
|
|
4240
|
+
const text = await readBody(req, MAX_REQUEST_BODY_BYTES, bodyReadTimeoutMs);
|
|
4218
4241
|
body = JSON.parse(text);
|
|
4219
4242
|
} catch (error) {
|
|
4220
4243
|
if (error instanceof HTTPPayloadTooLargeError) {
|
|
@@ -4227,6 +4250,16 @@ async function handleToolForward(name, req, client, res) {
|
|
|
4227
4250
|
);
|
|
4228
4251
|
return;
|
|
4229
4252
|
}
|
|
4253
|
+
if (error instanceof HTTPBodyUnreadError) {
|
|
4254
|
+
fail(
|
|
4255
|
+
res,
|
|
4256
|
+
408,
|
|
4257
|
+
HTTP_BODY_UNREAD,
|
|
4258
|
+
`request body never completed: ${error.message}`,
|
|
4259
|
+
`send the whole body in one request (Content-Length must match what you write) and retry; this gateway waits ${bodyReadTimeoutMs}ms for one body before it answers this way`
|
|
4260
|
+
);
|
|
4261
|
+
return;
|
|
4262
|
+
}
|
|
4230
4263
|
fail(
|
|
4231
4264
|
res,
|
|
4232
4265
|
400,
|
|
@@ -4280,7 +4313,7 @@ async function handleToolForward(name, req, client, res) {
|
|
|
4280
4313
|
return;
|
|
4281
4314
|
}
|
|
4282
4315
|
}
|
|
4283
|
-
ok(res, result, attribution);
|
|
4316
|
+
ok(res, result, attribution, declared.dimension);
|
|
4284
4317
|
} catch (error) {
|
|
4285
4318
|
await failEngine(res, error, spec, engineBody(checked.value));
|
|
4286
4319
|
}
|
|
@@ -4294,9 +4327,9 @@ function handleEvidenceStats(res) {
|
|
|
4294
4327
|
"run `glasspaned --evidence-stats` against the same --state-dir this socket serves to get count/totalBytes/listFailure/dir"
|
|
4295
4328
|
);
|
|
4296
4329
|
}
|
|
4297
|
-
async function handleRecipeValidate(req, res) {
|
|
4330
|
+
async function handleRecipeValidate(req, res, bodyReadTimeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
4298
4331
|
try {
|
|
4299
|
-
await readBody(req, MAX_REQUEST_BODY_BYTES);
|
|
4332
|
+
await readBody(req, MAX_REQUEST_BODY_BYTES, bodyReadTimeoutMs);
|
|
4300
4333
|
} catch (error) {
|
|
4301
4334
|
if (error instanceof HTTPPayloadTooLargeError) {
|
|
4302
4335
|
fail(
|
|
@@ -4308,6 +4341,16 @@ async function handleRecipeValidate(req, res) {
|
|
|
4308
4341
|
);
|
|
4309
4342
|
return;
|
|
4310
4343
|
}
|
|
4344
|
+
if (error instanceof HTTPBodyUnreadError) {
|
|
4345
|
+
fail(
|
|
4346
|
+
res,
|
|
4347
|
+
408,
|
|
4348
|
+
HTTP_BODY_UNREAD,
|
|
4349
|
+
`request body never completed: ${error.message}`,
|
|
4350
|
+
`send the whole body in one request and retry; this gateway waits ${bodyReadTimeoutMs}ms for one body before it answers this way`
|
|
4351
|
+
);
|
|
4352
|
+
return;
|
|
4353
|
+
}
|
|
4311
4354
|
fail(res, 400, HTTP_BAD_REQUEST, "could not read the request body", "resend the request");
|
|
4312
4355
|
return;
|
|
4313
4356
|
}
|
|
@@ -4340,7 +4383,7 @@ function tokenMatches(expected, received) {
|
|
|
4340
4383
|
return timingSafeEqual(expectedDigest, receivedDigest);
|
|
4341
4384
|
}
|
|
4342
4385
|
function sha256(data) {
|
|
4343
|
-
return
|
|
4386
|
+
return createHash3("sha256").update(data).digest();
|
|
4344
4387
|
}
|
|
4345
4388
|
function parseOperationId(value) {
|
|
4346
4389
|
if (typeof value !== "string") {
|
|
@@ -4414,8 +4457,11 @@ function makeHardFailureNote(failure, format) {
|
|
|
4414
4457
|
const sentence = `incomplete: ${failure.operationId} failed with ${failure.line} \u2014 the fan-out stopped there, so the packs above are what this request read, not the whole list`;
|
|
4415
4458
|
return format === "html" ? `<p class="gp-incomplete">${escapeHTML(sentence)}</p>` : `> ${sentence}`;
|
|
4416
4459
|
}
|
|
4417
|
-
function ok(res, result, attribution) {
|
|
4418
|
-
|
|
4460
|
+
function ok(res, result, attribution, dimension) {
|
|
4461
|
+
const body = { ok: true, result };
|
|
4462
|
+
if (attribution !== void 0) body.attribution = attribution;
|
|
4463
|
+
if (dimension !== void 0) body.dimension = dimensionAttributionLine(dimension);
|
|
4464
|
+
writeJson(res, 200, body);
|
|
4419
4465
|
}
|
|
4420
4466
|
function fail(res, status, code, message, remedy) {
|
|
4421
4467
|
writeJson(res, status, { ok: false, error: { code, message: forHttpCaller(message), remedy: forHttpCaller(remedy) } });
|
|
@@ -4547,25 +4593,54 @@ var HTTPPayloadTooLargeError = class extends Error {
|
|
|
4547
4593
|
this.name = "HTTPPayloadTooLargeError";
|
|
4548
4594
|
}
|
|
4549
4595
|
};
|
|
4550
|
-
|
|
4596
|
+
var HTTPBodyUnreadError = class extends Error {
|
|
4597
|
+
constructor(reason, timeoutMs) {
|
|
4598
|
+
super(`request body did not arrive (${reason}; ceiling ${timeoutMs}ms)`);
|
|
4599
|
+
this.name = "HTTPBodyUnreadError";
|
|
4600
|
+
}
|
|
4601
|
+
};
|
|
4602
|
+
var DEFAULT_BODY_READ_TIMEOUT_MS = 15e3;
|
|
4603
|
+
function readBody(req, limit, timeoutMs = DEFAULT_BODY_READ_TIMEOUT_MS) {
|
|
4551
4604
|
return new Promise((resolve, reject) => {
|
|
4552
4605
|
let size = 0;
|
|
4553
4606
|
const chunks = [];
|
|
4607
|
+
let settled = false;
|
|
4608
|
+
const finish = (action) => {
|
|
4609
|
+
if (settled) return;
|
|
4610
|
+
settled = true;
|
|
4611
|
+
clearTimeout(timer);
|
|
4612
|
+
action();
|
|
4613
|
+
};
|
|
4614
|
+
const timer = setTimeout(() => {
|
|
4615
|
+
finish(() => {
|
|
4616
|
+
chunks.length = 0;
|
|
4617
|
+
req.pause();
|
|
4618
|
+
reject(new HTTPBodyUnreadError("no complete body within the read ceiling", timeoutMs));
|
|
4619
|
+
});
|
|
4620
|
+
}, timeoutMs);
|
|
4621
|
+
timer.unref?.();
|
|
4554
4622
|
req.on("data", (chunk) => {
|
|
4555
4623
|
size += chunk.length;
|
|
4556
4624
|
if (size > limit) {
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4625
|
+
finish(() => {
|
|
4626
|
+
chunks.length = 0;
|
|
4627
|
+
req.pause();
|
|
4628
|
+
reject(new HTTPPayloadTooLargeError(limit));
|
|
4629
|
+
});
|
|
4560
4630
|
return;
|
|
4561
4631
|
}
|
|
4562
4632
|
chunks.push(chunk);
|
|
4563
4633
|
});
|
|
4564
|
-
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
4565
|
-
req.on("
|
|
4634
|
+
req.on("end", () => finish(() => resolve(Buffer.concat(chunks).toString("utf8"))));
|
|
4635
|
+
req.on("close", () => finish(() => {
|
|
4636
|
+
chunks.length = 0;
|
|
4637
|
+
reject(new HTTPBodyUnreadError("the connection closed before the body ended", timeoutMs));
|
|
4638
|
+
}));
|
|
4639
|
+
req.on("error", (error) => finish(() => reject(error)));
|
|
4566
4640
|
});
|
|
4567
4641
|
}
|
|
4568
4642
|
export {
|
|
4643
|
+
DEFAULT_BODY_READ_TIMEOUT_MS,
|
|
4569
4644
|
DEFAULT_GATEWAY_HOST,
|
|
4570
4645
|
DEFAULT_GATEWAY_PORT,
|
|
4571
4646
|
EVIDENCE_FANOUT_BUDGET_MS,
|
package/dist/index.js
CHANGED
|
@@ -769,14 +769,21 @@ function attributeCapability(engineMethod, query = {}) {
|
|
|
769
769
|
};
|
|
770
770
|
} catch (error) {
|
|
771
771
|
if (error instanceof KernelProviderError) {
|
|
772
|
-
return {
|
|
772
|
+
return {
|
|
773
|
+
status: "refused",
|
|
774
|
+
error: {
|
|
775
|
+
code: GP_E_INTERNAL,
|
|
776
|
+
message: `provider registration refused (${error.code}): ${error.message}`,
|
|
777
|
+
remedy: error.remedy
|
|
778
|
+
}
|
|
779
|
+
};
|
|
773
780
|
}
|
|
774
781
|
if (error instanceof KernelSchemaError) {
|
|
775
782
|
return {
|
|
776
783
|
status: "refused",
|
|
777
784
|
error: {
|
|
778
|
-
code:
|
|
779
|
-
message:
|
|
785
|
+
code: GP_E_INTERNAL,
|
|
786
|
+
message: `the kernel refused a registration this product writes itself (${error.code}): ${error.message} \u2014 built with the version from ${registrationVersionSource(measured, usable(own))}`,
|
|
780
787
|
remedy: "fix shellProviderDrafts in mcp-shell/src/providers.ts; it is the contract both harnesses read, so it cannot be relaxed at a call site \u2014 run node scripts/check-kernel-corpus.mjs and compare against kernel/fixtures/provider-registration.ok-01.json"
|
|
781
788
|
}
|
|
782
789
|
};
|
|
@@ -1449,6 +1456,7 @@ import net from "node:net";
|
|
|
1449
1456
|
import os2 from "node:os";
|
|
1450
1457
|
import path3 from "node:path";
|
|
1451
1458
|
import fs4 from "node:fs";
|
|
1459
|
+
import { createHash } from "node:crypto";
|
|
1452
1460
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1453
1461
|
function daemonRestoreCommand() {
|
|
1454
1462
|
const here = path3.dirname(fileURLToPath3(import.meta.url));
|
|
@@ -2053,9 +2061,9 @@ var EngineJsonRpcClient = class {
|
|
|
2053
2061
|
);
|
|
2054
2062
|
return;
|
|
2055
2063
|
}
|
|
2056
|
-
const
|
|
2064
|
+
const bodyJson = jsonOrString(frame.result);
|
|
2057
2065
|
this.report(
|
|
2058
|
-
`late engine reply for '${entry.method}' (id ${entry.id}), ${entry.deadlineMs}ms deadline already reported: the engine did answer,
|
|
2066
|
+
`late engine reply for '${entry.method}' (id ${entry.id}), ${entry.deadlineMs}ms deadline already reported: the engine did answer, ${lateReplyBodyNote(bodyJson, operationIdOf(frame.result))}${byOrder}`
|
|
2059
2067
|
);
|
|
2060
2068
|
if (frame.result === void 0) {
|
|
2061
2069
|
return;
|
|
@@ -2210,6 +2218,13 @@ function attribute(error, entry, inferred = true) {
|
|
|
2210
2218
|
function truncate(text) {
|
|
2211
2219
|
return text.length <= LOG_BODY_CHARS ? text : `${text.slice(0, LOG_BODY_CHARS)}\u2026 [+${text.length - LOG_BODY_CHARS} chars not logged]`;
|
|
2212
2220
|
}
|
|
2221
|
+
var ARCHIVE_OPERATION_ID = /^op_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
2222
|
+
function lateReplyBodyNote(serializedBody, operationId) {
|
|
2223
|
+
const bytes = Buffer.byteLength(serializedBody, "utf8");
|
|
2224
|
+
const digest = createHash("sha256").update(serializedBody, "utf8").digest("hex").slice(0, 12);
|
|
2225
|
+
const named2 = typeof operationId === "string" && ARCHIVE_OPERATION_ID.test(operationId) ? `, for ${operationId}` : "";
|
|
2226
|
+
return `with a ${bytes}-byte body (sha256 ${digest})${named2} that is on-screen accessibility content \u2014 element titles and values, sometimes a document's text \u2014 so its text is not written to this log; the reply itself is in the daemon's evidence archive under the state root, read it back with gp_last_evidence {operationId} or gp_export_evidence {operationId}`;
|
|
2227
|
+
}
|
|
2213
2228
|
function nonBlankOr(value, fallback) {
|
|
2214
2229
|
if (value === void 0 || value === null) {
|
|
2215
2230
|
return fallback;
|
|
@@ -2220,6 +2235,9 @@ function nonBlankOr(value, fallback) {
|
|
|
2220
2235
|
function jsonOrString(value) {
|
|
2221
2236
|
return String(JSON.stringify(value));
|
|
2222
2237
|
}
|
|
2238
|
+
function operationIdOf(result) {
|
|
2239
|
+
return typeof result === "object" && result !== null ? result.operationId : void 0;
|
|
2240
|
+
}
|
|
2223
2241
|
var ReconnectingSocketIo = class {
|
|
2224
2242
|
constructor(socketPath, open) {
|
|
2225
2243
|
this.socketPath = socketPath;
|
|
@@ -2764,7 +2782,7 @@ function exponentialFromDigits(digits, power) {
|
|
|
2764
2782
|
import fs5 from "node:fs";
|
|
2765
2783
|
import os3 from "node:os";
|
|
2766
2784
|
import path4 from "node:path";
|
|
2767
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2785
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
2768
2786
|
import { z as z9 } from "zod";
|
|
2769
2787
|
var CROCKFORD_BODY_RE = "[0-9A-HJKMNP-TV-Z]{26}";
|
|
2770
2788
|
var PROJECT_ID_RE = new RegExp(`^prj_${CROCKFORD_BODY_RE}$`);
|
|
@@ -3068,7 +3086,7 @@ function registryDigest(filePath) {
|
|
|
3068
3086
|
return registryBytesDigest(bytes);
|
|
3069
3087
|
}
|
|
3070
3088
|
function registryBytesDigest(bytes) {
|
|
3071
|
-
return
|
|
3089
|
+
return createHash2("sha256").update(bytes).digest("hex");
|
|
3072
3090
|
}
|
|
3073
3091
|
function registryDriftReason(filePath, openedAs, now) {
|
|
3074
3092
|
if (now === openedAs) {
|
|
@@ -4819,7 +4837,7 @@ var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
|
4819
4837
|
"2025-03-26",
|
|
4820
4838
|
MCP_PROTOCOL_VERSION
|
|
4821
4839
|
];
|
|
4822
|
-
var SERVER_INFO = { name: "glasspane-mcp", version: "1.
|
|
4840
|
+
var SERVER_INFO = { name: "glasspane-mcp", version: "1.24.0" };
|
|
4823
4841
|
var PARSE_ERROR = -32700;
|
|
4824
4842
|
var INVALID_REQUEST = -32600;
|
|
4825
4843
|
var METHOD_NOT_FOUND = -32601;
|
package/dist/stdio-session.js
CHANGED
|
@@ -764,14 +764,21 @@ function attributeCapability(engineMethod, query = {}) {
|
|
|
764
764
|
};
|
|
765
765
|
} catch (error) {
|
|
766
766
|
if (error instanceof KernelProviderError) {
|
|
767
|
-
return {
|
|
767
|
+
return {
|
|
768
|
+
status: "refused",
|
|
769
|
+
error: {
|
|
770
|
+
code: GP_E_INTERNAL,
|
|
771
|
+
message: `provider registration refused (${error.code}): ${error.message}`,
|
|
772
|
+
remedy: error.remedy
|
|
773
|
+
}
|
|
774
|
+
};
|
|
768
775
|
}
|
|
769
776
|
if (error instanceof KernelSchemaError) {
|
|
770
777
|
return {
|
|
771
778
|
status: "refused",
|
|
772
779
|
error: {
|
|
773
|
-
code:
|
|
774
|
-
message:
|
|
780
|
+
code: GP_E_INTERNAL,
|
|
781
|
+
message: `the kernel refused a registration this product writes itself (${error.code}): ${error.message} \u2014 built with the version from ${registrationVersionSource(measured, usable(own))}`,
|
|
775
782
|
remedy: "fix shellProviderDrafts in mcp-shell/src/providers.ts; it is the contract both harnesses read, so it cannot be relaxed at a call site \u2014 run node scripts/check-kernel-corpus.mjs and compare against kernel/fixtures/provider-registration.ok-01.json"
|
|
776
783
|
}
|
|
777
784
|
};
|
|
@@ -1444,6 +1451,7 @@ import net from "node:net";
|
|
|
1444
1451
|
import os2 from "node:os";
|
|
1445
1452
|
import path3 from "node:path";
|
|
1446
1453
|
import fs4 from "node:fs";
|
|
1454
|
+
import { createHash } from "node:crypto";
|
|
1447
1455
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1448
1456
|
function daemonRestoreCommand() {
|
|
1449
1457
|
const here = path3.dirname(fileURLToPath3(import.meta.url));
|
|
@@ -2048,9 +2056,9 @@ var EngineJsonRpcClient = class {
|
|
|
2048
2056
|
);
|
|
2049
2057
|
return;
|
|
2050
2058
|
}
|
|
2051
|
-
const
|
|
2059
|
+
const bodyJson = jsonOrString(frame.result);
|
|
2052
2060
|
this.report(
|
|
2053
|
-
`late engine reply for '${entry.method}' (id ${entry.id}), ${entry.deadlineMs}ms deadline already reported: the engine did answer,
|
|
2061
|
+
`late engine reply for '${entry.method}' (id ${entry.id}), ${entry.deadlineMs}ms deadline already reported: the engine did answer, ${lateReplyBodyNote(bodyJson, operationIdOf(frame.result))}${byOrder}`
|
|
2054
2062
|
);
|
|
2055
2063
|
if (frame.result === void 0) {
|
|
2056
2064
|
return;
|
|
@@ -2205,6 +2213,13 @@ function attribute(error, entry, inferred = true) {
|
|
|
2205
2213
|
function truncate(text) {
|
|
2206
2214
|
return text.length <= LOG_BODY_CHARS ? text : `${text.slice(0, LOG_BODY_CHARS)}\u2026 [+${text.length - LOG_BODY_CHARS} chars not logged]`;
|
|
2207
2215
|
}
|
|
2216
|
+
var ARCHIVE_OPERATION_ID = /^op_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
2217
|
+
function lateReplyBodyNote(serializedBody, operationId) {
|
|
2218
|
+
const bytes = Buffer.byteLength(serializedBody, "utf8");
|
|
2219
|
+
const digest = createHash("sha256").update(serializedBody, "utf8").digest("hex").slice(0, 12);
|
|
2220
|
+
const named2 = typeof operationId === "string" && ARCHIVE_OPERATION_ID.test(operationId) ? `, for ${operationId}` : "";
|
|
2221
|
+
return `with a ${bytes}-byte body (sha256 ${digest})${named2} that is on-screen accessibility content \u2014 element titles and values, sometimes a document's text \u2014 so its text is not written to this log; the reply itself is in the daemon's evidence archive under the state root, read it back with gp_last_evidence {operationId} or gp_export_evidence {operationId}`;
|
|
2222
|
+
}
|
|
2208
2223
|
function nonBlankOr(value, fallback) {
|
|
2209
2224
|
if (value === void 0 || value === null) {
|
|
2210
2225
|
return fallback;
|
|
@@ -2215,6 +2230,9 @@ function nonBlankOr(value, fallback) {
|
|
|
2215
2230
|
function jsonOrString(value) {
|
|
2216
2231
|
return String(JSON.stringify(value));
|
|
2217
2232
|
}
|
|
2233
|
+
function operationIdOf(result) {
|
|
2234
|
+
return typeof result === "object" && result !== null ? result.operationId : void 0;
|
|
2235
|
+
}
|
|
2218
2236
|
var ReconnectingSocketIo = class {
|
|
2219
2237
|
constructor(socketPath, open) {
|
|
2220
2238
|
this.socketPath = socketPath;
|
|
@@ -2759,7 +2777,7 @@ function exponentialFromDigits(digits, power) {
|
|
|
2759
2777
|
import fs5 from "node:fs";
|
|
2760
2778
|
import os3 from "node:os";
|
|
2761
2779
|
import path4 from "node:path";
|
|
2762
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2780
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
2763
2781
|
import { z as z9 } from "zod";
|
|
2764
2782
|
var CROCKFORD_BODY_RE = "[0-9A-HJKMNP-TV-Z]{26}";
|
|
2765
2783
|
var PROJECT_ID_RE = new RegExp(`^prj_${CROCKFORD_BODY_RE}$`);
|
|
@@ -3063,7 +3081,7 @@ function registryDigest(filePath) {
|
|
|
3063
3081
|
return registryBytesDigest(bytes);
|
|
3064
3082
|
}
|
|
3065
3083
|
function registryBytesDigest(bytes) {
|
|
3066
|
-
return
|
|
3084
|
+
return createHash2("sha256").update(bytes).digest("hex");
|
|
3067
3085
|
}
|
|
3068
3086
|
function registryDriftReason(filePath, openedAs, now) {
|
|
3069
3087
|
if (now === openedAs) {
|
|
@@ -4814,7 +4832,7 @@ var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
|
4814
4832
|
"2025-03-26",
|
|
4815
4833
|
MCP_PROTOCOL_VERSION
|
|
4816
4834
|
];
|
|
4817
|
-
var SERVER_INFO = { name: "glasspane-mcp", version: "1.
|
|
4835
|
+
var SERVER_INFO = { name: "glasspane-mcp", version: "1.24.0" };
|
|
4818
4836
|
var PARSE_ERROR = -32700;
|
|
4819
4837
|
var INVALID_REQUEST = -32600;
|
|
4820
4838
|
var METHOD_NOT_FOUND = -32601;
|
package/package.json
CHANGED