@copilotkit/shared 1.70.1 → 1.70.3
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/attachments/types.d.cts +10 -0
- package/dist/attachments/types.d.cts.map +1 -1
- package/dist/attachments/types.d.mts +10 -0
- package/dist/attachments/types.d.mts.map +1 -1
- package/dist/index.cjs +8 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -4
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +226 -197
- package/dist/index.umd.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/telemetry/index.cjs +15 -0
- package/dist/telemetry/index.d.cts +5 -0
- package/dist/telemetry/index.d.mts +4 -2
- package/dist/telemetry/index.mjs +6 -0
- package/dist/telemetry/telemetry-client.cjs +4 -13
- package/dist/telemetry/telemetry-client.cjs.map +1 -1
- package/dist/telemetry/telemetry-client.d.cts +2 -8
- package/dist/telemetry/telemetry-client.d.cts.map +1 -1
- package/dist/telemetry/telemetry-client.d.mts +2 -8
- package/dist/telemetry/telemetry-client.d.mts.map +1 -1
- package/dist/telemetry/telemetry-client.mjs +4 -12
- package/dist/telemetry/telemetry-client.mjs.map +1 -1
- package/dist/telemetry/telemetry-disabled.cjs +20 -0
- package/dist/telemetry/telemetry-disabled.cjs.map +1 -0
- package/dist/telemetry/telemetry-disabled.d.cts +16 -0
- package/dist/telemetry/telemetry-disabled.d.cts.map +1 -0
- package/dist/telemetry/telemetry-disabled.d.mts +16 -0
- package/dist/telemetry/telemetry-disabled.d.mts.map +1 -0
- package/dist/telemetry/telemetry-disabled.mjs +19 -0
- package/dist/telemetry/telemetry-disabled.mjs.map +1 -0
- package/dist/utils/index.cjs +1 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +2 -1
- package/dist/utils/index.d.cts.map +1 -1
- package/dist/utils/index.d.mts +2 -1
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +1 -0
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/inspector-learning.cjs +185 -0
- package/dist/utils/inspector-learning.cjs.map +1 -0
- package/dist/utils/inspector-learning.d.cts +91 -0
- package/dist/utils/inspector-learning.d.cts.map +1 -0
- package/dist/utils/inspector-learning.d.mts +91 -0
- package/dist/utils/inspector-learning.d.mts.map +1 -0
- package/dist/utils/inspector-learning.mjs +182 -0
- package/dist/utils/inspector-learning.mjs.map +1 -0
- package/dist/utils/types.cjs.map +1 -1
- package/dist/utils/types.d.cts +13 -2
- package/dist/utils/types.d.cts.map +1 -1
- package/dist/utils/types.d.mts +13 -2
- package/dist/utils/types.d.mts.map +1 -1
- package/dist/utils/types.mjs.map +1 -1
- package/package.json +12 -1
- package/src/__tests__/license-context.test.ts +22 -1
- package/src/__tests__/root-entry-browser-safety.test.ts +85 -0
- package/src/attachments/types.ts +10 -0
- package/src/index.ts +27 -2
- package/src/telemetry/telemetry-client.ts +2 -17
- package/src/telemetry/telemetry-disabled.ts +22 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/inspector-learning.test.ts +213 -0
- package/src/utils/inspector-learning.ts +423 -0
- package/src/utils/types.ts +16 -1
- package/tsdown.config.ts +4 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/utils/inspector-learning.ts
|
|
3
|
+
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
const string = (value, maximum) => typeof value === "string" && value.trim().length > 0 && value.length <= maximum;
|
|
5
|
+
const integer = (value, minimum = 0) => typeof value === "number" && Number.isSafeInteger(value) && value >= minimum;
|
|
6
|
+
const date = (value) => typeof value === "string" && !Number.isNaN(Date.parse(value));
|
|
7
|
+
/**
|
|
8
|
+
* Accepts only the configured Intelligence web-app origin.
|
|
9
|
+
*
|
|
10
|
+
* Learning links are server-authored actions. Treating every HTTPS origin as
|
|
11
|
+
* trusted would let a misconfigured or compromised upstream turn Inspector
|
|
12
|
+
* copy into a navigation primitive for an arbitrary site.
|
|
13
|
+
*/
|
|
14
|
+
function parseInspectorLearningUrl(value, trustedOrigin) {
|
|
15
|
+
if (typeof value !== "string" || value.length > 4e3 || typeof trustedOrigin !== "string" || trustedOrigin.length > 2e3) return;
|
|
16
|
+
try {
|
|
17
|
+
const parsed = new URL(value);
|
|
18
|
+
const configured = new URL(trustedOrigin);
|
|
19
|
+
const loopback = [
|
|
20
|
+
"localhost",
|
|
21
|
+
"127.0.0.1",
|
|
22
|
+
"[::1]"
|
|
23
|
+
].includes(configured.hostname);
|
|
24
|
+
if (parsed.username || parsed.password || configured.username || configured.password || configured.pathname !== "/" || configured.search || configured.hash || configured.protocol !== "https:" && !(configured.protocol === "http:" && loopback) || parsed.origin !== configured.origin) return;
|
|
25
|
+
return parsed.toString();
|
|
26
|
+
} catch {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function parseEvidence(value) {
|
|
31
|
+
if (record(value) && value.status === "unavailable") return Object.keys(value).length === 1 ? { status: "unavailable" } : void 0;
|
|
32
|
+
if (!record(value) || value.status !== "available" || !string(value.threadId, 128) || !date(value.updatedAt)) return;
|
|
33
|
+
if (value.threadName !== null && !(typeof value.threadName === "string" && value.threadName.length <= 4e3)) return;
|
|
34
|
+
if (!Array.isArray(value.messageIds) || value.messageIds.length > 200 || !value.messageIds.every((id) => string(id, 128))) return;
|
|
35
|
+
return {
|
|
36
|
+
status: "available",
|
|
37
|
+
threadId: value.threadId,
|
|
38
|
+
threadName: value.threadName,
|
|
39
|
+
messageIds: [...value.messageIds],
|
|
40
|
+
updatedAt: value.updatedAt
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function parseInsight(value) {
|
|
44
|
+
if (!record(value) || !string(value.id, 128) || !string(value.statement, 4e3) || !string(value.impact, 4e3) || !integer(value.totalThreadCount) || typeof value.evidenceTruncated !== "boolean" || !Array.isArray(value.evidence) || value.evidence.length > 100) return;
|
|
45
|
+
const evidence = value.evidence.map(parseEvidence);
|
|
46
|
+
if (evidence.some((item) => item === void 0)) return void 0;
|
|
47
|
+
return {
|
|
48
|
+
id: value.id,
|
|
49
|
+
statement: value.statement,
|
|
50
|
+
impact: value.impact,
|
|
51
|
+
totalThreadCount: value.totalThreadCount,
|
|
52
|
+
evidenceTruncated: value.evidenceTruncated,
|
|
53
|
+
evidence
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function parseSkill(value) {
|
|
57
|
+
if (!record(value) || !string(value.id, 128) || !string(value.name, 128) || !string(value.description, 4e3) || !integer(value.revision, 1) || typeof value.skillMd !== "string" || value.skillMd.length > 131072) return;
|
|
58
|
+
const sourceInsight = value.sourceInsight === null ? null : parseInsight(value.sourceInsight);
|
|
59
|
+
if (sourceInsight === void 0) return void 0;
|
|
60
|
+
return {
|
|
61
|
+
id: value.id,
|
|
62
|
+
name: value.name,
|
|
63
|
+
description: value.description,
|
|
64
|
+
revision: value.revision,
|
|
65
|
+
skillMd: value.skillMd,
|
|
66
|
+
sourceInsight
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function parsePage(value, pageSize, parseItem) {
|
|
70
|
+
if (!record(value) || !integer(value.page, 1) || value.pageSize !== pageSize || !integer(value.total) || !integer(value.totalPages) || !Array.isArray(value.items) || value.items.length > pageSize) return;
|
|
71
|
+
const items = value.items.map(parseItem);
|
|
72
|
+
if (items.some((item) => item === void 0)) return void 0;
|
|
73
|
+
if (value.total === 0) {
|
|
74
|
+
if (value.page !== 1 || value.totalPages !== 0 || items.length !== 0) return;
|
|
75
|
+
} else {
|
|
76
|
+
const totalPages = Math.ceil(value.total / pageSize);
|
|
77
|
+
const expectedItems = Math.min(pageSize, value.total - (value.page - 1) * pageSize);
|
|
78
|
+
if (value.totalPages !== totalPages || value.page > totalPages || items.length !== expectedItems) return;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
page: value.page,
|
|
82
|
+
pageSize,
|
|
83
|
+
total: value.total,
|
|
84
|
+
totalPages: value.totalPages,
|
|
85
|
+
items
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function parseConfiguration(value) {
|
|
89
|
+
if (!record(value)) return void 0;
|
|
90
|
+
if (value.state === "not_configured" || value.state === "selection_required") return { state: value.state };
|
|
91
|
+
if (value.state === "invalid" && (value.reason === "container" || value.reason === "instrumentation")) return {
|
|
92
|
+
state: value.state,
|
|
93
|
+
reason: value.reason
|
|
94
|
+
};
|
|
95
|
+
if (value.state === "configured" && record(value.container) && string(value.container.id, 128) && string(value.container.name, 128)) return {
|
|
96
|
+
state: "configured",
|
|
97
|
+
container: {
|
|
98
|
+
id: value.container.id,
|
|
99
|
+
name: value.container.name
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const runStatuses = new Set([
|
|
104
|
+
"queued",
|
|
105
|
+
"freezing",
|
|
106
|
+
"batching",
|
|
107
|
+
"reducing",
|
|
108
|
+
"finalizing",
|
|
109
|
+
"succeeded",
|
|
110
|
+
"failed"
|
|
111
|
+
]);
|
|
112
|
+
/** Validates and copies an untrusted Learning snapshot at each process boundary. */
|
|
113
|
+
function parseInspectorLearningSnapshotV1(value) {
|
|
114
|
+
if (!record(value) || value.schemaVersion !== 1 || !string(value.projectKey, 128) || !string(value.snapshotVersion, 128) || !integer(value.pendingThreadCount) || !integer(value.pendingCandidateCount) || !record(value.run) || typeof value.run.hasActiveRun !== "boolean" || typeof value.run.hasEverSucceeded !== "boolean") return;
|
|
115
|
+
const configuration = parseConfiguration(value.configuration);
|
|
116
|
+
const skillsPage = parsePage(value.skillsPage, 3, parseSkill);
|
|
117
|
+
const insightsPage = parsePage(value.insightsPage, 4, parseInsight);
|
|
118
|
+
if (!configuration || !skillsPage || !insightsPage) return void 0;
|
|
119
|
+
let latest = null;
|
|
120
|
+
if (value.run.latest !== null) {
|
|
121
|
+
const latestRun = value.run.latest;
|
|
122
|
+
if (!record(latestRun)) return;
|
|
123
|
+
const { status, completedAt } = latestRun;
|
|
124
|
+
if (typeof status !== "string" || !runStatuses.has(status) || completedAt !== null && !date(completedAt)) return void 0;
|
|
125
|
+
latest = {
|
|
126
|
+
status,
|
|
127
|
+
completedAt
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (!record(value.links)) return void 0;
|
|
131
|
+
const originUrl = parseInspectorLearningUrl(value.webAppOrigin, value.webAppOrigin);
|
|
132
|
+
if (!originUrl || new URL(originUrl).pathname !== "/") return void 0;
|
|
133
|
+
const webAppOrigin = new URL(originUrl).origin;
|
|
134
|
+
const learning = parseInspectorLearningUrl(value.links.learning, webAppOrigin);
|
|
135
|
+
const candidates = value.links.candidates === null ? null : parseInspectorLearningUrl(value.links.candidates, webAppOrigin);
|
|
136
|
+
const runs = value.links.runs === null ? null : parseInspectorLearningUrl(value.links.runs, webAppOrigin);
|
|
137
|
+
if (!learning || candidates === void 0 || runs === void 0 || value.pendingThreadCount > 0 && runs === null || value.pendingCandidateCount > 0 && candidates === null) return void 0;
|
|
138
|
+
return {
|
|
139
|
+
schemaVersion: 1,
|
|
140
|
+
projectKey: value.projectKey,
|
|
141
|
+
snapshotVersion: value.snapshotVersion,
|
|
142
|
+
webAppOrigin,
|
|
143
|
+
configuration,
|
|
144
|
+
pendingThreadCount: value.pendingThreadCount,
|
|
145
|
+
run: {
|
|
146
|
+
hasActiveRun: value.run.hasActiveRun,
|
|
147
|
+
hasEverSucceeded: value.run.hasEverSucceeded,
|
|
148
|
+
latest
|
|
149
|
+
},
|
|
150
|
+
pendingCandidateCount: value.pendingCandidateCount,
|
|
151
|
+
skillsPage,
|
|
152
|
+
insightsPage,
|
|
153
|
+
links: {
|
|
154
|
+
learning,
|
|
155
|
+
candidates,
|
|
156
|
+
runs
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** Normalizes the browser-owned request without accepting container scope. */
|
|
161
|
+
function parseInspectorLearningRequestV1(value) {
|
|
162
|
+
if (!record(value)) return void 0;
|
|
163
|
+
if (Object.keys(value).some((key) => ![
|
|
164
|
+
"agentId",
|
|
165
|
+
"skillsPage",
|
|
166
|
+
"insightsPage"
|
|
167
|
+
].includes(key))) return;
|
|
168
|
+
const agentId = value.agentId;
|
|
169
|
+
const skillsPage = value.skillsPage;
|
|
170
|
+
const insightsPage = value.insightsPage;
|
|
171
|
+
if (agentId !== void 0 && !string(agentId, 128)) return void 0;
|
|
172
|
+
if (skillsPage !== void 0 && !integer(skillsPage, 1)) return void 0;
|
|
173
|
+
if (insightsPage !== void 0 && !integer(insightsPage, 1)) return void 0;
|
|
174
|
+
return {
|
|
175
|
+
...agentId === void 0 ? {} : { agentId },
|
|
176
|
+
...skillsPage === void 0 ? {} : { skillsPage },
|
|
177
|
+
...insightsPage === void 0 ? {} : { insightsPage }
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
exports.parseInspectorLearningRequestV1 = parseInspectorLearningRequestV1;
|
|
183
|
+
exports.parseInspectorLearningSnapshotV1 = parseInspectorLearningSnapshotV1;
|
|
184
|
+
exports.parseInspectorLearningUrl = parseInspectorLearningUrl;
|
|
185
|
+
//# sourceMappingURL=inspector-learning.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspector-learning.cjs","names":[],"sources":["../../src/utils/inspector-learning.ts"],"sourcesContent":["/** A bounded page in the Inspector Learning snapshot. */\nexport interface InspectorLearningPage<T> {\n readonly page: number;\n readonly pageSize: number;\n readonly total: number;\n readonly totalPages: number;\n readonly items: readonly T[];\n}\n\nexport type InspectorLearningEvidence =\n | {\n readonly status: \"available\";\n readonly threadId: string;\n readonly threadName: string | null;\n readonly messageIds: readonly string[];\n readonly updatedAt: string;\n }\n | { readonly status: \"unavailable\" };\n\nexport interface InspectorLearningInsight {\n readonly id: string;\n readonly statement: string;\n readonly impact: string;\n readonly totalThreadCount: number;\n readonly evidenceTruncated: boolean;\n readonly evidence: readonly InspectorLearningEvidence[];\n}\n\nexport interface InspectorLearningSkill {\n readonly id: string;\n readonly name: string;\n readonly description: string;\n readonly revision: number;\n readonly skillMd: string;\n readonly sourceInsight: InspectorLearningInsight | null;\n}\n\nexport interface InspectorLearningSnapshotV1 {\n readonly schemaVersion: 1;\n readonly projectKey: string;\n readonly snapshotVersion: string;\n readonly webAppOrigin: string;\n readonly configuration:\n | { readonly state: \"not_configured\" }\n | {\n readonly state: \"invalid\";\n readonly reason: \"container\" | \"instrumentation\";\n }\n | {\n readonly state: \"configured\";\n readonly container: { readonly id: string; readonly name: string };\n }\n | { readonly state: \"selection_required\" };\n readonly pendingThreadCount: number;\n readonly run: {\n readonly hasActiveRun: boolean;\n readonly hasEverSucceeded: boolean;\n readonly latest: {\n readonly completedAt: string | null;\n readonly status:\n | \"queued\"\n | \"freezing\"\n | \"batching\"\n | \"reducing\"\n | \"finalizing\"\n | \"succeeded\"\n | \"failed\";\n } | null;\n };\n readonly pendingCandidateCount: number;\n readonly skillsPage: InspectorLearningPage<InspectorLearningSkill>;\n readonly insightsPage: InspectorLearningPage<InspectorLearningInsight>;\n readonly links: {\n readonly learning: string;\n readonly candidates: string | null;\n readonly runs: string | null;\n };\n}\n\nexport interface InspectorLearningRequestV1 {\n readonly agentId?: string;\n readonly skillsPage?: number;\n readonly insightsPage?: number;\n}\n\ntype RecordValue = Record<string, unknown>;\n\nconst record = (value: unknown): value is RecordValue =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\nconst string = (value: unknown, maximum: number): value is string =>\n typeof value === \"string\" &&\n value.trim().length > 0 &&\n value.length <= maximum;\nconst integer = (value: unknown, minimum = 0): value is number =>\n typeof value === \"number\" && Number.isSafeInteger(value) && value >= minimum;\nconst date = (value: unknown): value is string =>\n typeof value === \"string\" && !Number.isNaN(Date.parse(value));\n\n/**\n * Accepts only the configured Intelligence web-app origin.\n *\n * Learning links are server-authored actions. Treating every HTTPS origin as\n * trusted would let a misconfigured or compromised upstream turn Inspector\n * copy into a navigation primitive for an arbitrary site.\n */\nexport function parseInspectorLearningUrl(\n value: unknown,\n trustedOrigin: unknown,\n): string | undefined {\n if (\n typeof value !== \"string\" ||\n value.length > 4_000 ||\n typeof trustedOrigin !== \"string\" ||\n trustedOrigin.length > 2_000\n ) {\n return undefined;\n }\n try {\n const parsed = new URL(value);\n const configured = new URL(trustedOrigin);\n const loopback = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(\n configured.hostname,\n );\n if (\n parsed.username ||\n parsed.password ||\n configured.username ||\n configured.password ||\n configured.pathname !== \"/\" ||\n configured.search ||\n configured.hash ||\n (configured.protocol !== \"https:\" &&\n !(configured.protocol === \"http:\" && loopback)) ||\n parsed.origin !== configured.origin\n ) {\n return undefined;\n }\n return parsed.toString();\n } catch {\n return undefined;\n }\n}\n\nfunction parseEvidence(value: unknown): InspectorLearningEvidence | undefined {\n if (record(value) && value.status === \"unavailable\") {\n return Object.keys(value).length === 1\n ? { status: \"unavailable\" }\n : undefined;\n }\n if (\n !record(value) ||\n value.status !== \"available\" ||\n !string(value.threadId, 128) ||\n !date(value.updatedAt)\n ) {\n return undefined;\n }\n if (\n value.threadName !== null &&\n !(typeof value.threadName === \"string\" && value.threadName.length <= 4_000)\n ) {\n return undefined;\n }\n if (\n !Array.isArray(value.messageIds) ||\n value.messageIds.length > 200 ||\n !value.messageIds.every((id) => string(id, 128))\n ) {\n return undefined;\n }\n return {\n status: \"available\",\n threadId: value.threadId,\n threadName: value.threadName,\n messageIds: [...value.messageIds],\n updatedAt: value.updatedAt,\n };\n}\n\nfunction parseInsight(value: unknown): InspectorLearningInsight | undefined {\n if (\n !record(value) ||\n !string(value.id, 128) ||\n !string(value.statement, 4_000) ||\n !string(value.impact, 4_000) ||\n !integer(value.totalThreadCount) ||\n typeof value.evidenceTruncated !== \"boolean\" ||\n !Array.isArray(value.evidence) ||\n value.evidence.length > 100\n ) {\n return undefined;\n }\n const evidence = value.evidence.map(parseEvidence);\n if (evidence.some((item) => item === undefined)) return undefined;\n return {\n id: value.id,\n statement: value.statement,\n impact: value.impact,\n totalThreadCount: value.totalThreadCount,\n evidenceTruncated: value.evidenceTruncated,\n evidence: evidence as InspectorLearningEvidence[],\n };\n}\n\nfunction parseSkill(value: unknown): InspectorLearningSkill | undefined {\n if (\n !record(value) ||\n !string(value.id, 128) ||\n !string(value.name, 128) ||\n !string(value.description, 4_000) ||\n !integer(value.revision, 1) ||\n typeof value.skillMd !== \"string\" ||\n value.skillMd.length > 131_072\n ) {\n return undefined;\n }\n const sourceInsight =\n value.sourceInsight === null ? null : parseInsight(value.sourceInsight);\n if (sourceInsight === undefined) return undefined;\n return {\n id: value.id,\n name: value.name,\n description: value.description,\n revision: value.revision,\n skillMd: value.skillMd,\n sourceInsight,\n };\n}\n\nfunction parsePage<T>(\n value: unknown,\n pageSize: 3 | 4,\n parseItem: (item: unknown) => T | undefined,\n): InspectorLearningPage<T> | undefined {\n if (\n !record(value) ||\n !integer(value.page, 1) ||\n value.pageSize !== pageSize ||\n !integer(value.total) ||\n !integer(value.totalPages) ||\n !Array.isArray(value.items) ||\n value.items.length > pageSize\n ) {\n return undefined;\n }\n const items = value.items.map(parseItem);\n if (items.some((item) => item === undefined)) return undefined;\n if (value.total === 0) {\n if (value.page !== 1 || value.totalPages !== 0 || items.length !== 0) {\n return undefined;\n }\n } else {\n const totalPages = Math.ceil(value.total / pageSize);\n const expectedItems = Math.min(\n pageSize,\n value.total - (value.page - 1) * pageSize,\n );\n if (\n value.totalPages !== totalPages ||\n value.page > totalPages ||\n items.length !== expectedItems\n ) {\n return undefined;\n }\n }\n return {\n page: value.page,\n pageSize,\n total: value.total,\n totalPages: value.totalPages,\n items: items as T[],\n };\n}\n\nfunction parseConfiguration(\n value: unknown,\n): InspectorLearningSnapshotV1[\"configuration\"] | undefined {\n if (!record(value)) return undefined;\n if (\n value.state === \"not_configured\" ||\n value.state === \"selection_required\"\n ) {\n return { state: value.state };\n }\n if (\n value.state === \"invalid\" &&\n (value.reason === \"container\" || value.reason === \"instrumentation\")\n ) {\n return { state: value.state, reason: value.reason };\n }\n if (\n value.state === \"configured\" &&\n record(value.container) &&\n string(value.container.id, 128) &&\n string(value.container.name, 128)\n ) {\n return {\n state: \"configured\",\n container: { id: value.container.id, name: value.container.name },\n };\n }\n return undefined;\n}\n\nconst runStatuses = new Set([\n \"queued\",\n \"freezing\",\n \"batching\",\n \"reducing\",\n \"finalizing\",\n \"succeeded\",\n \"failed\",\n]);\n\n/** Validates and copies an untrusted Learning snapshot at each process boundary. */\nexport function parseInspectorLearningSnapshotV1(\n value: unknown,\n): InspectorLearningSnapshotV1 | undefined {\n if (\n !record(value) ||\n value.schemaVersion !== 1 ||\n !string(value.projectKey, 128) ||\n !string(value.snapshotVersion, 128) ||\n !integer(value.pendingThreadCount) ||\n !integer(value.pendingCandidateCount) ||\n !record(value.run) ||\n typeof value.run.hasActiveRun !== \"boolean\" ||\n typeof value.run.hasEverSucceeded !== \"boolean\"\n ) {\n return undefined;\n }\n const configuration = parseConfiguration(value.configuration);\n const skillsPage = parsePage(value.skillsPage, 3, parseSkill);\n const insightsPage = parsePage(value.insightsPage, 4, parseInsight);\n if (!configuration || !skillsPage || !insightsPage) return undefined;\n let latest: InspectorLearningSnapshotV1[\"run\"][\"latest\"] = null;\n if (value.run.latest !== null) {\n const latestRun = value.run.latest;\n if (!record(latestRun)) {\n return undefined;\n }\n const { status, completedAt } = latestRun;\n if (\n typeof status !== \"string\" ||\n !runStatuses.has(status) ||\n (completedAt !== null && !date(completedAt))\n )\n return undefined;\n latest = {\n status: status as NonNullable<typeof latest>[\"status\"],\n completedAt: completedAt as NonNullable<typeof latest>[\"completedAt\"],\n };\n }\n if (!record(value.links)) return undefined;\n const originUrl = parseInspectorLearningUrl(\n value.webAppOrigin,\n value.webAppOrigin,\n );\n if (!originUrl || new URL(originUrl).pathname !== \"/\") return undefined;\n const webAppOrigin = new URL(originUrl).origin;\n const learning = parseInspectorLearningUrl(\n value.links.learning,\n webAppOrigin,\n );\n const candidates =\n value.links.candidates === null\n ? null\n : parseInspectorLearningUrl(value.links.candidates, webAppOrigin);\n const runs =\n value.links.runs === null\n ? null\n : parseInspectorLearningUrl(value.links.runs, webAppOrigin);\n if (\n !learning ||\n candidates === undefined ||\n runs === undefined ||\n (value.pendingThreadCount > 0 && runs === null) ||\n (value.pendingCandidateCount > 0 && candidates === null)\n )\n return undefined;\n return {\n schemaVersion: 1,\n projectKey: value.projectKey,\n snapshotVersion: value.snapshotVersion,\n webAppOrigin,\n configuration,\n pendingThreadCount: value.pendingThreadCount,\n run: {\n hasActiveRun: value.run.hasActiveRun,\n hasEverSucceeded: value.run.hasEverSucceeded,\n latest,\n },\n pendingCandidateCount: value.pendingCandidateCount,\n skillsPage,\n insightsPage,\n links: { learning, candidates, runs },\n };\n}\n\n/** Normalizes the browser-owned request without accepting container scope. */\nexport function parseInspectorLearningRequestV1(\n value: unknown,\n): InspectorLearningRequestV1 | undefined {\n if (!record(value)) return undefined;\n if (\n Object.keys(value).some(\n (key) => ![\"agentId\", \"skillsPage\", \"insightsPage\"].includes(key),\n )\n ) {\n return undefined;\n }\n const agentId = value.agentId;\n const skillsPage = value.skillsPage;\n const insightsPage = value.insightsPage;\n if (agentId !== undefined && !string(agentId, 128)) return undefined;\n if (skillsPage !== undefined && !integer(skillsPage, 1)) return undefined;\n if (insightsPage !== undefined && !integer(insightsPage, 1)) return undefined;\n return {\n ...(agentId === undefined ? {} : { agentId }),\n ...(skillsPage === undefined ? {} : { skillsPage }),\n ...(insightsPage === undefined ? {} : { insightsPage }),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//#region src/utils/inspector-learning.d.ts
|
|
2
|
+
/** A bounded page in the Inspector Learning snapshot. */
|
|
3
|
+
interface InspectorLearningPage<T> {
|
|
4
|
+
readonly page: number;
|
|
5
|
+
readonly pageSize: number;
|
|
6
|
+
readonly total: number;
|
|
7
|
+
readonly totalPages: number;
|
|
8
|
+
readonly items: readonly T[];
|
|
9
|
+
}
|
|
10
|
+
type InspectorLearningEvidence = {
|
|
11
|
+
readonly status: "available";
|
|
12
|
+
readonly threadId: string;
|
|
13
|
+
readonly threadName: string | null;
|
|
14
|
+
readonly messageIds: readonly string[];
|
|
15
|
+
readonly updatedAt: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly status: "unavailable";
|
|
18
|
+
};
|
|
19
|
+
interface InspectorLearningInsight {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly statement: string;
|
|
22
|
+
readonly impact: string;
|
|
23
|
+
readonly totalThreadCount: number;
|
|
24
|
+
readonly evidenceTruncated: boolean;
|
|
25
|
+
readonly evidence: readonly InspectorLearningEvidence[];
|
|
26
|
+
}
|
|
27
|
+
interface InspectorLearningSkill {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly description: string;
|
|
31
|
+
readonly revision: number;
|
|
32
|
+
readonly skillMd: string;
|
|
33
|
+
readonly sourceInsight: InspectorLearningInsight | null;
|
|
34
|
+
}
|
|
35
|
+
interface InspectorLearningSnapshotV1 {
|
|
36
|
+
readonly schemaVersion: 1;
|
|
37
|
+
readonly projectKey: string;
|
|
38
|
+
readonly snapshotVersion: string;
|
|
39
|
+
readonly webAppOrigin: string;
|
|
40
|
+
readonly configuration: {
|
|
41
|
+
readonly state: "not_configured";
|
|
42
|
+
} | {
|
|
43
|
+
readonly state: "invalid";
|
|
44
|
+
readonly reason: "container" | "instrumentation";
|
|
45
|
+
} | {
|
|
46
|
+
readonly state: "configured";
|
|
47
|
+
readonly container: {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly name: string;
|
|
50
|
+
};
|
|
51
|
+
} | {
|
|
52
|
+
readonly state: "selection_required";
|
|
53
|
+
};
|
|
54
|
+
readonly pendingThreadCount: number;
|
|
55
|
+
readonly run: {
|
|
56
|
+
readonly hasActiveRun: boolean;
|
|
57
|
+
readonly hasEverSucceeded: boolean;
|
|
58
|
+
readonly latest: {
|
|
59
|
+
readonly completedAt: string | null;
|
|
60
|
+
readonly status: "queued" | "freezing" | "batching" | "reducing" | "finalizing" | "succeeded" | "failed";
|
|
61
|
+
} | null;
|
|
62
|
+
};
|
|
63
|
+
readonly pendingCandidateCount: number;
|
|
64
|
+
readonly skillsPage: InspectorLearningPage<InspectorLearningSkill>;
|
|
65
|
+
readonly insightsPage: InspectorLearningPage<InspectorLearningInsight>;
|
|
66
|
+
readonly links: {
|
|
67
|
+
readonly learning: string;
|
|
68
|
+
readonly candidates: string | null;
|
|
69
|
+
readonly runs: string | null;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
interface InspectorLearningRequestV1 {
|
|
73
|
+
readonly agentId?: string;
|
|
74
|
+
readonly skillsPage?: number;
|
|
75
|
+
readonly insightsPage?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Accepts only the configured Intelligence web-app origin.
|
|
79
|
+
*
|
|
80
|
+
* Learning links are server-authored actions. Treating every HTTPS origin as
|
|
81
|
+
* trusted would let a misconfigured or compromised upstream turn Inspector
|
|
82
|
+
* copy into a navigation primitive for an arbitrary site.
|
|
83
|
+
*/
|
|
84
|
+
declare function parseInspectorLearningUrl(value: unknown, trustedOrigin: unknown): string | undefined;
|
|
85
|
+
/** Validates and copies an untrusted Learning snapshot at each process boundary. */
|
|
86
|
+
declare function parseInspectorLearningSnapshotV1(value: unknown): InspectorLearningSnapshotV1 | undefined;
|
|
87
|
+
/** Normalizes the browser-owned request without accepting container scope. */
|
|
88
|
+
declare function parseInspectorLearningRequestV1(value: unknown): InspectorLearningRequestV1 | undefined;
|
|
89
|
+
//#endregion
|
|
90
|
+
export { InspectorLearningEvidence, InspectorLearningInsight, InspectorLearningPage, InspectorLearningRequestV1, InspectorLearningSkill, InspectorLearningSnapshotV1, parseInspectorLearningRequestV1, parseInspectorLearningSnapshotV1, parseInspectorLearningUrl };
|
|
91
|
+
//# sourceMappingURL=inspector-learning.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspector-learning.d.cts","names":[],"sources":["../../src/utils/inspector-learning.ts"],"mappings":";;UACiB,qBAAA;EAAA,SACN,IAAA;EAAA,SACA,QAAA;EAAA,SACA,KAAA;EAAA,SACA,UAAA;EAAA,SACA,KAAA,WAAgB,CAAA;AAAA;AAAA,KAGf,yBAAA;EAAA,SAEG,MAAA;EAAA,SACA,QAAA;EAAA,SACA,UAAA;EAAA,SACA,UAAA;EAAA,SACA,SAAA;AAAA;EAAA,SAEA,MAAA;AAAA;AAAA,UAEE,wBAAA;EAAA,SACN,EAAA;EAAA,SACA,SAAA;EAAA,SACA,MAAA;EAAA,SACA,gBAAA;EAAA,SACA,iBAAA;EAAA,SACA,QAAA,WAAmB,yBAAA;AAAA;AAAA,UAGb,sBAAA;EAAA,SACN,EAAA;EAAA,SACA,IAAA;EAAA,SACA,WAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA;EAAA,SACA,aAAA,EAAe,wBAAA;AAAA;AAAA,UAGT,2BAAA;EAAA,SACN,aAAA;EAAA,SACA,UAAA;EAAA,SACA,eAAA;EAAA,SACA,YAAA;EAAA,SACA,aAAA;IAAA,SACM,KAAA;EAAA;IAAA,SAEA,KAAA;IAAA,SACA,MAAA;EAAA;IAAA,SAGA,KAAA;IAAA,SACA,SAAA;MAAA,SAAsB,EAAA;MAAA,SAAqB,IAAA;IAAA;EAAA;IAAA,SAE3C,KAAA;EAAA;EAAA,SACN,kBAAA;EAAA,SACA,GAAA;IAAA,SACE,YAAA;IAAA,SACA,gBAAA;IAAA,SACA,MAAA;MAAA,SACE,WAAA;MAAA,SACA,MAAA;IAAA;EAAA;EAAA,SAUJ,qBAAA;EAAA,SACA,UAAA,EAAY,qBAAA,CAAsB,sBAAA;EAAA,SAClC,YAAA,EAAc,qBAAA,CAAsB,wBAAA;EAAA,SACpC,KAAA;IAAA,SACE,QAAA;IAAA,SACA,UAAA;IAAA,SACA,IAAA;EAAA;AAAA;AAAA,UAII,0BAAA;EAAA,SACN,OAAA;EAAA,SACA,UAAA;EAAA,SACA,YAAA;AAAA;;;;;;;;iBAuBK,yBAAA,CACd,KAAA,WACA,aAAA;;iBAgNc,gCAAA,CACd,KAAA,YACC,2BAAA;;iBAmFa,+BAAA,CACd,KAAA,YACC,0BAAA"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//#region src/utils/inspector-learning.d.ts
|
|
2
|
+
/** A bounded page in the Inspector Learning snapshot. */
|
|
3
|
+
interface InspectorLearningPage<T> {
|
|
4
|
+
readonly page: number;
|
|
5
|
+
readonly pageSize: number;
|
|
6
|
+
readonly total: number;
|
|
7
|
+
readonly totalPages: number;
|
|
8
|
+
readonly items: readonly T[];
|
|
9
|
+
}
|
|
10
|
+
type InspectorLearningEvidence = {
|
|
11
|
+
readonly status: "available";
|
|
12
|
+
readonly threadId: string;
|
|
13
|
+
readonly threadName: string | null;
|
|
14
|
+
readonly messageIds: readonly string[];
|
|
15
|
+
readonly updatedAt: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly status: "unavailable";
|
|
18
|
+
};
|
|
19
|
+
interface InspectorLearningInsight {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly statement: string;
|
|
22
|
+
readonly impact: string;
|
|
23
|
+
readonly totalThreadCount: number;
|
|
24
|
+
readonly evidenceTruncated: boolean;
|
|
25
|
+
readonly evidence: readonly InspectorLearningEvidence[];
|
|
26
|
+
}
|
|
27
|
+
interface InspectorLearningSkill {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly description: string;
|
|
31
|
+
readonly revision: number;
|
|
32
|
+
readonly skillMd: string;
|
|
33
|
+
readonly sourceInsight: InspectorLearningInsight | null;
|
|
34
|
+
}
|
|
35
|
+
interface InspectorLearningSnapshotV1 {
|
|
36
|
+
readonly schemaVersion: 1;
|
|
37
|
+
readonly projectKey: string;
|
|
38
|
+
readonly snapshotVersion: string;
|
|
39
|
+
readonly webAppOrigin: string;
|
|
40
|
+
readonly configuration: {
|
|
41
|
+
readonly state: "not_configured";
|
|
42
|
+
} | {
|
|
43
|
+
readonly state: "invalid";
|
|
44
|
+
readonly reason: "container" | "instrumentation";
|
|
45
|
+
} | {
|
|
46
|
+
readonly state: "configured";
|
|
47
|
+
readonly container: {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly name: string;
|
|
50
|
+
};
|
|
51
|
+
} | {
|
|
52
|
+
readonly state: "selection_required";
|
|
53
|
+
};
|
|
54
|
+
readonly pendingThreadCount: number;
|
|
55
|
+
readonly run: {
|
|
56
|
+
readonly hasActiveRun: boolean;
|
|
57
|
+
readonly hasEverSucceeded: boolean;
|
|
58
|
+
readonly latest: {
|
|
59
|
+
readonly completedAt: string | null;
|
|
60
|
+
readonly status: "queued" | "freezing" | "batching" | "reducing" | "finalizing" | "succeeded" | "failed";
|
|
61
|
+
} | null;
|
|
62
|
+
};
|
|
63
|
+
readonly pendingCandidateCount: number;
|
|
64
|
+
readonly skillsPage: InspectorLearningPage<InspectorLearningSkill>;
|
|
65
|
+
readonly insightsPage: InspectorLearningPage<InspectorLearningInsight>;
|
|
66
|
+
readonly links: {
|
|
67
|
+
readonly learning: string;
|
|
68
|
+
readonly candidates: string | null;
|
|
69
|
+
readonly runs: string | null;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
interface InspectorLearningRequestV1 {
|
|
73
|
+
readonly agentId?: string;
|
|
74
|
+
readonly skillsPage?: number;
|
|
75
|
+
readonly insightsPage?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Accepts only the configured Intelligence web-app origin.
|
|
79
|
+
*
|
|
80
|
+
* Learning links are server-authored actions. Treating every HTTPS origin as
|
|
81
|
+
* trusted would let a misconfigured or compromised upstream turn Inspector
|
|
82
|
+
* copy into a navigation primitive for an arbitrary site.
|
|
83
|
+
*/
|
|
84
|
+
declare function parseInspectorLearningUrl(value: unknown, trustedOrigin: unknown): string | undefined;
|
|
85
|
+
/** Validates and copies an untrusted Learning snapshot at each process boundary. */
|
|
86
|
+
declare function parseInspectorLearningSnapshotV1(value: unknown): InspectorLearningSnapshotV1 | undefined;
|
|
87
|
+
/** Normalizes the browser-owned request without accepting container scope. */
|
|
88
|
+
declare function parseInspectorLearningRequestV1(value: unknown): InspectorLearningRequestV1 | undefined;
|
|
89
|
+
//#endregion
|
|
90
|
+
export { InspectorLearningEvidence, InspectorLearningInsight, InspectorLearningPage, InspectorLearningRequestV1, InspectorLearningSkill, InspectorLearningSnapshotV1, parseInspectorLearningRequestV1, parseInspectorLearningSnapshotV1, parseInspectorLearningUrl };
|
|
91
|
+
//# sourceMappingURL=inspector-learning.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspector-learning.d.mts","names":[],"sources":["../../src/utils/inspector-learning.ts"],"mappings":";;UACiB,qBAAA;EAAA,SACN,IAAA;EAAA,SACA,QAAA;EAAA,SACA,KAAA;EAAA,SACA,UAAA;EAAA,SACA,KAAA,WAAgB,CAAA;AAAA;AAAA,KAGf,yBAAA;EAAA,SAEG,MAAA;EAAA,SACA,QAAA;EAAA,SACA,UAAA;EAAA,SACA,UAAA;EAAA,SACA,SAAA;AAAA;EAAA,SAEA,MAAA;AAAA;AAAA,UAEE,wBAAA;EAAA,SACN,EAAA;EAAA,SACA,SAAA;EAAA,SACA,MAAA;EAAA,SACA,gBAAA;EAAA,SACA,iBAAA;EAAA,SACA,QAAA,WAAmB,yBAAA;AAAA;AAAA,UAGb,sBAAA;EAAA,SACN,EAAA;EAAA,SACA,IAAA;EAAA,SACA,WAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA;EAAA,SACA,aAAA,EAAe,wBAAA;AAAA;AAAA,UAGT,2BAAA;EAAA,SACN,aAAA;EAAA,SACA,UAAA;EAAA,SACA,eAAA;EAAA,SACA,YAAA;EAAA,SACA,aAAA;IAAA,SACM,KAAA;EAAA;IAAA,SAEA,KAAA;IAAA,SACA,MAAA;EAAA;IAAA,SAGA,KAAA;IAAA,SACA,SAAA;MAAA,SAAsB,EAAA;MAAA,SAAqB,IAAA;IAAA;EAAA;IAAA,SAE3C,KAAA;EAAA;EAAA,SACN,kBAAA;EAAA,SACA,GAAA;IAAA,SACE,YAAA;IAAA,SACA,gBAAA;IAAA,SACA,MAAA;MAAA,SACE,WAAA;MAAA,SACA,MAAA;IAAA;EAAA;EAAA,SAUJ,qBAAA;EAAA,SACA,UAAA,EAAY,qBAAA,CAAsB,sBAAA;EAAA,SAClC,YAAA,EAAc,qBAAA,CAAsB,wBAAA;EAAA,SACpC,KAAA;IAAA,SACE,QAAA;IAAA,SACA,UAAA;IAAA,SACA,IAAA;EAAA;AAAA;AAAA,UAII,0BAAA;EAAA,SACN,OAAA;EAAA,SACA,UAAA;EAAA,SACA,YAAA;AAAA;;;;;;;;iBAuBK,yBAAA,CACd,KAAA,WACA,aAAA;;iBAgNc,gCAAA,CACd,KAAA,YACC,2BAAA;;iBAmFa,+BAAA,CACd,KAAA,YACC,0BAAA"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
//#region src/utils/inspector-learning.ts
|
|
2
|
+
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
const string = (value, maximum) => typeof value === "string" && value.trim().length > 0 && value.length <= maximum;
|
|
4
|
+
const integer = (value, minimum = 0) => typeof value === "number" && Number.isSafeInteger(value) && value >= minimum;
|
|
5
|
+
const date = (value) => typeof value === "string" && !Number.isNaN(Date.parse(value));
|
|
6
|
+
/**
|
|
7
|
+
* Accepts only the configured Intelligence web-app origin.
|
|
8
|
+
*
|
|
9
|
+
* Learning links are server-authored actions. Treating every HTTPS origin as
|
|
10
|
+
* trusted would let a misconfigured or compromised upstream turn Inspector
|
|
11
|
+
* copy into a navigation primitive for an arbitrary site.
|
|
12
|
+
*/
|
|
13
|
+
function parseInspectorLearningUrl(value, trustedOrigin) {
|
|
14
|
+
if (typeof value !== "string" || value.length > 4e3 || typeof trustedOrigin !== "string" || trustedOrigin.length > 2e3) return;
|
|
15
|
+
try {
|
|
16
|
+
const parsed = new URL(value);
|
|
17
|
+
const configured = new URL(trustedOrigin);
|
|
18
|
+
const loopback = [
|
|
19
|
+
"localhost",
|
|
20
|
+
"127.0.0.1",
|
|
21
|
+
"[::1]"
|
|
22
|
+
].includes(configured.hostname);
|
|
23
|
+
if (parsed.username || parsed.password || configured.username || configured.password || configured.pathname !== "/" || configured.search || configured.hash || configured.protocol !== "https:" && !(configured.protocol === "http:" && loopback) || parsed.origin !== configured.origin) return;
|
|
24
|
+
return parsed.toString();
|
|
25
|
+
} catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function parseEvidence(value) {
|
|
30
|
+
if (record(value) && value.status === "unavailable") return Object.keys(value).length === 1 ? { status: "unavailable" } : void 0;
|
|
31
|
+
if (!record(value) || value.status !== "available" || !string(value.threadId, 128) || !date(value.updatedAt)) return;
|
|
32
|
+
if (value.threadName !== null && !(typeof value.threadName === "string" && value.threadName.length <= 4e3)) return;
|
|
33
|
+
if (!Array.isArray(value.messageIds) || value.messageIds.length > 200 || !value.messageIds.every((id) => string(id, 128))) return;
|
|
34
|
+
return {
|
|
35
|
+
status: "available",
|
|
36
|
+
threadId: value.threadId,
|
|
37
|
+
threadName: value.threadName,
|
|
38
|
+
messageIds: [...value.messageIds],
|
|
39
|
+
updatedAt: value.updatedAt
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function parseInsight(value) {
|
|
43
|
+
if (!record(value) || !string(value.id, 128) || !string(value.statement, 4e3) || !string(value.impact, 4e3) || !integer(value.totalThreadCount) || typeof value.evidenceTruncated !== "boolean" || !Array.isArray(value.evidence) || value.evidence.length > 100) return;
|
|
44
|
+
const evidence = value.evidence.map(parseEvidence);
|
|
45
|
+
if (evidence.some((item) => item === void 0)) return void 0;
|
|
46
|
+
return {
|
|
47
|
+
id: value.id,
|
|
48
|
+
statement: value.statement,
|
|
49
|
+
impact: value.impact,
|
|
50
|
+
totalThreadCount: value.totalThreadCount,
|
|
51
|
+
evidenceTruncated: value.evidenceTruncated,
|
|
52
|
+
evidence
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function parseSkill(value) {
|
|
56
|
+
if (!record(value) || !string(value.id, 128) || !string(value.name, 128) || !string(value.description, 4e3) || !integer(value.revision, 1) || typeof value.skillMd !== "string" || value.skillMd.length > 131072) return;
|
|
57
|
+
const sourceInsight = value.sourceInsight === null ? null : parseInsight(value.sourceInsight);
|
|
58
|
+
if (sourceInsight === void 0) return void 0;
|
|
59
|
+
return {
|
|
60
|
+
id: value.id,
|
|
61
|
+
name: value.name,
|
|
62
|
+
description: value.description,
|
|
63
|
+
revision: value.revision,
|
|
64
|
+
skillMd: value.skillMd,
|
|
65
|
+
sourceInsight
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function parsePage(value, pageSize, parseItem) {
|
|
69
|
+
if (!record(value) || !integer(value.page, 1) || value.pageSize !== pageSize || !integer(value.total) || !integer(value.totalPages) || !Array.isArray(value.items) || value.items.length > pageSize) return;
|
|
70
|
+
const items = value.items.map(parseItem);
|
|
71
|
+
if (items.some((item) => item === void 0)) return void 0;
|
|
72
|
+
if (value.total === 0) {
|
|
73
|
+
if (value.page !== 1 || value.totalPages !== 0 || items.length !== 0) return;
|
|
74
|
+
} else {
|
|
75
|
+
const totalPages = Math.ceil(value.total / pageSize);
|
|
76
|
+
const expectedItems = Math.min(pageSize, value.total - (value.page - 1) * pageSize);
|
|
77
|
+
if (value.totalPages !== totalPages || value.page > totalPages || items.length !== expectedItems) return;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
page: value.page,
|
|
81
|
+
pageSize,
|
|
82
|
+
total: value.total,
|
|
83
|
+
totalPages: value.totalPages,
|
|
84
|
+
items
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function parseConfiguration(value) {
|
|
88
|
+
if (!record(value)) return void 0;
|
|
89
|
+
if (value.state === "not_configured" || value.state === "selection_required") return { state: value.state };
|
|
90
|
+
if (value.state === "invalid" && (value.reason === "container" || value.reason === "instrumentation")) return {
|
|
91
|
+
state: value.state,
|
|
92
|
+
reason: value.reason
|
|
93
|
+
};
|
|
94
|
+
if (value.state === "configured" && record(value.container) && string(value.container.id, 128) && string(value.container.name, 128)) return {
|
|
95
|
+
state: "configured",
|
|
96
|
+
container: {
|
|
97
|
+
id: value.container.id,
|
|
98
|
+
name: value.container.name
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const runStatuses = new Set([
|
|
103
|
+
"queued",
|
|
104
|
+
"freezing",
|
|
105
|
+
"batching",
|
|
106
|
+
"reducing",
|
|
107
|
+
"finalizing",
|
|
108
|
+
"succeeded",
|
|
109
|
+
"failed"
|
|
110
|
+
]);
|
|
111
|
+
/** Validates and copies an untrusted Learning snapshot at each process boundary. */
|
|
112
|
+
function parseInspectorLearningSnapshotV1(value) {
|
|
113
|
+
if (!record(value) || value.schemaVersion !== 1 || !string(value.projectKey, 128) || !string(value.snapshotVersion, 128) || !integer(value.pendingThreadCount) || !integer(value.pendingCandidateCount) || !record(value.run) || typeof value.run.hasActiveRun !== "boolean" || typeof value.run.hasEverSucceeded !== "boolean") return;
|
|
114
|
+
const configuration = parseConfiguration(value.configuration);
|
|
115
|
+
const skillsPage = parsePage(value.skillsPage, 3, parseSkill);
|
|
116
|
+
const insightsPage = parsePage(value.insightsPage, 4, parseInsight);
|
|
117
|
+
if (!configuration || !skillsPage || !insightsPage) return void 0;
|
|
118
|
+
let latest = null;
|
|
119
|
+
if (value.run.latest !== null) {
|
|
120
|
+
const latestRun = value.run.latest;
|
|
121
|
+
if (!record(latestRun)) return;
|
|
122
|
+
const { status, completedAt } = latestRun;
|
|
123
|
+
if (typeof status !== "string" || !runStatuses.has(status) || completedAt !== null && !date(completedAt)) return void 0;
|
|
124
|
+
latest = {
|
|
125
|
+
status,
|
|
126
|
+
completedAt
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (!record(value.links)) return void 0;
|
|
130
|
+
const originUrl = parseInspectorLearningUrl(value.webAppOrigin, value.webAppOrigin);
|
|
131
|
+
if (!originUrl || new URL(originUrl).pathname !== "/") return void 0;
|
|
132
|
+
const webAppOrigin = new URL(originUrl).origin;
|
|
133
|
+
const learning = parseInspectorLearningUrl(value.links.learning, webAppOrigin);
|
|
134
|
+
const candidates = value.links.candidates === null ? null : parseInspectorLearningUrl(value.links.candidates, webAppOrigin);
|
|
135
|
+
const runs = value.links.runs === null ? null : parseInspectorLearningUrl(value.links.runs, webAppOrigin);
|
|
136
|
+
if (!learning || candidates === void 0 || runs === void 0 || value.pendingThreadCount > 0 && runs === null || value.pendingCandidateCount > 0 && candidates === null) return void 0;
|
|
137
|
+
return {
|
|
138
|
+
schemaVersion: 1,
|
|
139
|
+
projectKey: value.projectKey,
|
|
140
|
+
snapshotVersion: value.snapshotVersion,
|
|
141
|
+
webAppOrigin,
|
|
142
|
+
configuration,
|
|
143
|
+
pendingThreadCount: value.pendingThreadCount,
|
|
144
|
+
run: {
|
|
145
|
+
hasActiveRun: value.run.hasActiveRun,
|
|
146
|
+
hasEverSucceeded: value.run.hasEverSucceeded,
|
|
147
|
+
latest
|
|
148
|
+
},
|
|
149
|
+
pendingCandidateCount: value.pendingCandidateCount,
|
|
150
|
+
skillsPage,
|
|
151
|
+
insightsPage,
|
|
152
|
+
links: {
|
|
153
|
+
learning,
|
|
154
|
+
candidates,
|
|
155
|
+
runs
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** Normalizes the browser-owned request without accepting container scope. */
|
|
160
|
+
function parseInspectorLearningRequestV1(value) {
|
|
161
|
+
if (!record(value)) return void 0;
|
|
162
|
+
if (Object.keys(value).some((key) => ![
|
|
163
|
+
"agentId",
|
|
164
|
+
"skillsPage",
|
|
165
|
+
"insightsPage"
|
|
166
|
+
].includes(key))) return;
|
|
167
|
+
const agentId = value.agentId;
|
|
168
|
+
const skillsPage = value.skillsPage;
|
|
169
|
+
const insightsPage = value.insightsPage;
|
|
170
|
+
if (agentId !== void 0 && !string(agentId, 128)) return void 0;
|
|
171
|
+
if (skillsPage !== void 0 && !integer(skillsPage, 1)) return void 0;
|
|
172
|
+
if (insightsPage !== void 0 && !integer(insightsPage, 1)) return void 0;
|
|
173
|
+
return {
|
|
174
|
+
...agentId === void 0 ? {} : { agentId },
|
|
175
|
+
...skillsPage === void 0 ? {} : { skillsPage },
|
|
176
|
+
...insightsPage === void 0 ? {} : { insightsPage }
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
export { parseInspectorLearningRequestV1, parseInspectorLearningSnapshotV1, parseInspectorLearningUrl };
|
|
182
|
+
//# sourceMappingURL=inspector-learning.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspector-learning.mjs","names":[],"sources":["../../src/utils/inspector-learning.ts"],"sourcesContent":["/** A bounded page in the Inspector Learning snapshot. */\nexport interface InspectorLearningPage<T> {\n readonly page: number;\n readonly pageSize: number;\n readonly total: number;\n readonly totalPages: number;\n readonly items: readonly T[];\n}\n\nexport type InspectorLearningEvidence =\n | {\n readonly status: \"available\";\n readonly threadId: string;\n readonly threadName: string | null;\n readonly messageIds: readonly string[];\n readonly updatedAt: string;\n }\n | { readonly status: \"unavailable\" };\n\nexport interface InspectorLearningInsight {\n readonly id: string;\n readonly statement: string;\n readonly impact: string;\n readonly totalThreadCount: number;\n readonly evidenceTruncated: boolean;\n readonly evidence: readonly InspectorLearningEvidence[];\n}\n\nexport interface InspectorLearningSkill {\n readonly id: string;\n readonly name: string;\n readonly description: string;\n readonly revision: number;\n readonly skillMd: string;\n readonly sourceInsight: InspectorLearningInsight | null;\n}\n\nexport interface InspectorLearningSnapshotV1 {\n readonly schemaVersion: 1;\n readonly projectKey: string;\n readonly snapshotVersion: string;\n readonly webAppOrigin: string;\n readonly configuration:\n | { readonly state: \"not_configured\" }\n | {\n readonly state: \"invalid\";\n readonly reason: \"container\" | \"instrumentation\";\n }\n | {\n readonly state: \"configured\";\n readonly container: { readonly id: string; readonly name: string };\n }\n | { readonly state: \"selection_required\" };\n readonly pendingThreadCount: number;\n readonly run: {\n readonly hasActiveRun: boolean;\n readonly hasEverSucceeded: boolean;\n readonly latest: {\n readonly completedAt: string | null;\n readonly status:\n | \"queued\"\n | \"freezing\"\n | \"batching\"\n | \"reducing\"\n | \"finalizing\"\n | \"succeeded\"\n | \"failed\";\n } | null;\n };\n readonly pendingCandidateCount: number;\n readonly skillsPage: InspectorLearningPage<InspectorLearningSkill>;\n readonly insightsPage: InspectorLearningPage<InspectorLearningInsight>;\n readonly links: {\n readonly learning: string;\n readonly candidates: string | null;\n readonly runs: string | null;\n };\n}\n\nexport interface InspectorLearningRequestV1 {\n readonly agentId?: string;\n readonly skillsPage?: number;\n readonly insightsPage?: number;\n}\n\ntype RecordValue = Record<string, unknown>;\n\nconst record = (value: unknown): value is RecordValue =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\nconst string = (value: unknown, maximum: number): value is string =>\n typeof value === \"string\" &&\n value.trim().length > 0 &&\n value.length <= maximum;\nconst integer = (value: unknown, minimum = 0): value is number =>\n typeof value === \"number\" && Number.isSafeInteger(value) && value >= minimum;\nconst date = (value: unknown): value is string =>\n typeof value === \"string\" && !Number.isNaN(Date.parse(value));\n\n/**\n * Accepts only the configured Intelligence web-app origin.\n *\n * Learning links are server-authored actions. Treating every HTTPS origin as\n * trusted would let a misconfigured or compromised upstream turn Inspector\n * copy into a navigation primitive for an arbitrary site.\n */\nexport function parseInspectorLearningUrl(\n value: unknown,\n trustedOrigin: unknown,\n): string | undefined {\n if (\n typeof value !== \"string\" ||\n value.length > 4_000 ||\n typeof trustedOrigin !== \"string\" ||\n trustedOrigin.length > 2_000\n ) {\n return undefined;\n }\n try {\n const parsed = new URL(value);\n const configured = new URL(trustedOrigin);\n const loopback = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(\n configured.hostname,\n );\n if (\n parsed.username ||\n parsed.password ||\n configured.username ||\n configured.password ||\n configured.pathname !== \"/\" ||\n configured.search ||\n configured.hash ||\n (configured.protocol !== \"https:\" &&\n !(configured.protocol === \"http:\" && loopback)) ||\n parsed.origin !== configured.origin\n ) {\n return undefined;\n }\n return parsed.toString();\n } catch {\n return undefined;\n }\n}\n\nfunction parseEvidence(value: unknown): InspectorLearningEvidence | undefined {\n if (record(value) && value.status === \"unavailable\") {\n return Object.keys(value).length === 1\n ? { status: \"unavailable\" }\n : undefined;\n }\n if (\n !record(value) ||\n value.status !== \"available\" ||\n !string(value.threadId, 128) ||\n !date(value.updatedAt)\n ) {\n return undefined;\n }\n if (\n value.threadName !== null &&\n !(typeof value.threadName === \"string\" && value.threadName.length <= 4_000)\n ) {\n return undefined;\n }\n if (\n !Array.isArray(value.messageIds) ||\n value.messageIds.length > 200 ||\n !value.messageIds.every((id) => string(id, 128))\n ) {\n return undefined;\n }\n return {\n status: \"available\",\n threadId: value.threadId,\n threadName: value.threadName,\n messageIds: [...value.messageIds],\n updatedAt: value.updatedAt,\n };\n}\n\nfunction parseInsight(value: unknown): InspectorLearningInsight | undefined {\n if (\n !record(value) ||\n !string(value.id, 128) ||\n !string(value.statement, 4_000) ||\n !string(value.impact, 4_000) ||\n !integer(value.totalThreadCount) ||\n typeof value.evidenceTruncated !== \"boolean\" ||\n !Array.isArray(value.evidence) ||\n value.evidence.length > 100\n ) {\n return undefined;\n }\n const evidence = value.evidence.map(parseEvidence);\n if (evidence.some((item) => item === undefined)) return undefined;\n return {\n id: value.id,\n statement: value.statement,\n impact: value.impact,\n totalThreadCount: value.totalThreadCount,\n evidenceTruncated: value.evidenceTruncated,\n evidence: evidence as InspectorLearningEvidence[],\n };\n}\n\nfunction parseSkill(value: unknown): InspectorLearningSkill | undefined {\n if (\n !record(value) ||\n !string(value.id, 128) ||\n !string(value.name, 128) ||\n !string(value.description, 4_000) ||\n !integer(value.revision, 1) ||\n typeof value.skillMd !== \"string\" ||\n value.skillMd.length > 131_072\n ) {\n return undefined;\n }\n const sourceInsight =\n value.sourceInsight === null ? null : parseInsight(value.sourceInsight);\n if (sourceInsight === undefined) return undefined;\n return {\n id: value.id,\n name: value.name,\n description: value.description,\n revision: value.revision,\n skillMd: value.skillMd,\n sourceInsight,\n };\n}\n\nfunction parsePage<T>(\n value: unknown,\n pageSize: 3 | 4,\n parseItem: (item: unknown) => T | undefined,\n): InspectorLearningPage<T> | undefined {\n if (\n !record(value) ||\n !integer(value.page, 1) ||\n value.pageSize !== pageSize ||\n !integer(value.total) ||\n !integer(value.totalPages) ||\n !Array.isArray(value.items) ||\n value.items.length > pageSize\n ) {\n return undefined;\n }\n const items = value.items.map(parseItem);\n if (items.some((item) => item === undefined)) return undefined;\n if (value.total === 0) {\n if (value.page !== 1 || value.totalPages !== 0 || items.length !== 0) {\n return undefined;\n }\n } else {\n const totalPages = Math.ceil(value.total / pageSize);\n const expectedItems = Math.min(\n pageSize,\n value.total - (value.page - 1) * pageSize,\n );\n if (\n value.totalPages !== totalPages ||\n value.page > totalPages ||\n items.length !== expectedItems\n ) {\n return undefined;\n }\n }\n return {\n page: value.page,\n pageSize,\n total: value.total,\n totalPages: value.totalPages,\n items: items as T[],\n };\n}\n\nfunction parseConfiguration(\n value: unknown,\n): InspectorLearningSnapshotV1[\"configuration\"] | undefined {\n if (!record(value)) return undefined;\n if (\n value.state === \"not_configured\" ||\n value.state === \"selection_required\"\n ) {\n return { state: value.state };\n }\n if (\n value.state === \"invalid\" &&\n (value.reason === \"container\" || value.reason === \"instrumentation\")\n ) {\n return { state: value.state, reason: value.reason };\n }\n if (\n value.state === \"configured\" &&\n record(value.container) &&\n string(value.container.id, 128) &&\n string(value.container.name, 128)\n ) {\n return {\n state: \"configured\",\n container: { id: value.container.id, name: value.container.name },\n };\n }\n return undefined;\n}\n\nconst runStatuses = new Set([\n \"queued\",\n \"freezing\",\n \"batching\",\n \"reducing\",\n \"finalizing\",\n \"succeeded\",\n \"failed\",\n]);\n\n/** Validates and copies an untrusted Learning snapshot at each process boundary. */\nexport function parseInspectorLearningSnapshotV1(\n value: unknown,\n): InspectorLearningSnapshotV1 | undefined {\n if (\n !record(value) ||\n value.schemaVersion !== 1 ||\n !string(value.projectKey, 128) ||\n !string(value.snapshotVersion, 128) ||\n !integer(value.pendingThreadCount) ||\n !integer(value.pendingCandidateCount) ||\n !record(value.run) ||\n typeof value.run.hasActiveRun !== \"boolean\" ||\n typeof value.run.hasEverSucceeded !== \"boolean\"\n ) {\n return undefined;\n }\n const configuration = parseConfiguration(value.configuration);\n const skillsPage = parsePage(value.skillsPage, 3, parseSkill);\n const insightsPage = parsePage(value.insightsPage, 4, parseInsight);\n if (!configuration || !skillsPage || !insightsPage) return undefined;\n let latest: InspectorLearningSnapshotV1[\"run\"][\"latest\"] = null;\n if (value.run.latest !== null) {\n const latestRun = value.run.latest;\n if (!record(latestRun)) {\n return undefined;\n }\n const { status, completedAt } = latestRun;\n if (\n typeof status !== \"string\" ||\n !runStatuses.has(status) ||\n (completedAt !== null && !date(completedAt))\n )\n return undefined;\n latest = {\n status: status as NonNullable<typeof latest>[\"status\"],\n completedAt: completedAt as NonNullable<typeof latest>[\"completedAt\"],\n };\n }\n if (!record(value.links)) return undefined;\n const originUrl = parseInspectorLearningUrl(\n value.webAppOrigin,\n value.webAppOrigin,\n );\n if (!originUrl || new URL(originUrl).pathname !== \"/\") return undefined;\n const webAppOrigin = new URL(originUrl).origin;\n const learning = parseInspectorLearningUrl(\n value.links.learning,\n webAppOrigin,\n );\n const candidates =\n value.links.candidates === null\n ? null\n : parseInspectorLearningUrl(value.links.candidates, webAppOrigin);\n const runs =\n value.links.runs === null\n ? null\n : parseInspectorLearningUrl(value.links.runs, webAppOrigin);\n if (\n !learning ||\n candidates === undefined ||\n runs === undefined ||\n (value.pendingThreadCount > 0 && runs === null) ||\n (value.pendingCandidateCount > 0 && candidates === null)\n )\n return undefined;\n return {\n schemaVersion: 1,\n projectKey: value.projectKey,\n snapshotVersion: value.snapshotVersion,\n webAppOrigin,\n configuration,\n pendingThreadCount: value.pendingThreadCount,\n run: {\n hasActiveRun: value.run.hasActiveRun,\n hasEverSucceeded: value.run.hasEverSucceeded,\n latest,\n },\n pendingCandidateCount: value.pendingCandidateCount,\n skillsPage,\n insightsPage,\n links: { learning, candidates, runs },\n };\n}\n\n/** Normalizes the browser-owned request without accepting container scope. */\nexport function parseInspectorLearningRequestV1(\n value: unknown,\n): InspectorLearningRequestV1 | undefined {\n if (!record(value)) return undefined;\n if (\n Object.keys(value).some(\n (key) => ![\"agentId\", \"skillsPage\", \"insightsPage\"].includes(key),\n )\n ) {\n return undefined;\n }\n const agentId = value.agentId;\n const skillsPage = value.skillsPage;\n const insightsPage = value.insightsPage;\n if (agentId !== undefined && !string(agentId, 128)) return undefined;\n if (skillsPage !== undefined && !integer(skillsPage, 1)) return undefined;\n if (insightsPage !== undefined && !integer(insightsPage, 1)) return undefined;\n return {\n ...(agentId === undefined ? {} : { agentId }),\n ...(skillsPage === undefined ? {} : { skillsPage }),\n ...(insightsPage === undefined ? {} : { insightsPage }),\n };\n}\n"],"mappings":"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"}
|