@botlearn-course/daemon 0.0.19 → 0.0.20-beta.2
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/agent-service-sandbox.d.ts +9 -1
- package/dist/agent-service-sandbox.js +490 -16
- package/dist/agent-service-ws-protocol.d.ts +3 -3
- package/dist/agent-service-ws-protocol.js +6 -2
- package/dist/cli.js +19 -1
- package/dist/file-candidates.d.ts +28 -1
- package/dist/file-candidates.js +57 -11
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/run-dispatcher.d.ts +3 -2
- package/dist/run-dispatcher.js +62 -5
- package/dist/runtime-env.js +4 -4
- package/dist/runtime-quiescence.d.ts +16 -0
- package/dist/runtime-quiescence.js +42 -0
- package/dist/runtimes/engine.js +1 -1
- package/dist/tool-observation.d.ts +7 -4
- package/dist/tool-observation.js +40 -18
- package/dist/trace-projection.d.ts +21 -0
- package/dist/trace-projection.js +56 -0
- package/dist/types.d.ts +1 -1
- package/dist/workspace-entry-set.d.ts +31 -0
- package/dist/workspace-entry-set.js +164 -0
- package/dist/workspace-materialization.d.ts +16 -0
- package/dist/workspace-materialization.js +136 -0
- package/dist/workspace-quota.d.ts +4 -0
- package/dist/workspace-quota.js +42 -0
- package/dist/workspace-restore.d.ts +42 -0
- package/dist/workspace-restore.js +347 -0
- package/dist/workspace-snapshot-control.d.ts +29 -0
- package/dist/workspace-snapshot-control.js +169 -0
- package/dist/workspace-snapshot-policy.d.ts +24 -0
- package/dist/workspace-snapshot-policy.js +45 -0
- package/dist/workspace-snapshot-staging.d.ts +27 -0
- package/dist/workspace-snapshot-staging.js +275 -0
- package/dist/workspace.d.ts +56 -0
- package/dist/workspace.js +553 -1
- package/package.json +1 -1
package/dist/tool-observation.js
CHANGED
|
@@ -2,7 +2,11 @@ import path from "node:path";
|
|
|
2
2
|
import { REDACTED, redactSecretsDeep, } from "./redaction.js";
|
|
3
3
|
export const AGENT_TOOL_OBSERVATION_SCHEMA_VERSION = "agent-tool-observation/0.1";
|
|
4
4
|
const DETAIL_MAX_CHARS = 4_000;
|
|
5
|
+
const DETAIL_FULL_MAX_CHARS = 64_000;
|
|
5
6
|
const MAX_PROJECT_DEPTH = 8;
|
|
7
|
+
const MAX_PROJECT_DEPTH_FULL = 16;
|
|
8
|
+
const MAX_PROJECT_ARRAY_ITEMS = 50;
|
|
9
|
+
const MAX_PROJECT_ARRAY_ITEMS_FULL = 500;
|
|
6
10
|
const SAFE_TOOL_NAME = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,79}$/;
|
|
7
11
|
const CALL_FIELDS = new Set([
|
|
8
12
|
"arguments",
|
|
@@ -37,27 +41,39 @@ const RESULT_FIELDS = new Set([
|
|
|
37
41
|
/**
|
|
38
42
|
* Project a provider tool envelope into durable teacher evidence.
|
|
39
43
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
+
* Only allowlisted operational argument/result fields survive, credentials are removed,
|
|
45
|
+
* and host paths become workspace-relative. `detail_preview` stays a bounded feed-sized
|
|
46
|
+
* excerpt; `detail_full` carries the same allowlisted projection at audit fidelity (deeper
|
|
47
|
+
* walk, larger arrays, 64k chars). Message text, request IDs, and arbitrary envelope
|
|
48
|
+
* metadata never cross this boundary.
|
|
44
49
|
*/
|
|
45
50
|
export function buildToolObservation(block, runtime, workspaceDir) {
|
|
46
51
|
if (block.kind !== "tool_call" && block.kind !== "tool_result")
|
|
47
52
|
return null;
|
|
48
53
|
const fields = block.kind === "tool_call" ? CALL_FIELDS : RESULT_FIELDS;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
projected
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const resolvedWorkspace = path.resolve(workspaceDir);
|
|
55
|
+
const serializeProjection = (depthLimit, arrayLimit) => {
|
|
56
|
+
const projected = collectAllowedFields(block.raw, fields, {
|
|
57
|
+
depthLimit,
|
|
58
|
+
arrayLimit,
|
|
59
|
+
});
|
|
60
|
+
if (typeof block.text === "string" && block.text) {
|
|
61
|
+
projected[block.kind === "tool_call" ? "target" : "summary"] = block.text;
|
|
62
|
+
}
|
|
63
|
+
if (Object.keys(projected).length === 0)
|
|
64
|
+
return "";
|
|
65
|
+
return JSON.stringify(normalizeWorkspacePaths(redactSecretsDeep(projected), resolvedWorkspace), null, 2);
|
|
66
|
+
};
|
|
67
|
+
const serialized = serializeProjection(MAX_PROJECT_DEPTH, MAX_PROJECT_ARRAY_ITEMS);
|
|
57
68
|
const detailTruncated = serialized.length > DETAIL_MAX_CHARS;
|
|
58
69
|
const detailPreview = detailTruncated
|
|
59
70
|
? `${serialized.slice(0, DETAIL_MAX_CHARS - 1)}…`
|
|
60
71
|
: serialized;
|
|
72
|
+
const serializedFull = serializeProjection(MAX_PROJECT_DEPTH_FULL, MAX_PROJECT_ARRAY_ITEMS_FULL);
|
|
73
|
+
const detailFullTruncated = serializedFull.length > DETAIL_FULL_MAX_CHARS;
|
|
74
|
+
const detailFull = detailFullTruncated
|
|
75
|
+
? `${serializedFull.slice(0, DETAIL_FULL_MAX_CHARS - 1)}…`
|
|
76
|
+
: serializedFull;
|
|
61
77
|
return {
|
|
62
78
|
schema_version: AGENT_TOOL_OBSERVATION_SCHEMA_VERSION,
|
|
63
79
|
kind: block.kind,
|
|
@@ -70,16 +86,22 @@ export function buildToolObservation(block, runtime, workspaceDir) {
|
|
|
70
86
|
...(block.name && SAFE_TOOL_NAME.test(block.name) ? { name: block.name } : {}),
|
|
71
87
|
...(detailPreview ? { detail_preview: detailPreview } : {}),
|
|
72
88
|
detail_truncated: detailTruncated,
|
|
73
|
-
|
|
89
|
+
...(detailFull
|
|
90
|
+
? { detail_full: detailFull, detail_full_truncated: detailFullTruncated }
|
|
91
|
+
: {}),
|
|
92
|
+
redacted: detailPreview.includes(REDACTED) || detailFull.includes(REDACTED),
|
|
74
93
|
};
|
|
75
94
|
}
|
|
76
|
-
function collectAllowedFields(value, allowed,
|
|
77
|
-
|
|
95
|
+
function collectAllowedFields(value, allowed, limits = {
|
|
96
|
+
depthLimit: MAX_PROJECT_DEPTH,
|
|
97
|
+
arrayLimit: MAX_PROJECT_ARRAY_ITEMS,
|
|
98
|
+
}, depth = 0, out = {}) {
|
|
99
|
+
if (value === null || typeof value !== "object" || depth >= limits.depthLimit) {
|
|
78
100
|
return out;
|
|
79
101
|
}
|
|
80
102
|
if (Array.isArray(value)) {
|
|
81
|
-
for (const item of value.slice(0,
|
|
82
|
-
collectAllowedFields(item, allowed, depth + 1, out);
|
|
103
|
+
for (const item of value.slice(0, limits.arrayLimit)) {
|
|
104
|
+
collectAllowedFields(item, allowed, limits, depth + 1, out);
|
|
83
105
|
}
|
|
84
106
|
return out;
|
|
85
107
|
}
|
|
@@ -96,7 +118,7 @@ function collectAllowedFields(value, allowed, depth = 0, out = {}) {
|
|
|
96
118
|
out[key] = item;
|
|
97
119
|
continue;
|
|
98
120
|
}
|
|
99
|
-
collectAllowedFields(item, allowed, depth + 1, out);
|
|
121
|
+
collectAllowedFields(item, allowed, limits, depth + 1, out);
|
|
100
122
|
}
|
|
101
123
|
return out;
|
|
102
124
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const AGENT_REASONING_TRACE_SCHEMA_VERSION = "agent-reasoning-trace/0.1";
|
|
2
|
+
export declare const REASONING_TRACE_MAX_CHARS = 64000;
|
|
3
|
+
export interface ReasoningTracePayload extends Record<string, unknown> {
|
|
4
|
+
schema_version: typeof AGENT_REASONING_TRACE_SCHEMA_VERSION;
|
|
5
|
+
kind: "reasoning";
|
|
6
|
+
runtime: string;
|
|
7
|
+
text: string;
|
|
8
|
+
truncated: boolean;
|
|
9
|
+
redacted: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Best-effort extraction of provider reasoning text from one raw stream envelope.
|
|
13
|
+
*
|
|
14
|
+
* Providers ship reasoning under a small set of well-known field names (Anthropic
|
|
15
|
+
* `thinking` content items, OpenAI-compatible `reasoning`/`reasoning_content`). The walk
|
|
16
|
+
* is bounded and collects only string values under those names; everything else in the
|
|
17
|
+
* envelope stays local to the sandbox transcript.
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractReasoningText(raw: unknown): string;
|
|
20
|
+
/** Project accumulated reasoning text into one durable, redacted, bounded trace payload. */
|
|
21
|
+
export declare function buildReasoningTracePayload(text: string, runtime: string, alreadyTruncated?: boolean): ReasoningTracePayload;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { redactSecretString, REDACTED } from "./redaction.js";
|
|
2
|
+
export const AGENT_REASONING_TRACE_SCHEMA_VERSION = "agent-reasoning-trace/0.1";
|
|
3
|
+
export const REASONING_TRACE_MAX_CHARS = 64_000;
|
|
4
|
+
const MAX_EXTRACT_DEPTH = 6;
|
|
5
|
+
const MAX_EXTRACT_ARRAY_ITEMS = 50;
|
|
6
|
+
// Provider-normalized field names whose string values carry model reasoning text.
|
|
7
|
+
const REASONING_TEXT_KEYS = new Set([
|
|
8
|
+
"thinking",
|
|
9
|
+
"reasoning",
|
|
10
|
+
"reasoning_content",
|
|
11
|
+
"reasoningText",
|
|
12
|
+
]);
|
|
13
|
+
/**
|
|
14
|
+
* Best-effort extraction of provider reasoning text from one raw stream envelope.
|
|
15
|
+
*
|
|
16
|
+
* Providers ship reasoning under a small set of well-known field names (Anthropic
|
|
17
|
+
* `thinking` content items, OpenAI-compatible `reasoning`/`reasoning_content`). The walk
|
|
18
|
+
* is bounded and collects only string values under those names; everything else in the
|
|
19
|
+
* envelope stays local to the sandbox transcript.
|
|
20
|
+
*/
|
|
21
|
+
export function extractReasoningText(raw) {
|
|
22
|
+
const parts = [];
|
|
23
|
+
collectReasoningStrings(raw, 0, parts);
|
|
24
|
+
return parts.join("");
|
|
25
|
+
}
|
|
26
|
+
function collectReasoningStrings(value, depth, out) {
|
|
27
|
+
if (value === null || typeof value !== "object" || depth >= MAX_EXTRACT_DEPTH)
|
|
28
|
+
return;
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
for (const item of value.slice(0, MAX_EXTRACT_ARRAY_ITEMS)) {
|
|
31
|
+
collectReasoningStrings(item, depth + 1, out);
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
for (const [key, item] of Object.entries(value)) {
|
|
36
|
+
if (REASONING_TEXT_KEYS.has(key) && typeof item === "string" && item) {
|
|
37
|
+
out.push(item);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
collectReasoningStrings(item, depth + 1, out);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Project accumulated reasoning text into one durable, redacted, bounded trace payload. */
|
|
44
|
+
export function buildReasoningTracePayload(text, runtime, alreadyTruncated = false) {
|
|
45
|
+
const redactedText = redactSecretString(text);
|
|
46
|
+
const truncated = alreadyTruncated || redactedText.length > REASONING_TRACE_MAX_CHARS;
|
|
47
|
+
const bounded = redactedText.slice(0, REASONING_TRACE_MAX_CHARS);
|
|
48
|
+
return {
|
|
49
|
+
schema_version: AGENT_REASONING_TRACE_SCHEMA_VERSION,
|
|
50
|
+
kind: "reasoning",
|
|
51
|
+
runtime,
|
|
52
|
+
text: bounded,
|
|
53
|
+
truncated,
|
|
54
|
+
redacted: bounded.includes(REDACTED),
|
|
55
|
+
};
|
|
56
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export interface RunStartPayload {
|
|
|
54
54
|
* `POST /daemon/runs/{id}/events` 接受的事件类型。
|
|
55
55
|
* 与后端 DaemonRunEventIn.type 的 Literal 严格一致 —— 发送其他类型会得到 422。
|
|
56
56
|
*/
|
|
57
|
-
export type RunEventType = "run.accepted" | "run.started" | "run.block" | "run.observation" | "run.message" | "run.completed" | "run.failed" | "run.cancelled";
|
|
57
|
+
export type RunEventType = "run.accepted" | "run.started" | "run.block" | "run.observation" | "run.trace" | "run.message" | "run.completed" | "run.failed" | "run.cancelled";
|
|
58
58
|
export interface RunEvent {
|
|
59
59
|
type: RunEventType;
|
|
60
60
|
/** End-to-end correlation id assigned by Course Service. */
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const WORKSPACE_ENTRY_SET_SCHEMA: "agent-workspace-entry-set/1";
|
|
2
|
+
export declare const UNICODE_CASE_FOLD_VERSION: "15.1.0";
|
|
3
|
+
export interface WorkspaceEntrySetLimits {
|
|
4
|
+
maxPathSegmentBytes: number;
|
|
5
|
+
maxPathBytes: number;
|
|
6
|
+
maxDepth: number;
|
|
7
|
+
maxFileBytes: number;
|
|
8
|
+
maxFileCount: number;
|
|
9
|
+
maxTotalBytes: number;
|
|
10
|
+
maxEntrySetBytes: number;
|
|
11
|
+
}
|
|
12
|
+
export interface WorkspaceEntry {
|
|
13
|
+
type: "file" | "directory";
|
|
14
|
+
path: string;
|
|
15
|
+
mode: "0600" | "0700";
|
|
16
|
+
sizeBytes?: number;
|
|
17
|
+
sha256?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CanonicalWorkspaceEntrySet {
|
|
20
|
+
entries: WorkspaceEntry[];
|
|
21
|
+
canonicalBytes: Buffer;
|
|
22
|
+
contentSha256: string;
|
|
23
|
+
fileCount: number;
|
|
24
|
+
totalBytes: number;
|
|
25
|
+
}
|
|
26
|
+
export declare class WorkspaceEntrySetError extends Error {
|
|
27
|
+
readonly code: string;
|
|
28
|
+
constructor(code: string);
|
|
29
|
+
}
|
|
30
|
+
export declare function defaultCaseFold(value: string): string;
|
|
31
|
+
export declare function validateWorkspaceEntrySet(raw: unknown, limits: WorkspaceEntrySetLimits): CanonicalWorkspaceEntrySet;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export const WORKSPACE_ENTRY_SET_SCHEMA = "agent-workspace-entry-set/1";
|
|
3
|
+
export const UNICODE_CASE_FOLD_VERSION = "15.1.0";
|
|
4
|
+
const CASE_FOLD_DATA = "41=61;42=62;43=63;44=64;45=65;46=66;47=67;48=68;49=69;4A=6A;4B=6B;4C=6C;4D=6D;4E=6E;4F=6F;50=70;51=71;52=72;53=73;54=74;55=75;56=76;57=77;58=78;59=79;5A=7A;B5=3BC;C0=E0;C1=E1;C2=E2;C3=E3;C4=E4;C5=E5;C6=E6;C7=E7;C8=E8;C9=E9;CA=EA;CB=EB;CC=EC;CD=ED;CE=EE;CF=EF;D0=F0;D1=F1;D2=F2;D3=F3;D4=F4;D5=F5;D6=F6;D8=F8;D9=F9;DA=FA;DB=FB;DC=FC;DD=FD;DE=FE;DF=73,73;100=101;102=103;104=105;106=107;108=109;10A=10B;10C=10D;10E=10F;110=111;112=113;114=115;116=117;118=119;11A=11B;11C=11D;11E=11F;120=121;122=123;124=125;126=127;128=129;12A=12B;12C=12D;12E=12F;130=69,307;132=133;134=135;136=137;139=13A;13B=13C;13D=13E;13F=140;141=142;143=144;145=146;147=148;149=2BC,6E;14A=14B;14C=14D;14E=14F;150=151;152=153;154=155;156=157;158=159;15A=15B;15C=15D;15E=15F;160=161;162=163;164=165;166=167;168=169;16A=16B;16C=16D;16E=16F;170=171;172=173;174=175;176=177;178=FF;179=17A;17B=17C;17D=17E;17F=73;181=253;182=183;184=185;186=254;187=188;189=256;18A=257;18B=18C;18E=1DD;18F=259;190=25B;191=192;193=260;194=263;196=269;197=268;198=199;19C=26F;19D=272;19F=275;1A0=1A1;1A2=1A3;1A4=1A5;1A6=280;1A7=1A8;1A9=283;1AC=1AD;1AE=288;1AF=1B0;1B1=28A;1B2=28B;1B3=1B4;1B5=1B6;1B7=292;1B8=1B9;1BC=1BD;1C4=1C6;1C5=1C6;1C7=1C9;1C8=1C9;1CA=1CC;1CB=1CC;1CD=1CE;1CF=1D0;1D1=1D2;1D3=1D4;1D5=1D6;1D7=1D8;1D9=1DA;1DB=1DC;1DE=1DF;1E0=1E1;1E2=1E3;1E4=1E5;1E6=1E7;1E8=1E9;1EA=1EB;1EC=1ED;1EE=1EF;1F0=6A,30C;1F1=1F3;1F2=1F3;1F4=1F5;1F6=195;1F7=1BF;1F8=1F9;1FA=1FB;1FC=1FD;1FE=1FF;200=201;202=203;204=205;206=207;208=209;20A=20B;20C=20D;20E=20F;210=211;212=213;214=215;216=217;218=219;21A=21B;21C=21D;21E=21F;220=19E;222=223;224=225;226=227;228=229;22A=22B;22C=22D;22E=22F;230=231;232=233;23A=2C65;23B=23C;23D=19A;23E=2C66;241=242;243=180;244=289;245=28C;246=247;248=249;24A=24B;24C=24D;24E=24F;345=3B9;370=371;372=373;376=377;37F=3F3;386=3AC;388=3AD;389=3AE;38A=3AF;38C=3CC;38E=3CD;38F=3CE;390=3B9,308,301;391=3B1;392=3B2;393=3B3;394=3B4;395=3B5;396=3B6;397=3B7;398=3B8;399=3B9;39A=3BA;39B=3BB;39C=3BC;39D=3BD;39E=3BE;39F=3BF;3A0=3C0;3A1=3C1;3A3=3C3;3A4=3C4;3A5=3C5;3A6=3C6;3A7=3C7;3A8=3C8;3A9=3C9;3AA=3CA;3AB=3CB;3B0=3C5,308,301;3C2=3C3;3CF=3D7;3D0=3B2;3D1=3B8;3D5=3C6;3D6=3C0;3D8=3D9;3DA=3DB;3DC=3DD;3DE=3DF;3E0=3E1;3E2=3E3;3E4=3E5;3E6=3E7;3E8=3E9;3EA=3EB;3EC=3ED;3EE=3EF;3F0=3BA;3F1=3C1;3F4=3B8;3F5=3B5;3F7=3F8;3F9=3F2;3FA=3FB;3FD=37B;3FE=37C;3FF=37D;400=450;401=451;402=452;403=453;404=454;405=455;406=456;407=457;408=458;409=459;40A=45A;40B=45B;40C=45C;40D=45D;40E=45E;40F=45F;410=430;411=431;412=432;413=433;414=434;415=435;416=436;417=437;418=438;419=439;41A=43A;41B=43B;41C=43C;41D=43D;41E=43E;41F=43F;420=440;421=441;422=442;423=443;424=444;425=445;426=446;427=447;428=448;429=449;42A=44A;42B=44B;42C=44C;42D=44D;42E=44E;42F=44F;460=461;462=463;464=465;466=467;468=469;46A=46B;46C=46D;46E=46F;470=471;472=473;474=475;476=477;478=479;47A=47B;47C=47D;47E=47F;480=481;48A=48B;48C=48D;48E=48F;490=491;492=493;494=495;496=497;498=499;49A=49B;49C=49D;49E=49F;4A0=4A1;4A2=4A3;4A4=4A5;4A6=4A7;4A8=4A9;4AA=4AB;4AC=4AD;4AE=4AF;4B0=4B1;4B2=4B3;4B4=4B5;4B6=4B7;4B8=4B9;4BA=4BB;4BC=4BD;4BE=4BF;4C0=4CF;4C1=4C2;4C3=4C4;4C5=4C6;4C7=4C8;4C9=4CA;4CB=4CC;4CD=4CE;4D0=4D1;4D2=4D3;4D4=4D5;4D6=4D7;4D8=4D9;4DA=4DB;4DC=4DD;4DE=4DF;4E0=4E1;4E2=4E3;4E4=4E5;4E6=4E7;4E8=4E9;4EA=4EB;4EC=4ED;4EE=4EF;4F0=4F1;4F2=4F3;4F4=4F5;4F6=4F7;4F8=4F9;4FA=4FB;4FC=4FD;4FE=4FF;500=501;502=503;504=505;506=507;508=509;50A=50B;50C=50D;50E=50F;510=511;512=513;514=515;516=517;518=519;51A=51B;51C=51D;51E=51F;520=521;522=523;524=525;526=527;528=529;52A=52B;52C=52D;52E=52F;531=561;532=562;533=563;534=564;535=565;536=566;537=567;538=568;539=569;53A=56A;53B=56B;53C=56C;53D=56D;53E=56E;53F=56F;540=570;541=571;542=572;543=573;544=574;545=575;546=576;547=577;548=578;549=579;54A=57A;54B=57B;54C=57C;54D=57D;54E=57E;54F=57F;550=580;551=581;552=582;553=583;554=584;555=585;556=586;587=565,582;10A0=2D00;10A1=2D01;10A2=2D02;10A3=2D03;10A4=2D04;10A5=2D05;10A6=2D06;10A7=2D07;10A8=2D08;10A9=2D09;10AA=2D0A;10AB=2D0B;10AC=2D0C;10AD=2D0D;10AE=2D0E;10AF=2D0F;10B0=2D10;10B1=2D11;10B2=2D12;10B3=2D13;10B4=2D14;10B5=2D15;10B6=2D16;10B7=2D17;10B8=2D18;10B9=2D19;10BA=2D1A;10BB=2D1B;10BC=2D1C;10BD=2D1D;10BE=2D1E;10BF=2D1F;10C0=2D20;10C1=2D21;10C2=2D22;10C3=2D23;10C4=2D24;10C5=2D25;10C7=2D27;10CD=2D2D;13F8=13F0;13F9=13F1;13FA=13F2;13FB=13F3;13FC=13F4;13FD=13F5;1C80=432;1C81=434;1C82=43E;1C83=441;1C84=442;1C85=442;1C86=44A;1C87=463;1C88=A64B;1C90=10D0;1C91=10D1;1C92=10D2;1C93=10D3;1C94=10D4;1C95=10D5;1C96=10D6;1C97=10D7;1C98=10D8;1C99=10D9;1C9A=10DA;1C9B=10DB;1C9C=10DC;1C9D=10DD;1C9E=10DE;1C9F=10DF;1CA0=10E0;1CA1=10E1;1CA2=10E2;1CA3=10E3;1CA4=10E4;1CA5=10E5;1CA6=10E6;1CA7=10E7;1CA8=10E8;1CA9=10E9;1CAA=10EA;1CAB=10EB;1CAC=10EC;1CAD=10ED;1CAE=10EE;1CAF=10EF;1CB0=10F0;1CB1=10F1;1CB2=10F2;1CB3=10F3;1CB4=10F4;1CB5=10F5;1CB6=10F6;1CB7=10F7;1CB8=10F8;1CB9=10F9;1CBA=10FA;1CBD=10FD;1CBE=10FE;1CBF=10FF;1E00=1E01;1E02=1E03;1E04=1E05;1E06=1E07;1E08=1E09;1E0A=1E0B;1E0C=1E0D;1E0E=1E0F;1E10=1E11;1E12=1E13;1E14=1E15;1E16=1E17;1E18=1E19;1E1A=1E1B;1E1C=1E1D;1E1E=1E1F;1E20=1E21;1E22=1E23;1E24=1E25;1E26=1E27;1E28=1E29;1E2A=1E2B;1E2C=1E2D;1E2E=1E2F;1E30=1E31;1E32=1E33;1E34=1E35;1E36=1E37;1E38=1E39;1E3A=1E3B;1E3C=1E3D;1E3E=1E3F;1E40=1E41;1E42=1E43;1E44=1E45;1E46=1E47;1E48=1E49;1E4A=1E4B;1E4C=1E4D;1E4E=1E4F;1E50=1E51;1E52=1E53;1E54=1E55;1E56=1E57;1E58=1E59;1E5A=1E5B;1E5C=1E5D;1E5E=1E5F;1E60=1E61;1E62=1E63;1E64=1E65;1E66=1E67;1E68=1E69;1E6A=1E6B;1E6C=1E6D;1E6E=1E6F;1E70=1E71;1E72=1E73;1E74=1E75;1E76=1E77;1E78=1E79;1E7A=1E7B;1E7C=1E7D;1E7E=1E7F;1E80=1E81;1E82=1E83;1E84=1E85;1E86=1E87;1E88=1E89;1E8A=1E8B;1E8C=1E8D;1E8E=1E8F;1E90=1E91;1E92=1E93;1E94=1E95;1E96=68,331;1E97=74,308;1E98=77,30A;1E99=79,30A;1E9A=61,2BE;1E9B=1E61;1E9E=73,73;1EA0=1EA1;1EA2=1EA3;1EA4=1EA5;1EA6=1EA7;1EA8=1EA9;1EAA=1EAB;1EAC=1EAD;1EAE=1EAF;1EB0=1EB1;1EB2=1EB3;1EB4=1EB5;1EB6=1EB7;1EB8=1EB9;1EBA=1EBB;1EBC=1EBD;1EBE=1EBF;1EC0=1EC1;1EC2=1EC3;1EC4=1EC5;1EC6=1EC7;1EC8=1EC9;1ECA=1ECB;1ECC=1ECD;1ECE=1ECF;1ED0=1ED1;1ED2=1ED3;1ED4=1ED5;1ED6=1ED7;1ED8=1ED9;1EDA=1EDB;1EDC=1EDD;1EDE=1EDF;1EE0=1EE1;1EE2=1EE3;1EE4=1EE5;1EE6=1EE7;1EE8=1EE9;1EEA=1EEB;1EEC=1EED;1EEE=1EEF;1EF0=1EF1;1EF2=1EF3;1EF4=1EF5;1EF6=1EF7;1EF8=1EF9;1EFA=1EFB;1EFC=1EFD;1EFE=1EFF;1F08=1F00;1F09=1F01;1F0A=1F02;1F0B=1F03;1F0C=1F04;1F0D=1F05;1F0E=1F06;1F0F=1F07;1F18=1F10;1F19=1F11;1F1A=1F12;1F1B=1F13;1F1C=1F14;1F1D=1F15;1F28=1F20;1F29=1F21;1F2A=1F22;1F2B=1F23;1F2C=1F24;1F2D=1F25;1F2E=1F26;1F2F=1F27;1F38=1F30;1F39=1F31;1F3A=1F32;1F3B=1F33;1F3C=1F34;1F3D=1F35;1F3E=1F36;1F3F=1F37;1F48=1F40;1F49=1F41;1F4A=1F42;1F4B=1F43;1F4C=1F44;1F4D=1F45;1F50=3C5,313;1F52=3C5,313,300;1F54=3C5,313,301;1F56=3C5,313,342;1F59=1F51;1F5B=1F53;1F5D=1F55;1F5F=1F57;1F68=1F60;1F69=1F61;1F6A=1F62;1F6B=1F63;1F6C=1F64;1F6D=1F65;1F6E=1F66;1F6F=1F67;1F80=1F00,3B9;1F81=1F01,3B9;1F82=1F02,3B9;1F83=1F03,3B9;1F84=1F04,3B9;1F85=1F05,3B9;1F86=1F06,3B9;1F87=1F07,3B9;1F88=1F00,3B9;1F89=1F01,3B9;1F8A=1F02,3B9;1F8B=1F03,3B9;1F8C=1F04,3B9;1F8D=1F05,3B9;1F8E=1F06,3B9;1F8F=1F07,3B9;1F90=1F20,3B9;1F91=1F21,3B9;1F92=1F22,3B9;1F93=1F23,3B9;1F94=1F24,3B9;1F95=1F25,3B9;1F96=1F26,3B9;1F97=1F27,3B9;1F98=1F20,3B9;1F99=1F21,3B9;1F9A=1F22,3B9;1F9B=1F23,3B9;1F9C=1F24,3B9;1F9D=1F25,3B9;1F9E=1F26,3B9;1F9F=1F27,3B9;1FA0=1F60,3B9;1FA1=1F61,3B9;1FA2=1F62,3B9;1FA3=1F63,3B9;1FA4=1F64,3B9;1FA5=1F65,3B9;1FA6=1F66,3B9;1FA7=1F67,3B9;1FA8=1F60,3B9;1FA9=1F61,3B9;1FAA=1F62,3B9;1FAB=1F63,3B9;1FAC=1F64,3B9;1FAD=1F65,3B9;1FAE=1F66,3B9;1FAF=1F67,3B9;1FB2=1F70,3B9;1FB3=3B1,3B9;1FB4=3AC,3B9;1FB6=3B1,342;1FB7=3B1,342,3B9;1FB8=1FB0;1FB9=1FB1;1FBA=1F70;1FBB=1F71;1FBC=3B1,3B9;1FBE=3B9;1FC2=1F74,3B9;1FC3=3B7,3B9;1FC4=3AE,3B9;1FC6=3B7,342;1FC7=3B7,342,3B9;1FC8=1F72;1FC9=1F73;1FCA=1F74;1FCB=1F75;1FCC=3B7,3B9;1FD2=3B9,308,300;1FD3=3B9,308,301;1FD6=3B9,342;1FD7=3B9,308,342;1FD8=1FD0;1FD9=1FD1;1FDA=1F76;1FDB=1F77;1FE2=3C5,308,300;1FE3=3C5,308,301;1FE4=3C1,313;1FE6=3C5,342;1FE7=3C5,308,342;1FE8=1FE0;1FE9=1FE1;1FEA=1F7A;1FEB=1F7B;1FEC=1FE5;1FF2=1F7C,3B9;1FF3=3C9,3B9;1FF4=3CE,3B9;1FF6=3C9,342;1FF7=3C9,342,3B9;1FF8=1F78;1FF9=1F79;1FFA=1F7C;1FFB=1F7D;1FFC=3C9,3B9;2126=3C9;212A=6B;212B=E5;2132=214E;2160=2170;2161=2171;2162=2172;2163=2173;2164=2174;2165=2175;2166=2176;2167=2177;2168=2178;2169=2179;216A=217A;216B=217B;216C=217C;216D=217D;216E=217E;216F=217F;2183=2184;24B6=24D0;24B7=24D1;24B8=24D2;24B9=24D3;24BA=24D4;24BB=24D5;24BC=24D6;24BD=24D7;24BE=24D8;24BF=24D9;24C0=24DA;24C1=24DB;24C2=24DC;24C3=24DD;24C4=24DE;24C5=24DF;24C6=24E0;24C7=24E1;24C8=24E2;24C9=24E3;24CA=24E4;24CB=24E5;24CC=24E6;24CD=24E7;24CE=24E8;24CF=24E9;2C00=2C30;2C01=2C31;2C02=2C32;2C03=2C33;2C04=2C34;2C05=2C35;2C06=2C36;2C07=2C37;2C08=2C38;2C09=2C39;2C0A=2C3A;2C0B=2C3B;2C0C=2C3C;2C0D=2C3D;2C0E=2C3E;2C0F=2C3F;2C10=2C40;2C11=2C41;2C12=2C42;2C13=2C43;2C14=2C44;2C15=2C45;2C16=2C46;2C17=2C47;2C18=2C48;2C19=2C49;2C1A=2C4A;2C1B=2C4B;2C1C=2C4C;2C1D=2C4D;2C1E=2C4E;2C1F=2C4F;2C20=2C50;2C21=2C51;2C22=2C52;2C23=2C53;2C24=2C54;2C25=2C55;2C26=2C56;2C27=2C57;2C28=2C58;2C29=2C59;2C2A=2C5A;2C2B=2C5B;2C2C=2C5C;2C2D=2C5D;2C2E=2C5E;2C2F=2C5F;2C60=2C61;2C62=26B;2C63=1D7D;2C64=27D;2C67=2C68;2C69=2C6A;2C6B=2C6C;2C6D=251;2C6E=271;2C6F=250;2C70=252;2C72=2C73;2C75=2C76;2C7E=23F;2C7F=240;2C80=2C81;2C82=2C83;2C84=2C85;2C86=2C87;2C88=2C89;2C8A=2C8B;2C8C=2C8D;2C8E=2C8F;2C90=2C91;2C92=2C93;2C94=2C95;2C96=2C97;2C98=2C99;2C9A=2C9B;2C9C=2C9D;2C9E=2C9F;2CA0=2CA1;2CA2=2CA3;2CA4=2CA5;2CA6=2CA7;2CA8=2CA9;2CAA=2CAB;2CAC=2CAD;2CAE=2CAF;2CB0=2CB1;2CB2=2CB3;2CB4=2CB5;2CB6=2CB7;2CB8=2CB9;2CBA=2CBB;2CBC=2CBD;2CBE=2CBF;2CC0=2CC1;2CC2=2CC3;2CC4=2CC5;2CC6=2CC7;2CC8=2CC9;2CCA=2CCB;2CCC=2CCD;2CCE=2CCF;2CD0=2CD1;2CD2=2CD3;2CD4=2CD5;2CD6=2CD7;2CD8=2CD9;2CDA=2CDB;2CDC=2CDD;2CDE=2CDF;2CE0=2CE1;2CE2=2CE3;2CEB=2CEC;2CED=2CEE;2CF2=2CF3;A640=A641;A642=A643;A644=A645;A646=A647;A648=A649;A64A=A64B;A64C=A64D;A64E=A64F;A650=A651;A652=A653;A654=A655;A656=A657;A658=A659;A65A=A65B;A65C=A65D;A65E=A65F;A660=A661;A662=A663;A664=A665;A666=A667;A668=A669;A66A=A66B;A66C=A66D;A680=A681;A682=A683;A684=A685;A686=A687;A688=A689;A68A=A68B;A68C=A68D;A68E=A68F;A690=A691;A692=A693;A694=A695;A696=A697;A698=A699;A69A=A69B;A722=A723;A724=A725;A726=A727;A728=A729;A72A=A72B;A72C=A72D;A72E=A72F;A732=A733;A734=A735;A736=A737;A738=A739;A73A=A73B;A73C=A73D;A73E=A73F;A740=A741;A742=A743;A744=A745;A746=A747;A748=A749;A74A=A74B;A74C=A74D;A74E=A74F;A750=A751;A752=A753;A754=A755;A756=A757;A758=A759;A75A=A75B;A75C=A75D;A75E=A75F;A760=A761;A762=A763;A764=A765;A766=A767;A768=A769;A76A=A76B;A76C=A76D;A76E=A76F;A779=A77A;A77B=A77C;A77D=1D79;A77E=A77F;A780=A781;A782=A783;A784=A785;A786=A787;A78B=A78C;A78D=265;A790=A791;A792=A793;A796=A797;A798=A799;A79A=A79B;A79C=A79D;A79E=A79F;A7A0=A7A1;A7A2=A7A3;A7A4=A7A5;A7A6=A7A7;A7A8=A7A9;A7AA=266;A7AB=25C;A7AC=261;A7AD=26C;A7AE=26A;A7B0=29E;A7B1=287;A7B2=29D;A7B3=AB53;A7B4=A7B5;A7B6=A7B7;A7B8=A7B9;A7BA=A7BB;A7BC=A7BD;A7BE=A7BF;A7C0=A7C1;A7C2=A7C3;A7C4=A794;A7C5=282;A7C6=1D8E;A7C7=A7C8;A7C9=A7CA;A7D0=A7D1;A7D6=A7D7;A7D8=A7D9;A7F5=A7F6;AB70=13A0;AB71=13A1;AB72=13A2;AB73=13A3;AB74=13A4;AB75=13A5;AB76=13A6;AB77=13A7;AB78=13A8;AB79=13A9;AB7A=13AA;AB7B=13AB;AB7C=13AC;AB7D=13AD;AB7E=13AE;AB7F=13AF;AB80=13B0;AB81=13B1;AB82=13B2;AB83=13B3;AB84=13B4;AB85=13B5;AB86=13B6;AB87=13B7;AB88=13B8;AB89=13B9;AB8A=13BA;AB8B=13BB;AB8C=13BC;AB8D=13BD;AB8E=13BE;AB8F=13BF;AB90=13C0;AB91=13C1;AB92=13C2;AB93=13C3;AB94=13C4;AB95=13C5;AB96=13C6;AB97=13C7;AB98=13C8;AB99=13C9;AB9A=13CA;AB9B=13CB;AB9C=13CC;AB9D=13CD;AB9E=13CE;AB9F=13CF;ABA0=13D0;ABA1=13D1;ABA2=13D2;ABA3=13D3;ABA4=13D4;ABA5=13D5;ABA6=13D6;ABA7=13D7;ABA8=13D8;ABA9=13D9;ABAA=13DA;ABAB=13DB;ABAC=13DC;ABAD=13DD;ABAE=13DE;ABAF=13DF;ABB0=13E0;ABB1=13E1;ABB2=13E2;ABB3=13E3;ABB4=13E4;ABB5=13E5;ABB6=13E6;ABB7=13E7;ABB8=13E8;ABB9=13E9;ABBA=13EA;ABBB=13EB;ABBC=13EC;ABBD=13ED;ABBE=13EE;ABBF=13EF;FB00=66,66;FB01=66,69;FB02=66,6C;FB03=66,66,69;FB04=66,66,6C;FB05=73,74;FB06=73,74;FB13=574,576;FB14=574,565;FB15=574,56B;FB16=57E,576;FB17=574,56D;FF21=FF41;FF22=FF42;FF23=FF43;FF24=FF44;FF25=FF45;FF26=FF46;FF27=FF47;FF28=FF48;FF29=FF49;FF2A=FF4A;FF2B=FF4B;FF2C=FF4C;FF2D=FF4D;FF2E=FF4E;FF2F=FF4F;FF30=FF50;FF31=FF51;FF32=FF52;FF33=FF53;FF34=FF54;FF35=FF55;FF36=FF56;FF37=FF57;FF38=FF58;FF39=FF59;FF3A=FF5A;10400=10428;10401=10429;10402=1042A;10403=1042B;10404=1042C;10405=1042D;10406=1042E;10407=1042F;10408=10430;10409=10431;1040A=10432;1040B=10433;1040C=10434;1040D=10435;1040E=10436;1040F=10437;10410=10438;10411=10439;10412=1043A;10413=1043B;10414=1043C;10415=1043D;10416=1043E;10417=1043F;10418=10440;10419=10441;1041A=10442;1041B=10443;1041C=10444;1041D=10445;1041E=10446;1041F=10447;10420=10448;10421=10449;10422=1044A;10423=1044B;10424=1044C;10425=1044D;10426=1044E;10427=1044F;104B0=104D8;104B1=104D9;104B2=104DA;104B3=104DB;104B4=104DC;104B5=104DD;104B6=104DE;104B7=104DF;104B8=104E0;104B9=104E1;104BA=104E2;104BB=104E3;104BC=104E4;104BD=104E5;104BE=104E6;104BF=104E7;104C0=104E8;104C1=104E9;104C2=104EA;104C3=104EB;104C4=104EC;104C5=104ED;104C6=104EE;104C7=104EF;104C8=104F0;104C9=104F1;104CA=104F2;104CB=104F3;104CC=104F4;104CD=104F5;104CE=104F6;104CF=104F7;104D0=104F8;104D1=104F9;104D2=104FA;104D3=104FB;10570=10597;10571=10598;10572=10599;10573=1059A;10574=1059B;10575=1059C;10576=1059D;10577=1059E;10578=1059F;10579=105A0;1057A=105A1;1057C=105A3;1057D=105A4;1057E=105A5;1057F=105A6;10580=105A7;10581=105A8;10582=105A9;10583=105AA;10584=105AB;10585=105AC;10586=105AD;10587=105AE;10588=105AF;10589=105B0;1058A=105B1;1058C=105B3;1058D=105B4;1058E=105B5;1058F=105B6;10590=105B7;10591=105B8;10592=105B9;10594=105BB;10595=105BC;10C80=10CC0;10C81=10CC1;10C82=10CC2;10C83=10CC3;10C84=10CC4;10C85=10CC5;10C86=10CC6;10C87=10CC7;10C88=10CC8;10C89=10CC9;10C8A=10CCA;10C8B=10CCB;10C8C=10CCC;10C8D=10CCD;10C8E=10CCE;10C8F=10CCF;10C90=10CD0;10C91=10CD1;10C92=10CD2;10C93=10CD3;10C94=10CD4;10C95=10CD5;10C96=10CD6;10C97=10CD7;10C98=10CD8;10C99=10CD9;10C9A=10CDA;10C9B=10CDB;10C9C=10CDC;10C9D=10CDD;10C9E=10CDE;10C9F=10CDF;10CA0=10CE0;10CA1=10CE1;10CA2=10CE2;10CA3=10CE3;10CA4=10CE4;10CA5=10CE5;10CA6=10CE6;10CA7=10CE7;10CA8=10CE8;10CA9=10CE9;10CAA=10CEA;10CAB=10CEB;10CAC=10CEC;10CAD=10CED;10CAE=10CEE;10CAF=10CEF;10CB0=10CF0;10CB1=10CF1;10CB2=10CF2;118A0=118C0;118A1=118C1;118A2=118C2;118A3=118C3;118A4=118C4;118A5=118C5;118A6=118C6;118A7=118C7;118A8=118C8;118A9=118C9;118AA=118CA;118AB=118CB;118AC=118CC;118AD=118CD;118AE=118CE;118AF=118CF;118B0=118D0;118B1=118D1;118B2=118D2;118B3=118D3;118B4=118D4;118B5=118D5;118B6=118D6;118B7=118D7;118B8=118D8;118B9=118D9;118BA=118DA;118BB=118DB;118BC=118DC;118BD=118DD;118BE=118DE;118BF=118DF;16E40=16E60;16E41=16E61;16E42=16E62;16E43=16E63;16E44=16E64;16E45=16E65;16E46=16E66;16E47=16E67;16E48=16E68;16E49=16E69;16E4A=16E6A;16E4B=16E6B;16E4C=16E6C;16E4D=16E6D;16E4E=16E6E;16E4F=16E6F;16E50=16E70;16E51=16E71;16E52=16E72;16E53=16E73;16E54=16E74;16E55=16E75;16E56=16E76;16E57=16E77;16E58=16E78;16E59=16E79;16E5A=16E7A;16E5B=16E7B;16E5C=16E7C;16E5D=16E7D;16E5E=16E7E;16E5F=16E7F;1E900=1E922;1E901=1E923;1E902=1E924;1E903=1E925;1E904=1E926;1E905=1E927;1E906=1E928;1E907=1E929;1E908=1E92A;1E909=1E92B;1E90A=1E92C;1E90B=1E92D;1E90C=1E92E;1E90D=1E92F;1E90E=1E930;1E90F=1E931;1E910=1E932;1E911=1E933;1E912=1E934;1E913=1E935;1E914=1E936;1E915=1E937;1E916=1E938;1E917=1E939;1E918=1E93A;1E919=1E93B;1E91A=1E93C;1E91B=1E93D;1E91C=1E93E;1E91D=1E93F;1E91E=1E940;1E91F=1E941;1E920=1E942;1E921=1E943";
|
|
5
|
+
const RESERVED_ROOTS = new Set([".botlearn", ".deepseek", ".cache", "node_modules"]);
|
|
6
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
7
|
+
const CASE_FOLD_TABLE = new Map(CASE_FOLD_DATA.split(";").map((item) => {
|
|
8
|
+
const [source, encodedTargets] = item.split("=");
|
|
9
|
+
const target = String.fromCodePoint(...encodedTargets.split(",").map((value) => Number.parseInt(value, 16)));
|
|
10
|
+
return [Number.parseInt(source, 16), target];
|
|
11
|
+
}));
|
|
12
|
+
export class WorkspaceEntrySetError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code) {
|
|
15
|
+
super(code);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = "WorkspaceEntrySetError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function defaultCaseFold(value) {
|
|
21
|
+
let folded = "";
|
|
22
|
+
for (const character of value) {
|
|
23
|
+
folded += CASE_FOLD_TABLE.get(character.codePointAt(0)) ?? character;
|
|
24
|
+
}
|
|
25
|
+
return folded;
|
|
26
|
+
}
|
|
27
|
+
function fail(code) {
|
|
28
|
+
throw new WorkspaceEntrySetError(code);
|
|
29
|
+
}
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
function hasExactKeys(value, expected) {
|
|
34
|
+
const actual = Object.keys(value).sort();
|
|
35
|
+
return actual.length === expected.length &&
|
|
36
|
+
actual.every((key, index) => key === [...expected].sort()[index]);
|
|
37
|
+
}
|
|
38
|
+
function hasValidUnicodeScalars(value) {
|
|
39
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
40
|
+
const code = value.charCodeAt(index);
|
|
41
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
42
|
+
const next = value.charCodeAt(index + 1);
|
|
43
|
+
if (!(next >= 0xdc00 && next <= 0xdfff))
|
|
44
|
+
return false;
|
|
45
|
+
index += 1;
|
|
46
|
+
}
|
|
47
|
+
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
function validateLimits(limits) {
|
|
54
|
+
for (const value of Object.values(limits)) {
|
|
55
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
56
|
+
fail("invalid_limit");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function validatePath(value, limits) {
|
|
60
|
+
if (typeof value !== "string" || !value || value.includes("\0") || value.includes("\\") ||
|
|
61
|
+
!hasValidUnicodeScalars(value))
|
|
62
|
+
fail("invalid_path");
|
|
63
|
+
if (value !== value.normalize("NFC"))
|
|
64
|
+
fail("path_not_nfc");
|
|
65
|
+
if (value.startsWith("/") || value.endsWith("/"))
|
|
66
|
+
fail("invalid_path");
|
|
67
|
+
const segments = value.split("/");
|
|
68
|
+
if (segments.length > limits.maxDepth ||
|
|
69
|
+
segments.some((segment) => !segment || segment === "." || segment === ".."))
|
|
70
|
+
fail("invalid_path");
|
|
71
|
+
if (RESERVED_ROOTS.has(defaultCaseFold(segments[0])))
|
|
72
|
+
fail("reserved_workspace_path");
|
|
73
|
+
if (segments.some((segment) => Buffer.byteLength(segment, "utf8") > limits.maxPathSegmentBytes)) {
|
|
74
|
+
fail("path_segment_too_long");
|
|
75
|
+
}
|
|
76
|
+
if (Buffer.byteLength(value, "utf8") > limits.maxPathBytes)
|
|
77
|
+
fail("path_too_long");
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function parseEntry(raw, limits) {
|
|
81
|
+
if (!isRecord(raw))
|
|
82
|
+
fail("invalid_entry");
|
|
83
|
+
if (raw.type === "directory") {
|
|
84
|
+
if (!hasExactKeys(raw, ["type", "path", "mode"]) || raw.mode !== "0700") {
|
|
85
|
+
fail("invalid_directory_entry");
|
|
86
|
+
}
|
|
87
|
+
return { type: "directory", path: validatePath(raw.path, limits), mode: "0700" };
|
|
88
|
+
}
|
|
89
|
+
if (raw.type !== "file" ||
|
|
90
|
+
!hasExactKeys(raw, ["type", "path", "mode", "sizeBytes", "sha256"]))
|
|
91
|
+
fail("invalid_file_entry");
|
|
92
|
+
if (raw.mode !== "0600" && raw.mode !== "0700")
|
|
93
|
+
fail("invalid_file_mode");
|
|
94
|
+
if (!Number.isSafeInteger(raw.sizeBytes) || raw.sizeBytes < 0 ||
|
|
95
|
+
raw.sizeBytes > limits.maxFileBytes)
|
|
96
|
+
fail("invalid_file_size");
|
|
97
|
+
if (typeof raw.sha256 !== "string" || !SHA256.test(raw.sha256)) {
|
|
98
|
+
fail("invalid_file_sha256");
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
type: "file",
|
|
102
|
+
path: validatePath(raw.path, limits),
|
|
103
|
+
mode: raw.mode,
|
|
104
|
+
sizeBytes: raw.sizeBytes,
|
|
105
|
+
sha256: raw.sha256,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function validateWorkspaceEntrySet(raw, limits) {
|
|
109
|
+
validateLimits(limits);
|
|
110
|
+
if (!isRecord(raw) || !hasExactKeys(raw, ["schemaVersion", "entries"])) {
|
|
111
|
+
fail("invalid_entry_set");
|
|
112
|
+
}
|
|
113
|
+
if (raw.schemaVersion !== WORKSPACE_ENTRY_SET_SCHEMA)
|
|
114
|
+
fail("unsupported_entry_set_schema");
|
|
115
|
+
if (!Array.isArray(raw.entries))
|
|
116
|
+
fail("invalid_entry_set");
|
|
117
|
+
const entries = raw.entries.map((entry) => parseEntry(entry, limits));
|
|
118
|
+
const paths = entries.map((entry) => entry.path);
|
|
119
|
+
const sortedPaths = [...paths].sort((left, right) => Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8")));
|
|
120
|
+
if (paths.some((value, index) => value !== sortedPaths[index]))
|
|
121
|
+
fail("entries_not_sorted");
|
|
122
|
+
if (new Set(paths).size !== paths.length)
|
|
123
|
+
fail("duplicate_path");
|
|
124
|
+
if (new Set(paths.map(defaultCaseFold)).size !== paths.length)
|
|
125
|
+
fail("portable_path_conflict");
|
|
126
|
+
const entriesByPath = new Map(entries.map((entry) => [entry.path, entry]));
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
const segments = entry.path.split("/");
|
|
129
|
+
for (let depth = 1; depth < segments.length; depth += 1) {
|
|
130
|
+
const parent = entriesByPath.get(segments.slice(0, depth).join("/"));
|
|
131
|
+
if (!parent || parent.type !== "directory")
|
|
132
|
+
fail("invalid_path_tree");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const files = entries.filter((entry) => entry.type === "file");
|
|
136
|
+
if (files.length > limits.maxFileCount)
|
|
137
|
+
fail("file_count_exceeded");
|
|
138
|
+
const totalBytes = files.reduce((total, entry) => total + (entry.sizeBytes ?? 0), 0);
|
|
139
|
+
if (!Number.isSafeInteger(totalBytes) || totalBytes > limits.maxTotalBytes) {
|
|
140
|
+
fail("workspace_bytes_exceeded");
|
|
141
|
+
}
|
|
142
|
+
const canonical = {
|
|
143
|
+
schemaVersion: WORKSPACE_ENTRY_SET_SCHEMA,
|
|
144
|
+
entries: entries.map((entry) => entry.type === "file"
|
|
145
|
+
? {
|
|
146
|
+
type: entry.type,
|
|
147
|
+
path: entry.path,
|
|
148
|
+
mode: entry.mode,
|
|
149
|
+
sizeBytes: entry.sizeBytes,
|
|
150
|
+
sha256: entry.sha256,
|
|
151
|
+
}
|
|
152
|
+
: { type: entry.type, path: entry.path, mode: entry.mode }),
|
|
153
|
+
};
|
|
154
|
+
const canonicalBytes = Buffer.from(JSON.stringify(canonical), "utf8");
|
|
155
|
+
if (canonicalBytes.length > limits.maxEntrySetBytes)
|
|
156
|
+
fail("entry_set_bytes_exceeded");
|
|
157
|
+
return {
|
|
158
|
+
entries,
|
|
159
|
+
canonicalBytes,
|
|
160
|
+
contentSha256: createHash("sha256").update(canonicalBytes).digest("hex"),
|
|
161
|
+
fileCount: files.length,
|
|
162
|
+
totalBytes,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const WORKSPACE_MATERIALIZATION_SCHEMA: "agent-workspace-materialization/1";
|
|
2
|
+
export interface WorkspaceMaterializationMarker {
|
|
3
|
+
schemaVersion: typeof WORKSPACE_MATERIALIZATION_SCHEMA;
|
|
4
|
+
sandboxId: string;
|
|
5
|
+
sandboxGeneration: number;
|
|
6
|
+
runtimeSessionId: string;
|
|
7
|
+
workspaceContinuityState: "healthy" | "degraded";
|
|
8
|
+
snapshotId: string | null;
|
|
9
|
+
revision: number;
|
|
10
|
+
contentSha256: string | null;
|
|
11
|
+
writtenAt: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function workspaceMaterializationMarkerPath(sandboxId: string, runtimeSessionId: string): string;
|
|
14
|
+
export declare function writeWorkspaceMaterializationMarker(marker: WorkspaceMaterializationMarker): void;
|
|
15
|
+
export declare function readWorkspaceMaterializationMarker(sandboxId: string, runtimeSessionId: string): WorkspaceMaterializationMarker | null;
|
|
16
|
+
export declare function removeWorkspaceMaterializationMarker(sandboxId: string, runtimeSessionId: string): boolean;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ensureDaemonHome } from "./auth-store.js";
|
|
5
|
+
export const WORKSPACE_MATERIALIZATION_SCHEMA = "agent-workspace-materialization/1";
|
|
6
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
8
|
+
function assertUuid(value, label) {
|
|
9
|
+
if (!UUID.test(value))
|
|
10
|
+
throw new Error(label + " must be a UUID");
|
|
11
|
+
}
|
|
12
|
+
function markerDirectory(sandboxId) {
|
|
13
|
+
assertUuid(sandboxId, "sandboxId");
|
|
14
|
+
return path.join(ensureDaemonHome(), "agent-service-sandboxes", sandboxId, "workspace-materializations");
|
|
15
|
+
}
|
|
16
|
+
export function workspaceMaterializationMarkerPath(sandboxId, runtimeSessionId) {
|
|
17
|
+
assertUuid(runtimeSessionId, "runtimeSessionId");
|
|
18
|
+
return path.join(markerDirectory(sandboxId), runtimeSessionId + ".json");
|
|
19
|
+
}
|
|
20
|
+
function validateMarker(raw) {
|
|
21
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
22
|
+
throw new Error("workspace materialization marker must be an object");
|
|
23
|
+
}
|
|
24
|
+
const marker = raw;
|
|
25
|
+
const expectedKeys = [
|
|
26
|
+
"schemaVersion",
|
|
27
|
+
"sandboxId",
|
|
28
|
+
"sandboxGeneration",
|
|
29
|
+
"runtimeSessionId",
|
|
30
|
+
"workspaceContinuityState",
|
|
31
|
+
"snapshotId",
|
|
32
|
+
"revision",
|
|
33
|
+
"contentSha256",
|
|
34
|
+
"writtenAt",
|
|
35
|
+
].sort();
|
|
36
|
+
if (Object.keys(marker).sort().some((key, index) => key !== expectedKeys[index]) ||
|
|
37
|
+
Object.keys(marker).length !== expectedKeys.length)
|
|
38
|
+
throw new Error("workspace materialization marker fields are invalid");
|
|
39
|
+
if (marker.schemaVersion !== WORKSPACE_MATERIALIZATION_SCHEMA) {
|
|
40
|
+
throw new Error("workspace materialization marker schema is unsupported");
|
|
41
|
+
}
|
|
42
|
+
if (typeof marker.sandboxId !== "string")
|
|
43
|
+
throw new Error("sandboxId is required");
|
|
44
|
+
if (typeof marker.runtimeSessionId !== "string") {
|
|
45
|
+
throw new Error("runtimeSessionId is required");
|
|
46
|
+
}
|
|
47
|
+
assertUuid(marker.sandboxId, "sandboxId");
|
|
48
|
+
assertUuid(marker.runtimeSessionId, "runtimeSessionId");
|
|
49
|
+
if (!Number.isSafeInteger(marker.sandboxGeneration) ||
|
|
50
|
+
marker.sandboxGeneration < 1 ||
|
|
51
|
+
!Number.isSafeInteger(marker.revision) ||
|
|
52
|
+
marker.revision < 0 ||
|
|
53
|
+
typeof marker.writtenAt !== "string" ||
|
|
54
|
+
Number.isNaN(Date.parse(marker.writtenAt)))
|
|
55
|
+
throw new Error("workspace materialization marker values are invalid");
|
|
56
|
+
if (marker.workspaceContinuityState !== "healthy" &&
|
|
57
|
+
marker.workspaceContinuityState !== "degraded")
|
|
58
|
+
throw new Error("workspace materialization continuity is invalid");
|
|
59
|
+
if (marker.snapshotId !== null) {
|
|
60
|
+
if (typeof marker.snapshotId !== "string")
|
|
61
|
+
throw new Error("snapshotId is invalid");
|
|
62
|
+
assertUuid(marker.snapshotId, "snapshotId");
|
|
63
|
+
}
|
|
64
|
+
const revisionZero = marker.revision === 0;
|
|
65
|
+
if (revisionZero !== (marker.snapshotId === null) ||
|
|
66
|
+
revisionZero !== (marker.contentSha256 === null) ||
|
|
67
|
+
(!revisionZero &&
|
|
68
|
+
(typeof marker.contentSha256 !== "string" || !SHA256.test(marker.contentSha256)))) {
|
|
69
|
+
throw new Error("workspace materialization marker pointer is inconsistent");
|
|
70
|
+
}
|
|
71
|
+
return marker;
|
|
72
|
+
}
|
|
73
|
+
export function writeWorkspaceMaterializationMarker(marker) {
|
|
74
|
+
validateMarker(marker);
|
|
75
|
+
const directory = markerDirectory(marker.sandboxId);
|
|
76
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
77
|
+
try {
|
|
78
|
+
chmodSync(directory, 0o700);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Windows best effort.
|
|
82
|
+
}
|
|
83
|
+
const target = workspaceMaterializationMarkerPath(marker.sandboxId, marker.runtimeSessionId);
|
|
84
|
+
const staging = target + ".tmp-" + process.pid + "-" + randomBytes(4).toString("hex");
|
|
85
|
+
try {
|
|
86
|
+
writeFileSync(staging, JSON.stringify(marker), { encoding: "utf8", mode: 0o600 });
|
|
87
|
+
const file = openSync(staging, "r");
|
|
88
|
+
try {
|
|
89
|
+
fsyncSync(file);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
closeSync(file);
|
|
93
|
+
}
|
|
94
|
+
renameSync(staging, target);
|
|
95
|
+
try {
|
|
96
|
+
chmodSync(target, 0o600);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Windows best effort.
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const parent = openSync(directory, "r");
|
|
103
|
+
try {
|
|
104
|
+
fsyncSync(parent);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
closeSync(parent);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Some platforms cannot fsync a directory.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
try {
|
|
116
|
+
unlinkSync(staging);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Staging may not have been created.
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function readWorkspaceMaterializationMarker(sandboxId, runtimeSessionId) {
|
|
125
|
+
const file = workspaceMaterializationMarkerPath(sandboxId, runtimeSessionId);
|
|
126
|
+
if (!existsSync(file))
|
|
127
|
+
return null;
|
|
128
|
+
return validateMarker(JSON.parse(readFileSync(file, "utf8")));
|
|
129
|
+
}
|
|
130
|
+
export function removeWorkspaceMaterializationMarker(sandboxId, runtimeSessionId) {
|
|
131
|
+
const file = workspaceMaterializationMarkerPath(sandboxId, runtimeSessionId);
|
|
132
|
+
if (!existsSync(file))
|
|
133
|
+
return false;
|
|
134
|
+
unlinkSync(file);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const RUNTIME_WORKSPACE_QUOTA_BYTES: number;
|
|
2
|
+
export declare const RUNTIME_WORKSPACE_INODE_QUOTA = 2000;
|
|
3
|
+
export declare function assertRuntimeWorkspaceQuota(workspaceRoot: string): void;
|
|
4
|
+
export declare function applyRuntimeWorkspaceQuota(runtimeSessionId: string, sandboxGeneration: number): Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFileSync, statfsSync, statSync } from "node:fs";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
export const RUNTIME_WORKSPACE_QUOTA_BYTES = 512 * 1024 * 1024;
|
|
5
|
+
export const RUNTIME_WORKSPACE_INODE_QUOTA = 2_000;
|
|
6
|
+
const WORKSPACE_BACKING_BYTES = 3 * 1024 * 1024 * 1024;
|
|
7
|
+
const QUOTA_MARKER = "/var/lib/botlearn-runtime/workspace-quota.ready";
|
|
8
|
+
const RUNTIME_LAUNCHER = "/opt/botlearn/bin/botlearn-runtime-launcher";
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
export function assertRuntimeWorkspaceQuota(workspaceRoot) {
|
|
11
|
+
if (process.env.BOTLEARN_DAEMON_ENABLE_FAKE_RUNTIME === "1" || process.env.NODE_ENV === "test") {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (process.env.BOTLEARN_RUNTIME_WORKSPACE_QUOTA_BYTES !== String(RUNTIME_WORKSPACE_QUOTA_BYTES) ||
|
|
15
|
+
process.env.BOTLEARN_RUNTIME_WORKSPACE_INODE_QUOTA !== String(RUNTIME_WORKSPACE_INODE_QUOTA)) {
|
|
16
|
+
throw new Error("workspace_runtime_quota_unverified");
|
|
17
|
+
}
|
|
18
|
+
const filesystem = statfsSync(workspaceRoot, { bigint: true });
|
|
19
|
+
const marker = statSync(QUOTA_MARKER);
|
|
20
|
+
if (filesystem.blocks * filesystem.bsize < BigInt(WORKSPACE_BACKING_BYTES) ||
|
|
21
|
+
marker.uid !== 0 || (marker.mode & 0o777) !== 0o600 ||
|
|
22
|
+
readFileSync(QUOTA_MARKER, "utf8") !== "workspace-filesystem-quota/1\n") {
|
|
23
|
+
throw new Error("workspace_runtime_quota_probe_failed");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function applyRuntimeWorkspaceQuota(runtimeSessionId, sandboxGeneration) {
|
|
27
|
+
if (process.env.BOTLEARN_DAEMON_ENABLE_FAKE_RUNTIME === "1" || process.env.NODE_ENV === "test") {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const args = ["--workspace-quota-apply", runtimeSessionId, String(sandboxGeneration)];
|
|
31
|
+
if (process.env.BOTLEARN_RUNTIME_LAUNCH_MODE === "sudo") {
|
|
32
|
+
await execFileAsync("/usr/bin/sudo", ["-n", "--", RUNTIME_LAUNCHER, ...args], {
|
|
33
|
+
timeout: 10_000,
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (process.env.BOTLEARN_RUNTIME_LAUNCH_MODE === "direct-uid") {
|
|
38
|
+
await execFileAsync(RUNTIME_LAUNCHER, args, { timeout: 10_000 });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
throw new Error("workspace_runtime_quota_helper_unavailable");
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type WorkspaceEntry, type WorkspaceEntrySetLimits } from "./workspace-entry-set.js";
|
|
2
|
+
export declare class WorkspaceRestoreError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly retryable: boolean;
|
|
5
|
+
constructor(code: string, retryable?: boolean);
|
|
6
|
+
}
|
|
7
|
+
export interface WorkspaceRestoreDescriptor {
|
|
8
|
+
sandboxId: string;
|
|
9
|
+
sandboxGeneration: number;
|
|
10
|
+
runtimeSessionId: string;
|
|
11
|
+
continuityState: "healthy" | "degraded";
|
|
12
|
+
continuityErrorCode: string | null;
|
|
13
|
+
snapshotId: string | null;
|
|
14
|
+
revision: number;
|
|
15
|
+
contentSha256: string | null;
|
|
16
|
+
expectedFileCount: number;
|
|
17
|
+
expectedEntryCount: number;
|
|
18
|
+
expectedTotalBytes: number;
|
|
19
|
+
}
|
|
20
|
+
export interface WorkspaceRestoreOptions {
|
|
21
|
+
workspaceDirectory: string;
|
|
22
|
+
controlDirectory: string;
|
|
23
|
+
descriptor: WorkspaceRestoreDescriptor;
|
|
24
|
+
entrySet: unknown;
|
|
25
|
+
limits: WorkspaceEntrySetLimits;
|
|
26
|
+
requiredFreeBytes: number;
|
|
27
|
+
downloadFile(entry: WorkspaceEntry, partPath: string): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export interface WorkspaceRestoreCapacityOptions {
|
|
30
|
+
workspaceDirectory: string;
|
|
31
|
+
descriptor: WorkspaceRestoreDescriptor;
|
|
32
|
+
requiredFreeBytes: number;
|
|
33
|
+
entryCopies?: 1 | 2;
|
|
34
|
+
}
|
|
35
|
+
export declare function workspaceRestoreJournalPath(controlDirectory: string, runtimeSessionId: string): string;
|
|
36
|
+
export declare function recoverWorkspaceRestoreSwap(options: {
|
|
37
|
+
workspaceDirectory: string;
|
|
38
|
+
controlDirectory: string;
|
|
39
|
+
descriptor: WorkspaceRestoreDescriptor;
|
|
40
|
+
}): boolean;
|
|
41
|
+
export declare function restoreWorkspaceSnapshot(options: WorkspaceRestoreOptions): Promise<void>;
|
|
42
|
+
export declare function assertWorkspaceRestoreCapacity(options: WorkspaceRestoreCapacityOptions): void;
|