@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/server.js
CHANGED
|
@@ -1142,14 +1142,14 @@ var require_util = __commonJS({
|
|
|
1142
1142
|
}
|
|
1143
1143
|
const port = url2.port != null ? url2.port : url2.protocol === "https:" ? 443 : 80;
|
|
1144
1144
|
let origin = url2.origin != null ? url2.origin : `${url2.protocol || ""}//${url2.hostname || ""}:${port}`;
|
|
1145
|
-
let
|
|
1145
|
+
let path26 = url2.path != null ? url2.path : `${url2.pathname || ""}${url2.search || ""}`;
|
|
1146
1146
|
if (origin[origin.length - 1] === "/") {
|
|
1147
1147
|
origin = origin.slice(0, origin.length - 1);
|
|
1148
1148
|
}
|
|
1149
|
-
if (
|
|
1150
|
-
|
|
1149
|
+
if (path26 && path26[0] !== "/") {
|
|
1150
|
+
path26 = `/${path26}`;
|
|
1151
1151
|
}
|
|
1152
|
-
return new URL(`${origin}${
|
|
1152
|
+
return new URL(`${origin}${path26}`);
|
|
1153
1153
|
}
|
|
1154
1154
|
if (!isHttpOrHttpsPrefixed(url2.origin || url2.protocol)) {
|
|
1155
1155
|
throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`.");
|
|
@@ -2020,9 +2020,9 @@ var require_diagnostics = __commonJS({
|
|
|
2020
2020
|
"undici:client:sendHeaders",
|
|
2021
2021
|
(evt) => {
|
|
2022
2022
|
const {
|
|
2023
|
-
request: { method, path:
|
|
2023
|
+
request: { method, path: path26, origin }
|
|
2024
2024
|
} = evt;
|
|
2025
|
-
debugLog("sending request to %s %s%s", method, origin,
|
|
2025
|
+
debugLog("sending request to %s %s%s", method, origin, path26);
|
|
2026
2026
|
}
|
|
2027
2027
|
);
|
|
2028
2028
|
}
|
|
@@ -2040,14 +2040,14 @@ var require_diagnostics = __commonJS({
|
|
|
2040
2040
|
"undici:request:headers",
|
|
2041
2041
|
(evt) => {
|
|
2042
2042
|
const {
|
|
2043
|
-
request: { method, path:
|
|
2043
|
+
request: { method, path: path26, origin },
|
|
2044
2044
|
response: { statusCode }
|
|
2045
2045
|
} = evt;
|
|
2046
2046
|
debugLog(
|
|
2047
2047
|
"received response to %s %s%s - HTTP %d",
|
|
2048
2048
|
method,
|
|
2049
2049
|
origin,
|
|
2050
|
-
|
|
2050
|
+
path26,
|
|
2051
2051
|
statusCode
|
|
2052
2052
|
);
|
|
2053
2053
|
}
|
|
@@ -2056,23 +2056,23 @@ var require_diagnostics = __commonJS({
|
|
|
2056
2056
|
"undici:request:trailers",
|
|
2057
2057
|
(evt) => {
|
|
2058
2058
|
const {
|
|
2059
|
-
request: { method, path:
|
|
2059
|
+
request: { method, path: path26, origin }
|
|
2060
2060
|
} = evt;
|
|
2061
|
-
debugLog("trailers received from %s %s%s", method, origin,
|
|
2061
|
+
debugLog("trailers received from %s %s%s", method, origin, path26);
|
|
2062
2062
|
}
|
|
2063
2063
|
);
|
|
2064
2064
|
diagnosticsChannel.subscribe(
|
|
2065
2065
|
"undici:request:error",
|
|
2066
2066
|
(evt) => {
|
|
2067
2067
|
const {
|
|
2068
|
-
request: { method, path:
|
|
2068
|
+
request: { method, path: path26, origin },
|
|
2069
2069
|
error: error51
|
|
2070
2070
|
} = evt;
|
|
2071
2071
|
debugLog(
|
|
2072
2072
|
"request to %s %s%s errored - %s",
|
|
2073
2073
|
method,
|
|
2074
2074
|
origin,
|
|
2075
|
-
|
|
2075
|
+
path26,
|
|
2076
2076
|
error51.message
|
|
2077
2077
|
);
|
|
2078
2078
|
}
|
|
@@ -2227,7 +2227,7 @@ var require_request = __commonJS({
|
|
|
2227
2227
|
};
|
|
2228
2228
|
var Request = class {
|
|
2229
2229
|
constructor(origin, {
|
|
2230
|
-
path:
|
|
2230
|
+
path: path26,
|
|
2231
2231
|
method,
|
|
2232
2232
|
body,
|
|
2233
2233
|
headers,
|
|
@@ -2244,11 +2244,11 @@ var require_request = __commonJS({
|
|
|
2244
2244
|
maxRedirections,
|
|
2245
2245
|
typeOfService
|
|
2246
2246
|
}, handler) {
|
|
2247
|
-
if (typeof
|
|
2247
|
+
if (typeof path26 !== "string") {
|
|
2248
2248
|
throw new InvalidArgumentError("path must be a string");
|
|
2249
|
-
} else if (
|
|
2249
|
+
} else if (path26[0] !== "/" && !(path26.startsWith("http://") || path26.startsWith("https://")) && method !== "CONNECT") {
|
|
2250
2250
|
throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
|
|
2251
|
-
} else if (invalidPathRegex.test(
|
|
2251
|
+
} else if (invalidPathRegex.test(path26)) {
|
|
2252
2252
|
throw new InvalidArgumentError("invalid request path");
|
|
2253
2253
|
}
|
|
2254
2254
|
if (typeof method !== "string") {
|
|
@@ -2323,7 +2323,7 @@ var require_request = __commonJS({
|
|
|
2323
2323
|
this.completed = false;
|
|
2324
2324
|
this.aborted = false;
|
|
2325
2325
|
this.upgrade = upgrade || null;
|
|
2326
|
-
this.path = query ? serializePathWithQuery(
|
|
2326
|
+
this.path = query ? serializePathWithQuery(path26, query) : path26;
|
|
2327
2327
|
this.origin = origin;
|
|
2328
2328
|
this.protocol = getProtocolFromUrlString(origin);
|
|
2329
2329
|
this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" || method === "QUERY" : idempotent;
|
|
@@ -7409,7 +7409,7 @@ var require_client_h1 = __commonJS({
|
|
|
7409
7409
|
}
|
|
7410
7410
|
}
|
|
7411
7411
|
function writeH1(client, request) {
|
|
7412
|
-
const { method, path:
|
|
7412
|
+
const { method, path: path26, host, upgrade, blocking, reset } = request;
|
|
7413
7413
|
let { body, headers, contentLength } = request;
|
|
7414
7414
|
const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH";
|
|
7415
7415
|
if (util.isFormDataLike(body)) {
|
|
@@ -7485,7 +7485,7 @@ var require_client_h1 = __commonJS({
|
|
|
7485
7485
|
socket[kBlocking] = true;
|
|
7486
7486
|
}
|
|
7487
7487
|
setTypeOfService(socket, request);
|
|
7488
|
-
let header = `${method} ${
|
|
7488
|
+
let header = `${method} ${path26} HTTP/1.1\r
|
|
7489
7489
|
`;
|
|
7490
7490
|
if (typeof host === "string") {
|
|
7491
7491
|
header += `host: ${host}\r
|
|
@@ -8566,7 +8566,7 @@ var require_client_h2 = __commonJS({
|
|
|
8566
8566
|
const headersTimeout = request.headersTimeout ?? client[kHeadersTimeout];
|
|
8567
8567
|
const bodyTimeout = request.bodyTimeout ?? client[kBodyTimeout];
|
|
8568
8568
|
const session = client[kHTTP2Session];
|
|
8569
|
-
const { method, path:
|
|
8569
|
+
const { method, path: path26, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request;
|
|
8570
8570
|
if (upgrade != null && upgrade !== "websocket") {
|
|
8571
8571
|
util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`));
|
|
8572
8572
|
return false;
|
|
@@ -8629,7 +8629,7 @@ var require_client_h2 = __commonJS({
|
|
|
8629
8629
|
}
|
|
8630
8630
|
headers[HTTP2_HEADER_METHOD] = "CONNECT";
|
|
8631
8631
|
headers[HTTP2_HEADER_PROTOCOL] = "websocket";
|
|
8632
|
-
headers[HTTP2_HEADER_PATH] =
|
|
8632
|
+
headers[HTTP2_HEADER_PATH] = path26;
|
|
8633
8633
|
if (protocol === "ws:" || protocol === "wss:") {
|
|
8634
8634
|
headers[HTTP2_HEADER_SCHEME] = protocol === "ws:" ? "http" : "https";
|
|
8635
8635
|
} else {
|
|
@@ -8651,7 +8651,7 @@ var require_client_h2 = __commonJS({
|
|
|
8651
8651
|
setupUpgradeStream(stream, state);
|
|
8652
8652
|
return true;
|
|
8653
8653
|
}
|
|
8654
|
-
headers[HTTP2_HEADER_PATH] =
|
|
8654
|
+
headers[HTTP2_HEADER_PATH] = path26;
|
|
8655
8655
|
headers[HTTP2_HEADER_SCHEME] = protocol === "http:" ? "http" : "https";
|
|
8656
8656
|
const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH";
|
|
8657
8657
|
let body = state.body;
|
|
@@ -11321,10 +11321,10 @@ var require_proxy_agent = __commonJS({
|
|
|
11321
11321
|
};
|
|
11322
11322
|
const {
|
|
11323
11323
|
origin,
|
|
11324
|
-
path:
|
|
11324
|
+
path: path26 = "/",
|
|
11325
11325
|
headers = {}
|
|
11326
11326
|
} = opts;
|
|
11327
|
-
opts.path = origin +
|
|
11327
|
+
opts.path = origin + path26;
|
|
11328
11328
|
if (!("host" in headers) && !("Host" in headers)) {
|
|
11329
11329
|
const { host } = new URL(origin);
|
|
11330
11330
|
headers.host = host;
|
|
@@ -13589,20 +13589,20 @@ var require_mock_utils = __commonJS({
|
|
|
13589
13589
|
}
|
|
13590
13590
|
return normalizedQp;
|
|
13591
13591
|
}
|
|
13592
|
-
function safeUrl(
|
|
13593
|
-
if (typeof
|
|
13594
|
-
return
|
|
13592
|
+
function safeUrl(path26) {
|
|
13593
|
+
if (typeof path26 !== "string") {
|
|
13594
|
+
return path26;
|
|
13595
13595
|
}
|
|
13596
|
-
const pathSegments =
|
|
13596
|
+
const pathSegments = path26.split("?", 3);
|
|
13597
13597
|
if (pathSegments.length !== 2) {
|
|
13598
|
-
return
|
|
13598
|
+
return path26;
|
|
13599
13599
|
}
|
|
13600
13600
|
const qp = new URLSearchParams(pathSegments.pop());
|
|
13601
13601
|
qp.sort();
|
|
13602
13602
|
return [...pathSegments, qp.toString()].join("?");
|
|
13603
13603
|
}
|
|
13604
|
-
function matchKey(mockDispatch2, { path:
|
|
13605
|
-
const pathMatch = matchValue(mockDispatch2.path,
|
|
13604
|
+
function matchKey(mockDispatch2, { path: path26, method, body, headers }) {
|
|
13605
|
+
const pathMatch = matchValue(mockDispatch2.path, path26);
|
|
13606
13606
|
const methodMatch = matchValue(mockDispatch2.method, method);
|
|
13607
13607
|
const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
|
|
13608
13608
|
const headersMatch = matchHeaders(mockDispatch2, headers);
|
|
@@ -13629,8 +13629,8 @@ var require_mock_utils = __commonJS({
|
|
|
13629
13629
|
const basePath = key.query ? serializePathWithQuery(key.path, key.query) : key.path;
|
|
13630
13630
|
const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
|
|
13631
13631
|
const resolvedPathWithoutTrailingSlash = removeTrailingSlash(resolvedPath);
|
|
13632
|
-
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path:
|
|
13633
|
-
return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(
|
|
13632
|
+
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path26, ignoreTrailingSlash }) => {
|
|
13633
|
+
return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path26)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path26), resolvedPath);
|
|
13634
13634
|
});
|
|
13635
13635
|
if (matchedMockDispatches.length === 0) {
|
|
13636
13636
|
throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
|
|
@@ -13669,22 +13669,22 @@ var require_mock_utils = __commonJS({
|
|
|
13669
13669
|
mockDispatches.splice(index, 1);
|
|
13670
13670
|
}
|
|
13671
13671
|
}
|
|
13672
|
-
function removeTrailingSlash(
|
|
13673
|
-
if (typeof
|
|
13674
|
-
return
|
|
13672
|
+
function removeTrailingSlash(path26) {
|
|
13673
|
+
if (typeof path26 !== "string") {
|
|
13674
|
+
return path26;
|
|
13675
13675
|
}
|
|
13676
|
-
while (
|
|
13677
|
-
|
|
13676
|
+
while (path26.endsWith("/")) {
|
|
13677
|
+
path26 = path26.slice(0, -1);
|
|
13678
13678
|
}
|
|
13679
|
-
if (
|
|
13680
|
-
|
|
13679
|
+
if (path26.length === 0) {
|
|
13680
|
+
path26 = "/";
|
|
13681
13681
|
}
|
|
13682
|
-
return
|
|
13682
|
+
return path26;
|
|
13683
13683
|
}
|
|
13684
13684
|
function buildKey(opts) {
|
|
13685
|
-
const { path:
|
|
13685
|
+
const { path: path26, method, body, headers, query } = opts;
|
|
13686
13686
|
return {
|
|
13687
|
-
path:
|
|
13687
|
+
path: path26,
|
|
13688
13688
|
method,
|
|
13689
13689
|
body,
|
|
13690
13690
|
headers,
|
|
@@ -14555,10 +14555,10 @@ var require_pending_interceptors_formatter = __commonJS({
|
|
|
14555
14555
|
}
|
|
14556
14556
|
format(pendingInterceptors) {
|
|
14557
14557
|
const withPrettyHeaders = pendingInterceptors.map(
|
|
14558
|
-
({ method, path:
|
|
14558
|
+
({ method, path: path26, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
|
|
14559
14559
|
Method: method,
|
|
14560
14560
|
Origin: origin,
|
|
14561
|
-
Path:
|
|
14561
|
+
Path: path26,
|
|
14562
14562
|
"Status code": statusCode,
|
|
14563
14563
|
Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
|
|
14564
14564
|
Invocations: timesInvoked,
|
|
@@ -14640,9 +14640,9 @@ var require_mock_agent = __commonJS({
|
|
|
14640
14640
|
const acceptNonStandardSearchParameters = this[kMockAgentAcceptsNonStandardSearchParameters];
|
|
14641
14641
|
const dispatchOpts = { ...opts };
|
|
14642
14642
|
if (acceptNonStandardSearchParameters && dispatchOpts.path) {
|
|
14643
|
-
const [
|
|
14643
|
+
const [path26, searchParams] = dispatchOpts.path.split("?");
|
|
14644
14644
|
const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters);
|
|
14645
|
-
dispatchOpts.path = `${
|
|
14645
|
+
dispatchOpts.path = `${path26}?${normalizedSearchParams}`;
|
|
14646
14646
|
}
|
|
14647
14647
|
return this[kAgent].dispatch(dispatchOpts, handler);
|
|
14648
14648
|
}
|
|
@@ -14770,8 +14770,8 @@ var require_snapshot_utils = __commonJS({
|
|
|
14770
14770
|
match: new Set(matchHeaders.map((header) => caseSensitive ? header : header.toLowerCase()))
|
|
14771
14771
|
};
|
|
14772
14772
|
}
|
|
14773
|
-
var
|
|
14774
|
-
var hashId =
|
|
14773
|
+
var crypto7 = runtimeFeatures.has("crypto") ? __require("node:crypto") : null;
|
|
14774
|
+
var hashId = crypto7?.hash ? (value) => crypto7.hash("sha256", value, "base64url") : (value) => Buffer.from(value).toString("base64url");
|
|
14775
14775
|
function isUndiciHeaders(headers) {
|
|
14776
14776
|
return Array.isArray(headers) && (headers.length & 1) === 0;
|
|
14777
14777
|
}
|
|
@@ -15058,12 +15058,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
15058
15058
|
* @return {Promise<void>} - Resolves when snapshots are loaded
|
|
15059
15059
|
*/
|
|
15060
15060
|
async loadSnapshots(filePath) {
|
|
15061
|
-
const
|
|
15062
|
-
if (!
|
|
15061
|
+
const path26 = filePath || this.#snapshotPath;
|
|
15062
|
+
if (!path26) {
|
|
15063
15063
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
15064
15064
|
}
|
|
15065
15065
|
try {
|
|
15066
|
-
const data = await readFile2(resolve(
|
|
15066
|
+
const data = await readFile2(resolve(path26), "utf8");
|
|
15067
15067
|
const parsed = JSON.parse(data);
|
|
15068
15068
|
if (Array.isArray(parsed)) {
|
|
15069
15069
|
this.#snapshots.clear();
|
|
@@ -15077,7 +15077,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
15077
15077
|
if (error51.code === "ENOENT") {
|
|
15078
15078
|
this.#snapshots.clear();
|
|
15079
15079
|
} else {
|
|
15080
|
-
throw new UndiciError(`Failed to load snapshots from ${
|
|
15080
|
+
throw new UndiciError(`Failed to load snapshots from ${path26}`, { cause: error51 });
|
|
15081
15081
|
}
|
|
15082
15082
|
}
|
|
15083
15083
|
}
|
|
@@ -15088,11 +15088,11 @@ var require_snapshot_recorder = __commonJS({
|
|
|
15088
15088
|
* @returns {Promise<void>} - Resolves when snapshots are saved
|
|
15089
15089
|
*/
|
|
15090
15090
|
async saveSnapshots(filePath) {
|
|
15091
|
-
const
|
|
15092
|
-
if (!
|
|
15091
|
+
const path26 = filePath || this.#snapshotPath;
|
|
15092
|
+
if (!path26) {
|
|
15093
15093
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
15094
15094
|
}
|
|
15095
|
-
const resolvedPath = resolve(
|
|
15095
|
+
const resolvedPath = resolve(path26);
|
|
15096
15096
|
await mkdir(dirname(resolvedPath), { recursive: true });
|
|
15097
15097
|
const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot]) => ({
|
|
15098
15098
|
hash: hash2,
|
|
@@ -15729,15 +15729,15 @@ var require_redirect_handler = __commonJS({
|
|
|
15729
15729
|
return;
|
|
15730
15730
|
}
|
|
15731
15731
|
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
|
|
15732
|
-
const
|
|
15733
|
-
const redirectUrlString = `${origin}${
|
|
15732
|
+
const path26 = search ? `${pathname}${search}` : pathname;
|
|
15733
|
+
const redirectUrlString = `${origin}${path26}`;
|
|
15734
15734
|
for (const historyUrl of this.history) {
|
|
15735
15735
|
if (historyUrl.toString() === redirectUrlString) {
|
|
15736
15736
|
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.`);
|
|
15737
15737
|
}
|
|
15738
15738
|
}
|
|
15739
15739
|
this.opts.headers = cleanRequestHeaders(this.opts.headers, removeContentHeaders, this.opts.origin !== origin, this.stripHeadersOnRedirect, this.stripHeadersOnCrossOriginRedirect);
|
|
15740
|
-
this.opts.path =
|
|
15740
|
+
this.opts.path = path26;
|
|
15741
15741
|
this.opts.origin = origin;
|
|
15742
15742
|
this.opts.query = null;
|
|
15743
15743
|
}
|
|
@@ -17565,10 +17565,10 @@ var require_cache_handler = __commonJS({
|
|
|
17565
17565
|
}
|
|
17566
17566
|
return locationUrl.pathname + locationUrl.search;
|
|
17567
17567
|
}
|
|
17568
|
-
function deleteCachedUri(store, cacheKey,
|
|
17568
|
+
function deleteCachedUri(store, cacheKey, path26) {
|
|
17569
17569
|
deleteCachedValue(store, {
|
|
17570
17570
|
...cacheKey,
|
|
17571
|
-
path:
|
|
17571
|
+
path: path26
|
|
17572
17572
|
});
|
|
17573
17573
|
for (let i = 0; i < util.safeHTTPMethods.length; i++) {
|
|
17574
17574
|
const method = util.safeHTTPMethods[i];
|
|
@@ -17576,7 +17576,7 @@ var require_cache_handler = __commonJS({
|
|
|
17576
17576
|
deleteCachedValue(store, {
|
|
17577
17577
|
...cacheKey,
|
|
17578
17578
|
method,
|
|
17579
|
-
path:
|
|
17579
|
+
path: path26
|
|
17580
17580
|
});
|
|
17581
17581
|
}
|
|
17582
17582
|
}
|
|
@@ -17587,9 +17587,9 @@ var require_cache_handler = __commonJS({
|
|
|
17587
17587
|
}
|
|
17588
17588
|
const values = Array.isArray(headerValue) ? headerValue : [headerValue];
|
|
17589
17589
|
for (let i = 0; i < values.length; i++) {
|
|
17590
|
-
const
|
|
17591
|
-
if (
|
|
17592
|
-
deleteCachedUri(store, cacheKey,
|
|
17590
|
+
const path26 = getSameOriginPath(cacheKey, values[i]);
|
|
17591
|
+
if (path26 !== void 0) {
|
|
17592
|
+
deleteCachedUri(store, cacheKey, path26);
|
|
17593
17593
|
}
|
|
17594
17594
|
}
|
|
17595
17595
|
}
|
|
@@ -21462,10 +21462,10 @@ var require_subresource_integrity = __commonJS({
|
|
|
21462
21462
|
var assert2 = __require("node:assert");
|
|
21463
21463
|
var { runtimeFeatures } = require_runtime_features();
|
|
21464
21464
|
var validSRIHashAlgorithmTokenSet = /* @__PURE__ */ new Map([["sha256", 0], ["sha384", 1], ["sha512", 2]]);
|
|
21465
|
-
var
|
|
21465
|
+
var crypto7;
|
|
21466
21466
|
if (runtimeFeatures.has("crypto")) {
|
|
21467
|
-
|
|
21468
|
-
const cryptoHashes =
|
|
21467
|
+
crypto7 = __require("node:crypto");
|
|
21468
|
+
const cryptoHashes = crypto7.getHashes();
|
|
21469
21469
|
if (cryptoHashes.length === 0) {
|
|
21470
21470
|
validSRIHashAlgorithmTokenSet.clear();
|
|
21471
21471
|
}
|
|
@@ -21555,7 +21555,7 @@ var require_subresource_integrity = __commonJS({
|
|
|
21555
21555
|
return result;
|
|
21556
21556
|
}
|
|
21557
21557
|
var applyAlgorithmToBytes = (algorithm, bytes) => {
|
|
21558
|
-
return
|
|
21558
|
+
return crypto7.hash(algorithm, bytes, "base64");
|
|
21559
21559
|
};
|
|
21560
21560
|
function caseSensitiveMatch(actualValue, expectedValue) {
|
|
21561
21561
|
let actualValueLength = actualValue.length;
|
|
@@ -22586,13 +22586,13 @@ var require_fetch = __commonJS({
|
|
|
22586
22586
|
function dispatch({ body }) {
|
|
22587
22587
|
const url2 = requestCurrentURL(request);
|
|
22588
22588
|
const agent = fetchParams.controller.dispatcher;
|
|
22589
|
-
const
|
|
22589
|
+
const path26 = url2.pathname + url2.search;
|
|
22590
22590
|
const hasTrailingQuestionMark = url2.search.length === 0 && url2.href[url2.href.length - url2.hash.length - 1] === "?";
|
|
22591
22591
|
return dispatchWithProtocolPreference(body);
|
|
22592
22592
|
function dispatchWithProtocolPreference(body2, allowH2) {
|
|
22593
22593
|
return new Promise((resolve, reject2) => agent.dispatch(
|
|
22594
22594
|
{
|
|
22595
|
-
path: hasTrailingQuestionMark ? `${
|
|
22595
|
+
path: hasTrailingQuestionMark ? `${path26}?` : path26,
|
|
22596
22596
|
origin: url2.origin,
|
|
22597
22597
|
method: request.method,
|
|
22598
22598
|
body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body2,
|
|
@@ -23504,9 +23504,9 @@ var require_util4 = __commonJS({
|
|
|
23504
23504
|
}
|
|
23505
23505
|
}
|
|
23506
23506
|
}
|
|
23507
|
-
function validateCookiePath(
|
|
23508
|
-
for (let i = 0; i <
|
|
23509
|
-
const code =
|
|
23507
|
+
function validateCookiePath(path26) {
|
|
23508
|
+
for (let i = 0; i < path26.length; ++i) {
|
|
23509
|
+
const code = path26.charCodeAt(i);
|
|
23510
23510
|
if (code < 32 || // exclude CTLs (0-31)
|
|
23511
23511
|
code > 126 || // exclude non-ascii and DEL
|
|
23512
23512
|
code === 59) {
|
|
@@ -24538,7 +24538,7 @@ var require_connection = __commonJS({
|
|
|
24538
24538
|
var { WebsocketFrameSend } = require_frame();
|
|
24539
24539
|
var assert2 = __require("node:assert");
|
|
24540
24540
|
var { runtimeFeatures } = require_runtime_features();
|
|
24541
|
-
var
|
|
24541
|
+
var crypto7 = runtimeFeatures.has("crypto") ? __require("node:crypto") : null;
|
|
24542
24542
|
var warningEmitted = false;
|
|
24543
24543
|
function establishWebSocketConnection(url2, protocols, client, handler, options) {
|
|
24544
24544
|
const requestURL = url2;
|
|
@@ -24558,7 +24558,7 @@ var require_connection = __commonJS({
|
|
|
24558
24558
|
const headersList = getHeadersList(new Headers2(options.headers));
|
|
24559
24559
|
request.headersList = headersList;
|
|
24560
24560
|
}
|
|
24561
|
-
const keyValue =
|
|
24561
|
+
const keyValue = crypto7.randomBytes(16).toString("base64");
|
|
24562
24562
|
request.headersList.append("sec-websocket-key", keyValue, true);
|
|
24563
24563
|
request.headersList.append("sec-websocket-version", "13", true);
|
|
24564
24564
|
for (const protocol of protocols) {
|
|
@@ -24598,7 +24598,7 @@ var require_connection = __commonJS({
|
|
|
24598
24598
|
return;
|
|
24599
24599
|
}
|
|
24600
24600
|
const secWSAccept = response.headersList.get("Sec-WebSocket-Accept");
|
|
24601
|
-
const digest =
|
|
24601
|
+
const digest = crypto7.hash("sha1", keyValue + uid, "base64");
|
|
24602
24602
|
if (secWSAccept !== digest) {
|
|
24603
24603
|
failWebsocketConnection(handler, 1002, "Incorrect hash received in Sec-WebSocket-Accept header.");
|
|
24604
24604
|
return;
|
|
@@ -26878,11 +26878,11 @@ var require_undici = __commonJS({
|
|
|
26878
26878
|
if (typeof opts.path !== "string") {
|
|
26879
26879
|
throw new InvalidArgumentError("invalid opts.path");
|
|
26880
26880
|
}
|
|
26881
|
-
let
|
|
26881
|
+
let path26 = opts.path;
|
|
26882
26882
|
if (!opts.path.startsWith("/")) {
|
|
26883
|
-
|
|
26883
|
+
path26 = `/${path26}`;
|
|
26884
26884
|
}
|
|
26885
|
-
url2 = new URL(util.parseOrigin(url2).origin +
|
|
26885
|
+
url2 = new URL(util.parseOrigin(url2).origin + path26);
|
|
26886
26886
|
} else {
|
|
26887
26887
|
if (!opts) {
|
|
26888
26888
|
opts = typeof url2 === "object" ? url2 : {};
|
|
@@ -30187,8 +30187,8 @@ var require_utils2 = __commonJS({
|
|
|
30187
30187
|
}
|
|
30188
30188
|
return ind;
|
|
30189
30189
|
}
|
|
30190
|
-
function removeDotSegments(
|
|
30191
|
-
let input =
|
|
30190
|
+
function removeDotSegments(path26) {
|
|
30191
|
+
let input = path26;
|
|
30192
30192
|
const output = [];
|
|
30193
30193
|
let nextSlash = -1;
|
|
30194
30194
|
let len = 0;
|
|
@@ -30440,8 +30440,8 @@ var require_schemes = __commonJS({
|
|
|
30440
30440
|
wsComponent.secure = void 0;
|
|
30441
30441
|
}
|
|
30442
30442
|
if (wsComponent.resourceName) {
|
|
30443
|
-
const [
|
|
30444
|
-
wsComponent.path =
|
|
30443
|
+
const [path26, query] = wsComponent.resourceName.split("?");
|
|
30444
|
+
wsComponent.path = path26 && path26 !== "/" ? path26 : void 0;
|
|
30445
30445
|
wsComponent.query = query;
|
|
30446
30446
|
wsComponent.resourceName = void 0;
|
|
30447
30447
|
}
|
|
@@ -33840,12 +33840,12 @@ var require_dist = __commonJS({
|
|
|
33840
33840
|
throw new Error(`Unknown format "${name}"`);
|
|
33841
33841
|
return f;
|
|
33842
33842
|
};
|
|
33843
|
-
function addFormats(ajv, list,
|
|
33843
|
+
function addFormats(ajv, list, fs22, exportName) {
|
|
33844
33844
|
var _a3;
|
|
33845
33845
|
var _b;
|
|
33846
33846
|
(_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
33847
33847
|
for (const f of list)
|
|
33848
|
-
ajv.addFormat(f,
|
|
33848
|
+
ajv.addFormat(f, fs22[f]);
|
|
33849
33849
|
}
|
|
33850
33850
|
module.exports = exports = formatsPlugin;
|
|
33851
33851
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -33858,8 +33858,8 @@ var require_windows = __commonJS({
|
|
|
33858
33858
|
"../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module) {
|
|
33859
33859
|
module.exports = isexe;
|
|
33860
33860
|
isexe.sync = sync;
|
|
33861
|
-
var
|
|
33862
|
-
function checkPathExt(
|
|
33861
|
+
var fs22 = __require("fs");
|
|
33862
|
+
function checkPathExt(path26, options) {
|
|
33863
33863
|
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
33864
33864
|
if (!pathext) {
|
|
33865
33865
|
return true;
|
|
@@ -33870,25 +33870,25 @@ var require_windows = __commonJS({
|
|
|
33870
33870
|
}
|
|
33871
33871
|
for (var i = 0; i < pathext.length; i++) {
|
|
33872
33872
|
var p = pathext[i].toLowerCase();
|
|
33873
|
-
if (p &&
|
|
33873
|
+
if (p && path26.substr(-p.length).toLowerCase() === p) {
|
|
33874
33874
|
return true;
|
|
33875
33875
|
}
|
|
33876
33876
|
}
|
|
33877
33877
|
return false;
|
|
33878
33878
|
}
|
|
33879
|
-
function checkStat(stat,
|
|
33879
|
+
function checkStat(stat, path26, options) {
|
|
33880
33880
|
if (!stat.isSymbolicLink() && !stat.isFile()) {
|
|
33881
33881
|
return false;
|
|
33882
33882
|
}
|
|
33883
|
-
return checkPathExt(
|
|
33883
|
+
return checkPathExt(path26, options);
|
|
33884
33884
|
}
|
|
33885
|
-
function isexe(
|
|
33886
|
-
|
|
33887
|
-
cb(er, er ? false : checkStat(stat,
|
|
33885
|
+
function isexe(path26, options, cb) {
|
|
33886
|
+
fs22.stat(path26, function(er, stat) {
|
|
33887
|
+
cb(er, er ? false : checkStat(stat, path26, options));
|
|
33888
33888
|
});
|
|
33889
33889
|
}
|
|
33890
|
-
function sync(
|
|
33891
|
-
return checkStat(
|
|
33890
|
+
function sync(path26, options) {
|
|
33891
|
+
return checkStat(fs22.statSync(path26), path26, options);
|
|
33892
33892
|
}
|
|
33893
33893
|
}
|
|
33894
33894
|
});
|
|
@@ -33898,14 +33898,14 @@ var require_mode = __commonJS({
|
|
|
33898
33898
|
"../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module) {
|
|
33899
33899
|
module.exports = isexe;
|
|
33900
33900
|
isexe.sync = sync;
|
|
33901
|
-
var
|
|
33902
|
-
function isexe(
|
|
33903
|
-
|
|
33901
|
+
var fs22 = __require("fs");
|
|
33902
|
+
function isexe(path26, options, cb) {
|
|
33903
|
+
fs22.stat(path26, function(er, stat) {
|
|
33904
33904
|
cb(er, er ? false : checkStat(stat, options));
|
|
33905
33905
|
});
|
|
33906
33906
|
}
|
|
33907
|
-
function sync(
|
|
33908
|
-
return checkStat(
|
|
33907
|
+
function sync(path26, options) {
|
|
33908
|
+
return checkStat(fs22.statSync(path26), options);
|
|
33909
33909
|
}
|
|
33910
33910
|
function checkStat(stat, options) {
|
|
33911
33911
|
return stat.isFile() && checkMode(stat, options);
|
|
@@ -33929,7 +33929,7 @@ var require_mode = __commonJS({
|
|
|
33929
33929
|
// ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
|
|
33930
33930
|
var require_isexe = __commonJS({
|
|
33931
33931
|
"../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
|
|
33932
|
-
var
|
|
33932
|
+
var fs22 = __require("fs");
|
|
33933
33933
|
var core;
|
|
33934
33934
|
if (process.platform === "win32" || global.TESTING_WINDOWS) {
|
|
33935
33935
|
core = require_windows();
|
|
@@ -33938,7 +33938,7 @@ var require_isexe = __commonJS({
|
|
|
33938
33938
|
}
|
|
33939
33939
|
module.exports = isexe;
|
|
33940
33940
|
isexe.sync = sync;
|
|
33941
|
-
function isexe(
|
|
33941
|
+
function isexe(path26, options, cb) {
|
|
33942
33942
|
if (typeof options === "function") {
|
|
33943
33943
|
cb = options;
|
|
33944
33944
|
options = {};
|
|
@@ -33948,7 +33948,7 @@ var require_isexe = __commonJS({
|
|
|
33948
33948
|
throw new TypeError("callback not provided");
|
|
33949
33949
|
}
|
|
33950
33950
|
return new Promise(function(resolve, reject2) {
|
|
33951
|
-
isexe(
|
|
33951
|
+
isexe(path26, options || {}, function(er, is) {
|
|
33952
33952
|
if (er) {
|
|
33953
33953
|
reject2(er);
|
|
33954
33954
|
} else {
|
|
@@ -33957,7 +33957,7 @@ var require_isexe = __commonJS({
|
|
|
33957
33957
|
});
|
|
33958
33958
|
});
|
|
33959
33959
|
}
|
|
33960
|
-
core(
|
|
33960
|
+
core(path26, options || {}, function(er, is) {
|
|
33961
33961
|
if (er) {
|
|
33962
33962
|
if (er.code === "EACCES" || options && options.ignoreErrors) {
|
|
33963
33963
|
er = null;
|
|
@@ -33967,9 +33967,9 @@ var require_isexe = __commonJS({
|
|
|
33967
33967
|
cb(er, is);
|
|
33968
33968
|
});
|
|
33969
33969
|
}
|
|
33970
|
-
function sync(
|
|
33970
|
+
function sync(path26, options) {
|
|
33971
33971
|
try {
|
|
33972
|
-
return core.sync(
|
|
33972
|
+
return core.sync(path26, options || {});
|
|
33973
33973
|
} catch (er) {
|
|
33974
33974
|
if (options && options.ignoreErrors || er.code === "EACCES") {
|
|
33975
33975
|
return false;
|
|
@@ -33985,7 +33985,7 @@ var require_isexe = __commonJS({
|
|
|
33985
33985
|
var require_which = __commonJS({
|
|
33986
33986
|
"../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module) {
|
|
33987
33987
|
var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
|
|
33988
|
-
var
|
|
33988
|
+
var path26 = __require("path");
|
|
33989
33989
|
var COLON = isWindows ? ";" : ":";
|
|
33990
33990
|
var isexe = require_isexe();
|
|
33991
33991
|
var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
|
|
@@ -34023,7 +34023,7 @@ var require_which = __commonJS({
|
|
|
34023
34023
|
return opt.all && found.length ? resolve(found) : reject2(getNotFoundError(cmd));
|
|
34024
34024
|
const ppRaw = pathEnv[i];
|
|
34025
34025
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
34026
|
-
const pCmd =
|
|
34026
|
+
const pCmd = path26.join(pathPart, cmd);
|
|
34027
34027
|
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
34028
34028
|
resolve(subStep(p, i, 0));
|
|
34029
34029
|
});
|
|
@@ -34050,7 +34050,7 @@ var require_which = __commonJS({
|
|
|
34050
34050
|
for (let i = 0; i < pathEnv.length; i++) {
|
|
34051
34051
|
const ppRaw = pathEnv[i];
|
|
34052
34052
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
34053
|
-
const pCmd =
|
|
34053
|
+
const pCmd = path26.join(pathPart, cmd);
|
|
34054
34054
|
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
34055
34055
|
for (let j = 0; j < pathExt.length; j++) {
|
|
34056
34056
|
const cur = p + pathExt[j];
|
|
@@ -34098,7 +34098,7 @@ var require_path_key = __commonJS({
|
|
|
34098
34098
|
var require_resolveCommand = __commonJS({
|
|
34099
34099
|
"../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
|
|
34100
34100
|
"use strict";
|
|
34101
|
-
var
|
|
34101
|
+
var path26 = __require("path");
|
|
34102
34102
|
var which = require_which();
|
|
34103
34103
|
var getPathKey = require_path_key();
|
|
34104
34104
|
function resolveCommandAttempt(parsed, withoutPathExt) {
|
|
@@ -34116,7 +34116,7 @@ var require_resolveCommand = __commonJS({
|
|
|
34116
34116
|
try {
|
|
34117
34117
|
resolved = which.sync(parsed.command, {
|
|
34118
34118
|
path: env[getPathKey({ env })],
|
|
34119
|
-
pathExt: withoutPathExt ?
|
|
34119
|
+
pathExt: withoutPathExt ? path26.delimiter : void 0
|
|
34120
34120
|
});
|
|
34121
34121
|
} catch (e) {
|
|
34122
34122
|
} finally {
|
|
@@ -34125,7 +34125,7 @@ var require_resolveCommand = __commonJS({
|
|
|
34125
34125
|
}
|
|
34126
34126
|
}
|
|
34127
34127
|
if (resolved) {
|
|
34128
|
-
resolved =
|
|
34128
|
+
resolved = path26.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
|
|
34129
34129
|
}
|
|
34130
34130
|
return resolved;
|
|
34131
34131
|
}
|
|
@@ -34179,8 +34179,8 @@ var require_shebang_command = __commonJS({
|
|
|
34179
34179
|
if (!match) {
|
|
34180
34180
|
return null;
|
|
34181
34181
|
}
|
|
34182
|
-
const [
|
|
34183
|
-
const binary =
|
|
34182
|
+
const [path26, argument] = match[0].replace(/#! ?/, "").split(" ");
|
|
34183
|
+
const binary = path26.split("/").pop();
|
|
34184
34184
|
if (binary === "env") {
|
|
34185
34185
|
return argument;
|
|
34186
34186
|
}
|
|
@@ -34193,16 +34193,16 @@ var require_shebang_command = __commonJS({
|
|
|
34193
34193
|
var require_readShebang = __commonJS({
|
|
34194
34194
|
"../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
|
|
34195
34195
|
"use strict";
|
|
34196
|
-
var
|
|
34196
|
+
var fs22 = __require("fs");
|
|
34197
34197
|
var shebangCommand = require_shebang_command();
|
|
34198
34198
|
function readShebang(command) {
|
|
34199
34199
|
const size = 150;
|
|
34200
34200
|
const buffer = Buffer.alloc(size);
|
|
34201
34201
|
let fd;
|
|
34202
34202
|
try {
|
|
34203
|
-
fd =
|
|
34204
|
-
|
|
34205
|
-
|
|
34203
|
+
fd = fs22.openSync(command, "r");
|
|
34204
|
+
fs22.readSync(fd, buffer, 0, size, 0);
|
|
34205
|
+
fs22.closeSync(fd);
|
|
34206
34206
|
} catch (e) {
|
|
34207
34207
|
}
|
|
34208
34208
|
return shebangCommand(buffer.toString());
|
|
@@ -34215,7 +34215,7 @@ var require_readShebang = __commonJS({
|
|
|
34215
34215
|
var require_parse2 = __commonJS({
|
|
34216
34216
|
"../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js"(exports, module) {
|
|
34217
34217
|
"use strict";
|
|
34218
|
-
var
|
|
34218
|
+
var path26 = __require("path");
|
|
34219
34219
|
var resolveCommand = require_resolveCommand();
|
|
34220
34220
|
var escape2 = require_escape();
|
|
34221
34221
|
var readShebang = require_readShebang();
|
|
@@ -34240,7 +34240,7 @@ var require_parse2 = __commonJS({
|
|
|
34240
34240
|
const needsShell = !isExecutableRegExp.test(commandFile);
|
|
34241
34241
|
if (parsed.options.forceShell || needsShell) {
|
|
34242
34242
|
const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
|
|
34243
|
-
parsed.command =
|
|
34243
|
+
parsed.command = path26.normalize(parsed.command);
|
|
34244
34244
|
parsed.command = escape2.command(parsed.command);
|
|
34245
34245
|
parsed.args = parsed.args.map((arg) => escape2.argument(arg, needsDoubleEscapeMetaChars));
|
|
34246
34246
|
const shellCommand = [parsed.command].concat(parsed.args).join(" ");
|
|
@@ -34455,201 +34455,11 @@ var require_content_type = __commonJS({
|
|
|
34455
34455
|
});
|
|
34456
34456
|
|
|
34457
34457
|
// src/server.ts
|
|
34458
|
-
import
|
|
34458
|
+
import fs21 from "node:fs/promises";
|
|
34459
34459
|
import {
|
|
34460
34460
|
createServer
|
|
34461
34461
|
} from "node:http";
|
|
34462
|
-
import
|
|
34463
|
-
|
|
34464
|
-
// src/identity.ts
|
|
34465
|
-
import crypto from "node:crypto";
|
|
34466
|
-
var SingleUserIdentity = class _SingleUserIdentity {
|
|
34467
|
-
describe = "single user (local)";
|
|
34468
|
-
static PRINCIPAL = { id: "local", displayName: "Local user" };
|
|
34469
|
-
/** Long-lived, minted per server run, only ever sent in an `Authorization` header. */
|
|
34470
|
-
sessionToken = crypto.randomBytes(32).toString("base64url");
|
|
34471
|
-
/**
|
|
34472
|
-
* Single-use and short-lived, because it travels in the launch URL's fragment where it
|
|
34473
|
-
* can end up in shell history or a terminal scrollback (§14).
|
|
34474
|
-
*/
|
|
34475
|
-
handoffToken = crypto.randomBytes(32).toString("base64url");
|
|
34476
|
-
handoffExpiresAt = Date.now() + 1e4;
|
|
34477
|
-
get launchToken() {
|
|
34478
|
-
if (this.handoffToken === void 0) throw new Error("handoff token already consumed");
|
|
34479
|
-
return this.handoffToken;
|
|
34480
|
-
}
|
|
34481
|
-
/**
|
|
34482
|
-
* Exchanges the handoff token for the session token, once.
|
|
34483
|
-
*
|
|
34484
|
-
* Cleared on the first attempt whether or not it matched: a wrong guess is either a bug
|
|
34485
|
-
* or an attack, and in both cases the right answer is that this token is now spent.
|
|
34486
|
-
*/
|
|
34487
|
-
redeemHandoff(presented) {
|
|
34488
|
-
const expected = this.handoffToken;
|
|
34489
|
-
const expiresAt = this.handoffExpiresAt;
|
|
34490
|
-
this.handoffToken = void 0;
|
|
34491
|
-
if (expected === void 0 || Date.now() > expiresAt) return void 0;
|
|
34492
|
-
return timingSafeEquals(presented, expected) ? this.sessionToken : void 0;
|
|
34493
|
-
}
|
|
34494
|
-
async authenticate(request) {
|
|
34495
|
-
const header = request.headers.authorization;
|
|
34496
|
-
if (header === void 0 || !header.startsWith("Bearer ")) return void 0;
|
|
34497
|
-
return timingSafeEquals(header.slice("Bearer ".length), this.sessionToken) ? _SingleUserIdentity.PRINCIPAL : void 0;
|
|
34498
|
-
}
|
|
34499
|
-
};
|
|
34500
|
-
function timingSafeEquals(a, b) {
|
|
34501
|
-
const left = Buffer.from(a);
|
|
34502
|
-
const right = Buffer.from(b);
|
|
34503
|
-
if (left.length !== right.length) return false;
|
|
34504
|
-
return crypto.timingSafeEqual(left, right);
|
|
34505
|
-
}
|
|
34506
|
-
function storageKeyFor(principal) {
|
|
34507
|
-
return crypto.createHash("sha256").update(principal.id).digest("hex").slice(0, 32);
|
|
34508
|
-
}
|
|
34509
|
-
|
|
34510
|
-
// src/roles.ts
|
|
34511
|
-
var ADMIN_ONLY_MESSAGES = [
|
|
34512
|
-
// Credentials and where inference goes (invariant 5: `profiles`, `activeProfileId`).
|
|
34513
|
-
"saveProfile",
|
|
34514
|
-
"deleteProfile",
|
|
34515
|
-
"duplicateProfile",
|
|
34516
|
-
"setActiveProfile",
|
|
34517
|
-
"testConnection",
|
|
34518
|
-
"importConfig",
|
|
34519
|
-
"exportConfig",
|
|
34520
|
-
// Processes this machine will spawn.
|
|
34521
|
-
"saveMcpServer",
|
|
34522
|
-
"saveMcpServers",
|
|
34523
|
-
"deleteMcpServer",
|
|
34524
|
-
"duplicateMcpServer",
|
|
34525
|
-
"restartMcpServer",
|
|
34526
|
-
"connectMcpServer",
|
|
34527
|
-
"setMcpServerEnabled",
|
|
34528
|
-
"setMcpToolPermission",
|
|
34529
|
-
// Names an interpreter and a tools directory — `python.uvPath` is on invariant 5 for this.
|
|
34530
|
-
"setPython",
|
|
34531
|
-
"deletePythonTool",
|
|
34532
|
-
"approvePythonTool",
|
|
34533
|
-
// Names an executable that costs money to run.
|
|
34534
|
-
"setExpert",
|
|
34535
|
-
"assessJunior",
|
|
34536
|
-
"clearAssessment",
|
|
34537
|
-
// TLS trust and client identity for every outbound connection.
|
|
34538
|
-
"saveNetwork",
|
|
34539
|
-
// Where the corpus is sent, and what is embedded into it.
|
|
34540
|
-
"saveSearchConnection",
|
|
34541
|
-
"deleteSearchConnection",
|
|
34542
|
-
"setActiveSearchConnection",
|
|
34543
|
-
"saveEmbedder",
|
|
34544
|
-
"setDispatcher",
|
|
34545
|
-
"startIndexing",
|
|
34546
|
-
"indexDocs",
|
|
34547
|
-
"clearDocsIndex",
|
|
34548
|
-
"syncVectorStore",
|
|
34549
|
-
// Reading beyond the workspace, and where skills come from.
|
|
34550
|
-
"setReadRoots",
|
|
34551
|
-
"saveSkillDirs",
|
|
34552
|
-
"deleteSkillFile",
|
|
34553
|
-
// Unattended execution with a pre-granted tool list.
|
|
34554
|
-
"saveSchedule",
|
|
34555
|
-
"deleteSchedule",
|
|
34556
|
-
"setScheduleEnabled",
|
|
34557
|
-
"runScheduleNow",
|
|
34558
|
-
"duplicateSchedule",
|
|
34559
|
-
// Approvals are stored per workspace but govern what runs without asking.
|
|
34560
|
-
"setAutoApprove",
|
|
34561
|
-
"revokeAllowedTool",
|
|
34562
|
-
"revokeAllowedCommand"
|
|
34563
|
-
];
|
|
34564
|
-
var ADMIN_ONLY = new Set(ADMIN_ONLY_MESSAGES);
|
|
34565
|
-
var PERSONAL_SETTINGS = /* @__PURE__ */ new Set([
|
|
34566
|
-
"setMode",
|
|
34567
|
-
"setAccentColor",
|
|
34568
|
-
"setExpertColor",
|
|
34569
|
-
"setMaxIterations",
|
|
34570
|
-
"setTaskExpertLimits"
|
|
34571
|
-
]);
|
|
34572
|
-
function isAdminOnly(messageType) {
|
|
34573
|
-
if (PERSONAL_SETTINGS.has(messageType)) return false;
|
|
34574
|
-
if (ADMIN_ONLY.has(messageType)) return true;
|
|
34575
|
-
return /^(save|set|delete|duplicate|clear|restart|connect|revoke|import|export)/.test(messageType);
|
|
34576
|
-
}
|
|
34577
|
-
var SINGLE_USER_POLICY = {
|
|
34578
|
-
shared: false,
|
|
34579
|
-
roleFor: () => "admin"
|
|
34580
|
-
};
|
|
34581
|
-
function refusalFor(messageType) {
|
|
34582
|
-
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.`;
|
|
34583
|
-
}
|
|
34584
|
-
|
|
34585
|
-
// src/security.ts
|
|
34586
|
-
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
34587
|
-
function checkRequest(request, policy, options) {
|
|
34588
|
-
const host = request.headers.host;
|
|
34589
|
-
if (host === void 0 || !policy.allowedHosts.includes(host.toLowerCase())) {
|
|
34590
|
-
return {
|
|
34591
|
-
status: 421,
|
|
34592
|
-
reason: `Host "${host ?? "(absent)"}" is not one this server answers to. This is what blocks DNS rebinding.`
|
|
34593
|
-
};
|
|
34594
|
-
}
|
|
34595
|
-
const origin = request.headers.origin;
|
|
34596
|
-
if (origin !== void 0 && !policy.allowedOrigins.includes(origin.toLowerCase())) {
|
|
34597
|
-
return { status: 403, reason: `Origin "${origin}" is not allowed.` };
|
|
34598
|
-
}
|
|
34599
|
-
const fetchSite = request.headers["sec-fetch-site"];
|
|
34600
|
-
if (typeof fetchSite === "string" && fetchSite !== "same-origin" && fetchSite !== "none") {
|
|
34601
|
-
return { status: 403, reason: `Cross-site request (Sec-Fetch-Site: ${fetchSite}) is not allowed.` };
|
|
34602
|
-
}
|
|
34603
|
-
const method = (request.method ?? "GET").toUpperCase();
|
|
34604
|
-
if (options.requireOrigin && !SAFE_METHODS.has(method) && origin === void 0) {
|
|
34605
|
-
return { status: 403, reason: `Missing Origin header on a ${method}.` };
|
|
34606
|
-
}
|
|
34607
|
-
return void 0;
|
|
34608
|
-
}
|
|
34609
|
-
function securityHeaders() {
|
|
34610
|
-
return {
|
|
34611
|
-
"Content-Security-Policy": [
|
|
34612
|
-
"default-src 'none'",
|
|
34613
|
-
"script-src 'self'",
|
|
34614
|
-
// The UI styles through the CSSOM rather than inline attributes, but the browser
|
|
34615
|
-
// build also needs a stylesheet for the page shell.
|
|
34616
|
-
"style-src 'self' 'unsafe-inline'",
|
|
34617
|
-
"img-src 'self' data:",
|
|
34618
|
-
"font-src 'self'",
|
|
34619
|
-
"connect-src 'self'",
|
|
34620
|
-
"frame-ancestors 'none'",
|
|
34621
|
-
"base-uri 'none'",
|
|
34622
|
-
"form-action 'none'"
|
|
34623
|
-
].join("; "),
|
|
34624
|
-
"X-Content-Type-Options": "nosniff",
|
|
34625
|
-
"Referrer-Policy": "no-referrer",
|
|
34626
|
-
// Nothing here needs a camera, a microphone or a location.
|
|
34627
|
-
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), interest-cohort=()",
|
|
34628
|
-
"Cache-Control": "no-store"
|
|
34629
|
-
// Deliberately no Access-Control-Allow-Origin: no other origin may read these replies.
|
|
34630
|
-
};
|
|
34631
|
-
}
|
|
34632
|
-
function reject(response, rejected) {
|
|
34633
|
-
response.writeHead(rejected.status, { "Content-Type": "text/plain", ...securityHeaders() });
|
|
34634
|
-
response.end(rejected.reason);
|
|
34635
|
-
}
|
|
34636
|
-
async function readJsonBody(request, maxBytes = 32 * 1024 * 1024) {
|
|
34637
|
-
const chunks = [];
|
|
34638
|
-
let total = 0;
|
|
34639
|
-
for await (const chunk of request) {
|
|
34640
|
-
const buffer = chunk;
|
|
34641
|
-
total += buffer.length;
|
|
34642
|
-
if (total > maxBytes) throw new Error("Request body too large.");
|
|
34643
|
-
chunks.push(buffer);
|
|
34644
|
-
}
|
|
34645
|
-
if (total === 0) return void 0;
|
|
34646
|
-
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
34647
|
-
}
|
|
34648
|
-
|
|
34649
|
-
// src/session.ts
|
|
34650
|
-
import { watch as fsWatch } from "node:fs";
|
|
34651
|
-
import fs18 from "node:fs/promises";
|
|
34652
|
-
import path22 from "node:path";
|
|
34462
|
+
import path25 from "node:path";
|
|
34653
34463
|
|
|
34654
34464
|
// ../../packages/core/dist/platform/http.js
|
|
34655
34465
|
var import_undici = __toESM(require_undici(), 1);
|
|
@@ -35583,10 +35393,10 @@ function mergeDefs(...defs) {
|
|
|
35583
35393
|
function cloneDef(schema) {
|
|
35584
35394
|
return mergeDefs(schema._zod.def);
|
|
35585
35395
|
}
|
|
35586
|
-
function getElementAtPath(obj,
|
|
35587
|
-
if (!
|
|
35396
|
+
function getElementAtPath(obj, path26) {
|
|
35397
|
+
if (!path26)
|
|
35588
35398
|
return obj;
|
|
35589
|
-
return
|
|
35399
|
+
return path26.reduce((acc, key) => acc?.[key], obj);
|
|
35590
35400
|
}
|
|
35591
35401
|
function promiseAllObject(promisesObj) {
|
|
35592
35402
|
const keys = Object.keys(promisesObj);
|
|
@@ -35995,11 +35805,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
35995
35805
|
}
|
|
35996
35806
|
return false;
|
|
35997
35807
|
}
|
|
35998
|
-
function prefixIssues(
|
|
35808
|
+
function prefixIssues(path26, issues) {
|
|
35999
35809
|
return issues.map((iss) => {
|
|
36000
35810
|
var _a3;
|
|
36001
35811
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
36002
|
-
iss.path.unshift(
|
|
35812
|
+
iss.path.unshift(path26);
|
|
36003
35813
|
return iss;
|
|
36004
35814
|
});
|
|
36005
35815
|
}
|
|
@@ -36146,16 +35956,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
36146
35956
|
}
|
|
36147
35957
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
36148
35958
|
const fieldErrors = { _errors: [] };
|
|
36149
|
-
const processError = (error52,
|
|
35959
|
+
const processError = (error52, path26 = []) => {
|
|
36150
35960
|
for (const issue2 of error52.issues) {
|
|
36151
35961
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
36152
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
35962
|
+
issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
|
|
36153
35963
|
} else if (issue2.code === "invalid_key") {
|
|
36154
|
-
processError({ issues: issue2.issues }, [...
|
|
35964
|
+
processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
|
|
36155
35965
|
} else if (issue2.code === "invalid_element") {
|
|
36156
|
-
processError({ issues: issue2.issues }, [...
|
|
35966
|
+
processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
|
|
36157
35967
|
} else {
|
|
36158
|
-
const fullpath = [...
|
|
35968
|
+
const fullpath = [...path26, ...issue2.path];
|
|
36159
35969
|
if (fullpath.length === 0) {
|
|
36160
35970
|
fieldErrors._errors.push(mapper(issue2));
|
|
36161
35971
|
} else {
|
|
@@ -36182,17 +35992,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
36182
35992
|
}
|
|
36183
35993
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
36184
35994
|
const result = { errors: [] };
|
|
36185
|
-
const processError = (error52,
|
|
35995
|
+
const processError = (error52, path26 = []) => {
|
|
36186
35996
|
var _a3, _b;
|
|
36187
35997
|
for (const issue2 of error52.issues) {
|
|
36188
35998
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
36189
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
35999
|
+
issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
|
|
36190
36000
|
} else if (issue2.code === "invalid_key") {
|
|
36191
|
-
processError({ issues: issue2.issues }, [...
|
|
36001
|
+
processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
|
|
36192
36002
|
} else if (issue2.code === "invalid_element") {
|
|
36193
|
-
processError({ issues: issue2.issues }, [...
|
|
36003
|
+
processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
|
|
36194
36004
|
} else {
|
|
36195
|
-
const fullpath = [...
|
|
36005
|
+
const fullpath = [...path26, ...issue2.path];
|
|
36196
36006
|
if (fullpath.length === 0) {
|
|
36197
36007
|
result.errors.push(mapper(issue2));
|
|
36198
36008
|
continue;
|
|
@@ -36224,8 +36034,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
36224
36034
|
}
|
|
36225
36035
|
function toDotPath(_path) {
|
|
36226
36036
|
const segs = [];
|
|
36227
|
-
const
|
|
36228
|
-
for (const seg of
|
|
36037
|
+
const path26 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
36038
|
+
for (const seg of path26) {
|
|
36229
36039
|
if (typeof seg === "number")
|
|
36230
36040
|
segs.push(`[${seg}]`);
|
|
36231
36041
|
else if (typeof seg === "symbol")
|
|
@@ -48917,13 +48727,13 @@ function resolveRef(ref, ctx) {
|
|
|
48917
48727
|
if (!ref.startsWith("#")) {
|
|
48918
48728
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
48919
48729
|
}
|
|
48920
|
-
const
|
|
48921
|
-
if (
|
|
48730
|
+
const path26 = ref.slice(1).split("/").filter(Boolean);
|
|
48731
|
+
if (path26.length === 0) {
|
|
48922
48732
|
return ctx.rootSchema;
|
|
48923
48733
|
}
|
|
48924
48734
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
48925
|
-
if (
|
|
48926
|
-
const key =
|
|
48735
|
+
if (path26[0] === defsKey) {
|
|
48736
|
+
const key = path26[1];
|
|
48927
48737
|
if (!key || !ctx.defs[key]) {
|
|
48928
48738
|
throw new Error(`Reference not found: ${ref}`);
|
|
48929
48739
|
}
|
|
@@ -49367,6 +49177,12 @@ function resolveToolPermission(toolName, namespacedName, disabledTools, alwaysAl
|
|
|
49367
49177
|
function namespacedToolName(serverName, toolName) {
|
|
49368
49178
|
return `${serverName}__${toolName}`;
|
|
49369
49179
|
}
|
|
49180
|
+
function parseNamespacedToolName(name) {
|
|
49181
|
+
const index = name.indexOf("__");
|
|
49182
|
+
if (index <= 0)
|
|
49183
|
+
return void 0;
|
|
49184
|
+
return { serverName: name.slice(0, index), toolName: name.slice(index + 2) };
|
|
49185
|
+
}
|
|
49370
49186
|
var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "npx.cmd", "pnpm", "pnpm.cmd", "pnpx", "bunx", "uvx", "yarn", "yarn.cmd"]);
|
|
49371
49187
|
function isPackageRunnerCommand(command) {
|
|
49372
49188
|
const base = command.split(/[\\/]/).pop()?.toLowerCase() ?? "";
|
|
@@ -49408,6 +49224,20 @@ var scheduleSchema = external_exports.object({
|
|
|
49408
49224
|
* Control tools are always available regardless; they perform no work.
|
|
49409
49225
|
*/
|
|
49410
49226
|
allowedTools: external_exports.array(external_exports.string()),
|
|
49227
|
+
/**
|
|
49228
|
+
* Which skills this run is told about, by name.
|
|
49229
|
+
*
|
|
49230
|
+
* **Absent means all of them**, which is what every schedule written before this existed
|
|
49231
|
+
* means, and the only reading that cannot silently take knowledge away from a job that was
|
|
49232
|
+
* working. An empty array is a real choice — "this run needs none" — and is honoured.
|
|
49233
|
+
*
|
|
49234
|
+
* Why a list rather than the retrieval the chat uses: a scheduled run's tools are an
|
|
49235
|
+
* allowlist the user ticked, and it may well not include `search_docs`, so telling the run
|
|
49236
|
+
* that notes exist and to go and search for them can leave it with nothing to search with.
|
|
49237
|
+
* Choosing the relevant ones up front is also simply better for a job that does the same
|
|
49238
|
+
* thing every night — it knows in advance which conventions apply, where the chat cannot.
|
|
49239
|
+
*/
|
|
49240
|
+
allowedSkills: external_exports.array(external_exports.string()).optional(),
|
|
49411
49241
|
/**
|
|
49412
49242
|
* When the timer will next run this, in epoch ms.
|
|
49413
49243
|
*
|
|
@@ -49443,6 +49273,12 @@ var scheduleSchema = external_exports.object({
|
|
|
49443
49273
|
var MAX_REMEMBERED_RUNS = 20;
|
|
49444
49274
|
var schedulesSchema = external_exports.record(external_exports.string(), scheduleSchema);
|
|
49445
49275
|
var ALWAYS_AVAILABLE_TO_SCHEDULES = ["attempt_completion", "notify"];
|
|
49276
|
+
function skillsForSchedule(skills, allowed) {
|
|
49277
|
+
if (allowed === void 0)
|
|
49278
|
+
return [...skills];
|
|
49279
|
+
const wanted = new Set(allowed);
|
|
49280
|
+
return skills.filter((skill) => wanted.has(skill.name));
|
|
49281
|
+
}
|
|
49446
49282
|
|
|
49447
49283
|
// ../../packages/core/dist/providers/types.js
|
|
49448
49284
|
var wireFormatSchema = external_exports.enum(["openai", "anthropic", "gemini"]);
|
|
@@ -49710,14 +49546,30 @@ var skillsConfigSchema = external_exports.object({
|
|
|
49710
49546
|
}).partial();
|
|
49711
49547
|
var retrievalConfigSchema = external_exports.object({
|
|
49712
49548
|
/**
|
|
49713
|
-
*
|
|
49549
|
+
* **On by default since 0.33.0**, at the user's request: looking a tool up first is the
|
|
49550
|
+
* behaviour they want, and a corporate install with several MCP servers is the case this
|
|
49551
|
+
* product is actually deployed into.
|
|
49714
49552
|
*
|
|
49715
|
-
* The
|
|
49716
|
-
*
|
|
49717
|
-
*
|
|
49718
|
-
*
|
|
49553
|
+
* The cost it trades against is real and unchanged — models are measurably better at
|
|
49554
|
+
* native tool-calling than at naming a tool inside `call_tool`. Two things keep that from
|
|
49555
|
+
* biting a small install: nothing is hidden unless there is something to hide (a workspace
|
|
49556
|
+
* with no MCP or Python tools registers no dispatcher tools at all, so it pays nothing),
|
|
49557
|
+
* and the switch is one click away in Settings → Search, which reports exactly how many
|
|
49558
|
+
* tools it is hiding.
|
|
49719
49559
|
*/
|
|
49720
49560
|
dispatcher: external_exports.boolean(),
|
|
49561
|
+
/**
|
|
49562
|
+
* The same treatment for skills: their names and descriptions leave the prompt and are
|
|
49563
|
+
* found with `search_docs` instead.
|
|
49564
|
+
*
|
|
49565
|
+
* On by default, and paired with `dispatcher` rather than independent of it in practice —
|
|
49566
|
+
* but a separate key because the trade is different. A tool's schema is large and its name
|
|
49567
|
+
* is guessable from the task; a skill's summary is one line and is the *only* thing that
|
|
49568
|
+
* makes the model aware the skill exists at all. So hiding skills saves less and risks
|
|
49569
|
+
* more, which is why a count and a standing instruction to search stay in the prompt even
|
|
49570
|
+
* when the list does not — see `renderSkillsHintForPrompt`.
|
|
49571
|
+
*/
|
|
49572
|
+
skills: external_exports.boolean(),
|
|
49721
49573
|
/**
|
|
49722
49574
|
* Where the documentation corpus is indexed. Absent means `search_docs` still works,
|
|
49723
49575
|
* matching names and descriptions from the live registry instead of by meaning — see
|
|
@@ -49725,6 +49577,12 @@ var retrievalConfigSchema = external_exports.object({
|
|
|
49725
49577
|
*/
|
|
49726
49578
|
docsIndex: external_exports.string()
|
|
49727
49579
|
}).partial();
|
|
49580
|
+
function dispatcherEnabled(retrieval) {
|
|
49581
|
+
return retrieval?.dispatcher !== false;
|
|
49582
|
+
}
|
|
49583
|
+
function skillRetrievalEnabled(retrieval) {
|
|
49584
|
+
return dispatcherEnabled(retrieval) && retrieval?.skills !== false;
|
|
49585
|
+
}
|
|
49728
49586
|
var embedderConfigSchema = external_exports.object({
|
|
49729
49587
|
profileId: external_exports.string().min(1),
|
|
49730
49588
|
model: external_exports.string().min(1),
|
|
@@ -49791,6 +49649,18 @@ var configSchema = external_exports.object({
|
|
|
49791
49649
|
*/
|
|
49792
49650
|
schedules: schedulesSchema,
|
|
49793
49651
|
activeProfileId: external_exports.string(),
|
|
49652
|
+
/**
|
|
49653
|
+
* The profile that writes Python tool source, when it should not be the chat model.
|
|
49654
|
+
*
|
|
49655
|
+
* A cheap model is fine at deciding a tool is needed and describing it, and much worse at
|
|
49656
|
+
* writing the file. Naming a profile here splits the two: the chat model sends a
|
|
49657
|
+
* specification and this one produces the source, which goes through the ordinary approval
|
|
49658
|
+
* prompt showing the real bytes.
|
|
49659
|
+
*
|
|
49660
|
+
* Absent means the chat model writes it, which is the behaviour every release so far has
|
|
49661
|
+
* had. User-scope only for the same reason as `profiles`: it names where inference goes.
|
|
49662
|
+
*/
|
|
49663
|
+
programmingProfileId: external_exports.string(),
|
|
49794
49664
|
certDir: external_exports.string(),
|
|
49795
49665
|
python: pythonConfigSchema,
|
|
49796
49666
|
/**
|
|
@@ -49860,10 +49730,276 @@ function parseConfig(raw) {
|
|
|
49860
49730
|
return result.data;
|
|
49861
49731
|
}
|
|
49862
49732
|
|
|
49733
|
+
// ../../packages/core/dist/session/variables.js
|
|
49734
|
+
var sessionVariableSchema = external_exports.object({
|
|
49735
|
+
name: external_exports.string().min(1),
|
|
49736
|
+
value: external_exports.string(),
|
|
49737
|
+
/** Shown beside the value. For "which one of these is the staging URL". */
|
|
49738
|
+
description: external_exports.string().optional()
|
|
49739
|
+
});
|
|
49740
|
+
var sessionVariablesSchema = external_exports.array(sessionVariableSchema);
|
|
49741
|
+
var VALID_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
49742
|
+
function isValidVariableName(name) {
|
|
49743
|
+
return VALID_NAME.test(name);
|
|
49744
|
+
}
|
|
49745
|
+
function resolveSessionVariables(adminVariables, userVariables) {
|
|
49746
|
+
const byName = /* @__PURE__ */ new Map();
|
|
49747
|
+
for (const variable of userVariables) {
|
|
49748
|
+
byName.set(variable.name, { ...variable, scope: "user" });
|
|
49749
|
+
}
|
|
49750
|
+
for (const variable of adminVariables) {
|
|
49751
|
+
const displaced = byName.get(variable.name);
|
|
49752
|
+
byName.set(variable.name, {
|
|
49753
|
+
...variable,
|
|
49754
|
+
scope: "admin",
|
|
49755
|
+
...displaced !== void 0 ? { overriddenUserValue: displaced.value } : {}
|
|
49756
|
+
});
|
|
49757
|
+
}
|
|
49758
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
49759
|
+
}
|
|
49760
|
+
function toEnvironment(variables) {
|
|
49761
|
+
const env = {};
|
|
49762
|
+
for (const variable of variables) {
|
|
49763
|
+
if (!isValidVariableName(variable.name))
|
|
49764
|
+
continue;
|
|
49765
|
+
env[variable.name] = variable.value;
|
|
49766
|
+
}
|
|
49767
|
+
return env;
|
|
49768
|
+
}
|
|
49769
|
+
|
|
49770
|
+
// ../../packages/core/dist/python/codeGenerator.js
|
|
49771
|
+
function buildCodeGenerationPrompt(request) {
|
|
49772
|
+
const lines = [
|
|
49773
|
+
"Write one complete Python file implementing the tool described below.",
|
|
49774
|
+
"",
|
|
49775
|
+
"Requirements, all load-bearing:",
|
|
49776
|
+
"- Define a function named `run`. It is the entry point and nothing else is called.",
|
|
49777
|
+
"- Annotate every parameter and the return type. The tool\u2019s schema is derived from those",
|
|
49778
|
+
" hints, so an unannotated parameter cannot be passed by the caller.",
|
|
49779
|
+
"- Write a module docstring. It becomes the tool description the model reads when choosing",
|
|
49780
|
+
" this tool, so say what it does, not how.",
|
|
49781
|
+
"- Document parameters in a Google-style `Args:` block.",
|
|
49782
|
+
"- Declare any third-party dependency in a PEP 723 inline block. Standard library needs none.",
|
|
49783
|
+
"",
|
|
49784
|
+
"**Return the file and nothing else.** No explanation, no fenced code block, no preamble.",
|
|
49785
|
+
"Anything that is not Python will be written to the file verbatim and fail to parse.",
|
|
49786
|
+
"",
|
|
49787
|
+
`Tool name: ${request.toolName}`,
|
|
49788
|
+
"",
|
|
49789
|
+
"What it must do:",
|
|
49790
|
+
request.specification
|
|
49791
|
+
];
|
|
49792
|
+
if (request.existingSource !== void 0 && request.existingSource.length > 0) {
|
|
49793
|
+
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);
|
|
49794
|
+
}
|
|
49795
|
+
return lines.join("\n");
|
|
49796
|
+
}
|
|
49797
|
+
function unwrapFencedSource(text) {
|
|
49798
|
+
const trimmed = text.trim();
|
|
49799
|
+
if (!trimmed.startsWith("```"))
|
|
49800
|
+
return text;
|
|
49801
|
+
const firstNewline = trimmed.indexOf("\n");
|
|
49802
|
+
if (firstNewline === -1)
|
|
49803
|
+
return text;
|
|
49804
|
+
const opening = trimmed.slice(0, firstNewline).trim();
|
|
49805
|
+
if (!/^```[a-zA-Z0-9]*$/.test(opening))
|
|
49806
|
+
return text;
|
|
49807
|
+
if (!trimmed.endsWith("```"))
|
|
49808
|
+
return text;
|
|
49809
|
+
return trimmed.slice(firstNewline + 1, trimmed.length - 3).replace(/\s+$/, "") + "\n";
|
|
49810
|
+
}
|
|
49811
|
+
|
|
49812
|
+
// ../../packages/core/dist/review/types.js
|
|
49813
|
+
function describeSubmission(request) {
|
|
49814
|
+
const what = request.kind === "python-tool" ? "tool" : "skill";
|
|
49815
|
+
return [
|
|
49816
|
+
`Submitted "${request.name}" for review. It is not saved and not callable yet.`,
|
|
49817
|
+
"",
|
|
49818
|
+
`An administrator has to read the ${what} and approve it before it can run. This is not an`,
|
|
49819
|
+
"error and there is nothing to retry \u2014 submitting again would only add a second copy to the",
|
|
49820
|
+
"queue. Tell the user it is waiting for approval and carry on with whatever else the task",
|
|
49821
|
+
"needs."
|
|
49822
|
+
].join("\n");
|
|
49823
|
+
}
|
|
49824
|
+
|
|
49825
|
+
// ../../packages/core/dist/guide/steps.js
|
|
49826
|
+
var GUIDE_STEPS = [
|
|
49827
|
+
{
|
|
49828
|
+
id: "orientation",
|
|
49829
|
+
title: "Where everything is",
|
|
49830
|
+
opensPanel: true,
|
|
49831
|
+
completionEvents: ["onCommand:lightCode.openPanel"],
|
|
49832
|
+
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.",
|
|
49833
|
+
body: [
|
|
49834
|
+
"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.",
|
|
49835
|
+
"The numbers in the picture are the four things worth knowing before anything else."
|
|
49836
|
+
]
|
|
49837
|
+
},
|
|
49838
|
+
{
|
|
49839
|
+
id: "providers",
|
|
49840
|
+
title: "Providers - point it at a model",
|
|
49841
|
+
tab: "providers",
|
|
49842
|
+
completionEvents: ["onContext:lightCode.hasProvider"],
|
|
49843
|
+
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.",
|
|
49844
|
+
body: [
|
|
49845
|
+
"Nothing ships configured. There are no default endpoints, so a fresh install contacts nothing until you fill this in.",
|
|
49846
|
+
"**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.",
|
|
49847
|
+
"**Test connection** is the field worth using first: it loads certificates, gets a token, lists models, and tells you which of the three failed."
|
|
49848
|
+
]
|
|
49849
|
+
},
|
|
49850
|
+
{
|
|
49851
|
+
id: "network",
|
|
49852
|
+
title: "Network - certificates, once, for everything",
|
|
49853
|
+
tab: "network",
|
|
49854
|
+
completionEvents: ["onStepSelected"],
|
|
49855
|
+
altText: "The Network tab, showing certificate directory, CA certificate, client certificate and key, PFX bundle, passphrase, and the verify-TLS toggle.",
|
|
49856
|
+
body: [
|
|
49857
|
+
"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.",
|
|
49858
|
+
"**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.",
|
|
49859
|
+
"**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."
|
|
49860
|
+
]
|
|
49861
|
+
},
|
|
49862
|
+
{
|
|
49863
|
+
id: "chat",
|
|
49864
|
+
title: "The chat - ask for something real",
|
|
49865
|
+
opensPanel: true,
|
|
49866
|
+
completionEvents: ["onContext:lightCode.hasChatted"],
|
|
49867
|
+
altText: "The chat header, with the mode selector, the expert budget, and the four header buttons labelled; below it, the composer.",
|
|
49868
|
+
body: [
|
|
49869
|
+
"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.",
|
|
49870
|
+
"**@** 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.",
|
|
49871
|
+
"**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."
|
|
49872
|
+
]
|
|
49873
|
+
},
|
|
49874
|
+
{
|
|
49875
|
+
id: "approvals",
|
|
49876
|
+
title: "Approvals - nothing happens without you",
|
|
49877
|
+
tab: "approvals",
|
|
49878
|
+
completionEvents: ["onStepSelected"],
|
|
49879
|
+
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.",
|
|
49880
|
+
body: [
|
|
49881
|
+
"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.",
|
|
49882
|
+
"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.",
|
|
49883
|
+
"Command matching is **exact, byte for byte**. Allowing `npm test` never allows `npm test && rm -rf /`.",
|
|
49884
|
+
"Before its first edit to a task it snapshots the workspace, so you can roll the whole thing back."
|
|
49885
|
+
]
|
|
49886
|
+
},
|
|
49887
|
+
{
|
|
49888
|
+
id: "mcp",
|
|
49889
|
+
title: "MCP - connect the servers you already run",
|
|
49890
|
+
tab: "mcp",
|
|
49891
|
+
completionEvents: ["onStepSelected"],
|
|
49892
|
+
altText: "The MCP tab, showing two servers with health, per-tool Always/Ask/Never controls, and the JSON configuration box.",
|
|
49893
|
+
body: [
|
|
49894
|
+
"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.",
|
|
49895
|
+
"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.",
|
|
49896
|
+
"Secrets go in as `${secret:NAME}` and are resolved from the OS keychain at spawn time, never written into the file."
|
|
49897
|
+
]
|
|
49898
|
+
},
|
|
49899
|
+
{
|
|
49900
|
+
id: "python",
|
|
49901
|
+
title: "Python - let it write its own tools",
|
|
49902
|
+
tab: "python",
|
|
49903
|
+
completionEvents: ["onStepSelected"],
|
|
49904
|
+
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.",
|
|
49905
|
+
body: [
|
|
49906
|
+
"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.",
|
|
49907
|
+
"**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.",
|
|
49908
|
+
"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."
|
|
49909
|
+
]
|
|
49910
|
+
},
|
|
49911
|
+
{
|
|
49912
|
+
id: "skills",
|
|
49913
|
+
title: "Skills - teach it your conventions",
|
|
49914
|
+
tab: "skills",
|
|
49915
|
+
completionEvents: ["onStepSelected"],
|
|
49916
|
+
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.",
|
|
49917
|
+
body: [
|
|
49918
|
+
"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.",
|
|
49919
|
+
"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.",
|
|
49920
|
+
'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.',
|
|
49921
|
+
"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."
|
|
49922
|
+
]
|
|
49923
|
+
},
|
|
49924
|
+
{
|
|
49925
|
+
id: "search",
|
|
49926
|
+
title: "Search - find things by meaning",
|
|
49927
|
+
tab: "search",
|
|
49928
|
+
completionEvents: ["onStepSelected"],
|
|
49929
|
+
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.",
|
|
49930
|
+
body: [
|
|
49931
|
+
"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.",
|
|
49932
|
+
"**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.",
|
|
49933
|
+
"**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."
|
|
49934
|
+
]
|
|
49935
|
+
},
|
|
49936
|
+
{
|
|
49937
|
+
id: "tools",
|
|
49938
|
+
title: "Tools - everything it can call",
|
|
49939
|
+
tab: "tools",
|
|
49940
|
+
completionEvents: ["onStepSelected"],
|
|
49941
|
+
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.",
|
|
49942
|
+
body: [
|
|
49943
|
+
"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.",
|
|
49944
|
+
"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."
|
|
49945
|
+
]
|
|
49946
|
+
},
|
|
49947
|
+
{
|
|
49948
|
+
id: "expert",
|
|
49949
|
+
title: "Expert - spend less on the hard parts",
|
|
49950
|
+
tab: "expert",
|
|
49951
|
+
completionEvents: ["onStepSelected"],
|
|
49952
|
+
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.",
|
|
49953
|
+
body: [
|
|
49954
|
+
"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.",
|
|
49955
|
+
"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.",
|
|
49956
|
+
"**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."
|
|
49957
|
+
]
|
|
49958
|
+
},
|
|
49959
|
+
{
|
|
49960
|
+
id: "schedules",
|
|
49961
|
+
title: "Schedules - let it run on its own",
|
|
49962
|
+
tab: "schedules",
|
|
49963
|
+
completionEvents: ["onStepSelected"],
|
|
49964
|
+
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.",
|
|
49965
|
+
body: [
|
|
49966
|
+
"A prompt on a timer. Runs in the background without touching the chat you are in, and keeps running with the panel closed.",
|
|
49967
|
+
"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.",
|
|
49968
|
+
"**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.",
|
|
49969
|
+
"Every run is logged with its full transcript, and `notify` raises a toast when a run has something to say."
|
|
49970
|
+
]
|
|
49971
|
+
},
|
|
49972
|
+
{
|
|
49973
|
+
id: "appearance",
|
|
49974
|
+
title: "Appearance - make it yours",
|
|
49975
|
+
tab: "appearance",
|
|
49976
|
+
completionEvents: ["onStepSelected"],
|
|
49977
|
+
altText: "The Appearance tab, showing the accent colour swatches, the expert colour swatches, and the reduced-motion toggle.",
|
|
49978
|
+
body: [
|
|
49979
|
+
"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.",
|
|
49980
|
+
"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."
|
|
49981
|
+
]
|
|
49982
|
+
},
|
|
49983
|
+
{
|
|
49984
|
+
id: "privacy",
|
|
49985
|
+
title: "What it does not do",
|
|
49986
|
+
completionEvents: ["onStepSelected"],
|
|
49987
|
+
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.",
|
|
49988
|
+
body: [
|
|
49989
|
+
"No telemetry. No update checks. No default endpoints - a fresh install contacts nothing. No remote assets in the panel.",
|
|
49990
|
+
"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.",
|
|
49991
|
+
"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.",
|
|
49992
|
+
"Source, issues and the full security section: [github.com/chosengenerationdev/light-code](https://github.com/chosengenerationdev/light-code)"
|
|
49993
|
+
]
|
|
49994
|
+
}
|
|
49995
|
+
];
|
|
49996
|
+
|
|
49863
49997
|
// ../../packages/core/dist/config/scopes.js
|
|
49864
49998
|
var USER_SCOPE_ONLY_KEYS = [
|
|
49865
49999
|
"profiles",
|
|
49866
50000
|
"activeProfileId",
|
|
50001
|
+
// Names where inference goes, exactly as the other two do.
|
|
50002
|
+
"programmingProfileId",
|
|
49867
50003
|
"certDir",
|
|
49868
50004
|
// The whole block, not just uvPath: toolsDir and venvPath also name where code is found
|
|
49869
50005
|
// and run from, and dynamicTools decides whether model-authored code runs at all.
|
|
@@ -50407,7 +50543,7 @@ function describeTlsError(error51) {
|
|
|
50407
50543
|
}
|
|
50408
50544
|
|
|
50409
50545
|
// ../../packages/core/dist/providers/auth/certs.js
|
|
50410
|
-
import
|
|
50546
|
+
import crypto from "node:crypto";
|
|
50411
50547
|
import fs4 from "node:fs/promises";
|
|
50412
50548
|
import path5 from "node:path";
|
|
50413
50549
|
var CertError = class extends Error {
|
|
@@ -50444,12 +50580,12 @@ function assertKeyMatchesCert(cert, key, passphrase) {
|
|
|
50444
50580
|
let publicKey;
|
|
50445
50581
|
let privateKey;
|
|
50446
50582
|
try {
|
|
50447
|
-
publicKey = new
|
|
50583
|
+
publicKey = new crypto.X509Certificate(cert).publicKey;
|
|
50448
50584
|
} catch (error51) {
|
|
50449
50585
|
throw new CertError(`The certificate could not be parsed: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
50450
50586
|
}
|
|
50451
50587
|
try {
|
|
50452
|
-
privateKey =
|
|
50588
|
+
privateKey = crypto.createPrivateKey(passphrase !== void 0 ? { key, passphrase } : { key });
|
|
50453
50589
|
} catch (error51) {
|
|
50454
50590
|
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
50455
50591
|
if (/bad decrypt|bad password|passphrase/i.test(message)) {
|
|
@@ -50459,14 +50595,14 @@ function assertKeyMatchesCert(cert, key, passphrase) {
|
|
|
50459
50595
|
}
|
|
50460
50596
|
const probe2 = Buffer.from("light-code-key-match-probe");
|
|
50461
50597
|
try {
|
|
50462
|
-
const signature =
|
|
50463
|
-
if (!
|
|
50598
|
+
const signature = crypto.sign(null, probe2, privateKey);
|
|
50599
|
+
if (!crypto.verify(null, probe2, publicKey, signature)) {
|
|
50464
50600
|
throw new CertError("The private key does not match the certificate.");
|
|
50465
50601
|
}
|
|
50466
50602
|
} catch (error51) {
|
|
50467
50603
|
if (error51 instanceof CertError)
|
|
50468
50604
|
throw error51;
|
|
50469
|
-
const derived =
|
|
50605
|
+
const derived = crypto.createPublicKey(privateKey).export({ type: "spki", format: "der" });
|
|
50470
50606
|
const expected = publicKey.export({ type: "spki", format: "der" });
|
|
50471
50607
|
if (!derived.equals(expected)) {
|
|
50472
50608
|
throw new CertError("The private key does not match the certificate.");
|
|
@@ -50498,7 +50634,7 @@ async function loadCerts(config2) {
|
|
|
50498
50634
|
loaded.cert = cert;
|
|
50499
50635
|
loaded.key = key;
|
|
50500
50636
|
try {
|
|
50501
|
-
loaded.notAfter = new Date(new
|
|
50637
|
+
loaded.notAfter = new Date(new crypto.X509Certificate(cert).validTo);
|
|
50502
50638
|
} catch {
|
|
50503
50639
|
}
|
|
50504
50640
|
return loaded;
|
|
@@ -51598,8 +51734,8 @@ var SUPERSEDED_MARKER = "[Superseded: this file was read again later in the conv
|
|
|
51598
51734
|
function readFilePath(argumentsJson) {
|
|
51599
51735
|
try {
|
|
51600
51736
|
const parsed = JSON.parse(argumentsJson.length > 0 ? argumentsJson : "{}");
|
|
51601
|
-
const
|
|
51602
|
-
return typeof
|
|
51737
|
+
const path26 = parsed.path;
|
|
51738
|
+
return typeof path26 === "string" && path26.length > 0 ? path26 : void 0;
|
|
51603
51739
|
} catch {
|
|
51604
51740
|
return void 0;
|
|
51605
51741
|
}
|
|
@@ -51612,8 +51748,8 @@ function dropSupersededReads(messages) {
|
|
|
51612
51748
|
for (const toolCall of message.toolCalls ?? []) {
|
|
51613
51749
|
if (toolCall.name !== "read_file")
|
|
51614
51750
|
continue;
|
|
51615
|
-
const
|
|
51616
|
-
if (
|
|
51751
|
+
const path26 = readFilePath(toolCall.arguments);
|
|
51752
|
+
if (path26 === void 0)
|
|
51617
51753
|
continue;
|
|
51618
51754
|
keyByCallId.set(toolCall.id, toolCall.arguments);
|
|
51619
51755
|
}
|
|
@@ -52003,8 +52139,20 @@ function buildSystemPrompt(workspaceRoot, options = {}) {
|
|
|
52003
52139
|
if (options.skills !== void 0 && options.skills.length > 0) {
|
|
52004
52140
|
lines.push("", options.skills);
|
|
52005
52141
|
}
|
|
52142
|
+
if (options.pythonToolsDisabled === true) {
|
|
52143
|
+
lines.push(
|
|
52144
|
+
"",
|
|
52145
|
+
"Python tools:",
|
|
52146
|
+
"- You cannot create runnable tools right now \u2014 the feature is switched off in Settings",
|
|
52147
|
+
" \u2192 Python.",
|
|
52148
|
+
// One line, unwrapped: it is the instruction that matters and a test asserts it verbatim.
|
|
52149
|
+
"- Do not write a script and call it a tool.",
|
|
52150
|
+
'- If the user asks for a "tool", say it is switched off and let them choose: enable it in',
|
|
52151
|
+
" Settings \u2192 Python, or have you write an ordinary script instead."
|
|
52152
|
+
);
|
|
52153
|
+
}
|
|
52006
52154
|
if (options.canWriteSkills === true) {
|
|
52007
|
-
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
|
|
52155
|
+
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.");
|
|
52008
52156
|
}
|
|
52009
52157
|
if (options.expertAvailable === true) {
|
|
52010
52158
|
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.");
|
|
@@ -52597,13 +52745,13 @@ var OpenSearchClient = class {
|
|
|
52597
52745
|
* `_bulk`, `_delete_by_query`, index creation — is refused here rather than merely
|
|
52598
52746
|
* unused, so no future edit or crafted argument can turn a read client into a writer.
|
|
52599
52747
|
*/
|
|
52600
|
-
async request(
|
|
52748
|
+
async request(path26, options = {}) {
|
|
52601
52749
|
const method = options.method ?? "GET";
|
|
52602
|
-
const isSearchPost = method === "POST" && /\/_search(\?|$)/.test(
|
|
52750
|
+
const isSearchPost = method === "POST" && /\/_search(\?|$)/.test(path26);
|
|
52603
52751
|
if (method !== "GET" && !isSearchPost) {
|
|
52604
|
-
throw new OpenSearchError(`Refusing ${method} ${
|
|
52752
|
+
throw new OpenSearchError(`Refusing ${method} ${path26}: this client is read-only. Indexing goes through the indexer, which the user starts from Settings.`);
|
|
52605
52753
|
}
|
|
52606
|
-
const url2 = `${this.base}${
|
|
52754
|
+
const url2 = `${this.base}${path26}`;
|
|
52607
52755
|
const request = {
|
|
52608
52756
|
method,
|
|
52609
52757
|
headers: this.headers()
|
|
@@ -52757,11 +52905,11 @@ function collectFields(properties, prefix, out) {
|
|
|
52757
52905
|
return;
|
|
52758
52906
|
for (const [name, raw] of Object.entries(properties)) {
|
|
52759
52907
|
const field = raw;
|
|
52760
|
-
const
|
|
52908
|
+
const path26 = prefix.length > 0 ? `${prefix}.${name}` : name;
|
|
52761
52909
|
if (typeof field.type === "string")
|
|
52762
|
-
out[
|
|
52910
|
+
out[path26] = field.type;
|
|
52763
52911
|
if (field.properties !== void 0)
|
|
52764
|
-
collectFields(field.properties,
|
|
52912
|
+
collectFields(field.properties, path26, out);
|
|
52765
52913
|
}
|
|
52766
52914
|
}
|
|
52767
52915
|
function describeStatus(status, url2, body) {
|
|
@@ -52780,23 +52928,23 @@ function describeStatus(status, url2, body) {
|
|
|
52780
52928
|
var TEXT_TYPES = /* @__PURE__ */ new Set(["text", "match_only_text", "search_as_you_type", "wildcard"]);
|
|
52781
52929
|
var KEYWORD_TYPES = /* @__PURE__ */ new Set(["keyword", "constant_keyword"]);
|
|
52782
52930
|
var NOISE_FIELDS = /* @__PURE__ */ new Set(["@version", "ecs", "tags", "stream", "input", "agent", "host", "event"]);
|
|
52783
|
-
function leafName(
|
|
52784
|
-
const parts =
|
|
52785
|
-
return parts[parts.length - 1] ??
|
|
52931
|
+
function leafName(path26) {
|
|
52932
|
+
const parts = path26.split(".");
|
|
52933
|
+
return parts[parts.length - 1] ?? path26;
|
|
52786
52934
|
}
|
|
52787
52935
|
function selectQueryFields(mapping, limit = 25) {
|
|
52788
52936
|
const text = [];
|
|
52789
52937
|
const keyword = [];
|
|
52790
|
-
for (const [
|
|
52791
|
-
if (NOISE_FIELDS.has(leafName(
|
|
52938
|
+
for (const [path26, type] of Object.entries(mapping)) {
|
|
52939
|
+
if (NOISE_FIELDS.has(leafName(path26)) || NOISE_FIELDS.has(path26.split(".")[0] ?? ""))
|
|
52792
52940
|
continue;
|
|
52793
52941
|
if (TEXT_TYPES.has(type)) {
|
|
52794
|
-
text.push(
|
|
52942
|
+
text.push(path26);
|
|
52795
52943
|
} else if (KEYWORD_TYPES.has(type)) {
|
|
52796
|
-
const parent =
|
|
52797
|
-
if (
|
|
52944
|
+
const parent = path26.replace(/\.keyword$/, "");
|
|
52945
|
+
if (path26.endsWith(".keyword") && TEXT_TYPES.has(mapping[parent] ?? ""))
|
|
52798
52946
|
continue;
|
|
52799
|
-
keyword.push(
|
|
52947
|
+
keyword.push(path26);
|
|
52800
52948
|
}
|
|
52801
52949
|
}
|
|
52802
52950
|
const byDepth = (a, b) => a.split(".").length - b.split(".").length || a.localeCompare(b);
|
|
@@ -52938,8 +53086,8 @@ var OpenSearchIndexWriter = class {
|
|
|
52938
53086
|
}
|
|
52939
53087
|
return headers;
|
|
52940
53088
|
}
|
|
52941
|
-
async request(
|
|
52942
|
-
const url2 = `${this.connection.url.replace(/\/+$/, "")}${
|
|
53089
|
+
async request(path26, method, body, signal) {
|
|
53090
|
+
const url2 = `${this.connection.url.replace(/\/+$/, "")}${path26}`;
|
|
52943
53091
|
const request = { method, headers: this.headers() };
|
|
52944
53092
|
if (body !== void 0) {
|
|
52945
53093
|
if (typeof body === "string") {
|
|
@@ -53094,13 +53242,13 @@ var OpenSearchIndexWriter = class {
|
|
|
53094
53242
|
for (const hit of hits) {
|
|
53095
53243
|
const source = hit._source ?? {};
|
|
53096
53244
|
const vector = source.vector;
|
|
53097
|
-
const
|
|
53098
|
-
if (typeof
|
|
53245
|
+
const path26 = source.path;
|
|
53246
|
+
if (typeof path26 !== "string" || !Array.isArray(vector))
|
|
53099
53247
|
continue;
|
|
53100
53248
|
documents.push({
|
|
53101
53249
|
id: hit._id ?? "",
|
|
53102
53250
|
text: typeof source.text === "string" ? source.text : "",
|
|
53103
|
-
path:
|
|
53251
|
+
path: path26,
|
|
53104
53252
|
startLine: typeof source.startLine === "number" ? source.startLine : 1,
|
|
53105
53253
|
endLine: typeof source.endLine === "number" ? source.endLine : 1,
|
|
53106
53254
|
vector
|
|
@@ -53174,8 +53322,8 @@ var RestTransport = class {
|
|
|
53174
53322
|
* threw would push every caller into catching and re-inspecting an error to find out
|
|
53175
53323
|
* whether it was really an error. `expectOk` is there for the cases that are.
|
|
53176
53324
|
*/
|
|
53177
|
-
async send(
|
|
53178
|
-
const url2 = `${this.connection.url.replace(/\/+$/, "")}${
|
|
53325
|
+
async send(path26, method, body, signal) {
|
|
53326
|
+
const url2 = `${this.connection.url.replace(/\/+$/, "")}${path26}`;
|
|
53179
53327
|
const request = { method, headers: this.headers() };
|
|
53180
53328
|
if (body !== void 0)
|
|
53181
53329
|
request.body = JSON.stringify(body);
|
|
@@ -53201,11 +53349,11 @@ var RestTransport = class {
|
|
|
53201
53349
|
return { status: response.status, body: parsed };
|
|
53202
53350
|
}
|
|
53203
53351
|
/** Sends, and throws unless the status is 2xx. */
|
|
53204
|
-
async expectOk(
|
|
53205
|
-
const result = await this.send(
|
|
53352
|
+
async expectOk(path26, method, body, signal) {
|
|
53353
|
+
const result = await this.send(path26, method, body, signal);
|
|
53206
53354
|
if (result.status < 200 || result.status >= 300) {
|
|
53207
53355
|
const detail = typeof result.body === "string" ? result.body : JSON.stringify(result.body ?? "");
|
|
53208
|
-
throw new VectorStoreError(`${method} ${
|
|
53356
|
+
throw new VectorStoreError(`${method} ${path26} on ${this.label} returned HTTP ${String(result.status)}. ${detail.slice(0, 300)}`, result.status);
|
|
53209
53357
|
}
|
|
53210
53358
|
return result.body;
|
|
53211
53359
|
}
|
|
@@ -53299,10 +53447,10 @@ var ChromaSearcher = class extends ChromaBase {
|
|
|
53299
53447
|
const matches = [];
|
|
53300
53448
|
for (let index = 0; index < ids.length; index++) {
|
|
53301
53449
|
const metadata = metadatas[index] ?? {};
|
|
53302
|
-
const
|
|
53303
|
-
if (
|
|
53450
|
+
const path26 = typeof metadata.path === "string" ? metadata.path : void 0;
|
|
53451
|
+
if (path26 === void 0)
|
|
53304
53452
|
continue;
|
|
53305
|
-
if (filtering && !
|
|
53453
|
+
if (filtering && !path26.startsWith(prefix))
|
|
53306
53454
|
continue;
|
|
53307
53455
|
const distance = distances[index];
|
|
53308
53456
|
const match = {
|
|
@@ -53314,7 +53462,7 @@ var ChromaSearcher = class extends ChromaBase {
|
|
|
53314
53462
|
*/
|
|
53315
53463
|
score: typeof distance === "number" ? 1 / (1 + Math.max(0, distance)) : 0,
|
|
53316
53464
|
text: documents[index] ?? (typeof metadata.text === "string" ? metadata.text : ""),
|
|
53317
|
-
path:
|
|
53465
|
+
path: path26
|
|
53318
53466
|
};
|
|
53319
53467
|
if (typeof metadata.startLine === "number")
|
|
53320
53468
|
match.startLine = metadata.startLine;
|
|
@@ -53419,13 +53567,13 @@ var ChromaIndexWriter = class extends ChromaBase {
|
|
|
53419
53567
|
for (let index = 0; index < ids.length; index++) {
|
|
53420
53568
|
const metadata = result.metadatas?.[index] ?? {};
|
|
53421
53569
|
const vector = result.embeddings?.[index];
|
|
53422
|
-
const
|
|
53423
|
-
if (
|
|
53570
|
+
const path26 = typeof metadata.path === "string" ? metadata.path : void 0;
|
|
53571
|
+
if (path26 === void 0 || !Array.isArray(vector))
|
|
53424
53572
|
continue;
|
|
53425
53573
|
documents.push({
|
|
53426
53574
|
id: ids[index] ?? "",
|
|
53427
53575
|
text: result.documents?.[index] ?? "",
|
|
53428
|
-
path:
|
|
53576
|
+
path: path26,
|
|
53429
53577
|
startLine: typeof metadata.startLine === "number" ? metadata.startLine : 1,
|
|
53430
53578
|
endLine: typeof metadata.endLine === "number" ? metadata.endLine : 1,
|
|
53431
53579
|
vector
|
|
@@ -53442,9 +53590,9 @@ var ChromaIndexWriter = class extends ChromaBase {
|
|
|
53442
53590
|
const result = await this.rest.expectOk(`${this.base}/collections/${found.id}/get`, "POST", { include: ["metadatas"], limit }, options.signal);
|
|
53443
53591
|
const paths = /* @__PURE__ */ new Set();
|
|
53444
53592
|
for (const metadata of result.metadatas ?? []) {
|
|
53445
|
-
const
|
|
53446
|
-
if (typeof
|
|
53447
|
-
paths.add(
|
|
53593
|
+
const path26 = metadata?.path;
|
|
53594
|
+
if (typeof path26 === "string")
|
|
53595
|
+
paths.add(path26);
|
|
53448
53596
|
}
|
|
53449
53597
|
return [...paths];
|
|
53450
53598
|
}
|
|
@@ -53471,14 +53619,14 @@ var MARKER_ID = "5f6d2a41-0000-5000-8000-6c69676874c0";
|
|
|
53471
53619
|
var MARKER_MARK = "light-code";
|
|
53472
53620
|
function toMatch(point) {
|
|
53473
53621
|
const payload = point.payload ?? {};
|
|
53474
|
-
const
|
|
53475
|
-
if (
|
|
53622
|
+
const path26 = typeof payload.path === "string" ? payload.path : void 0;
|
|
53623
|
+
if (path26 === void 0)
|
|
53476
53624
|
return void 0;
|
|
53477
53625
|
const match = {
|
|
53478
53626
|
id: typeof payload.chunkId === "string" ? payload.chunkId : point.id,
|
|
53479
53627
|
score: typeof point.score === "number" ? point.score : 0,
|
|
53480
53628
|
text: typeof payload.text === "string" ? payload.text : "",
|
|
53481
|
-
path:
|
|
53629
|
+
path: path26
|
|
53482
53630
|
};
|
|
53483
53631
|
if (typeof payload.startLine === "number")
|
|
53484
53632
|
match.startLine = payload.startLine;
|
|
@@ -53641,13 +53789,13 @@ var QdrantIndexWriter = class extends QdrantBase {
|
|
|
53641
53789
|
const documents = [];
|
|
53642
53790
|
for (const point of result.body.result?.points ?? []) {
|
|
53643
53791
|
const payload = point.payload ?? {};
|
|
53644
|
-
const
|
|
53645
|
-
if (
|
|
53792
|
+
const path26 = typeof payload.path === "string" ? payload.path : void 0;
|
|
53793
|
+
if (path26 === void 0 || !Array.isArray(point.vector))
|
|
53646
53794
|
continue;
|
|
53647
53795
|
documents.push({
|
|
53648
53796
|
id: typeof payload.chunkId === "string" ? payload.chunkId : point.id,
|
|
53649
53797
|
text: typeof payload.text === "string" ? payload.text : "",
|
|
53650
|
-
path:
|
|
53798
|
+
path: path26,
|
|
53651
53799
|
startLine: typeof payload.startLine === "number" ? payload.startLine : 1,
|
|
53652
53800
|
endLine: typeof payload.endLine === "number" ? payload.endLine : 1,
|
|
53653
53801
|
vector: point.vector
|
|
@@ -53674,9 +53822,9 @@ var QdrantIndexWriter = class extends QdrantBase {
|
|
|
53674
53822
|
throw new VectorStoreError(`Could not list "${collection}" (HTTP ${String(result.status)}).`, result.status);
|
|
53675
53823
|
}
|
|
53676
53824
|
for (const point of result.body.result?.points ?? []) {
|
|
53677
|
-
const
|
|
53678
|
-
if (typeof
|
|
53679
|
-
paths.add(
|
|
53825
|
+
const path26 = point.payload?.path;
|
|
53826
|
+
if (typeof path26 === "string")
|
|
53827
|
+
paths.add(path26);
|
|
53680
53828
|
}
|
|
53681
53829
|
offset = result.body.result?.next_page_offset;
|
|
53682
53830
|
if (offset === void 0 || offset === null)
|
|
@@ -54830,14 +54978,14 @@ function formatBytes(size) {
|
|
|
54830
54978
|
return `${(size / (1 << 10)).toFixed(1)}KB`;
|
|
54831
54979
|
return `${String(size)}B`;
|
|
54832
54980
|
}
|
|
54833
|
-
async function readTail(
|
|
54981
|
+
async function readTail(fs22, path26, size, count) {
|
|
54834
54982
|
let span = Math.min(size, CHUNK);
|
|
54835
54983
|
let text;
|
|
54836
54984
|
let start;
|
|
54837
54985
|
for (; ; ) {
|
|
54838
54986
|
start = Math.max(0, size - span);
|
|
54839
54987
|
const decoder = new StringDecoder("utf8");
|
|
54840
|
-
text = decoder.write(await
|
|
54988
|
+
text = decoder.write(await fs22.readBytesSlice(path26, start, size)) + decoder.end();
|
|
54841
54989
|
const enough = text.split("\n").length > count;
|
|
54842
54990
|
if (enough || start === 0 || span >= size)
|
|
54843
54991
|
break;
|
|
@@ -54855,7 +55003,7 @@ async function readTail(fs20, path24, size, count) {
|
|
|
54855
55003
|
hasMoreAfter: false
|
|
54856
55004
|
};
|
|
54857
55005
|
}
|
|
54858
|
-
async function readLineWindow(
|
|
55006
|
+
async function readLineWindow(fs22, path26, size, from, count) {
|
|
54859
55007
|
const decoder = new StringDecoder("utf8");
|
|
54860
55008
|
const lines = [];
|
|
54861
55009
|
let pending = "";
|
|
@@ -54870,7 +55018,7 @@ async function readLineWindow(fs20, path24, size, from, count) {
|
|
|
54870
55018
|
};
|
|
54871
55019
|
scan: while (position < size) {
|
|
54872
55020
|
const end = Math.min(size, position + CHUNK);
|
|
54873
|
-
pending += decoder.write(await
|
|
55021
|
+
pending += decoder.write(await fs22.readBytesSlice(path26, position, end));
|
|
54874
55022
|
position = end;
|
|
54875
55023
|
const parts = pending.split(/\r\n|\r|\n/);
|
|
54876
55024
|
pending = parts.pop() ?? "";
|
|
@@ -54893,13 +55041,13 @@ async function readLineWindow(fs20, path24, size, from, count) {
|
|
|
54893
55041
|
hasMoreAfter: !reachedEnd || lineNumber > from + lines.length
|
|
54894
55042
|
};
|
|
54895
55043
|
}
|
|
54896
|
-
async function countLines(
|
|
55044
|
+
async function countLines(fs22, path26, size) {
|
|
54897
55045
|
let newlines = 0;
|
|
54898
55046
|
let position = 0;
|
|
54899
55047
|
let lastByte = -1;
|
|
54900
55048
|
while (position < size) {
|
|
54901
55049
|
const end = Math.min(size, position + CHUNK);
|
|
54902
|
-
const buffer = await
|
|
55050
|
+
const buffer = await fs22.readBytesSlice(path26, position, end);
|
|
54903
55051
|
for (const byte of buffer)
|
|
54904
55052
|
if (byte === 10)
|
|
54905
55053
|
newlines += 1;
|
|
@@ -54972,7 +55120,7 @@ function chunkFile(content, options = {}) {
|
|
|
54972
55120
|
}
|
|
54973
55121
|
|
|
54974
55122
|
// ../../packages/core/dist/rag/indexer.js
|
|
54975
|
-
import
|
|
55123
|
+
import crypto2 from "node:crypto";
|
|
54976
55124
|
import fs5 from "node:fs/promises";
|
|
54977
55125
|
import path8 from "node:path";
|
|
54978
55126
|
var ALWAYS_SKIP = /* @__PURE__ */ new Set([
|
|
@@ -55067,7 +55215,7 @@ var SKIP_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
55067
55215
|
".env"
|
|
55068
55216
|
]);
|
|
55069
55217
|
function hashContent(content) {
|
|
55070
|
-
return
|
|
55218
|
+
return crypto2.createHash("sha256").update(content).digest("hex").slice(0, 32);
|
|
55071
55219
|
}
|
|
55072
55220
|
function chunkSignatureFor(options) {
|
|
55073
55221
|
return JSON.stringify([options?.windowLines ?? null, options?.overlapLines ?? null, options?.maxChars ?? null]);
|
|
@@ -58971,12 +59119,12 @@ function createFetchWithInit(baseFetch = fetch, baseInit) {
|
|
|
58971
59119
|
}
|
|
58972
59120
|
|
|
58973
59121
|
// ../../node_modules/.pnpm/pkce-challenge@5.0.1/node_modules/pkce-challenge/dist/index.node.js
|
|
58974
|
-
var
|
|
58975
|
-
|
|
59122
|
+
var crypto3;
|
|
59123
|
+
crypto3 = globalThis.crypto?.webcrypto ?? // Node.js [18-16] REPL
|
|
58976
59124
|
globalThis.crypto ?? // Node.js >18
|
|
58977
59125
|
import("node:crypto").then((m) => m.webcrypto);
|
|
58978
59126
|
async function getRandomValues(size) {
|
|
58979
|
-
return (await
|
|
59127
|
+
return (await crypto3).getRandomValues(new Uint8Array(size));
|
|
58980
59128
|
}
|
|
58981
59129
|
async function random(size) {
|
|
58982
59130
|
const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
|
|
@@ -58996,7 +59144,7 @@ async function generateVerifier(length) {
|
|
|
58996
59144
|
return await random(length);
|
|
58997
59145
|
}
|
|
58998
59146
|
async function generateChallenge(code_verifier) {
|
|
58999
|
-
const buffer = await (await
|
|
59147
|
+
const buffer = await (await crypto3).subtle.digest("SHA-256", new TextEncoder().encode(code_verifier));
|
|
59000
59148
|
return btoa(String.fromCharCode(...new Uint8Array(buffer))).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "");
|
|
59001
59149
|
}
|
|
59002
59150
|
async function pkceChallenge(length) {
|
|
@@ -61120,6 +61268,24 @@ async function loadSkills(dirs) {
|
|
|
61120
61268
|
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
61121
61269
|
return { skills, issues };
|
|
61122
61270
|
}
|
|
61271
|
+
function renderSkillsHintForPrompt(count) {
|
|
61272
|
+
if (count === 0)
|
|
61273
|
+
return "";
|
|
61274
|
+
const plural = count === 1 ? "note has" : "notes have";
|
|
61275
|
+
return [
|
|
61276
|
+
"## Skills",
|
|
61277
|
+
"",
|
|
61278
|
+
`${String(count)} ${plural} been recorded for this workspace: house conventions, internal`,
|
|
61279
|
+
"libraries, and gotchas specific to this codebase. They are not listed here.",
|
|
61280
|
+
"",
|
|
61281
|
+
"- Before working on an unfamiliar part of this workspace, or whenever the user mentions",
|
|
61282
|
+
" something internal you do not recognise, call search_docs to look for a relevant note.",
|
|
61283
|
+
'- Search by subject, in your own words \u2014 "how we call internal HTTP services", not a',
|
|
61284
|
+
" guessed file name.",
|
|
61285
|
+
"- A hit gives you the summary and a path. Read the file for the full text before acting",
|
|
61286
|
+
" on the subject."
|
|
61287
|
+
].join("\n");
|
|
61288
|
+
}
|
|
61123
61289
|
function renderSkillsForPrompt(skills) {
|
|
61124
61290
|
if (skills.length === 0)
|
|
61125
61291
|
return "";
|
|
@@ -61178,8 +61344,19 @@ function createWriteSkillTool(context) {
|
|
|
61178
61344
|
async execute(params) {
|
|
61179
61345
|
try {
|
|
61180
61346
|
const filePath = await resolveSkillPath(context.skillsDir, params.name);
|
|
61181
|
-
const
|
|
61182
|
-
|
|
61347
|
+
const before = await readIfPresent(filePath);
|
|
61348
|
+
const existed = before.length > 0;
|
|
61349
|
+
const rendered = renderSkill(params.name, params.description, params.body);
|
|
61350
|
+
if (context.submitForReview !== void 0) {
|
|
61351
|
+
return {
|
|
61352
|
+
content: await context.submitForReview({
|
|
61353
|
+
name: params.name,
|
|
61354
|
+
content: rendered,
|
|
61355
|
+
existingContent: before
|
|
61356
|
+
})
|
|
61357
|
+
};
|
|
61358
|
+
}
|
|
61359
|
+
await fs9.writeFile(filePath, rendered, "utf8");
|
|
61183
61360
|
await context.onChanged();
|
|
61184
61361
|
return {
|
|
61185
61362
|
content: `${existed ? "Updated" : "Recorded"} the skill "${params.name}" at ${filePath}.
|
|
@@ -61220,12 +61397,12 @@ import fs12 from "node:fs/promises";
|
|
|
61220
61397
|
import path15 from "node:path";
|
|
61221
61398
|
|
|
61222
61399
|
// ../../packages/core/dist/python/registry.js
|
|
61223
|
-
import
|
|
61400
|
+
import crypto4 from "node:crypto";
|
|
61224
61401
|
import fs10 from "node:fs/promises";
|
|
61225
61402
|
import path13 from "node:path";
|
|
61226
61403
|
var REGISTRY_FILE = ".registry.json";
|
|
61227
61404
|
function hashSource(source) {
|
|
61228
|
-
return
|
|
61405
|
+
return crypto4.createHash("sha256").update(source.replace(/\r\n/g, "\n")).digest("hex");
|
|
61229
61406
|
}
|
|
61230
61407
|
function isValidToolName(name) {
|
|
61231
61408
|
return /^[a-z][a-z0-9_]{0,63}$/.test(name);
|
|
@@ -61381,6 +61558,10 @@ var createParams = external_exports.object({
|
|
|
61381
61558
|
name: external_exports.string().describe("Tool name: lowercase letters, digits and underscores. Becomes py__<name> and <name>.py."),
|
|
61382
61559
|
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.")
|
|
61383
61560
|
});
|
|
61561
|
+
var specifyParams = external_exports.object({
|
|
61562
|
+
name: external_exports.string().describe("Tool name: lowercase letters, digits and underscores. Becomes py__<name> and <name>.py."),
|
|
61563
|
+
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.")
|
|
61564
|
+
});
|
|
61384
61565
|
var deleteParams2 = external_exports.object({
|
|
61385
61566
|
name: external_exports.string().describe("The tool to remove.")
|
|
61386
61567
|
});
|
|
@@ -61399,11 +61580,34 @@ async function readIfPresent2(filePath) {
|
|
|
61399
61580
|
}
|
|
61400
61581
|
}
|
|
61401
61582
|
function makeWriteTool(context, options) {
|
|
61583
|
+
const generator = context.generateSource;
|
|
61584
|
+
const pending = /* @__PURE__ */ new Map();
|
|
61585
|
+
const sourceFor = async (params) => {
|
|
61586
|
+
if (generator === void 0 || params.specification === void 0) {
|
|
61587
|
+
return { source: params.source };
|
|
61588
|
+
}
|
|
61589
|
+
const key = `${params.name}::${params.specification}`;
|
|
61590
|
+
let inFlight = pending.get(key);
|
|
61591
|
+
if (inFlight === void 0) {
|
|
61592
|
+
const toolPath = resolveToolPath(context.toolsDir, params.name);
|
|
61593
|
+
inFlight = (async () => {
|
|
61594
|
+
const before = await readIfPresent2(await toolPath);
|
|
61595
|
+
const generated = await generator({
|
|
61596
|
+
toolName: params.name,
|
|
61597
|
+
specification: params.specification ?? "",
|
|
61598
|
+
...before.length > 0 ? { existingSource: before } : {}
|
|
61599
|
+
});
|
|
61600
|
+
return { source: unwrapFencedSource(generated.source), producedBy: generated.producedBy };
|
|
61601
|
+
})();
|
|
61602
|
+
pending.set(key, inFlight);
|
|
61603
|
+
}
|
|
61604
|
+
return inFlight;
|
|
61605
|
+
};
|
|
61402
61606
|
return {
|
|
61403
61607
|
name: options.name,
|
|
61404
61608
|
group: "edit",
|
|
61405
61609
|
description: options.description,
|
|
61406
|
-
parametersSchema: createParams,
|
|
61610
|
+
parametersSchema: generator !== void 0 ? specifyParams : createParams,
|
|
61407
61611
|
/**
|
|
61408
61612
|
* A real diff of the real file: its current content against exactly the bytes that
|
|
61409
61613
|
* will be written. Not a summary and not the model's account of what it wrote
|
|
@@ -61413,7 +61617,14 @@ function makeWriteTool(context, options) {
|
|
|
61413
61617
|
async preview(params) {
|
|
61414
61618
|
const filePath = await resolveToolPath(context.toolsDir, params.name);
|
|
61415
61619
|
const before = await readIfPresent2(filePath);
|
|
61416
|
-
|
|
61620
|
+
const { source, producedBy } = await sourceFor(params);
|
|
61621
|
+
return {
|
|
61622
|
+
kind: "diff",
|
|
61623
|
+
path: filePath,
|
|
61624
|
+
before,
|
|
61625
|
+
after: source,
|
|
61626
|
+
...producedBy !== void 0 ? { note: `Written by ${producedBy}` } : {}
|
|
61627
|
+
};
|
|
61417
61628
|
},
|
|
61418
61629
|
async execute(params) {
|
|
61419
61630
|
try {
|
|
@@ -61428,15 +61639,26 @@ function makeWriteTool(context, options) {
|
|
|
61428
61639
|
isError: true
|
|
61429
61640
|
};
|
|
61430
61641
|
}
|
|
61642
|
+
const { source, producedBy } = await sourceFor(params);
|
|
61643
|
+
if (context.submitForReview !== void 0) {
|
|
61644
|
+
return {
|
|
61645
|
+
content: await context.submitForReview({
|
|
61646
|
+
name: params.name,
|
|
61647
|
+
content: source,
|
|
61648
|
+
existingContent: before,
|
|
61649
|
+
...producedBy !== void 0 ? { producedBy } : {}
|
|
61650
|
+
})
|
|
61651
|
+
};
|
|
61652
|
+
}
|
|
61431
61653
|
await fs11.mkdir(context.toolsDir, { recursive: true });
|
|
61432
|
-
await fs11.writeFile(filePath,
|
|
61654
|
+
await fs11.writeFile(filePath, source, "utf8");
|
|
61433
61655
|
const restore = async () => {
|
|
61434
61656
|
if (before.length > 0)
|
|
61435
61657
|
await fs11.writeFile(filePath, before, "utf8");
|
|
61436
61658
|
else
|
|
61437
61659
|
await fs11.rm(filePath, { force: true });
|
|
61438
61660
|
};
|
|
61439
|
-
const declared = parseInlineDependencies(
|
|
61661
|
+
const declared = parseInlineDependencies(source);
|
|
61440
61662
|
if (declared.length > 0) {
|
|
61441
61663
|
if (context.installDeps === void 0) {
|
|
61442
61664
|
await restore();
|
|
@@ -61464,7 +61686,7 @@ ${message}
|
|
|
61464
61686
|
|
|
61465
61687
|
${traceback ?? ""}`.trim(), isError: true };
|
|
61466
61688
|
}
|
|
61467
|
-
await approveTool(context.toolsDir, params.name,
|
|
61689
|
+
await approveTool(context.toolsDir, params.name, source, described);
|
|
61468
61690
|
await context.onChanged();
|
|
61469
61691
|
return {
|
|
61470
61692
|
content: `Saved and registered as py__${params.name}.
|
|
@@ -61628,7 +61850,7 @@ var PythonManager = class {
|
|
|
61628
61850
|
return;
|
|
61629
61851
|
}
|
|
61630
61852
|
try {
|
|
61631
|
-
const env = minimalPythonEnv();
|
|
61853
|
+
const env = minimalPythonEnv(this.options.sessionEnv?.() ?? {});
|
|
61632
61854
|
let interpreter;
|
|
61633
61855
|
if (config2.venvPath !== void 0 && config2.venvPath.trim().length > 0) {
|
|
61634
61856
|
this.venvPath = config2.venvPath.trim();
|
|
@@ -61725,8 +61947,10 @@ var PythonManager = class {
|
|
|
61725
61947
|
return [];
|
|
61726
61948
|
const worker = this.worker;
|
|
61727
61949
|
const uv = this.uv;
|
|
61950
|
+
const generated = this.options.generateSource?.();
|
|
61728
61951
|
const context = {
|
|
61729
61952
|
toolsDir: this.toolsDir,
|
|
61953
|
+
...generated !== void 0 ? { generateSource: generated } : {},
|
|
61730
61954
|
worker,
|
|
61731
61955
|
onChanged: () => this.refresh(),
|
|
61732
61956
|
...uv !== void 0 ? {
|
|
@@ -61737,7 +61961,7 @@ var PythonManager = class {
|
|
|
61737
61961
|
...this.indexUrl !== void 0 ? { indexUrl: this.indexUrl } : {},
|
|
61738
61962
|
extraIndexUrls: this.extraIndexUrls,
|
|
61739
61963
|
offline: this.offline,
|
|
61740
|
-
env: minimalPythonEnv()
|
|
61964
|
+
env: minimalPythonEnv(this.options.sessionEnv?.() ?? {})
|
|
61741
61965
|
})
|
|
61742
61966
|
} : {}
|
|
61743
61967
|
};
|
|
@@ -62143,6 +62367,31 @@ function renderDocsMatches(options, matches) {
|
|
|
62143
62367
|
}).filter((rendered) => rendered !== void 0).join("\n\n");
|
|
62144
62368
|
}
|
|
62145
62369
|
|
|
62370
|
+
// ../../packages/core/dist/agent/unfinished.js
|
|
62371
|
+
var MAX_PREAMBLE_LENGTH = 400;
|
|
62372
|
+
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;
|
|
62373
|
+
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;
|
|
62374
|
+
function lastSentence(text) {
|
|
62375
|
+
const trimmed = text.trim();
|
|
62376
|
+
const parts = trimmed.split(/(?<=[.!?])\s+/);
|
|
62377
|
+
return (parts[parts.length - 1] ?? trimmed).trim();
|
|
62378
|
+
}
|
|
62379
|
+
function looksUnfinished(text) {
|
|
62380
|
+
const trimmed = text.trim();
|
|
62381
|
+
if (trimmed.length === 0 || trimmed.length > MAX_PREAMBLE_LENGTH)
|
|
62382
|
+
return false;
|
|
62383
|
+
if (trimmed.endsWith("?"))
|
|
62384
|
+
return false;
|
|
62385
|
+
if (trimmed.endsWith(":"))
|
|
62386
|
+
return true;
|
|
62387
|
+
const last = lastSentence(trimmed);
|
|
62388
|
+
if (HANDING_BACK.test(last))
|
|
62389
|
+
return false;
|
|
62390
|
+
return FORWARD_LOOKING.test(last);
|
|
62391
|
+
}
|
|
62392
|
+
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.";
|
|
62393
|
+
var MAX_CONTINUE_NUDGES = 1;
|
|
62394
|
+
|
|
62146
62395
|
// ../../packages/core/dist/agent/truncate.js
|
|
62147
62396
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
62148
62397
|
import fs13 from "node:fs/promises";
|
|
@@ -62383,6 +62632,7 @@ async function runAgentTurn(provider, conversation, userMessage, toolRegistry, t
|
|
|
62383
62632
|
conversation.addUserMessage(userMessage, options.images);
|
|
62384
62633
|
const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
62385
62634
|
const mistakeCounts = /* @__PURE__ */ new Map();
|
|
62635
|
+
let continueNudges = 0;
|
|
62386
62636
|
const mode = options.mode ?? CODE_MODE;
|
|
62387
62637
|
const tools = toToolDefinitions(toolsForMode(toolRegistry, mode));
|
|
62388
62638
|
let checkpointTaken = false;
|
|
@@ -62416,12 +62666,18 @@ async function runAgentTurn(provider, conversation, userMessage, toolRegistry, t
|
|
|
62416
62666
|
return;
|
|
62417
62667
|
}
|
|
62418
62668
|
if (toolCall === void 0) {
|
|
62419
|
-
if (assistantText.length
|
|
62420
|
-
conversation.addAssistantMessage(assistantText);
|
|
62421
|
-
events.onDone();
|
|
62422
|
-
} else {
|
|
62669
|
+
if (assistantText.length === 0) {
|
|
62423
62670
|
events.onError("The provider finished without returning any text. Check the base URL and model name, and that the endpoint supports streaming chat completions.");
|
|
62671
|
+
return;
|
|
62672
|
+
}
|
|
62673
|
+
conversation.addAssistantMessage(assistantText);
|
|
62674
|
+
if (continueNudges < MAX_CONTINUE_NUDGES && looksUnfinished(assistantText)) {
|
|
62675
|
+
continueNudges++;
|
|
62676
|
+
conversation.addUserMessage(CONTINUE_PROMPT);
|
|
62677
|
+
events.onNudgedToContinue?.();
|
|
62678
|
+
continue;
|
|
62424
62679
|
}
|
|
62680
|
+
events.onDone();
|
|
62425
62681
|
return;
|
|
62426
62682
|
}
|
|
62427
62683
|
conversation.addAssistantMessage(assistantText, [toolCall]);
|
|
@@ -62536,17 +62792,17 @@ var WebviewApprovalGate = class {
|
|
|
62536
62792
|
// ../../packages/core/dist/platform/node/filesystem.js
|
|
62537
62793
|
import fs14 from "node:fs/promises";
|
|
62538
62794
|
var NodeFileSystem = class {
|
|
62539
|
-
async readFile(
|
|
62540
|
-
return fs14.readFile(
|
|
62795
|
+
async readFile(path26) {
|
|
62796
|
+
return fs14.readFile(path26, "utf8");
|
|
62541
62797
|
}
|
|
62542
|
-
async readBytes(
|
|
62543
|
-
return fs14.readFile(
|
|
62798
|
+
async readBytes(path26) {
|
|
62799
|
+
return fs14.readFile(path26);
|
|
62544
62800
|
}
|
|
62545
|
-
async readBytesSlice(
|
|
62801
|
+
async readBytesSlice(path26, start, end) {
|
|
62546
62802
|
const length = Math.max(0, end - start);
|
|
62547
62803
|
if (length === 0)
|
|
62548
62804
|
return Buffer.alloc(0);
|
|
62549
|
-
const handle = await fs14.open(
|
|
62805
|
+
const handle = await fs14.open(path26, "r");
|
|
62550
62806
|
try {
|
|
62551
62807
|
const buffer = Buffer.alloc(length);
|
|
62552
62808
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
@@ -62555,11 +62811,11 @@ var NodeFileSystem = class {
|
|
|
62555
62811
|
await handle.close();
|
|
62556
62812
|
}
|
|
62557
62813
|
}
|
|
62558
|
-
async writeFile(
|
|
62559
|
-
await fs14.writeFile(
|
|
62814
|
+
async writeFile(path26, contents) {
|
|
62815
|
+
await fs14.writeFile(path26, contents, "utf8");
|
|
62560
62816
|
}
|
|
62561
|
-
async stat(
|
|
62562
|
-
const stat = await fs14.lstat(
|
|
62817
|
+
async stat(path26) {
|
|
62818
|
+
const stat = await fs14.lstat(path26);
|
|
62563
62819
|
return {
|
|
62564
62820
|
size: stat.size,
|
|
62565
62821
|
mtimeMs: stat.mtimeMs,
|
|
@@ -62568,8 +62824,8 @@ var NodeFileSystem = class {
|
|
|
62568
62824
|
isSymbolicLink: stat.isSymbolicLink()
|
|
62569
62825
|
};
|
|
62570
62826
|
}
|
|
62571
|
-
async readdir(
|
|
62572
|
-
const entries = await fs14.readdir(
|
|
62827
|
+
async readdir(path26) {
|
|
62828
|
+
const entries = await fs14.readdir(path26, { withFileTypes: true });
|
|
62573
62829
|
return entries.map((entry) => ({
|
|
62574
62830
|
name: entry.name,
|
|
62575
62831
|
isFile: entry.isFile(),
|
|
@@ -62577,16 +62833,16 @@ var NodeFileSystem = class {
|
|
|
62577
62833
|
isSymbolicLink: entry.isSymbolicLink()
|
|
62578
62834
|
}));
|
|
62579
62835
|
}
|
|
62580
|
-
async exists(
|
|
62836
|
+
async exists(path26) {
|
|
62581
62837
|
try {
|
|
62582
|
-
await fs14.access(
|
|
62838
|
+
await fs14.access(path26);
|
|
62583
62839
|
return true;
|
|
62584
62840
|
} catch {
|
|
62585
62841
|
return false;
|
|
62586
62842
|
}
|
|
62587
62843
|
}
|
|
62588
|
-
async mkdir(
|
|
62589
|
-
await fs14.mkdir(
|
|
62844
|
+
async mkdir(path26) {
|
|
62845
|
+
await fs14.mkdir(path26, { recursive: true });
|
|
62590
62846
|
}
|
|
62591
62847
|
};
|
|
62592
62848
|
|
|
@@ -62864,11 +63120,25 @@ function wireChatBridge(services) {
|
|
|
62864
63120
|
workspaceRoot,
|
|
62865
63121
|
storageDir,
|
|
62866
63122
|
logger,
|
|
63123
|
+
// Read at worker spawn, so a changed variable applies to the next worker rather than being
|
|
63124
|
+
// frozen at construction. Added to the allowlist in minimalPythonEnv, never a way past it.
|
|
63125
|
+
...services.sessionEnv !== void 0 ? { sessionEnv: services.sessionEnv } : {},
|
|
63126
|
+
...services.submitForReview !== void 0 ? {
|
|
63127
|
+
submitForReview: (request) => services.submitForReview?.({ kind: "python-tool", ...request }) ?? Promise.resolve("")
|
|
63128
|
+
} : {},
|
|
63129
|
+
/*
|
|
63130
|
+
* A resolver, not a generator: the tool's *parameters* change shape depending on whether one
|
|
63131
|
+
* is configured — specification versus source — so the answer is needed when the tool list is
|
|
63132
|
+
* built, not when it is called. Refreshed by `loadSettings`, which runs before every turn, so
|
|
63133
|
+
* changing the profile mid-session takes effect on the next message.
|
|
63134
|
+
*/
|
|
63135
|
+
generateSource: () => cachedCodeGenerator,
|
|
62867
63136
|
// A tool created, updated or deleted during a chat changes both the Python tab and the
|
|
62868
63137
|
// documentation corpus. `postPython` refreshes the tab and schedules the reindex.
|
|
62869
63138
|
onToolsChanged: () => {
|
|
62870
63139
|
void postPython();
|
|
62871
63140
|
void postSchedules();
|
|
63141
|
+
void postTools();
|
|
62872
63142
|
}
|
|
62873
63143
|
});
|
|
62874
63144
|
const defaultSkillsDir = workspaceRoot !== void 0 ? path18.join(workspaceRoot, ".lightcode", "skills") : void 0;
|
|
@@ -63102,9 +63372,13 @@ function wireChatBridge(services) {
|
|
|
63102
63372
|
postExpertSpend();
|
|
63103
63373
|
}
|
|
63104
63374
|
let cachedModeId;
|
|
63375
|
+
let cachedCodeGenerator;
|
|
63376
|
+
let cachedProgrammingProfileId;
|
|
63105
63377
|
async function loadSettings() {
|
|
63106
63378
|
const { config: config2 } = await configManager.load();
|
|
63107
63379
|
cachedApprovals = config2.approvals?.[approvalsKey] ?? {};
|
|
63380
|
+
cachedCodeGenerator = codeGeneratorFor(config2);
|
|
63381
|
+
cachedProgrammingProfileId = config2.programmingProfileId;
|
|
63108
63382
|
cachedModeId = config2.modeId;
|
|
63109
63383
|
cachedMaxIterations = config2.maxIterations ?? 25;
|
|
63110
63384
|
cachedAccentColor = config2.ui?.accentColor ?? "#22C55E";
|
|
@@ -63130,9 +63404,17 @@ function wireChatBridge(services) {
|
|
|
63130
63404
|
maxIterations: cachedMaxIterations,
|
|
63131
63405
|
accentColor: cachedAccentColor,
|
|
63132
63406
|
expertColor: cachedExpertColor,
|
|
63133
|
-
readRoots: cachedReadRoots
|
|
63407
|
+
readRoots: cachedReadRoots,
|
|
63408
|
+
...cachedProgrammingProfileId !== void 0 ? { programmingProfileId: cachedProgrammingProfileId } : {},
|
|
63409
|
+
...guideCapability()
|
|
63134
63410
|
});
|
|
63135
63411
|
}
|
|
63412
|
+
function guideCapability() {
|
|
63413
|
+
return {
|
|
63414
|
+
nativeGuide: ui.openWalkthrough !== void 0,
|
|
63415
|
+
...services.guideMediaBase !== void 0 ? { guideMediaBase: services.guideMediaBase } : {}
|
|
63416
|
+
};
|
|
63417
|
+
}
|
|
63136
63418
|
const userGate = new WebviewApprovalGate(post);
|
|
63137
63419
|
const approvalGate = new PolicyApprovalGate(userGate, () => cachedApprovals);
|
|
63138
63420
|
let mcpJson = '{\n "mcpServers": {}\n}';
|
|
@@ -63145,6 +63427,7 @@ function wireChatBridge(services) {
|
|
|
63145
63427
|
onStateChanged: () => {
|
|
63146
63428
|
postMcp();
|
|
63147
63429
|
void postSchedules();
|
|
63430
|
+
void postTools();
|
|
63148
63431
|
scheduleDocsReindex("MCP tools changed");
|
|
63149
63432
|
}
|
|
63150
63433
|
}, logger, () => cachedApprovals.allowedTools ?? []);
|
|
@@ -63162,7 +63445,7 @@ function wireChatBridge(services) {
|
|
|
63162
63445
|
platform: process.platform === "win32" ? "win32" : "posix"
|
|
63163
63446
|
});
|
|
63164
63447
|
}
|
|
63165
|
-
function currentToolRegistry(expert, search, codebase, docs, dispatcher = false) {
|
|
63448
|
+
function currentToolRegistry(expert, search, codebase, docs, dispatcher = false, hideSkills = false) {
|
|
63166
63449
|
const combined = new ToolRegistry();
|
|
63167
63450
|
for (const tool of builtinTools.list())
|
|
63168
63451
|
combined.register(tool);
|
|
@@ -63192,7 +63475,13 @@ function wireChatBridge(services) {
|
|
|
63192
63475
|
}
|
|
63193
63476
|
}));
|
|
63194
63477
|
if (skillsDir !== void 0) {
|
|
63195
|
-
const context = {
|
|
63478
|
+
const context = {
|
|
63479
|
+
skillsDir,
|
|
63480
|
+
onChanged: refreshSkills,
|
|
63481
|
+
...services.submitForReview !== void 0 ? {
|
|
63482
|
+
submitForReview: (request) => services.submitForReview?.({ kind: "skill", ...request }) ?? Promise.resolve("")
|
|
63483
|
+
} : {}
|
|
63484
|
+
};
|
|
63196
63485
|
combined.register(createWriteSkillTool(context));
|
|
63197
63486
|
combined.register(createDeleteSkillTool(context));
|
|
63198
63487
|
}
|
|
@@ -63208,8 +63497,12 @@ function wireChatBridge(services) {
|
|
|
63208
63497
|
if (codebase !== void 0) {
|
|
63209
63498
|
combined.register(createSearchCodebaseTool({ ...codebase, observer: searchLog }));
|
|
63210
63499
|
}
|
|
63211
|
-
|
|
63500
|
+
const hasHiddenTools = dispatcher && combined.dispatchOnlyList().length > 0;
|
|
63501
|
+
const hasHiddenSkills = hideSkills && skills.length > 0;
|
|
63502
|
+
if (hasHiddenTools) {
|
|
63212
63503
|
combined.register(createCallToolTool());
|
|
63504
|
+
}
|
|
63505
|
+
if (dispatcher && (hasHiddenTools || hasHiddenSkills)) {
|
|
63213
63506
|
combined.register(createForgetDocsTool());
|
|
63214
63507
|
combined.register(createSearchDocsTool({
|
|
63215
63508
|
// Resolved per call, so a tool registered later in this same function is still
|
|
@@ -63419,12 +63712,26 @@ function wireChatBridge(services) {
|
|
|
63419
63712
|
await refreshSkills();
|
|
63420
63713
|
const activeMode = findMode(config2.modeId);
|
|
63421
63714
|
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"));
|
|
63715
|
+
const skillsSearchable = skillRetrievalEnabled(config2.retrieval) && schedule === void 0;
|
|
63716
|
+
const turnSkills = schedule === void 0 ? skills : skillsForSchedule(skills, schedule.allowedSkills);
|
|
63422
63717
|
const desiredPrompt = buildSystemPrompt(workspaceRoot, {
|
|
63423
63718
|
model: profile.model,
|
|
63424
63719
|
providerLabel: profile.label,
|
|
63425
63720
|
expertAvailable: expertCliInfo !== void 0,
|
|
63426
|
-
|
|
63721
|
+
/*
|
|
63722
|
+
* Either the whole list or a count and an instruction to search — never both, and
|
|
63723
|
+
* never neither. `renderSkillsHintForPrompt` explains why the count stays.
|
|
63724
|
+
*/
|
|
63725
|
+
skills: skillsSearchable ? renderSkillsHintForPrompt(skills.length) : renderSkillsForPrompt(turnSkills),
|
|
63726
|
+
skillsSearchable,
|
|
63427
63727
|
canWriteSkills: skillsDir !== void 0,
|
|
63728
|
+
/*
|
|
63729
|
+
* Read from config rather than from the registry, because the prompt is built before the
|
|
63730
|
+
* registry is. Only the *off* case is claimed: "on but uv is missing" leaves the model
|
|
63731
|
+
* equally toolless, but the Python tab reports that with the actual reason, and telling
|
|
63732
|
+
* the user to switch on something already switched on would be worse than saying nothing.
|
|
63733
|
+
*/
|
|
63734
|
+
pythonToolsDisabled: config2.python?.dynamicTools !== "on",
|
|
63428
63735
|
/*
|
|
63429
63736
|
* Junior mode's instructions are worse than useless without the expert to delegate
|
|
63430
63737
|
* to: the model would be told to consult something it has no tool for. The picker
|
|
@@ -63453,6 +63760,9 @@ function wireChatBridge(services) {
|
|
|
63453
63760
|
denylist,
|
|
63454
63761
|
readFiles,
|
|
63455
63762
|
readRoots: cachedReadRoots,
|
|
63763
|
+
// Resolved per turn by the host, so an edit applies to the next command rather than
|
|
63764
|
+
// needing a new session. Absent in the extension, where there is nothing to resolve.
|
|
63765
|
+
...services.sessionEnv !== void 0 ? { sessionEnv: services.sessionEnv() } : {},
|
|
63456
63766
|
/*
|
|
63457
63767
|
* Omitted for a scheduled run: there is nobody to answer, and a run that could grant
|
|
63458
63768
|
* itself new filesystem access would defeat the point of its allowlist.
|
|
@@ -63515,7 +63825,8 @@ function wireChatBridge(services) {
|
|
|
63515
63825
|
* behind a search that could never find them.
|
|
63516
63826
|
*/
|
|
63517
63827
|
search !== void 0 && embedder !== void 0 && docsIndex !== void 0 ? { searcher: search.searcher, embedder, index: docsIndex } : void 0,
|
|
63518
|
-
config2.retrieval
|
|
63828
|
+
dispatcherEnabled(config2.retrieval),
|
|
63829
|
+
skillsSearchable
|
|
63519
63830
|
);
|
|
63520
63831
|
const turnRegistry = schedule !== void 0 ? registryForSchedule(fullRegistry.list(), schedule) : fullRegistry;
|
|
63521
63832
|
if (schedule !== void 0) {
|
|
@@ -63526,6 +63837,9 @@ function wireChatBridge(services) {
|
|
|
63526
63837
|
post({ type: "contextUsage", usage: { ...breakdown, supersededCount, compactedCount } });
|
|
63527
63838
|
},
|
|
63528
63839
|
onCompacted: (summarisedCount) => post({ type: "compacted", summarisedCount }),
|
|
63840
|
+
onNudgedToContinue: () => {
|
|
63841
|
+
logger.warn("the model described an action without calling a tool; asked it to continue");
|
|
63842
|
+
},
|
|
63529
63843
|
onQueuedMessageConsumed: (text2) => {
|
|
63530
63844
|
post({ type: "queuedMessageConsumed", text: text2 });
|
|
63531
63845
|
cumulativeText = "";
|
|
@@ -64014,15 +64328,22 @@ function wireChatBridge(services) {
|
|
|
64014
64328
|
}
|
|
64015
64329
|
}
|
|
64016
64330
|
let indexingAbort;
|
|
64331
|
+
async function saveRetrieval(patch) {
|
|
64332
|
+
const { config: config2 } = await configManager.load();
|
|
64333
|
+
await configManager.save("user", { retrieval: { ...config2.retrieval, ...patch } });
|
|
64334
|
+
}
|
|
64017
64335
|
async function postDispatcher() {
|
|
64018
64336
|
const { config: config2 } = await configManager.load();
|
|
64019
|
-
|
|
64020
|
-
const
|
|
64337
|
+
await refreshSkills();
|
|
64338
|
+
const enabled = dispatcherEnabled(config2.retrieval);
|
|
64339
|
+
const hidden = currentToolRegistry(void 0, void 0, void 0, void 0, true, true).dispatchOnlyList().length;
|
|
64021
64340
|
const index = docsIndexName(config2);
|
|
64022
64341
|
post({
|
|
64023
64342
|
type: "dispatcher",
|
|
64024
64343
|
enabled,
|
|
64025
64344
|
hiddenTools: hidden,
|
|
64345
|
+
skills: skillRetrievalEnabled(config2.retrieval),
|
|
64346
|
+
hiddenSkills: skills.length,
|
|
64026
64347
|
...index !== void 0 ? { docsIndex: index } : {}
|
|
64027
64348
|
});
|
|
64028
64349
|
}
|
|
@@ -64634,6 +64955,28 @@ function wireChatBridge(services) {
|
|
|
64634
64955
|
post({ type: "error", message: error51 instanceof Error ? error51.message : String(error51) });
|
|
64635
64956
|
}
|
|
64636
64957
|
}
|
|
64958
|
+
function codeGeneratorFor(config2) {
|
|
64959
|
+
const id = config2.programmingProfileId;
|
|
64960
|
+
if (id === void 0 || id.length === 0)
|
|
64961
|
+
return void 0;
|
|
64962
|
+
const profile = config2.profiles?.find((candidate) => candidate.id === id);
|
|
64963
|
+
if (profile === void 0) {
|
|
64964
|
+
logger.warn(`programming provider "${id}" is configured but no such profile exists; the chat model will write tool source`);
|
|
64965
|
+
return void 0;
|
|
64966
|
+
}
|
|
64967
|
+
return async (request) => {
|
|
64968
|
+
const provider = createChatProvider(profile, httpClient, authStrategyFor(config2, profile), logger);
|
|
64969
|
+
let text = "";
|
|
64970
|
+
for await (const chunk of provider.streamChat([{ role: "user", content: buildCodeGenerationPrompt(request) }], {
|
|
64971
|
+
// No tools offered: it is being asked for a file, and offering tools invites it to use one.
|
|
64972
|
+
...request.signal !== void 0 ? { signal: request.signal } : {}
|
|
64973
|
+
})) {
|
|
64974
|
+
if (chunk.type === "text")
|
|
64975
|
+
text += chunk.text;
|
|
64976
|
+
}
|
|
64977
|
+
return { source: text, producedBy: profile.label };
|
|
64978
|
+
};
|
|
64979
|
+
}
|
|
64637
64980
|
async function postSettings() {
|
|
64638
64981
|
await loadSettings();
|
|
64639
64982
|
post({
|
|
@@ -64643,7 +64986,8 @@ function wireChatBridge(services) {
|
|
|
64643
64986
|
maxIterations: cachedMaxIterations,
|
|
64644
64987
|
accentColor: cachedAccentColor,
|
|
64645
64988
|
expertColor: cachedExpertColor,
|
|
64646
|
-
readRoots: cachedReadRoots
|
|
64989
|
+
readRoots: cachedReadRoots,
|
|
64990
|
+
...guideCapability()
|
|
64647
64991
|
});
|
|
64648
64992
|
}
|
|
64649
64993
|
async function handleAlwaysAllow(id, scope) {
|
|
@@ -64930,6 +65274,16 @@ function wireChatBridge(services) {
|
|
|
64930
65274
|
void handleSetMode(message.modeId);
|
|
64931
65275
|
} else if (message.type === "setMaxIterations") {
|
|
64932
65276
|
void configManager.save("user", { maxIterations: message.value }).then(() => postSettings()).catch((error51) => post({ type: "error", message: String(error51) }));
|
|
65277
|
+
} else if (message.type === "setProgrammingProfile") {
|
|
65278
|
+
void configManager.load().then(async ({ config: config2 }) => {
|
|
65279
|
+
const next = { ...config2 };
|
|
65280
|
+
if (message.id.length === 0)
|
|
65281
|
+
delete next.programmingProfileId;
|
|
65282
|
+
else
|
|
65283
|
+
next.programmingProfileId = message.id;
|
|
65284
|
+
await configManager.save("user", next);
|
|
65285
|
+
await postSettings();
|
|
65286
|
+
}).catch((error51) => post({ type: "error", message: String(error51) }));
|
|
64933
65287
|
} else if (message.type === "setReadRoots") {
|
|
64934
65288
|
void configManager.save("user", {
|
|
64935
65289
|
filesystem: { readRoots: message.roots.map((root) => root.trim()).filter((root) => root.length > 0) }
|
|
@@ -64997,11 +65351,17 @@ function wireChatBridge(services) {
|
|
|
64997
65351
|
} else if (message.type === "clearSearchLog") {
|
|
64998
65352
|
searchLog.clear();
|
|
64999
65353
|
} else if (message.type === "setDispatcher") {
|
|
65000
|
-
void
|
|
65354
|
+
void saveRetrieval({ dispatcher: message.enabled }).then(() => {
|
|
65001
65355
|
void postDispatcher();
|
|
65002
65356
|
if (message.enabled)
|
|
65003
65357
|
scheduleDocsReindex("dispatcher enabled");
|
|
65004
65358
|
}).catch((error51) => post({ type: "error", message: String(error51) }));
|
|
65359
|
+
} else if (message.type === "setSkillRetrieval") {
|
|
65360
|
+
void saveRetrieval({ skills: message.enabled }).then(() => {
|
|
65361
|
+
void postDispatcher();
|
|
65362
|
+
if (message.enabled)
|
|
65363
|
+
scheduleDocsReindex("skill retrieval enabled");
|
|
65364
|
+
}).catch((error51) => post({ type: "error", message: String(error51) }));
|
|
65005
65365
|
} else if (message.type === "startIndexing") {
|
|
65006
65366
|
void handleStartIndexing();
|
|
65007
65367
|
} else if (message.type === "cancelIndexing") {
|
|
@@ -65010,6 +65370,10 @@ function wireChatBridge(services) {
|
|
|
65010
65370
|
void handleSaveEmbedder(message.profileId, message.model, message.dimensions, message.indexName, message.indexPrefix);
|
|
65011
65371
|
} else if (message.type === "requestEmbedderModels") {
|
|
65012
65372
|
void handleRequestEmbedderModels(message.profileId);
|
|
65373
|
+
} else if (message.type === "openWalkthrough") {
|
|
65374
|
+
void ui.openWalkthrough?.();
|
|
65375
|
+
} else if (message.type === "requestTools") {
|
|
65376
|
+
void postTools();
|
|
65013
65377
|
} else if (message.type === "requestSchedules") {
|
|
65014
65378
|
void postSchedules();
|
|
65015
65379
|
} else if (message.type === "saveSchedule") {
|
|
@@ -65163,16 +65527,42 @@ ${entry.content}`);
|
|
|
65163
65527
|
function allToolsForPicker() {
|
|
65164
65528
|
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));
|
|
65165
65529
|
}
|
|
65530
|
+
async function postTools() {
|
|
65531
|
+
const { config: config2 } = await configManager.load();
|
|
65532
|
+
const dispatcher = config2.retrieval?.dispatcher === true;
|
|
65533
|
+
const registry2 = currentToolRegistry(void 0, void 0, void 0, void 0, dispatcher);
|
|
65534
|
+
const advertised = new Set(registry2.promptList().map((tool) => tool.name));
|
|
65535
|
+
const pythonNames = new Set(python.tools().map((tool) => tool.name));
|
|
65536
|
+
const mcpNames = new Set(mcp.enabledTools().map((tool) => tool.name));
|
|
65537
|
+
post({
|
|
65538
|
+
type: "tools",
|
|
65539
|
+
dispatcher,
|
|
65540
|
+
tools: registry2.list().map((tool) => {
|
|
65541
|
+
const server = mcpNames.has(tool.name) ? parseNamespacedToolName(tool.name)?.serverName : void 0;
|
|
65542
|
+
const source = pythonNames.has(tool.name) ? "python" : mcpNames.has(tool.name) ? "mcp" : "built-in";
|
|
65543
|
+
return {
|
|
65544
|
+
name: tool.name,
|
|
65545
|
+
description: tool.description,
|
|
65546
|
+
group: tool.group,
|
|
65547
|
+
source,
|
|
65548
|
+
...server !== void 0 ? { server } : {},
|
|
65549
|
+
advertised: advertised.has(tool.name)
|
|
65550
|
+
};
|
|
65551
|
+
}).sort((a, b) => a.name.localeCompare(b.name))
|
|
65552
|
+
});
|
|
65553
|
+
}
|
|
65166
65554
|
async function loadSchedules() {
|
|
65167
65555
|
const { config: config2 } = await configManager.load();
|
|
65168
65556
|
return config2.schedules ?? {};
|
|
65169
65557
|
}
|
|
65170
65558
|
async function postSchedules() {
|
|
65559
|
+
await refreshSkills();
|
|
65171
65560
|
const schedules = await loadSchedules();
|
|
65172
65561
|
post({
|
|
65173
65562
|
type: "schedules",
|
|
65174
65563
|
schedules: Object.values(schedules).sort((a, b) => a.name.localeCompare(b.name)),
|
|
65175
65564
|
tools: allToolsForPicker(),
|
|
65565
|
+
skills: skills.map((skill) => ({ name: skill.name, description: skill.description })),
|
|
65176
65566
|
...runningScheduleId !== void 0 ? { runningId: runningScheduleId } : {},
|
|
65177
65567
|
scheduler: {
|
|
65178
65568
|
running: scheduleTimer !== void 0,
|
|
@@ -65927,7 +66317,10 @@ var executeCommandTool = {
|
|
|
65927
66317
|
parametersSchema: paramsSchema10,
|
|
65928
66318
|
async execute(params, context) {
|
|
65929
66319
|
const cwd = params.cwd !== void 0 ? params.cwd : context.workspaceRoot;
|
|
65930
|
-
const proc = context.terminal.run(params.command, {
|
|
66320
|
+
const proc = context.terminal.run(params.command, {
|
|
66321
|
+
cwd,
|
|
66322
|
+
...context.sessionEnv !== void 0 ? { env: context.sessionEnv } : {}
|
|
66323
|
+
});
|
|
65931
66324
|
let output = "";
|
|
65932
66325
|
let truncated = false;
|
|
65933
66326
|
proc.onData((chunk) => {
|
|
@@ -66064,10 +66457,10 @@ function readSmall(raw, params) {
|
|
|
66064
66457
|
const end = params.limit !== void 0 ? start + params.limit : lines.length;
|
|
66065
66458
|
return number4(lines.slice(start, end), start + 1);
|
|
66066
66459
|
}
|
|
66067
|
-
async function readLarge(
|
|
66460
|
+
async function readLarge(fs22, realPath, params, size) {
|
|
66068
66461
|
const human = formatBytes(size);
|
|
66069
66462
|
if (params.tail !== void 0) {
|
|
66070
|
-
const part = await readTail(
|
|
66463
|
+
const part = await readTail(fs22, realPath, size, params.tail);
|
|
66071
66464
|
return [
|
|
66072
66465
|
`${human} file \u2014 last ${String(part.lines.length)} lines.`,
|
|
66073
66466
|
/*
|
|
@@ -66082,7 +66475,7 @@ async function readLarge(fs20, realPath, params, size) {
|
|
|
66082
66475
|
}
|
|
66083
66476
|
if (params.offset !== void 0) {
|
|
66084
66477
|
const limit = params.limit ?? DEFAULT_LARGE_LIMIT;
|
|
66085
|
-
const part = await readLineWindow(
|
|
66478
|
+
const part = await readLineWindow(fs22, realPath, size, params.offset, limit);
|
|
66086
66479
|
const shown = part.lines.length;
|
|
66087
66480
|
return [
|
|
66088
66481
|
`${human} file \u2014 lines ${String(params.offset)}\u2013${String(params.offset + shown - 1)}${part.hasMoreAfter ? ", more follows" : " (end of file)"}.`,
|
|
@@ -66090,7 +66483,7 @@ async function readLarge(fs20, realPath, params, size) {
|
|
|
66090
66483
|
number4(part.lines, params.offset)
|
|
66091
66484
|
].join("\n");
|
|
66092
66485
|
}
|
|
66093
|
-
const total = await countLines(
|
|
66486
|
+
const total = await countLines(fs22, realPath, size);
|
|
66094
66487
|
return [
|
|
66095
66488
|
`${realPathName(realPath)} is ${human} (${total.toLocaleString()} lines) \u2014 too large to read at once.`,
|
|
66096
66489
|
"",
|
|
@@ -66279,6 +66672,221 @@ function createDefaultToolRegistry() {
|
|
|
66279
66672
|
return registry2;
|
|
66280
66673
|
}
|
|
66281
66674
|
|
|
66675
|
+
// src/identity.ts
|
|
66676
|
+
import crypto5 from "node:crypto";
|
|
66677
|
+
var SingleUserIdentity = class _SingleUserIdentity {
|
|
66678
|
+
describe = "single user (local)";
|
|
66679
|
+
static PRINCIPAL = { id: "local", displayName: "Local user" };
|
|
66680
|
+
/** Long-lived, minted per server run, only ever sent in an `Authorization` header. */
|
|
66681
|
+
sessionToken = crypto5.randomBytes(32).toString("base64url");
|
|
66682
|
+
/**
|
|
66683
|
+
* Single-use and short-lived, because it travels in the launch URL's fragment where it
|
|
66684
|
+
* can end up in shell history or a terminal scrollback (§14).
|
|
66685
|
+
*/
|
|
66686
|
+
handoffToken = crypto5.randomBytes(32).toString("base64url");
|
|
66687
|
+
handoffExpiresAt = Date.now() + 1e4;
|
|
66688
|
+
get launchToken() {
|
|
66689
|
+
if (this.handoffToken === void 0) throw new Error("handoff token already consumed");
|
|
66690
|
+
return this.handoffToken;
|
|
66691
|
+
}
|
|
66692
|
+
/**
|
|
66693
|
+
* Exchanges the handoff token for the session token, once.
|
|
66694
|
+
*
|
|
66695
|
+
* Cleared on the first attempt whether or not it matched: a wrong guess is either a bug
|
|
66696
|
+
* or an attack, and in both cases the right answer is that this token is now spent.
|
|
66697
|
+
*/
|
|
66698
|
+
redeemHandoff(presented) {
|
|
66699
|
+
const expected = this.handoffToken;
|
|
66700
|
+
const expiresAt = this.handoffExpiresAt;
|
|
66701
|
+
this.handoffToken = void 0;
|
|
66702
|
+
if (expected === void 0 || Date.now() > expiresAt) return void 0;
|
|
66703
|
+
return timingSafeEquals(presented, expected) ? this.sessionToken : void 0;
|
|
66704
|
+
}
|
|
66705
|
+
async authenticate(request) {
|
|
66706
|
+
const header = request.headers.authorization;
|
|
66707
|
+
if (header === void 0 || !header.startsWith("Bearer ")) return void 0;
|
|
66708
|
+
return timingSafeEquals(header.slice("Bearer ".length), this.sessionToken) ? _SingleUserIdentity.PRINCIPAL : void 0;
|
|
66709
|
+
}
|
|
66710
|
+
};
|
|
66711
|
+
function timingSafeEquals(a, b) {
|
|
66712
|
+
const left = Buffer.from(a);
|
|
66713
|
+
const right = Buffer.from(b);
|
|
66714
|
+
if (left.length !== right.length) return false;
|
|
66715
|
+
return crypto5.timingSafeEqual(left, right);
|
|
66716
|
+
}
|
|
66717
|
+
function storageKeyFor(principal) {
|
|
66718
|
+
return crypto5.createHash("sha256").update(principal.id).digest("hex").slice(0, 32);
|
|
66719
|
+
}
|
|
66720
|
+
|
|
66721
|
+
// src/roles.ts
|
|
66722
|
+
var ADMIN_ONLY_MESSAGES = [
|
|
66723
|
+
/*
|
|
66724
|
+
* The *shared* provider set, and the default a new user inherits.
|
|
66725
|
+
*
|
|
66726
|
+
* A user's own profiles are theirs — see PERSONAL_SETTINGS. That is a reversal, made
|
|
66727
|
+
* deliberately: the original rule froze all of `profiles` because a second user was treated as
|
|
66728
|
+
* the same threat as a hostile repository. The threat that reasoning is about is one user
|
|
66729
|
+
* repointing *another's* gateway, and a per-user profile cannot do that — someone bringing
|
|
66730
|
+
* their own key is spending their own money against a host they chose.
|
|
66731
|
+
*/
|
|
66732
|
+
"saveSharedProfile",
|
|
66733
|
+
"deleteSharedProfile",
|
|
66734
|
+
"setDefaultProfile",
|
|
66735
|
+
// Writes a whole profile list, so it is not the same act as exporting one.
|
|
66736
|
+
"importConfig",
|
|
66737
|
+
// Processes this machine will spawn.
|
|
66738
|
+
"saveMcpServer",
|
|
66739
|
+
"saveMcpServers",
|
|
66740
|
+
"deleteMcpServer",
|
|
66741
|
+
"duplicateMcpServer",
|
|
66742
|
+
"restartMcpServer",
|
|
66743
|
+
"connectMcpServer",
|
|
66744
|
+
"setMcpServerEnabled",
|
|
66745
|
+
"setMcpToolPermission",
|
|
66746
|
+
// Names an interpreter and a tools directory — `python.uvPath` is on invariant 5 for this.
|
|
66747
|
+
"setPython",
|
|
66748
|
+
"deletePythonTool",
|
|
66749
|
+
"approvePythonTool",
|
|
66750
|
+
// Names an executable that costs money to run.
|
|
66751
|
+
"setExpert",
|
|
66752
|
+
"assessJunior",
|
|
66753
|
+
"clearAssessment",
|
|
66754
|
+
// TLS trust and client identity for every outbound connection.
|
|
66755
|
+
"saveNetwork",
|
|
66756
|
+
// Where the corpus is sent, and what is embedded into it.
|
|
66757
|
+
"saveSearchConnection",
|
|
66758
|
+
"deleteSearchConnection",
|
|
66759
|
+
"setActiveSearchConnection",
|
|
66760
|
+
"saveEmbedder",
|
|
66761
|
+
"setDispatcher",
|
|
66762
|
+
"startIndexing",
|
|
66763
|
+
"indexDocs",
|
|
66764
|
+
"clearDocsIndex",
|
|
66765
|
+
"syncVectorStore",
|
|
66766
|
+
// Reading beyond the workspace, and where skills come from.
|
|
66767
|
+
"setReadRoots",
|
|
66768
|
+
"saveSkillDirs",
|
|
66769
|
+
"deleteSkillFile",
|
|
66770
|
+
// Unattended execution with a pre-granted tool list.
|
|
66771
|
+
// Session variables an administrator sets for everyone. A user saving their own is
|
|
66772
|
+
// `saveUserVariables`, which is deliberately not here — it is theirs.
|
|
66773
|
+
// Approving model-authored code is the whole point of the queue.
|
|
66774
|
+
"decideReview",
|
|
66775
|
+
"saveAdminVariables",
|
|
66776
|
+
"saveAdminIds",
|
|
66777
|
+
"saveSchedule",
|
|
66778
|
+
"deleteSchedule",
|
|
66779
|
+
"setScheduleEnabled",
|
|
66780
|
+
"runScheduleNow",
|
|
66781
|
+
"duplicateSchedule",
|
|
66782
|
+
// Approvals are stored per workspace but govern what runs without asking.
|
|
66783
|
+
"setAutoApprove",
|
|
66784
|
+
"revokeAllowedTool",
|
|
66785
|
+
"revokeAllowedCommand"
|
|
66786
|
+
];
|
|
66787
|
+
var ADMIN_ONLY = new Set(ADMIN_ONLY_MESSAGES);
|
|
66788
|
+
var PERSONAL_SETTINGS = /* @__PURE__ */ new Set([
|
|
66789
|
+
// A user's own session variables. Caught by the unknown-mutating-verb rule, which is the
|
|
66790
|
+
// safety net working — the net is meant to be wrong in this direction, and this is where the
|
|
66791
|
+
// exception gets made deliberately rather than by weakening the rule.
|
|
66792
|
+
"saveUserVariables",
|
|
66793
|
+
/*
|
|
66794
|
+
* A user's own provider profiles, including their own API key.
|
|
66795
|
+
*
|
|
66796
|
+
* They cannot reach the shared ones: the config store strips a shared profile from anything
|
|
66797
|
+
* written to a user's file, so that boundary is storage rather than this list. Test Connection
|
|
66798
|
+
* is theirs too — a diagnostic against a profile they can already use, and refusing it would
|
|
66799
|
+
* leave someone unable to find out why their own key does not work.
|
|
66800
|
+
*/
|
|
66801
|
+
"saveProfile",
|
|
66802
|
+
"deleteProfile",
|
|
66803
|
+
"duplicateProfile",
|
|
66804
|
+
"setActiveProfile",
|
|
66805
|
+
"testConnection",
|
|
66806
|
+
"exportConfig",
|
|
66807
|
+
"setMode",
|
|
66808
|
+
"setAccentColor",
|
|
66809
|
+
"setExpertColor",
|
|
66810
|
+
"setMaxIterations",
|
|
66811
|
+
"setTaskExpertLimits"
|
|
66812
|
+
]);
|
|
66813
|
+
function isAdminOnly(messageType) {
|
|
66814
|
+
if (PERSONAL_SETTINGS.has(messageType)) return false;
|
|
66815
|
+
if (ADMIN_ONLY.has(messageType)) return true;
|
|
66816
|
+
return /^(save|set|delete|duplicate|clear|restart|connect|revoke|import|export)/.test(messageType);
|
|
66817
|
+
}
|
|
66818
|
+
var SINGLE_USER_POLICY = {
|
|
66819
|
+
shared: false,
|
|
66820
|
+
roleFor: () => "admin"
|
|
66821
|
+
};
|
|
66822
|
+
function refusalFor(messageType) {
|
|
66823
|
+
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.`;
|
|
66824
|
+
}
|
|
66825
|
+
|
|
66826
|
+
// src/security.ts
|
|
66827
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
66828
|
+
function checkRequest(request, policy, options) {
|
|
66829
|
+
const host = request.headers.host;
|
|
66830
|
+
if (host === void 0 || !policy.allowedHosts.includes(host.toLowerCase())) {
|
|
66831
|
+
return {
|
|
66832
|
+
status: 421,
|
|
66833
|
+
reason: `Host "${host ?? "(absent)"}" is not one this server answers to. This is what blocks DNS rebinding.`
|
|
66834
|
+
};
|
|
66835
|
+
}
|
|
66836
|
+
const origin = request.headers.origin;
|
|
66837
|
+
if (origin !== void 0 && !policy.allowedOrigins.includes(origin.toLowerCase())) {
|
|
66838
|
+
return { status: 403, reason: `Origin "${origin}" is not allowed.` };
|
|
66839
|
+
}
|
|
66840
|
+
const fetchSite = request.headers["sec-fetch-site"];
|
|
66841
|
+
if (typeof fetchSite === "string" && fetchSite !== "same-origin" && fetchSite !== "none") {
|
|
66842
|
+
return { status: 403, reason: `Cross-site request (Sec-Fetch-Site: ${fetchSite}) is not allowed.` };
|
|
66843
|
+
}
|
|
66844
|
+
const method = (request.method ?? "GET").toUpperCase();
|
|
66845
|
+
if (options.requireOrigin && !SAFE_METHODS.has(method) && origin === void 0) {
|
|
66846
|
+
return { status: 403, reason: `Missing Origin header on a ${method}.` };
|
|
66847
|
+
}
|
|
66848
|
+
return void 0;
|
|
66849
|
+
}
|
|
66850
|
+
function securityHeaders() {
|
|
66851
|
+
return {
|
|
66852
|
+
"Content-Security-Policy": [
|
|
66853
|
+
"default-src 'none'",
|
|
66854
|
+
"script-src 'self'",
|
|
66855
|
+
// The UI styles through the CSSOM rather than inline attributes, but the browser
|
|
66856
|
+
// build also needs a stylesheet for the page shell.
|
|
66857
|
+
"style-src 'self' 'unsafe-inline'",
|
|
66858
|
+
"img-src 'self' data:",
|
|
66859
|
+
"font-src 'self'",
|
|
66860
|
+
"connect-src 'self'",
|
|
66861
|
+
"frame-ancestors 'none'",
|
|
66862
|
+
"base-uri 'none'",
|
|
66863
|
+
"form-action 'none'"
|
|
66864
|
+
].join("; "),
|
|
66865
|
+
"X-Content-Type-Options": "nosniff",
|
|
66866
|
+
"Referrer-Policy": "no-referrer",
|
|
66867
|
+
// Nothing here needs a camera, a microphone or a location.
|
|
66868
|
+
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), interest-cohort=()",
|
|
66869
|
+
"Cache-Control": "no-store"
|
|
66870
|
+
// Deliberately no Access-Control-Allow-Origin: no other origin may read these replies.
|
|
66871
|
+
};
|
|
66872
|
+
}
|
|
66873
|
+
function reject(response, rejected) {
|
|
66874
|
+
response.writeHead(rejected.status, { "Content-Type": "text/plain", ...securityHeaders() });
|
|
66875
|
+
response.end(rejected.reason);
|
|
66876
|
+
}
|
|
66877
|
+
async function readJsonBody(request, maxBytes = 32 * 1024 * 1024) {
|
|
66878
|
+
const chunks = [];
|
|
66879
|
+
let total = 0;
|
|
66880
|
+
for await (const chunk of request) {
|
|
66881
|
+
const buffer = chunk;
|
|
66882
|
+
total += buffer.length;
|
|
66883
|
+
if (total > maxBytes) throw new Error("Request body too large.");
|
|
66884
|
+
chunks.push(buffer);
|
|
66885
|
+
}
|
|
66886
|
+
if (total === 0) return void 0;
|
|
66887
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
66888
|
+
}
|
|
66889
|
+
|
|
66282
66890
|
// src/fileSecretStore.ts
|
|
66283
66891
|
import fs17 from "node:fs/promises";
|
|
66284
66892
|
import path21 from "node:path";
|
|
@@ -66337,7 +66945,233 @@ var FileSecretStore = class {
|
|
|
66337
66945
|
}
|
|
66338
66946
|
};
|
|
66339
66947
|
|
|
66948
|
+
// src/reviewQueue.ts
|
|
66949
|
+
import crypto6 from "node:crypto";
|
|
66950
|
+
import fs18 from "node:fs/promises";
|
|
66951
|
+
import path22 from "node:path";
|
|
66952
|
+
var ReviewQueue = class {
|
|
66953
|
+
constructor(filePath) {
|
|
66954
|
+
this.filePath = filePath;
|
|
66955
|
+
}
|
|
66956
|
+
filePath;
|
|
66957
|
+
cache;
|
|
66958
|
+
async load() {
|
|
66959
|
+
if (this.cache !== void 0) return this.cache;
|
|
66960
|
+
try {
|
|
66961
|
+
const parsed = JSON.parse(await fs18.readFile(this.filePath, "utf8"));
|
|
66962
|
+
this.cache = Array.isArray(parsed) ? parsed : [];
|
|
66963
|
+
} catch {
|
|
66964
|
+
this.cache = [];
|
|
66965
|
+
}
|
|
66966
|
+
return this.cache;
|
|
66967
|
+
}
|
|
66968
|
+
async persist(items) {
|
|
66969
|
+
this.cache = items;
|
|
66970
|
+
await fs18.mkdir(path22.dirname(this.filePath), { recursive: true });
|
|
66971
|
+
const temporary = `${this.filePath}.tmp`;
|
|
66972
|
+
await fs18.writeFile(temporary, JSON.stringify(items, null, 2), { encoding: "utf8", mode: 384 });
|
|
66973
|
+
await fs18.rename(temporary, this.filePath);
|
|
66974
|
+
}
|
|
66975
|
+
async list() {
|
|
66976
|
+
return [...await this.load()];
|
|
66977
|
+
}
|
|
66978
|
+
async pending() {
|
|
66979
|
+
return (await this.load()).filter((item) => item.status === "pending");
|
|
66980
|
+
}
|
|
66981
|
+
async submit(request) {
|
|
66982
|
+
const items = await this.load();
|
|
66983
|
+
const superseded = items.findIndex(
|
|
66984
|
+
(item) => item.status === "pending" && item.kind === request.kind && item.name === request.name
|
|
66985
|
+
);
|
|
66986
|
+
const queued = {
|
|
66987
|
+
...request,
|
|
66988
|
+
id: crypto6.randomUUID(),
|
|
66989
|
+
submittedAt: Date.now(),
|
|
66990
|
+
status: "pending"
|
|
66991
|
+
};
|
|
66992
|
+
if (superseded === -1) items.push(queued);
|
|
66993
|
+
else items[superseded] = queued;
|
|
66994
|
+
await this.persist(items);
|
|
66995
|
+
return queued;
|
|
66996
|
+
}
|
|
66997
|
+
async decide(id, decision) {
|
|
66998
|
+
const items = await this.load();
|
|
66999
|
+
const item = items.find((candidate) => candidate.id === id);
|
|
67000
|
+
if (item === void 0 || item.status !== "pending") return void 0;
|
|
67001
|
+
item.status = decision.approved ? "approved" : "rejected";
|
|
67002
|
+
item.decidedBy = decision.by;
|
|
67003
|
+
item.decidedAt = Date.now();
|
|
67004
|
+
if (decision.reason !== void 0 && decision.reason.length > 0) item.reason = decision.reason;
|
|
67005
|
+
await this.persist(items);
|
|
67006
|
+
return item;
|
|
67007
|
+
}
|
|
67008
|
+
/**
|
|
67009
|
+
* Drops decided items older than the cutoff.
|
|
67010
|
+
*
|
|
67011
|
+
* Kept for a while rather than deleted on decision: "who approved this and when" is the question
|
|
67012
|
+
* a review queue exists to be able to answer afterwards, and the audit log records the decision
|
|
67013
|
+
* but not the source that was read.
|
|
67014
|
+
*/
|
|
67015
|
+
async prune(olderThanMs) {
|
|
67016
|
+
const cutoff = Date.now() - olderThanMs;
|
|
67017
|
+
const items = await this.load();
|
|
67018
|
+
const kept = items.filter((item) => item.status === "pending" || (item.decidedAt ?? 0) > cutoff);
|
|
67019
|
+
if (kept.length !== items.length) await this.persist(kept);
|
|
67020
|
+
}
|
|
67021
|
+
};
|
|
67022
|
+
|
|
67023
|
+
// src/sharedProfiles.ts
|
|
67024
|
+
var SHARED_PREFIX = "shared:";
|
|
67025
|
+
function isSharedProfileId(id) {
|
|
67026
|
+
return id.startsWith(SHARED_PREFIX);
|
|
67027
|
+
}
|
|
67028
|
+
function toSharedProfileId(id) {
|
|
67029
|
+
return `${SHARED_PREFIX}${id}`;
|
|
67030
|
+
}
|
|
67031
|
+
function isSharedSecretRef(ref) {
|
|
67032
|
+
return ref.startsWith(`profile:${SHARED_PREFIX}`);
|
|
67033
|
+
}
|
|
67034
|
+
function presentSharedProfiles(profiles) {
|
|
67035
|
+
return profiles.map((profile) => ({
|
|
67036
|
+
...profile,
|
|
67037
|
+
id: toSharedProfileId(profile.id),
|
|
67038
|
+
...profile.auth.type === "apiKey" && profile.auth.apiKeyRef !== void 0 ? { auth: { ...profile.auth, apiKeyRef: `profile:${toSharedProfileId(profile.id)}:apiKey` } } : {}
|
|
67039
|
+
}));
|
|
67040
|
+
}
|
|
67041
|
+
var SharedProfileConfigStore = class {
|
|
67042
|
+
constructor(inner, shared) {
|
|
67043
|
+
this.inner = inner;
|
|
67044
|
+
this.shared = shared;
|
|
67045
|
+
}
|
|
67046
|
+
inner;
|
|
67047
|
+
shared;
|
|
67048
|
+
async read(scope) {
|
|
67049
|
+
const raw = await this.inner.read(scope);
|
|
67050
|
+
if (scope !== "user") return raw;
|
|
67051
|
+
const shared = this.shared();
|
|
67052
|
+
const presented = presentSharedProfiles(shared.profiles);
|
|
67053
|
+
if (presented.length === 0) return raw;
|
|
67054
|
+
let parsed;
|
|
67055
|
+
try {
|
|
67056
|
+
parsed = raw === void 0 ? {} : JSON.parse(raw);
|
|
67057
|
+
} catch {
|
|
67058
|
+
return raw;
|
|
67059
|
+
}
|
|
67060
|
+
const own = Array.isArray(parsed["profiles"]) ? parsed["profiles"] : [];
|
|
67061
|
+
const merged = [...presented, ...own.filter((profile) => !isSharedProfileId(profile.id))];
|
|
67062
|
+
const activeId = typeof parsed["activeProfileId"] === "string" ? parsed["activeProfileId"] : void 0;
|
|
67063
|
+
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;
|
|
67064
|
+
const ownProgramming = typeof parsed["programmingProfileId"] === "string" ? parsed["programmingProfileId"] : void 0;
|
|
67065
|
+
const sharedProgramming = shared.defaultProgrammingProfileId !== void 0 ? toSharedProfileId(shared.defaultProgrammingProfileId) : void 0;
|
|
67066
|
+
const resolvedProgramming = ownProgramming !== void 0 && merged.some((profile) => profile.id === ownProgramming) ? ownProgramming : sharedProgramming !== void 0 && merged.some((profile) => profile.id === sharedProgramming) ? sharedProgramming : ownProgramming;
|
|
67067
|
+
return JSON.stringify({
|
|
67068
|
+
...parsed,
|
|
67069
|
+
profiles: merged,
|
|
67070
|
+
...resolvedActive !== void 0 ? { activeProfileId: resolvedActive } : {},
|
|
67071
|
+
...resolvedProgramming !== void 0 ? { programmingProfileId: resolvedProgramming } : {}
|
|
67072
|
+
});
|
|
67073
|
+
}
|
|
67074
|
+
async write(scope, contents) {
|
|
67075
|
+
if (scope !== "user") return this.inner.write(scope, contents);
|
|
67076
|
+
let parsed;
|
|
67077
|
+
try {
|
|
67078
|
+
parsed = JSON.parse(contents);
|
|
67079
|
+
} catch {
|
|
67080
|
+
return this.inner.write(scope, contents);
|
|
67081
|
+
}
|
|
67082
|
+
if (Array.isArray(parsed["profiles"])) {
|
|
67083
|
+
parsed["profiles"] = parsed["profiles"].filter(
|
|
67084
|
+
(profile) => !isSharedProfileId(profile.id)
|
|
67085
|
+
);
|
|
67086
|
+
}
|
|
67087
|
+
return this.inner.write(scope, JSON.stringify(parsed, null, 2));
|
|
67088
|
+
}
|
|
67089
|
+
watch(scope, onChange) {
|
|
67090
|
+
return this.inner.watch(scope, onChange);
|
|
67091
|
+
}
|
|
67092
|
+
};
|
|
67093
|
+
var RoutedSecretStore = class {
|
|
67094
|
+
constructor(own, shared) {
|
|
67095
|
+
this.own = own;
|
|
67096
|
+
this.shared = shared;
|
|
67097
|
+
}
|
|
67098
|
+
own;
|
|
67099
|
+
shared;
|
|
67100
|
+
storeFor(key) {
|
|
67101
|
+
return isSharedSecretRef(key) ? this.shared : this.own;
|
|
67102
|
+
}
|
|
67103
|
+
async get(key) {
|
|
67104
|
+
return this.storeFor(key).get(key);
|
|
67105
|
+
}
|
|
67106
|
+
async set(key, value) {
|
|
67107
|
+
return this.storeFor(key).set(key, value);
|
|
67108
|
+
}
|
|
67109
|
+
async delete(key) {
|
|
67110
|
+
return this.storeFor(key).delete(key);
|
|
67111
|
+
}
|
|
67112
|
+
/**
|
|
67113
|
+
* Clears the user's own only.
|
|
67114
|
+
*
|
|
67115
|
+
* "Clear all stored secrets" is offered to every user, and an administrator's key is not theirs
|
|
67116
|
+
* to destroy — one person tidying up would otherwise break the gateway for everybody. An
|
|
67117
|
+
* administrator clears the shared ones from the shared store.
|
|
67118
|
+
*/
|
|
67119
|
+
async clear() {
|
|
67120
|
+
return this.own.clear();
|
|
67121
|
+
}
|
|
67122
|
+
backendName() {
|
|
67123
|
+
return this.own.backendName();
|
|
67124
|
+
}
|
|
67125
|
+
};
|
|
67126
|
+
|
|
67127
|
+
// src/userVariables.ts
|
|
67128
|
+
import fs19 from "node:fs/promises";
|
|
67129
|
+
import { readFileSync } from "node:fs";
|
|
67130
|
+
import path23 from "node:path";
|
|
67131
|
+
var UserVariableStore = class {
|
|
67132
|
+
constructor(filePath) {
|
|
67133
|
+
this.filePath = filePath;
|
|
67134
|
+
}
|
|
67135
|
+
filePath;
|
|
67136
|
+
/**
|
|
67137
|
+
* Synchronous, because it is read on the path that builds a command's environment and an
|
|
67138
|
+
* `await` there would make every tool call wait on a file. It is a few hundred bytes.
|
|
67139
|
+
*/
|
|
67140
|
+
read() {
|
|
67141
|
+
return readVariablesFile(this.filePath);
|
|
67142
|
+
}
|
|
67143
|
+
async save(variables) {
|
|
67144
|
+
const parsed = sessionVariablesSchema.parse(variables);
|
|
67145
|
+
await fs19.mkdir(path23.dirname(this.filePath), { recursive: true });
|
|
67146
|
+
const temporary = `${this.filePath}.tmp`;
|
|
67147
|
+
await fs19.writeFile(temporary, JSON.stringify({ variables: parsed }, null, 2), {
|
|
67148
|
+
encoding: "utf8",
|
|
67149
|
+
mode: 384
|
|
67150
|
+
});
|
|
67151
|
+
await fs19.rename(temporary, this.filePath);
|
|
67152
|
+
return parsed;
|
|
67153
|
+
}
|
|
67154
|
+
};
|
|
67155
|
+
function userVariableStoreFor(dataDir, principal) {
|
|
67156
|
+
return new UserVariableStore(userVariablesPath(path23.join(dataDir, "users", storageKeyFor(principal))));
|
|
67157
|
+
}
|
|
67158
|
+
function userVariablesPath(userDir) {
|
|
67159
|
+
return path23.join(userDir, "variables.json");
|
|
67160
|
+
}
|
|
67161
|
+
function readVariablesFile(filePath) {
|
|
67162
|
+
try {
|
|
67163
|
+
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
67164
|
+
const parsed = sessionVariablesSchema.safeParse(raw["variables"]);
|
|
67165
|
+
return parsed.success ? parsed.data : [];
|
|
67166
|
+
} catch {
|
|
67167
|
+
return [];
|
|
67168
|
+
}
|
|
67169
|
+
}
|
|
67170
|
+
|
|
66340
67171
|
// src/session.ts
|
|
67172
|
+
import { watch as fsWatch } from "node:fs";
|
|
67173
|
+
import fs20 from "node:fs/promises";
|
|
67174
|
+
import path24 from "node:path";
|
|
66341
67175
|
var FileConfigStore = class {
|
|
66342
67176
|
constructor(userConfigPath, workspaceRoot) {
|
|
66343
67177
|
this.userConfigPath = userConfigPath;
|
|
@@ -66353,7 +67187,7 @@ var FileConfigStore = class {
|
|
|
66353
67187
|
const filePath = this.pathFor(scope);
|
|
66354
67188
|
if (filePath === void 0) return void 0;
|
|
66355
67189
|
try {
|
|
66356
|
-
return await
|
|
67190
|
+
return await fs20.readFile(filePath, "utf8");
|
|
66357
67191
|
} catch (error51) {
|
|
66358
67192
|
if (error51.code === "ENOENT") return void 0;
|
|
66359
67193
|
throw error51;
|
|
@@ -66362,8 +67196,8 @@ var FileConfigStore = class {
|
|
|
66362
67196
|
async write(scope, contents) {
|
|
66363
67197
|
const filePath = this.pathFor(scope);
|
|
66364
67198
|
if (filePath === void 0) throw new Error(`Cannot write ${scope} config: no workspace is open`);
|
|
66365
|
-
await
|
|
66366
|
-
await
|
|
67199
|
+
await fs20.mkdir(path24.dirname(filePath), { recursive: true });
|
|
67200
|
+
await fs20.writeFile(filePath, contents, { encoding: "utf8", mode: 384 });
|
|
66367
67201
|
}
|
|
66368
67202
|
watch(scope, onChange) {
|
|
66369
67203
|
const filePath = this.pathFor(scope);
|
|
@@ -66371,8 +67205,8 @@ var FileConfigStore = class {
|
|
|
66371
67205
|
};
|
|
66372
67206
|
let watcher;
|
|
66373
67207
|
try {
|
|
66374
|
-
watcher = fsWatch(
|
|
66375
|
-
if (filename ===
|
|
67208
|
+
watcher = fsWatch(path24.dirname(filePath), (_event, filename) => {
|
|
67209
|
+
if (filename === path24.basename(filePath)) onChange();
|
|
66376
67210
|
});
|
|
66377
67211
|
} catch {
|
|
66378
67212
|
}
|
|
@@ -66387,7 +67221,7 @@ var FileWorkspaceState = class {
|
|
|
66387
67221
|
values = {};
|
|
66388
67222
|
async load() {
|
|
66389
67223
|
try {
|
|
66390
|
-
const parsed = JSON.parse(await
|
|
67224
|
+
const parsed = JSON.parse(await fs20.readFile(this.filePath, "utf8"));
|
|
66391
67225
|
if (typeof parsed === "object" && parsed !== null) this.values = parsed;
|
|
66392
67226
|
} catch {
|
|
66393
67227
|
this.values = {};
|
|
@@ -66399,8 +67233,8 @@ var FileWorkspaceState = class {
|
|
|
66399
67233
|
async set(key, value) {
|
|
66400
67234
|
if (value === void 0) delete this.values[key];
|
|
66401
67235
|
else this.values[key] = value;
|
|
66402
|
-
await
|
|
66403
|
-
await
|
|
67236
|
+
await fs20.mkdir(path24.dirname(this.filePath), { recursive: true });
|
|
67237
|
+
await fs20.writeFile(this.filePath, JSON.stringify(this.values, null, 2), { encoding: "utf8", mode: 384 });
|
|
66404
67238
|
}
|
|
66405
67239
|
};
|
|
66406
67240
|
function createBrowserUi(workspaceRoot, post) {
|
|
@@ -66436,14 +67270,14 @@ function createBrowserUi(workspaceRoot, post) {
|
|
|
66436
67270
|
if (found.length >= limit || depth > 12) return;
|
|
66437
67271
|
let entries;
|
|
66438
67272
|
try {
|
|
66439
|
-
entries = await
|
|
67273
|
+
entries = await fs20.readdir(dir, { withFileTypes: true });
|
|
66440
67274
|
} catch {
|
|
66441
67275
|
return;
|
|
66442
67276
|
}
|
|
66443
67277
|
for (const entry of entries) {
|
|
66444
67278
|
if (found.length >= limit) return;
|
|
66445
67279
|
if (entry.name.startsWith(".") && entry.name !== ".env") continue;
|
|
66446
|
-
const full =
|
|
67280
|
+
const full = path24.join(dir, entry.name);
|
|
66447
67281
|
if (entry.isDirectory()) {
|
|
66448
67282
|
if (!skip.has(entry.name)) await walk(full, depth + 1);
|
|
66449
67283
|
} else if (needle.length === 0 || entry.name.toLowerCase().includes(needle)) {
|
|
@@ -66457,20 +67291,44 @@ function createBrowserUi(workspaceRoot, post) {
|
|
|
66457
67291
|
};
|
|
66458
67292
|
}
|
|
66459
67293
|
async function createSession(options) {
|
|
66460
|
-
const userDir =
|
|
66461
|
-
await
|
|
66462
|
-
const
|
|
67294
|
+
const userDir = path24.join(options.dataDir, "users", storageKeyFor(options.principal));
|
|
67295
|
+
await fs20.mkdir(userDir, { recursive: true, mode: 448 });
|
|
67296
|
+
const variableStore = new UserVariableStore(userVariablesPath(userDir));
|
|
67297
|
+
const userVariables = () => variableStore.read();
|
|
67298
|
+
const workspaceState = new FileWorkspaceState(path24.join(userDir, "workspace-state.json"));
|
|
66463
67299
|
await workspaceState.load();
|
|
66464
67300
|
const services = {
|
|
66465
67301
|
transport: options.transport,
|
|
66466
|
-
|
|
66467
|
-
|
|
67302
|
+
/*
|
|
67303
|
+
* A shared profile's API key belongs to the administrator and lives beside the shared config;
|
|
67304
|
+
* everything else is this user's. Routed by the reference, which is all a secret store gets.
|
|
67305
|
+
*/
|
|
67306
|
+
secrets: options.sharedSecrets === void 0 ? new FileSecretStore(path24.join(userDir, "secrets.json")) : new RoutedSecretStore(new FileSecretStore(path24.join(userDir, "secrets.json")), options.sharedSecrets),
|
|
67307
|
+
configStore: options.sharedProfiles === void 0 ? new FileConfigStore(path24.join(userDir, "config.json"), options.workspaceRoot) : new SharedProfileConfigStore(
|
|
67308
|
+
new FileConfigStore(path24.join(userDir, "config.json"), options.workspaceRoot),
|
|
67309
|
+
options.sharedProfiles
|
|
67310
|
+
),
|
|
66468
67311
|
workspaceState,
|
|
66469
67312
|
ui: createBrowserUi(options.workspaceRoot, options.logSink),
|
|
66470
67313
|
workspaceRoot: options.workspaceRoot,
|
|
66471
67314
|
storageDir: userDir,
|
|
66472
67315
|
ripgrepPath: options.ripgrepPath,
|
|
66473
|
-
logSink: options.logSink
|
|
67316
|
+
logSink: options.logSink,
|
|
67317
|
+
/*
|
|
67318
|
+
* Served from this origin, which is what `img-src 'self'` in the CSP permits and the whole
|
|
67319
|
+
* reason the diagrams are copied into the client bundle rather than fetched. A relative base
|
|
67320
|
+
* also survives whatever port the server happened to bind.
|
|
67321
|
+
*/
|
|
67322
|
+
guideMediaBase: "/guide",
|
|
67323
|
+
...options.submitForReview !== void 0 ? { submitForReview: options.submitForReview } : {},
|
|
67324
|
+
/*
|
|
67325
|
+
* Resolved per read, so both halves stay live — an administrator's edit and the user's own
|
|
67326
|
+
* each reach the next command rather than the next session.
|
|
67327
|
+
*
|
|
67328
|
+
* The administrator's win. That is a precedence rule and not a secrecy one: everything a
|
|
67329
|
+
* session spawns runs as the service account, so another user's agent can read these.
|
|
67330
|
+
*/
|
|
67331
|
+
sessionEnv: () => toEnvironment(resolveSessionVariables(options.adminVariables?.() ?? [], userVariables()))
|
|
66474
67332
|
};
|
|
66475
67333
|
new Logger({ level: "debug", sink: options.logSink }).info(
|
|
66476
67334
|
`session for ${options.principal.displayName} \u2192 ${userDir}`
|
|
@@ -66482,21 +67340,61 @@ async function createSession(options) {
|
|
|
66482
67340
|
var CLIENT_ASSETS = {
|
|
66483
67341
|
"/": "index.html",
|
|
66484
67342
|
"/index.html": "index.html",
|
|
67343
|
+
/*
|
|
67344
|
+
* The administrator's URL. The same page — the client asks the server what it may do rather
|
|
67345
|
+
* than being a second bundle — but a distinct address, because that is what a proxy rule can
|
|
67346
|
+
* be written against.
|
|
67347
|
+
*
|
|
67348
|
+
* **Reaching it is assumed to be restricted upstream.** Light Code does not re-derive who may
|
|
67349
|
+
* be here; the proxy, the firewall or a separate listener decides. The consequence, stated
|
|
67350
|
+
* once so nobody has to infer it: anyone who can reach `/admin` directly is an administrator,
|
|
67351
|
+
* so exposing the port without the proxy in front exposes this with it.
|
|
67352
|
+
*/
|
|
67353
|
+
"/admin": "index.html",
|
|
67354
|
+
"/admin/": "index.html",
|
|
66485
67355
|
"/client.js": "client.js",
|
|
66486
|
-
"/client.css": "client.css"
|
|
67356
|
+
"/client.css": "client.css",
|
|
67357
|
+
/*
|
|
67358
|
+
* The guide's diagrams, one entry per step and palette.
|
|
67359
|
+
*
|
|
67360
|
+
* Derived from `GUIDE_STEPS` rather than listed by hand, but still a *fixed table*: the keys
|
|
67361
|
+
* come from checked-in data, never from the request, so `serveAsset` keeps the property that
|
|
67362
|
+
* makes it safe — no part of the path is attacker-supplied and traversal is unreachable.
|
|
67363
|
+
*/
|
|
67364
|
+
...Object.fromEntries(
|
|
67365
|
+
GUIDE_STEPS.flatMap(
|
|
67366
|
+
(step) => ["light", "dark"].map((theme) => [
|
|
67367
|
+
`/guide/${step.id}-${theme}.svg`,
|
|
67368
|
+
`guide/${step.id}-${theme}.svg`
|
|
67369
|
+
])
|
|
67370
|
+
)
|
|
67371
|
+
)
|
|
66487
67372
|
};
|
|
66488
67373
|
var CONTENT_TYPES = {
|
|
66489
67374
|
".html": "text/html; charset=utf-8",
|
|
66490
67375
|
".js": "text/javascript; charset=utf-8",
|
|
66491
|
-
".css": "text/css; charset=utf-8"
|
|
67376
|
+
".css": "text/css; charset=utf-8",
|
|
67377
|
+
// Served as an image, and the CSP's `img-src 'self'` is what keeps it one: an SVG loaded
|
|
67378
|
+
// through <img> cannot run script, whatever it contains.
|
|
67379
|
+
".svg": "image/svg+xml"
|
|
66492
67380
|
};
|
|
66493
67381
|
async function startServer(options) {
|
|
66494
67382
|
const log = options.logSink ?? ((line) => process.stderr.write(`${line}
|
|
66495
67383
|
`));
|
|
66496
67384
|
const identity = options.identity ?? new SingleUserIdentity();
|
|
66497
67385
|
const roles = options.roles ?? SINGLE_USER_POLICY;
|
|
67386
|
+
const sharedStore = options.sharedConfig;
|
|
67387
|
+
const sharedSecretStore = new FileSecretStore(path25.join(options.dataDir, "shared-secrets.json"));
|
|
67388
|
+
const reviews = new ReviewQueue(path25.join(options.dataDir, "reviews.json"));
|
|
67389
|
+
let sharedCache = { variables: [], adminIds: [], profiles: [] };
|
|
66498
67390
|
const bindAddress = options.bindAddress ?? "127.0.0.1";
|
|
67391
|
+
if (sharedStore !== void 0) sharedCache = await sharedStore.load();
|
|
67392
|
+
const adminConnections = /* @__PURE__ */ new Set();
|
|
66499
67393
|
const connections = /* @__PURE__ */ new Map();
|
|
67394
|
+
function isAdminSession(principal) {
|
|
67395
|
+
if (!roles.shared) return true;
|
|
67396
|
+
return adminConnections.has(principal.id) && roles.roleFor(principal) === "admin";
|
|
67397
|
+
}
|
|
66500
67398
|
let policy = { allowedHosts: [], allowedOrigins: [] };
|
|
66501
67399
|
async function openConnection(principal, response) {
|
|
66502
67400
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -66525,7 +67423,37 @@ async function startServer(options) {
|
|
|
66525
67423
|
workspaceRoot: options.workspaceRoot,
|
|
66526
67424
|
dataDir: options.dataDir,
|
|
66527
67425
|
ripgrepPath: options.ripgrepPath,
|
|
66528
|
-
logSink: log
|
|
67426
|
+
logSink: log,
|
|
67427
|
+
/*
|
|
67428
|
+
* Read at use, not captured: an administrator saving a variable must reach a session that
|
|
67429
|
+
* is already open. `SharedConfigStore` caches, so this is a map lookup rather than a read.
|
|
67430
|
+
*/
|
|
67431
|
+
adminVariables: () => sharedCache.variables,
|
|
67432
|
+
/*
|
|
67433
|
+
* Only for someone who cannot approve their own work. An administrator keeps the ordinary
|
|
67434
|
+
* in-chat prompt — the same mechanism with the approver already at the screen — so this is
|
|
67435
|
+
* absent for them rather than a queue they would have to visit to approve themselves.
|
|
67436
|
+
*/
|
|
67437
|
+
...roles.shared && !isAdminSession(principal) ? {
|
|
67438
|
+
submitForReview: async (request) => {
|
|
67439
|
+
const queued = await reviews.submit({ ...request, authorId: principal.id, authorName: principal.displayName });
|
|
67440
|
+
log(`${principal.displayName} submitted ${request.kind} "${request.name}" for review`);
|
|
67441
|
+
await broadcastReviews();
|
|
67442
|
+
return describeSubmission(queued);
|
|
67443
|
+
}
|
|
67444
|
+
} : {},
|
|
67445
|
+
/*
|
|
67446
|
+
* Only in shared mode. Outside it there is one person and every profile is already theirs,
|
|
67447
|
+
* so wrapping the stores would add a prefix nobody needs and a second file nobody writes.
|
|
67448
|
+
*/
|
|
67449
|
+
...sharedStore !== void 0 ? {
|
|
67450
|
+
sharedProfiles: () => ({
|
|
67451
|
+
profiles: sharedCache.profiles,
|
|
67452
|
+
...sharedCache.defaultProfileId !== void 0 ? { defaultProfileId: sharedCache.defaultProfileId } : {},
|
|
67453
|
+
...sharedCache.defaultProgrammingProfileId !== void 0 ? { defaultProgrammingProfileId: sharedCache.defaultProgrammingProfileId } : {}
|
|
67454
|
+
}),
|
|
67455
|
+
sharedSecrets: sharedSecretStore
|
|
67456
|
+
} : {}
|
|
66529
67457
|
});
|
|
66530
67458
|
const originalDispose = connection.dispose;
|
|
66531
67459
|
connection.dispose = () => {
|
|
@@ -66583,8 +67511,18 @@ async function startServer(options) {
|
|
|
66583
67511
|
...securityHeaders()
|
|
66584
67512
|
});
|
|
66585
67513
|
response.write(": connected\n\n");
|
|
67514
|
+
const viaAdminUrl = url2.searchParams.get("view") === "admin";
|
|
67515
|
+
if (viaAdminUrl) adminConnections.add(principal.id);
|
|
67516
|
+
else adminConnections.delete(principal.id);
|
|
66586
67517
|
const connection = await openConnection(principal, response);
|
|
66587
67518
|
connections.set(principal.id, connection);
|
|
67519
|
+
connection.transport.post({
|
|
67520
|
+
type: "hostRole",
|
|
67521
|
+
role: isAdminSession(principal) ? "admin" : "user",
|
|
67522
|
+
shared: roles.shared,
|
|
67523
|
+
displayName: principal.displayName,
|
|
67524
|
+
sharedProfileIds: sharedCache.profiles.map((profile) => toSharedProfileId(profile.id))
|
|
67525
|
+
});
|
|
66588
67526
|
const heartbeat = setInterval(() => response.write(": ping\n\n"), 2e4);
|
|
66589
67527
|
const cleanup = () => {
|
|
66590
67528
|
clearInterval(heartbeat);
|
|
@@ -66602,18 +67540,154 @@ async function startServer(options) {
|
|
|
66602
67540
|
}
|
|
66603
67541
|
const body = await readJsonBody(request);
|
|
66604
67542
|
const type = typeof body?.type === "string" ? body.type : "";
|
|
66605
|
-
if (roles.shared &&
|
|
67543
|
+
if (roles.shared && !isAdminSession(principal) && isAdminOnly(type)) {
|
|
66606
67544
|
log(`refused "${type}" from ${principal.displayName} (${principal.id}): not an administrator`);
|
|
66607
67545
|
connection.transport.post({ type: "error", message: refusalFor(type) });
|
|
66608
67546
|
respondJson(response, 403, { ok: false });
|
|
66609
67547
|
return;
|
|
66610
67548
|
}
|
|
67549
|
+
if (await handleVariableMessage(principal, type, body, connection)) {
|
|
67550
|
+
respondJson(response, 202, { ok: true });
|
|
67551
|
+
return;
|
|
67552
|
+
}
|
|
66611
67553
|
connection.deliver(body);
|
|
66612
67554
|
respondJson(response, 202, { ok: true });
|
|
66613
67555
|
return;
|
|
66614
67556
|
}
|
|
66615
67557
|
reject(response, { status: 404, reason: "Not found." });
|
|
66616
67558
|
}
|
|
67559
|
+
async function postReviews(principal, connection) {
|
|
67560
|
+
const canDecide = isAdminSession(principal);
|
|
67561
|
+
const all = await reviews.list();
|
|
67562
|
+
const visible = canDecide ? all : all.filter((item) => item.authorId === principal.id);
|
|
67563
|
+
connection.transport.post({
|
|
67564
|
+
type: "reviews",
|
|
67565
|
+
canDecide,
|
|
67566
|
+
items: visible.sort((a, b) => b.submittedAt - a.submittedAt).map((item) => ({
|
|
67567
|
+
id: item.id,
|
|
67568
|
+
kind: item.kind,
|
|
67569
|
+
name: item.name,
|
|
67570
|
+
content: item.content,
|
|
67571
|
+
existingContent: item.existingContent,
|
|
67572
|
+
authorName: item.authorName,
|
|
67573
|
+
submittedAt: item.submittedAt,
|
|
67574
|
+
status: item.status,
|
|
67575
|
+
...item.producedBy !== void 0 ? { producedBy: item.producedBy } : {},
|
|
67576
|
+
...item.decidedBy !== void 0 ? { decidedBy: item.decidedBy } : {},
|
|
67577
|
+
...item.reason !== void 0 ? { reason: item.reason } : {}
|
|
67578
|
+
}))
|
|
67579
|
+
});
|
|
67580
|
+
}
|
|
67581
|
+
async function broadcastReviews() {
|
|
67582
|
+
for (const [id, connection] of connections) {
|
|
67583
|
+
await postReviews({ id, displayName: id }, connection);
|
|
67584
|
+
}
|
|
67585
|
+
}
|
|
67586
|
+
async function applyApproval(item) {
|
|
67587
|
+
if (options.workspaceRoot === void 0) return "No workspace is open, so there is nowhere to write it.";
|
|
67588
|
+
try {
|
|
67589
|
+
if (item.kind === "skill") {
|
|
67590
|
+
const dir2 = path25.join(options.workspaceRoot, ".lightcode", "skills");
|
|
67591
|
+
await fs21.mkdir(dir2, { recursive: true });
|
|
67592
|
+
await fs21.writeFile(path25.join(dir2, `${item.name}.md`), item.content, "utf8");
|
|
67593
|
+
return void 0;
|
|
67594
|
+
}
|
|
67595
|
+
const dir = path25.join(options.workspaceRoot, ".lightcode", "tools");
|
|
67596
|
+
await fs21.mkdir(dir, { recursive: true });
|
|
67597
|
+
await fs21.writeFile(path25.join(dir, `${item.name}.py`), item.content, "utf8");
|
|
67598
|
+
return void 0;
|
|
67599
|
+
} catch (error51) {
|
|
67600
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
67601
|
+
}
|
|
67602
|
+
}
|
|
67603
|
+
async function postVariables(principal, connection) {
|
|
67604
|
+
const store = userVariableStoreFor(options.dataDir, principal);
|
|
67605
|
+
const user = store.read();
|
|
67606
|
+
const admin = sharedCache.variables;
|
|
67607
|
+
connection.transport.post({
|
|
67608
|
+
type: "variables",
|
|
67609
|
+
user: [...user],
|
|
67610
|
+
admin: [...admin],
|
|
67611
|
+
resolved: resolveSessionVariables(admin, user),
|
|
67612
|
+
adminIds: sharedCache.adminIds,
|
|
67613
|
+
canEditAdmin: isAdminSession(principal)
|
|
67614
|
+
});
|
|
67615
|
+
}
|
|
67616
|
+
async function handleVariableMessage(principal, type, body, connection) {
|
|
67617
|
+
const payload = body;
|
|
67618
|
+
if (type === "requestReviews") {
|
|
67619
|
+
await postReviews(principal, connection);
|
|
67620
|
+
return true;
|
|
67621
|
+
}
|
|
67622
|
+
if (type === "decideReview") {
|
|
67623
|
+
const id = typeof body.id === "string" ? body.id : "";
|
|
67624
|
+
const approved = body.approved === true;
|
|
67625
|
+
const reason = typeof body.reason === "string" ? body.reason : void 0;
|
|
67626
|
+
const decided = await reviews.decide(id, {
|
|
67627
|
+
approved,
|
|
67628
|
+
by: principal.displayName,
|
|
67629
|
+
...reason !== void 0 ? { reason } : {}
|
|
67630
|
+
});
|
|
67631
|
+
if (decided === void 0) {
|
|
67632
|
+
connection.transport.post({
|
|
67633
|
+
type: "error",
|
|
67634
|
+
message: "That submission has already been decided. Reload to see the current queue."
|
|
67635
|
+
});
|
|
67636
|
+
return true;
|
|
67637
|
+
}
|
|
67638
|
+
if (approved) {
|
|
67639
|
+
const failure = await applyApproval(decided);
|
|
67640
|
+
if (failure !== void 0) {
|
|
67641
|
+
connection.transport.post({ type: "error", message: `Approved, but could not write it: ${failure}` });
|
|
67642
|
+
}
|
|
67643
|
+
}
|
|
67644
|
+
log(`${principal.displayName} ${approved ? "approved" : "rejected"} ${decided.kind} "${decided.name}"`);
|
|
67645
|
+
await broadcastReviews();
|
|
67646
|
+
return true;
|
|
67647
|
+
}
|
|
67648
|
+
if (type === "requestVariables") {
|
|
67649
|
+
await postVariables(principal, connection);
|
|
67650
|
+
return true;
|
|
67651
|
+
}
|
|
67652
|
+
if (type === "saveUserVariables") {
|
|
67653
|
+
const parsed = sessionVariablesSchema.safeParse(payload.variables);
|
|
67654
|
+
if (!parsed.success) {
|
|
67655
|
+
connection.transport.post({ type: "error", message: `Could not save variables: ${parsed.error.message}` });
|
|
67656
|
+
return true;
|
|
67657
|
+
}
|
|
67658
|
+
await userVariableStoreFor(options.dataDir, principal).save(parsed.data);
|
|
67659
|
+
await postVariables(principal, connection);
|
|
67660
|
+
return true;
|
|
67661
|
+
}
|
|
67662
|
+
if (type === "saveAdminVariables" || type === "saveAdminIds") {
|
|
67663
|
+
if (sharedStore === void 0) {
|
|
67664
|
+
connection.transport.post({
|
|
67665
|
+
type: "error",
|
|
67666
|
+
message: "There are no shared settings outside --server mode."
|
|
67667
|
+
});
|
|
67668
|
+
return true;
|
|
67669
|
+
}
|
|
67670
|
+
if (type === "saveAdminVariables") {
|
|
67671
|
+
const parsed = sessionVariablesSchema.safeParse(payload.variables);
|
|
67672
|
+
if (!parsed.success) {
|
|
67673
|
+
connection.transport.post({ type: "error", message: `Could not save variables: ${parsed.error.message}` });
|
|
67674
|
+
return true;
|
|
67675
|
+
}
|
|
67676
|
+
sharedCache = await sharedStore.save({ variables: parsed.data });
|
|
67677
|
+
} else {
|
|
67678
|
+
const ids = Array.isArray(payload.ids) ? payload.ids.filter((id) => typeof id === "string") : [];
|
|
67679
|
+
if (!ids.includes(principal.id)) {
|
|
67680
|
+
log(`${principal.displayName} removed themselves from the administrator list`);
|
|
67681
|
+
}
|
|
67682
|
+
sharedCache = await sharedStore.save({ adminIds: [...new Set(ids)] });
|
|
67683
|
+
}
|
|
67684
|
+
for (const [id, other] of connections) {
|
|
67685
|
+
await postVariables({ id, displayName: id }, other);
|
|
67686
|
+
}
|
|
67687
|
+
return true;
|
|
67688
|
+
}
|
|
67689
|
+
return false;
|
|
67690
|
+
}
|
|
66617
67691
|
async function serveAsset(pathname, response) {
|
|
66618
67692
|
const asset = CLIENT_ASSETS[pathname];
|
|
66619
67693
|
if (asset === void 0) {
|
|
@@ -66621,9 +67695,9 @@ async function startServer(options) {
|
|
|
66621
67695
|
return;
|
|
66622
67696
|
}
|
|
66623
67697
|
try {
|
|
66624
|
-
const body = await
|
|
67698
|
+
const body = await fs21.readFile(path25.join(options.clientDir, asset));
|
|
66625
67699
|
response.writeHead(200, {
|
|
66626
|
-
"Content-Type": CONTENT_TYPES[
|
|
67700
|
+
"Content-Type": CONTENT_TYPES[path25.extname(asset)] ?? "application/octet-stream",
|
|
66627
67701
|
...securityHeaders()
|
|
66628
67702
|
});
|
|
66629
67703
|
response.end(body);
|
|
@@ -66651,6 +67725,7 @@ function respondJson(response, status, body) {
|
|
|
66651
67725
|
response.end(JSON.stringify(body));
|
|
66652
67726
|
}
|
|
66653
67727
|
export {
|
|
67728
|
+
CLIENT_ASSETS,
|
|
66654
67729
|
startServer
|
|
66655
67730
|
};
|
|
66656
67731
|
/*! Bundled license information:
|