@webskill/sdk 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/agent.d.ts +3 -3
  2. package/dist/agent.js +2442 -3
  3. package/dist/{dist-ExSQky4C.js → approval-BnLLCOG0.js} +277 -2934
  4. package/dist/browser.d.ts +327 -8
  5. package/dist/browser.js +1017 -51
  6. package/dist/{openUiLibrary-BKXW7Iwx-CWWBVOkE.js → dist-CTsxblSD.js} +496 -28
  7. package/dist/{echarts-De78wXqV.js → echarts-BE7oV_Dl.js} +1 -1
  8. package/dist/{env-8cY40DXB-CGnEVZby.js → env-Bj1MI2Ww.js} +1 -1
  9. package/dist/errors-BDZNpC13.js +22 -0
  10. package/dist/{eventTypes-FllCrX-Z-DNDeHWoG.js → eventTypes-Y07N8IAC.js} +14 -2
  11. package/dist/external-_ZRQe-V9.js +53 -0
  12. package/dist/governance.d.ts +15 -3
  13. package/dist/governance.js +86 -4
  14. package/dist/{index-iBm9tJL_.d.ts → index-CQYo4tcT.d.ts} +469 -10
  15. package/dist/{index-C3XdItd_.d.ts → index-CU2md8R1.d.ts} +26 -18
  16. package/dist/{index-DkvBhJQy.d.ts → index-Oc3H89TJ.d.ts} +204 -4
  17. package/dist/index.d.ts +4 -4
  18. package/dist/index.js +1001 -5
  19. package/dist/kind-DxgS8LM-.js +77 -0
  20. package/dist/linkedDocument-xNXF-z8n.js +94 -0
  21. package/dist/llm-B7lLH0ZI.js +1767 -0
  22. package/dist/mcp.d.ts +56 -3
  23. package/dist/mcp.js +75 -5
  24. package/dist/memoryArtifactStore-D6dLeY55.js +48 -0
  25. package/dist/miniChart-owdCdaw-.js +533 -0
  26. package/dist/node.d.ts +3 -3
  27. package/dist/node.js +110 -13
  28. package/dist/openUiLibrary-CY_ADoe0.js +30 -0
  29. package/dist/openUiSpecLang-DuOCdw9G.js +1849 -0
  30. package/dist/pathSecurity-B1owvJAF.js +65 -0
  31. package/dist/processSandboxEntry.js +5 -1
  32. package/dist/renderResult-D9Q-Vu2x.js +143 -0
  33. package/dist/{rolldown-runtime-BOF7iYI8.js → rolldown-runtime-BHkdefai.js} +1 -2
  34. package/dist/sandboxWorkerEntry.js +5 -1
  35. package/dist/{dist-Bev6i6Ip.js → skill-CAJMsLod.js} +44 -314
  36. package/dist/{skillVersionStore-D-qHk9ZE-DheTIwAB.d.ts → skillVersionStore-B24Jjjry-C4zomhMZ.d.ts} +14 -2
  37. package/dist/surface-DVGiCmwq.js +145 -0
  38. package/dist/testing.d.ts +1 -1
  39. package/dist/testing.js +64 -3
  40. package/dist/{types-DLctJep_-B5G4uk2u.d.ts → types-C3V6lAeO-BIf7UB2e.d.ts} +79 -4
  41. package/dist/types-TUydnxaj.js +78 -0
  42. package/dist/ui-react.d.ts +12 -5
  43. package/dist/ui-react.js +128548 -4729
  44. package/dist/ui-vue.d.ts +1 -1
  45. package/dist/ui-vue.js +11 -2
  46. package/dist/ui.d.ts +3 -3
  47. package/dist/ui.js +1254 -3
  48. package/dist/urlSafety-CiSuCJvX.js +65 -0
  49. package/dist/webSkillApi-DOOyz2G5.js +106 -0
  50. package/dist/{webskillLitCatalog-D_zCqeQF-C9lrvvMr.js → webskillLitCatalog-DSqVFQjK.js} +11 -3
  51. package/package.json +1 -1
  52. package/dist/catalogComponents-BgAJN0p8-CYEXSk45.js +0 -124384
  53. package/dist/client-BCM6Z3yq-qUQNkKrK.js +0 -7787
  54. package/dist/dist-DqcL6jKO.js +0 -3509
  55. package/dist/dist-DxyxmXPU-DGC-dpXf.js +0 -5621
  56. package/dist/dist-GK6dtjRv.js +0 -1493
  57. package/dist/memoryArtifactStore-52Zn9npI-LbCQaqyx.js +0 -86
  58. package/dist/stdio-CFMoANJJ-BxrTeXh7.js +0 -31
  59. package/dist/testing-WPTyXQYt.js +0 -142
  60. package/dist/types-WovEf4ED-CZSDiiBU.js +0 -6215
package/dist/index.js CHANGED
@@ -1,14 +1,1010 @@
1
- import { A as jsonRenderer, B as resolveArchiveLimits, C as computeDigest, D as exportSkills, E as escapeXml, F as parseSkillPackManifest, G as unzipWithLimits, H as signSkill, I as readResponseWithLimit, J as verifySkillSignature, K as validateSkills, L as readSkillSignature, M as messageOf, N as normalizePath, O as isAtomicTempPath, P as parseSkillMarkdown, R as renderAvailableSkillsXml, S as checkSkillRules, T as detectSkillArchiveShapeFromFs, U as signaturePayloadBytes, V as resolveInsideRoot, W as stripArchiveRoot, Y as xmlRenderer, _ as assertSafePathSegment, a as MemoryFS, b as buildManifest, c as SKILL_MANIFEST_FILE, d as SKILL_PACK_FILE, f as SKILL_SIGNATURE_FILE, g as assertRemoteUrlAllowed, h as WebSkillError, i as MANIFEST_EXCLUDED_FILES, j as keyIdOf, k as isValidSkillName, l as SKILL_NAME_MAX_LENGTH, m as SkillReader, n as DEFAULT_ARCHIVE_LIMITS, o as SIGNATURE_SCHEMA_VERSION, p as SkillDiscovery, q as verifyManifest, r as FsTrustedKeyStore, s as SKILLS_LOCKFILE, t as ATOMIC_TMP_SUFFIX_PATTERN, u as SKILL_NAME_PATTERN, v as atomicWriteText, w as detectSkillArchiveShape, x as checkDependencyCycles, y as buildCatalog, z as renderCatalogJson } from "./dist-Bev6i6Ip.js";
2
- import { a as textParts, n as partsToText } from "./memoryArtifactStore-52Zn9npI-LbCQaqyx.js";
3
- import { $ as bridgeError, A as ProgressiveRouter, At as refineUserProfile, B as SUPPORTED_DOCUMENT_MIME, Bt as toLlmToolSpec, C as FsSessionStore, Ct as normalizeToolError, D as HookRunner, Dt as readProfileEntries, E as GoogleGenAiClient, Et as readBehaviorRecords, F as READ_SKILL_FILE_TOOL_NAME, Ft as schemaToForm, G as USER_PROFILE_EXPORT_VERSION, H as TEXT_BUDGETED_CONTENT_TYPES, Ht as toVercelToolSpecs, I as RUN_SNAPSHOT_SCHEMA_VERSION, It as scriptToolName, J as USER_PROFILE_PROMPT_HEADER, K as USER_PROFILE_KEY, L as RUN_TRACE_SCHEMA_VERSION, Lt as sealToolCallPairs, M as READ_LINKED_DOCUMENT_TOOL_NAME, Mt as resolveToolName, N as READ_SKILL_FILE_INPUT_SCHEMA, Nt as sampleBehaviorRecords, O as MAX_TOOL_STEP_ARG_BYTES, Ot as readUserProfile, P as READ_SKILL_FILE_TOOL, Pt as schemaSourceLabel, Q as applyUserProfileImport, R as SENSITIVE_ANNOTATION, Rt as summarizeRunUsage, S as FsRunTraceStore, St as normalizeToolContent, T as FullDisclosureRouter, Tt as parseUserProfileExport, U as TraceRecorder, Ut as validateUiSpecEvent, V as SerializingMemoryStore, Vt as toRecordDigests, W as UNSUPPORTED_DOCUMENT_MESSAGE, Wt as validateUiSpecNode, X as WebSkillRuntime, Y as USER_PROFILE_REFINE_PROMPT, Z as appendBehaviorRecords, _ as EventBus, _t as mergeCatalogEntries, a as ASK_USER_TOOL, at as extractChartSpec, b as FsMemoryStore, bt as networkUrlHost, c as AnthropicClient, ct as extractUiSpecEvents, d as DEFAULT_LOOP_LIMITS, dt as fromVercelResult, et as buildRenderResult, f as DEFAULT_MAX_DATA_SOURCE_BYTES, ft as fromVercelStreamPart, g as EMPTY_USER_PROFILE, gt as listSkillScripts, h as DEFAULT_USER_PROFILE_LIMITS, ht as isUnsupportedRunSnapshot, i as ASK_USER_MAX_FIELDS, it as exportUserProfile, j as READ_LINKED_DOCUMENT_TOOL, jt as renderUserProfileContext, k as OpenAiCompatibleClient, kt as redactToolStepArgs, l as BEHAVIOR_RECORDS_KEY, lt as findUnpairedToolCalls, m as DEFAULT_MAX_DOCUMENT_TEXT_BYTES, mt as isNetworkAllowed, n as ASK_USER_FIELD_TYPES, nt as createWebSkillApi, o as ASK_USER_TOOL_NAME, ot as extractSkillCandidate, p as DEFAULT_MAX_DOCUMENT_BYTES, pt as interruptedToolResult, q as USER_PROFILE_NO_INVENTION_RULE, r as ASK_USER_INPUT_SCHEMA, rt as diffUserProfile, s as AgentLoop, st as extractTodoTraceEvents, t as ALLOWED_TOOLS_EXCLUSION_REASON, tt as createScriptContext, u as CapabilityApproval, ut as formatSkillScriptManifest, v as FS_SESSION_PAGE_SIZE, vt as mergeProfileEntries, w as FsToolStepStore, wt as parseBridgeRequest, x as FsRunSnapshotStore, xt as normalizeErrorCode, y as FsArtifactStore, yt as networkPolicyLibSource, z as SESSION_SCHEMA_VERSION, zt as summarizeToolCalls } from "./dist-ExSQky4C.js";
1
+ import { n as messageOf, t as WebSkillError } from "./errors-BDZNpC13.js";
2
+ import { A as SKILL_MANIFEST_FILE, C as keyIdOf, D as verifySkillSignature, E as signaturePayloadBytes, M as buildManifest, N as computeDigest, O as MANIFEST_EXCLUDED_FILES, P as verifyManifest, S as SIGNATURE_SCHEMA_VERSION, T as signSkill, _ as SKILL_NAME_MAX_LENGTH, a as DEFAULT_ARCHIVE_LIMITS, b as parseSkillMarkdown, c as unzipWithLimits, d as stripArchiveRoot, f as SKILL_PACK_FILE, g as checkSkillRules, h as checkDependencyCycles, i as buildCatalog, j as SKILL_SIGNATURE_FILE, k as SKILLS_LOCKFILE, l as detectSkillArchiveShape, m as parseSkillPackManifest, n as SkillDiscovery, o as readResponseWithLimit, p as exportSkills, r as SkillReader, s as resolveArchiveLimits, t as validateSkills, u as detectSkillArchiveShapeFromFs, v as SKILL_NAME_PATTERN, w as readSkillSignature, x as FsTrustedKeyStore, y as isValidSkillName } from "./skill-CAJMsLod.js";
3
+ import { a as atomicWriteText, i as ATOMIC_TMP_SUFFIX_PATTERN, n as normalizePath, o as isAtomicTempPath, r as resolveInsideRoot, t as assertSafePathSegment } from "./pathSecurity-B1owvJAF.js";
4
+ import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
5
+ import { a as TEXT_EXTENSIONS, c as formatAttachmentText, i as IMAGE_MIME_TYPES, l as UNTRUSTED_LINE_LIMIT, n as DOCX_MIME, o as XLSX_MIME, r as FILE_MIME_TYPES, s as classifyAttachment, t as ATTACHMENT_TEXT_LIMIT, u as sanitizeUntrustedLine } from "./kind-DxgS8LM-.js";
6
+ import { $ as scriptToolName, A as readUserProfile, B as createScriptContext, C as TraceRecorder, D as mergeProfileEntries, E as appendBehaviorRecords, F as DEFAULT_USER_PROFILE_LIMITS, G as ASK_USER_TOOL_NAME, H as ASK_USER_INPUT_SCHEMA, I as EMPTY_USER_PROFILE, J as READ_SKILL_FILE_TOOL_NAME, K as READ_SKILL_FILE_INPUT_SCHEMA, L as SerializingMemoryStore, M as USER_PROFILE_NO_INVENTION_RULE, N as USER_PROFILE_PROMPT_HEADER, O as readBehaviorRecords, P as renderUserProfileContext, Q as schemaSourceLabel, R as EventBus, S as extractTodoTraceEvents, T as USER_PROFILE_KEY, U as ASK_USER_MAX_FIELDS, V as ASK_USER_FIELD_TYPES, W as ASK_USER_TOOL, X as formatSkillScriptManifest, Y as ALLOWED_TOOLS_EXCLUSION_REASON, Z as listSkillScripts, _ as RUN_SNAPSHOT_SCHEMA_VERSION, a as networkPolicyLibSource, at as xmlRenderer, b as MAX_TOOL_STEP_ARG_BYTES, c as bridgeError, d as FsMemoryStore, et as resolveToolName, f as WebSkillRuntime, g as FsRunSnapshotStore, h as redactToolStepArgs, i as isNetworkAllowed, it as renderAvailableSkillsXml, j as sampleBehaviorRecords, k as readProfileEntries, l as parseBridgeRequest, m as SENSITIVE_ANNOTATION, n as normalizeErrorCode, nt as ProgressiveRouter, o as networkUrlHost, p as AgentLoop, q as READ_SKILL_FILE_TOOL, r as normalizeToolError, rt as escapeXml, s as UPLOAD_FILES_UNAVAILABLE, t as CapabilityApproval, tt as toLlmToolSpec, u as FsArtifactStore, v as isUnsupportedRunSnapshot, w as BEHAVIOR_RECORDS_KEY, x as extractSkillCandidate, y as DEFAULT_LOOP_LIMITS, z as schemaToForm } from "./approval-BnLLCOG0.js";
7
+ import { a as GoogleGenAiClient, c as findUnpairedToolCalls, d as partsToText, f as promptText, i as toVercelToolSpecs, l as interruptedToolResult, n as fromVercelResult, o as AnthropicClient, p as textParts, r as fromVercelStreamPart, s as OpenAiCompatibleClient, u as sealToolCallPairs } from "./llm-B7lLH0ZI.js";
8
+ import { c as DEFAULT_MAX_DOCUMENT_BYTES, d as TEXT_BUDGETED_CONTENT_TYPES, i as UNSUPPORTED_DOCUMENT_MESSAGE, l as DEFAULT_MAX_DOCUMENT_TEXT_BYTES, n as READ_LINKED_DOCUMENT_TOOL_NAME, o as toBase64, r as SUPPORTED_DOCUMENT_MIME, s as DEFAULT_MAX_DATA_SOURCE_BYTES, t as READ_LINKED_DOCUMENT_TOOL, u as DEFAULT_MAX_UPLOAD_FILE_BYTES } from "./linkedDocument-xNXF-z8n.js";
9
+ import { n as normalizeToolContent, t as mergeCatalogEntries } from "./external-_ZRQe-V9.js";
10
+ import { n as extractChartSpec, t as buildRenderResult } from "./renderResult-D9Q-Vu2x.js";
11
+ import { n as validateUiSpecEvent, r as validateUiSpecNode, t as extractUiSpecEvents } from "./surface-DVGiCmwq.js";
12
+ import { t as createWebSkillApi } from "./webSkillApi-DOOyz2G5.js";
4
13
 
14
+ //#region ../core/src/fs/memoryFs.ts
15
+ const ROOT = "/";
16
+ /** 内存文件系统:路径一律规范化为 `/` 分隔,写入自动创建父目录 */
17
+ var MemoryFS = class {
18
+ kind = "memory";
19
+ #entries = /* @__PURE__ */ new Map();
20
+ constructor() {
21
+ this.#entries.set(ROOT, {
22
+ type: "directory",
23
+ content: /* @__PURE__ */ new Uint8Array(0),
24
+ mtimeMs: Date.now()
25
+ });
26
+ }
27
+ #normalize(path) {
28
+ const out = [];
29
+ for (const seg of path.replace(/\\/g, "/").split("/")) {
30
+ if (seg === "" || seg === ".") continue;
31
+ if (seg === "..") out.pop();
32
+ else out.push(seg);
33
+ }
34
+ return ROOT + out.join("/");
35
+ }
36
+ #parentOf(key) {
37
+ const i = key.lastIndexOf("/");
38
+ return i <= 0 ? ROOT : key.slice(0, i);
39
+ }
40
+ #ensureParents(key) {
41
+ const chain = [];
42
+ let dir = this.#parentOf(key);
43
+ while (!this.#entries.has(dir)) {
44
+ chain.push(dir);
45
+ dir = this.#parentOf(dir);
46
+ }
47
+ for (const d of chain.reverse()) this.#entries.set(d, {
48
+ type: "directory",
49
+ content: /* @__PURE__ */ new Uint8Array(0),
50
+ mtimeMs: Date.now()
51
+ });
52
+ }
53
+ #getFile(key) {
54
+ const entry = this.#entries.get(key);
55
+ if (!entry || entry.type !== "file") throw new WebSkillError("FS_NOT_FOUND", `File not found: ${key}`);
56
+ return entry;
57
+ }
58
+ #getDirectory(key) {
59
+ const entry = this.#entries.get(key);
60
+ if (!entry || entry.type !== "directory") throw new WebSkillError("FS_NOT_FOUND", `Directory not found: ${key}`);
61
+ return entry;
62
+ }
63
+ async readText(path) {
64
+ return new TextDecoder().decode(this.#getFile(this.#normalize(path)).content);
65
+ }
66
+ async writeText(path, content) {
67
+ await this.writeBinary(path, new TextEncoder().encode(content));
68
+ }
69
+ async appendText(path, content) {
70
+ const key = this.#normalize(path);
71
+ const existing = this.#entries.get(key);
72
+ if (existing && existing.type !== "file") throw new WebSkillError("FS_PERMISSION_DENIED", `Cannot append to a directory: ${key}`);
73
+ const prior = existing ? new TextDecoder().decode(existing.content) : "";
74
+ this.#ensureParents(key);
75
+ this.#entries.set(key, {
76
+ type: "file",
77
+ content: new TextEncoder().encode(prior + content),
78
+ mtimeMs: Date.now()
79
+ });
80
+ }
81
+ async readBinary(path) {
82
+ return this.#getFile(this.#normalize(path)).content;
83
+ }
84
+ async writeBinary(path, content) {
85
+ const key = this.#normalize(path);
86
+ this.#ensureParents(key);
87
+ this.#entries.set(key, {
88
+ type: "file",
89
+ content,
90
+ mtimeMs: Date.now()
91
+ });
92
+ }
93
+ async exists(path) {
94
+ return this.#entries.has(this.#normalize(path));
95
+ }
96
+ async stat(path) {
97
+ const key = this.#normalize(path);
98
+ const entry = this.#entries.get(key);
99
+ if (!entry) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${key}`);
100
+ return {
101
+ path: key,
102
+ type: entry.type,
103
+ size: entry.type === "file" ? entry.content.length : void 0,
104
+ mtimeMs: entry.mtimeMs
105
+ };
106
+ }
107
+ async list(path) {
108
+ const key = this.#normalize(path);
109
+ this.#getDirectory(key);
110
+ const prefix = key === ROOT ? ROOT : key + "/";
111
+ const out = [];
112
+ for (const [k, entry] of this.#entries) {
113
+ if (k === key || !k.startsWith(prefix)) continue;
114
+ if (k.slice(prefix.length).includes("/")) continue;
115
+ out.push({
116
+ path: k,
117
+ type: entry.type,
118
+ size: entry.type === "file" ? entry.content.length : void 0,
119
+ mtimeMs: entry.mtimeMs
120
+ });
121
+ }
122
+ return out;
123
+ }
124
+ async mkdir(path) {
125
+ const key = this.#normalize(path);
126
+ if (this.#entries.has(key)) return;
127
+ this.#ensureParents(key);
128
+ this.#entries.set(key, {
129
+ type: "directory",
130
+ content: /* @__PURE__ */ new Uint8Array(0),
131
+ mtimeMs: Date.now()
132
+ });
133
+ }
134
+ async remove(path, options) {
135
+ const key = this.#normalize(path);
136
+ if (!this.#entries.get(key)) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${key}`);
137
+ if (key === ROOT) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", "Cannot delete root directory");
138
+ const prefix = key + "/";
139
+ const descendants = [...this.#entries.keys()].filter((k) => k.startsWith(prefix));
140
+ if (descendants.length > 0 && !options?.recursive) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Directory not empty, use recursive: ${key}`);
141
+ for (const k of descendants) this.#entries.delete(k);
142
+ this.#entries.delete(key);
143
+ }
144
+ async rename(from, to) {
145
+ const src = this.#normalize(from);
146
+ const dst = this.#normalize(to);
147
+ const entry = this.#entries.get(src);
148
+ if (!entry) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${src}`);
149
+ const prefix = src + "/";
150
+ const descendants = [...this.#entries.entries()].filter(([k]) => k.startsWith(prefix));
151
+ this.#ensureParents(dst);
152
+ this.#entries.set(dst, entry);
153
+ for (const [k, v] of descendants) this.#entries.set(dst + k.slice(src.length), v);
154
+ for (const [k] of descendants) this.#entries.delete(k);
155
+ this.#entries.delete(src);
156
+ }
157
+ };
158
+
159
+ //#endregion
160
+ //#region ../core/src/render/jsonRenderer.ts
161
+ function renderCatalogJson(catalog) {
162
+ return JSON.stringify(catalog, null, 2);
163
+ }
164
+ const jsonRenderer = {
165
+ format: "json",
166
+ render: renderCatalogJson
167
+ };
168
+
169
+ //#endregion
170
+ //#region ../runtime/src/routing/fullDisclosureRouter.ts
171
+ const INSTRUCTIONS = `You are an agent that completes tasks by using skills.
172
+ The full instructions of every available skill are injected below. Follow them directly.
173
+ Script tools are named "<skillName>__<scriptName>".
174
+ When the task is complete, answer in plain text without calling any tool.
175
+
176
+ Never claim to have created, saved, or installed a skill unless a skill tool call actually succeeded.`;
177
+ /** 全量披露路由(对照用):注入每个技能的完整 SKILL.md 正文 */
178
+ var FullDisclosureRouter = class {
179
+ #discovery;
180
+ constructor(discovery) {
181
+ this.#discovery = discovery;
182
+ }
183
+ async route(catalog) {
184
+ const sections = [];
185
+ for (const entry of catalog.entries) {
186
+ const doc = await this.#discovery.loadDocument(entry.name);
187
+ sections.push(`<skill name="${entry.name}">\n${doc.body}\n</skill>`);
188
+ }
189
+ return {
190
+ strategy: "full-disclosure",
191
+ catalog,
192
+ systemPrompt: `${INSTRUCTIONS}\n\n${sections.join("\n\n")}`
193
+ };
194
+ }
195
+ };
196
+
197
+ //#endregion
198
+ //#region ../runtime/src/lifecycle/hooks.ts
199
+ /** 受控钩子执行器:逐个执行,超时/异常默认降级为 warning,可切严格模式 */
200
+ var HookRunner = class {
201
+ #hooks = /* @__PURE__ */ new Map();
202
+ /** 宿主可在装配后按 RuntimeConfig 调整(console Settings › Agent Runtime 即经此生效) */
203
+ timeoutMs;
204
+ failOnHookError;
205
+ onWarning;
206
+ constructor(options = {}) {
207
+ this.timeoutMs = options.timeoutMs ?? 5e3;
208
+ this.failOnHookError = options.failOnHookError ?? false;
209
+ this.onWarning = options.onWarning;
210
+ }
211
+ register(phase, hook) {
212
+ const key = phase;
213
+ const list = this.#hooks.get(key) ?? [];
214
+ list.push(hook);
215
+ this.#hooks.set(key, list);
216
+ return this;
217
+ }
218
+ /**
219
+ * 已注册钩子的**计数**,按注册相位分组(`'*'` 为全相位钩子)。
220
+ * 刻意不返回函数引用:那会给 UI 一条调用宿主钩子的执行路径,而面板只需要「装没装上」。
221
+ */
222
+ listRegisteredHooks() {
223
+ const counts = /* @__PURE__ */ new Map();
224
+ for (const [phase, hooks] of this.#hooks) counts.set(phase, hooks.length);
225
+ return counts;
226
+ }
227
+ async run(phase, ctx) {
228
+ const hooks = [...this.#hooks.get(phase) ?? [], ...this.#hooks.get("*") ?? []];
229
+ for (const hook of hooks) try {
230
+ await this.#withTimeout(Promise.resolve().then(() => hook(ctx)), phase);
231
+ } catch (e) {
232
+ if (this.failOnHookError) throw e instanceof WebSkillError ? e : new WebSkillError("RUN_FAILED", messageOf(e), e);
233
+ this.onWarning?.(messageOf(e));
234
+ }
235
+ }
236
+ #withTimeout(promise, phase) {
237
+ return new Promise((resolve, reject) => {
238
+ const timer = setTimeout(() => reject(new WebSkillError("RUN_TIMEOUT", `Lifecycle hook for phase "${phase}" timed out after ${this.timeoutMs}ms`)), this.timeoutMs);
239
+ promise.then((v) => {
240
+ clearTimeout(timer);
241
+ resolve(v);
242
+ }, (e) => {
243
+ clearTimeout(timer);
244
+ reject(e instanceof Error ? e : new Error(String(e)));
245
+ });
246
+ });
247
+ }
248
+ };
249
+
250
+ //#endregion
251
+ //#region ../runtime/src/profile/refine.ts
252
+ /**
253
+ * 提炼提示词。要求返回结论列表而不是散文:条目化是「可见、可编辑、可删除」的前提(FR-19.3)。
254
+ * `id` 由模型给出稳定 slug,才能在下一次提炼时更新同一条而不是无限追加。
255
+ */
256
+ const USER_PROFILE_REFINE_PROMPT = `You summarize what a single user is like, based on records of what they did in a chat application.
257
+
258
+ Return ONLY a JSON object with this shape, and nothing else:
259
+
260
+ {
261
+ "entries": [
262
+ { "id": "slug-case-stable-id", "text": "One sentence about the user.", "confidence": "high" | "low" }
263
+ ]
264
+ }
265
+
266
+ Rules:
267
+ - Each entry is one durable, reusable fact or preference about the user — how they like answers, what they work on, values they keep entering, languages they use.
268
+ - Reuse the same "id" across runs for the same conclusion so it can be updated instead of duplicated. Use lowercase words joined by hyphens.
269
+ - Use "high" only when several records agree. A single record is "low".
270
+ - Do not restate individual records. Do not include secrets, credentials, or full personal identifiers.
271
+ - Do not invent anything the records do not support. If the records say nothing useful, return {"entries": []}.`;
272
+ /** 采样后的记录 → 送模型的摘要。导出供测试与宿主复核实际请求内容(AC-19.6) @experimental */
273
+ function toRecordDigests(records) {
274
+ return records.map((record) => ({
275
+ kind: record.kind,
276
+ at: new Date(record.at).toISOString(),
277
+ value: record.value,
278
+ ...record.scene.skillName !== void 0 ? { skill: record.scene.skillName } : {},
279
+ ...record.scene.fieldName !== void 0 ? { field: record.scene.fieldName } : {},
280
+ ...record.scene.fieldSemantics !== void 0 ? { meaning: record.scene.fieldSemantics } : {},
281
+ ...record.scene.options !== void 0 ? { options: record.scene.options } : {},
282
+ ...record.confirmed === true ? { confirmed: true } : {}
283
+ }));
284
+ }
285
+ /** 模型可能把 JSON 包在 ```json 里;只取第一个对象,解析不出就当没结论 */
286
+ function parseEntries(content, now) {
287
+ const text = promptText(content ?? []);
288
+ const start = text.indexOf("{");
289
+ const end = text.lastIndexOf("}");
290
+ if (start < 0 || end <= start) return [];
291
+ let parsed;
292
+ try {
293
+ parsed = JSON.parse(text.slice(start, end + 1));
294
+ } catch {
295
+ return [];
296
+ }
297
+ const raw = parsed?.entries;
298
+ return readProfileEntries(Array.isArray(raw) ? raw.map((item) => ({
299
+ updatedAt: now,
300
+ ...item
301
+ })) : []);
302
+ }
303
+ /**
304
+ * 一次提炼(FR-19.3)。调用方负责持久化返回值;本函数不碰存储,
305
+ * 也不吞异常——「失败不影响会话结束」由调用点的 fire-and-forget 保证,
306
+ * 在这里吞掉会让手动提炼入口无法报错。
307
+ * @experimental
308
+ */
309
+ async function refineUserProfile(input) {
310
+ const limits = input.limits ?? DEFAULT_USER_PROFILE_LIMITS;
311
+ const now = (input.now ?? Date.now)();
312
+ const current = input.profile ?? EMPTY_USER_PROFILE;
313
+ const sampled = sampleBehaviorRecords(input.records, limits);
314
+ if (sampled.length === 0) return current;
315
+ const existing = current.entries.map((entry) => ({
316
+ id: entry.id,
317
+ text: entry.text
318
+ }));
319
+ const entries = parseEntries((await input.llm.complete({
320
+ ...input.model !== void 0 ? { model: input.model } : {},
321
+ ...input.signal !== void 0 ? { signal: input.signal } : {},
322
+ messages: [{
323
+ role: "system",
324
+ content: [{
325
+ type: "text",
326
+ text: USER_PROFILE_REFINE_PROMPT
327
+ }]
328
+ }, {
329
+ role: "user",
330
+ content: [{
331
+ type: "text",
332
+ text: `Existing conclusions:\n${JSON.stringify(existing)}\n\nRecords:\n${JSON.stringify(toRecordDigests(sampled))}`
333
+ }]
334
+ }]
335
+ })).content, now);
336
+ if (entries.length === 0) return current;
337
+ return mergeProfileEntries(current, entries, {
338
+ now,
339
+ limits
340
+ });
341
+ }
342
+
343
+ //#endregion
344
+ //#region ../runtime/src/profile/transfer.ts
345
+ /** 导出文件的版本;导入时逐字校验,不做向前兼容猜测 @experimental */
346
+ const USER_PROFILE_EXPORT_VERSION = 1;
347
+ /** 与偏好导出同一套判据:归一化后按子串匹配,命中即拒 */
348
+ const CREDENTIAL_MARKERS = [
349
+ "apikey",
350
+ "secret",
351
+ "token",
352
+ "password",
353
+ "passphrase",
354
+ "credential",
355
+ "privatekey"
356
+ ];
357
+ const isCredentialKey = (key) => {
358
+ const normalized = key.toLowerCase().replace(/[-_\s]/g, "");
359
+ return CREDENTIAL_MARKERS.some((marker) => normalized.includes(marker));
360
+ };
361
+ function findCredentialKey(value) {
362
+ if (Array.isArray(value)) {
363
+ for (const item of value) {
364
+ const hit = findCredentialKey(item);
365
+ if (hit !== void 0) return hit;
366
+ }
367
+ return;
368
+ }
369
+ if (typeof value !== "object" || value === null) return void 0;
370
+ for (const [key, child] of Object.entries(value)) {
371
+ if (isCredentialKey(key)) return key;
372
+ const hit = findCredentialKey(child);
373
+ if (hit !== void 0) return hit;
374
+ }
375
+ }
376
+ /**
377
+ * 导出画像(FR-19.7)。`excludeIds` 来自导出前的审阅界面:用户勾掉的条目不进文件。
378
+ * @experimental
379
+ */
380
+ function exportUserProfile(profile, options) {
381
+ const excluded = new Set(options.excludeIds ?? []);
382
+ const result = {
383
+ kind: "webskill.user-profile",
384
+ version: 1,
385
+ origin: options.origin,
386
+ exportedAt: (options.now ?? Date.now)(),
387
+ entries: profile.entries.filter((entry) => !excluded.has(entry.id))
388
+ };
389
+ const leaked = findCredentialKey(result);
390
+ if (leaked !== void 0) throw new WebSkillError("PROFILE_IMPORT_CREDENTIAL_REJECTED", `A user profile export must not contain credentials, but "${leaked}" was present`);
391
+ return result;
392
+ }
393
+ /** 解析并校验导入内容(FR-19.7)。含凭据是**拒绝**而不是忽略 @experimental */
394
+ function parseUserProfileExport(raw) {
395
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new WebSkillError("PROFILE_IMPORT_INVALID", "A user profile file must be a JSON object");
396
+ const record = raw;
397
+ if (record["kind"] !== "webskill.user-profile") throw new WebSkillError("PROFILE_IMPORT_INVALID", "A user profile file must have \"kind\": \"webskill.user-profile\"");
398
+ if (record["version"] !== 1) throw new WebSkillError("PROFILE_IMPORT_VERSION_UNSUPPORTED", `Unsupported user profile file version ${JSON.stringify(record["version"])}; expected ${1}`);
399
+ const leaked = findCredentialKey(record);
400
+ if (leaked !== void 0) throw new WebSkillError("PROFILE_IMPORT_CREDENTIAL_REJECTED", `The user profile file contains a credential field "${leaked}"; the import was rejected`);
401
+ const entries = readProfileEntries(record["entries"]);
402
+ if (entries.length === 0) throw new WebSkillError("PROFILE_IMPORT_INVALID", "The user profile file has no usable entries");
403
+ return {
404
+ kind: "webskill.user-profile",
405
+ version: 1,
406
+ origin: typeof record["origin"] === "string" ? record["origin"] : "unknown",
407
+ exportedAt: typeof record["exportedAt"] === "number" ? record["exportedAt"] : 0,
408
+ entries
409
+ };
410
+ }
411
+ function diffUserProfile(current, incoming) {
412
+ const byId = new Map(current.entries.map((entry) => [entry.id, entry]));
413
+ const added = [];
414
+ const updated = [];
415
+ for (const entry of incoming.entries) {
416
+ const existing = byId.get(entry.id);
417
+ if (existing === void 0) added.push(entry);
418
+ else if (existing.text !== entry.text) updated.push(entry);
419
+ }
420
+ return {
421
+ added,
422
+ updated
423
+ };
424
+ }
425
+ /** 确认后落库:同 id 覆盖,其余保留;不触碰模型配置与凭据 @experimental */
426
+ function applyUserProfileImport(current, incoming, options = {}) {
427
+ const now = (options.now ?? Date.now)();
428
+ const byId = new Map(current.entries.map((entry) => [entry.id, entry]));
429
+ for (const entry of incoming.entries) byId.set(entry.id, {
430
+ ...entry,
431
+ updatedAt: now
432
+ });
433
+ return {
434
+ version: 1,
435
+ entries: [...byId.values()].sort((a, b) => b.updatedAt - a.updatedAt).slice(0, DEFAULT_USER_PROFILE_LIMITS.entryLimit),
436
+ refinedAt: current.refinedAt
437
+ };
438
+ }
439
+
440
+ //#endregion
441
+ //#region ../runtime/src/engine/usage.ts
442
+ /**
443
+ * 从 run 的 trace 聚合大模型用量(请求次数 + 输入/输出 token)。
444
+ * 与 `summarizeToolCalls` 同型:消费方不再各自遍历 trace。
445
+ * @stable
446
+ */
447
+ function summarizeRunUsage(trace) {
448
+ let llmCalls = 0;
449
+ let inputTokens = 0;
450
+ let outputTokens = 0;
451
+ let hasUsage = false;
452
+ for (const event of trace) {
453
+ if (event.type === "llm.request") {
454
+ llmCalls += 1;
455
+ continue;
456
+ }
457
+ if (event.type !== "llm.usage") continue;
458
+ const input = event.data?.["inputTokens"];
459
+ const output = event.data?.["outputTokens"];
460
+ const hasInput = typeof input === "number" && Number.isFinite(input);
461
+ const hasOutput = typeof output === "number" && Number.isFinite(output);
462
+ if (hasInput) inputTokens += input;
463
+ if (hasOutput) outputTokens += output;
464
+ if (hasInput || hasOutput) hasUsage = true;
465
+ }
466
+ return {
467
+ llmCalls,
468
+ ...hasUsage ? {
469
+ inputTokens,
470
+ outputTokens
471
+ } : {}
472
+ };
473
+ }
474
+
475
+ //#endregion
476
+ //#region ../runtime/src/engine/runTrace.ts
477
+ const RUN_TRACE_SCHEMA_VERSION = 1;
478
+ /** 终止原因:取最后一条 run.completed/cancelled/failed 事件的 data.reason */
479
+ function extractEndReason(events) {
480
+ for (let i = events.length - 1; i >= 0; i--) {
481
+ const event = events[i];
482
+ if (event.type === "run.completed" || event.type === "run.cancelled" || event.type === "run.failed") {
483
+ const reason = event.data?.["reason"];
484
+ return typeof reason === "string" ? reason : void 0;
485
+ }
486
+ }
487
+ }
488
+ function summarize(trace) {
489
+ const endReason = extractEndReason(trace.events);
490
+ const durationMs = trace.endedAt === void 0 ? void 0 : Date.parse(trace.endedAt) - Date.parse(trace.startedAt);
491
+ const runUsage = summarizeRunUsage(trace.events);
492
+ return {
493
+ runId: trace.runId,
494
+ startedAt: trace.startedAt,
495
+ status: trace.status,
496
+ activeSkills: trace.activeSkills,
497
+ eventCount: trace.events.length,
498
+ turnCount: trace.events.filter((e) => e.type === "llm.request").length,
499
+ ...trace.sessionId !== void 0 ? { sessionId: trace.sessionId } : {},
500
+ ...trace.endedAt !== void 0 ? { endedAt: trace.endedAt } : {},
501
+ ...endReason !== void 0 ? { endReason } : {},
502
+ ...durationMs !== void 0 && Number.isFinite(durationMs) && durationMs >= 0 ? { durationMs } : {},
503
+ ...runUsage.inputTokens !== void 0 ? { usage: {
504
+ inputTokens: runUsage.inputTokens,
505
+ outputTokens: runUsage.outputTokens ?? 0
506
+ } } : {}
507
+ };
508
+ }
509
+ function parseTraceFile(raw, path) {
510
+ let data;
511
+ try {
512
+ data = JSON.parse(raw);
513
+ } catch {
514
+ return;
515
+ }
516
+ if (typeof data !== "object" || data === null) return void 0;
517
+ if (typeof data.runId !== "string" || typeof data.startedAt !== "string") return void 0;
518
+ const schemaVersion = typeof data.schemaVersion === "number" ? data.schemaVersion : 0;
519
+ if (schemaVersion > 1) throw new WebSkillError("RUN_TRACE_INCOMPATIBLE", `Run trace "${path}" declares schemaVersion ${schemaVersion}, which is newer than the supported ${1}`);
520
+ return {
521
+ schemaVersion,
522
+ runId: data.runId,
523
+ ...typeof data.sessionId === "string" ? { sessionId: data.sessionId } : {},
524
+ startedAt: data.startedAt,
525
+ ...typeof data.endedAt === "string" ? { endedAt: data.endedAt } : {},
526
+ status: typeof data.status === "string" ? data.status : "unknown",
527
+ activeSkills: Array.isArray(data.activeSkills) ? data.activeSkills.filter((s) => typeof s === "string") : [],
528
+ events: Array.isArray(data.events) ? data.events : []
529
+ };
530
+ }
531
+ const INDEX_FILE = "index.jsonl";
532
+ /**
533
+ * `FileSystemProvider` 后端:`<root>/<runId>.json` 存全量 trace,
534
+ * `<root>/index.jsonl` 是 append-only 的摘要旁路索引。
535
+ *
536
+ * 索引存在的唯一理由是**读次数**:0.0.1 的实现为了拼一份列表要整读每个 trace
537
+ * 文件,点一次指标再来一遍。有了索引,`list()` 与 `metrics()` 各只读一个文件。
538
+ *
539
+ * 索引与 trace 目录发散(写完 trace 崩在追加前)的检测不引入新的读放大:
540
+ * 目录列举本来就要做,比较 `*.json` 个数与索引行数即可;不等则整体重建。
541
+ * @stable
542
+ */
543
+ var FsRunTraceStore = class {
544
+ #root;
545
+ #fs;
546
+ #onError;
547
+ #onIndexError;
548
+ constructor(deps) {
549
+ this.#root = deps.root.replace(/\/+$/, "");
550
+ this.#fs = deps.fs;
551
+ this.#onError = deps.onError ?? ((error, run) => {
552
+ console.warn(`Failed to persist run trace "${run.id}": ${messageOf(error)}`);
553
+ });
554
+ this.#onIndexError = deps.onIndexError ?? ((error, run) => {
555
+ console.warn(`Run trace "${run.id}" was saved but its index entry could not be appended: ${messageOf(error)}. The index will be rebuilt on the next read.`);
556
+ });
557
+ }
558
+ #path(runId) {
559
+ return resolveInsideRoot(this.#root, `${runId}.json`);
560
+ }
561
+ async put(run) {
562
+ const trace = {
563
+ schemaVersion: 1,
564
+ runId: run.id,
565
+ sessionId: run.sessionId,
566
+ startedAt: run.startedAt,
567
+ ...run.endedAt !== void 0 ? { endedAt: run.endedAt } : {},
568
+ status: run.status === "completed" || run.status === "cancelled" ? run.status : "failed",
569
+ activeSkills: run.activeSkillNames,
570
+ events: run.trace
571
+ };
572
+ try {
573
+ await atomicWriteText(this.#fs, this.#path(run.id), JSON.stringify(trace, null, 2));
574
+ } catch (e) {
575
+ this.#onError(e, run);
576
+ return;
577
+ }
578
+ try {
579
+ await this.#fs.appendText(`${this.#root}/${INDEX_FILE}`, `${JSON.stringify(summarize(trace))}\n`);
580
+ } catch (e) {
581
+ this.#onIndexError(e, run);
582
+ }
583
+ }
584
+ async get(runId) {
585
+ assertSafePathSegment(runId, "run id");
586
+ const path = this.#path(runId);
587
+ let raw;
588
+ try {
589
+ raw = await this.#fs.readText(path);
590
+ } catch (e) {
591
+ if (e instanceof WebSkillError) throw e;
592
+ throw new WebSkillError("FS_NOT_FOUND", `Trace not found for run ${JSON.stringify(runId)}: ${path}`);
593
+ }
594
+ const trace = parseTraceFile(raw, path);
595
+ if (!trace) throw new WebSkillError("VALIDATION_FAILED", `Trace file is corrupted or malformed: ${path}`);
596
+ return trace;
597
+ }
598
+ async list(filter = {}) {
599
+ const { summaries } = await this.#readIndex();
600
+ const matched = applyFilter(summaries, filter);
601
+ const offset = Math.max(0, filter.offset ?? 0);
602
+ const sliced = matched.slice(offset);
603
+ return filter.limit !== void 0 ? sliced.slice(0, Math.max(0, filter.limit)) : sliced;
604
+ }
605
+ async metrics(filter = {}) {
606
+ const { summaries, skipped } = await this.#readIndex();
607
+ const runs = applyFilter(summaries, filter);
608
+ const succeeded = runs.filter((r) => r.status === "completed").length;
609
+ const failedRuns = runs.filter((r) => r.status === "failed");
610
+ const durations = runs.map((r) => r.durationMs).filter((ms) => ms !== void 0);
611
+ return {
612
+ totalRuns: runs.length,
613
+ succeeded,
614
+ failed: failedRuns.length,
615
+ successRate: runs.length === 0 ? 0 : succeeded / runs.length,
616
+ avgTurns: runs.length === 0 ? 0 : runs.reduce((sum, r) => sum + r.turnCount, 0) / runs.length,
617
+ skippedRuns: skipped,
618
+ avgDurationMs: durations.length === 0 ? 0 : durations.reduce((a, b) => a + b, 0) / durations.length,
619
+ recentFailures: failedRuns.slice(0, 5)
620
+ };
621
+ }
622
+ /** 健康路径恒为「一次目录列举 + 一次索引读」,与 run 数无关。 */
623
+ async #readIndex() {
624
+ if (!await this.#fs.exists(this.#root)) return {
625
+ summaries: [],
626
+ skipped: 0
627
+ };
628
+ const traceCount = (await this.#fs.list(this.#root)).filter((f) => f.type === "file" && f.path.endsWith(".json")).length;
629
+ const indexPath = `${this.#root}/${INDEX_FILE}`;
630
+ let lines = [];
631
+ if (await this.#fs.exists(indexPath)) lines = (await this.#fs.readText(indexPath)).split("\n").filter((line) => line.trim() !== "");
632
+ if (lines.length !== traceCount) return {
633
+ summaries: await this.#rebuildIndex(),
634
+ skipped: 0
635
+ };
636
+ const summaries = [];
637
+ let skipped = 0;
638
+ for (const line of lines) try {
639
+ summaries.push(JSON.parse(line));
640
+ } catch {
641
+ skipped += 1;
642
+ }
643
+ if (skipped > 0) return {
644
+ summaries: await this.#rebuildIndex(),
645
+ skipped
646
+ };
647
+ return {
648
+ summaries: sortByStartedAtDesc(summaries),
649
+ skipped: 0
650
+ };
651
+ }
652
+ /** 异常路径,允许 O(N):整读全部 trace 文件后用 temp + rename 原子替换索引 */
653
+ async #rebuildIndex() {
654
+ const summaries = [];
655
+ for (const entry of await this.#fs.list(this.#root)) {
656
+ if (entry.type !== "file" || !entry.path.endsWith(".json")) continue;
657
+ let trace;
658
+ try {
659
+ trace = parseTraceFile(await this.#fs.readText(entry.path), entry.path);
660
+ } catch {
661
+ continue;
662
+ }
663
+ if (!trace) continue;
664
+ summaries.push(summarize(trace));
665
+ }
666
+ const sorted = sortByStartedAtDesc(summaries);
667
+ const indexPath = `${this.#root}/${INDEX_FILE}`;
668
+ const tempPath = `${this.#root}/${INDEX_FILE}.rebuilding`;
669
+ try {
670
+ await this.#fs.writeText(tempPath, sorted.map((s) => `${JSON.stringify(s)}\n`).join(""));
671
+ await this.#fs.rename(tempPath, indexPath);
672
+ } catch {
673
+ await this.#fs.remove(tempPath).catch(() => void 0);
674
+ }
675
+ return sorted;
676
+ }
677
+ };
678
+ /** ISO 时间戳可按字典序比较 */
679
+ function sortByStartedAtDesc(summaries) {
680
+ return [...summaries].sort((a, b) => b.startedAt.localeCompare(a.startedAt));
681
+ }
682
+ function applyFilter(summaries, filter) {
683
+ let runs = summaries;
684
+ if (filter.status !== void 0 && filter.status !== "") runs = runs.filter((r) => r.status === filter.status);
685
+ if (filter.sessionId !== void 0 && filter.sessionId !== "") runs = runs.filter((r) => r.sessionId === filter.sessionId);
686
+ const q = filter.search?.trim().toLowerCase();
687
+ if (q !== void 0 && q !== "") runs = runs.filter((r) => r.runId.toLowerCase().includes(q) || r.activeSkills.some((s) => s.toLowerCase().includes(q)));
688
+ return runs;
689
+ }
690
+
691
+ //#endregion
692
+ //#region ../runtime/src/engine/toolCalls.ts
693
+ /**
694
+ * 从 run 的 trace 推导终态工具调用列表。
695
+ *
696
+ * 消费者过去要靠自己遍历 trace 才能拿到这份列表,而遍历 trace 的同一段代码
697
+ * 又顺手承担了「补发漏掉的 live 事件」的职责——两件事绑在一起,谁也删不掉。
698
+ * 幂等归 runtime(见 `AgentLoop` 的 per-run 已发集合)之后,
699
+ * 推导终态列表就是纯函数,独立导出。
700
+ * @stable
701
+ */
702
+ function summarizeToolCalls(run) {
703
+ const startedAtByCallId = /* @__PURE__ */ new Map();
704
+ for (const event of run.trace) {
705
+ if (event.type !== "tool.started") continue;
706
+ const callId = event.data?.["callId"];
707
+ if (typeof callId === "string" && !startedAtByCallId.has(callId)) startedAtByCallId.set(callId, event.ts);
708
+ }
709
+ const calls = [];
710
+ for (const event of run.trace) {
711
+ if (event.type !== "tool.completed" && event.type !== "tool.failed") continue;
712
+ const name = event.data?.["name"];
713
+ const callId = event.data?.["callId"];
714
+ if (typeof name !== "string" || typeof callId !== "string") continue;
715
+ const args = event.data?.["args"];
716
+ const durationMs = event.data?.["durationMs"];
717
+ const errorCode = event.data?.["code"];
718
+ const startedAt = startedAtByCallId.get(callId);
719
+ calls.push({
720
+ callId,
721
+ name,
722
+ status: event.type === "tool.completed" ? "completed" : "failed",
723
+ ...typeof args === "string" ? { args } : {},
724
+ ...typeof durationMs === "number" ? { durationMs } : {},
725
+ ...typeof errorCode === "string" ? { errorCode } : {},
726
+ ...startedAt !== void 0 ? { startedAt } : {},
727
+ ...event.type === "tool.failed" && typeof event.message === "string" ? { errorMessage: event.message } : {}
728
+ });
729
+ }
730
+ return calls;
731
+ }
732
+
733
+ //#endregion
734
+ //#region ../runtime/src/engine/session.ts
735
+ const SESSION_SCHEMA_VERSION = 1;
736
+ /** `FsSessionStore` 的缺省页长。缺省值属于实现,不属于调用方——否则「下推」只推了一半 */
737
+ const FS_SESSION_PAGE_SIZE = 50;
738
+ const toMeta = (record) => ({
739
+ id: record.id,
740
+ createdAt: record.createdAt,
741
+ ...record.title !== void 0 ? { title: record.title } : {},
742
+ ...record.titleLocked === true ? { titleLocked: true } : {},
743
+ ...record.archived === true ? { archived: true } : {},
744
+ ...record.modelId !== void 0 ? { modelId: record.modelId } : {},
745
+ messageCount: record.messages.length
746
+ });
747
+ const newSessionId = () => `session-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
748
+ const baseName = (path) => path.split("/").pop() ?? path;
749
+ /**
750
+ * 从新到旧的一页:无游标取末尾 `limit` 条,有游标取该位置**之前**的 `limit` 条。
751
+ * 游标编码成「本页起点下标」——对会话文件这种整体重写的存储来说下标是稳定的,
752
+ * 但它是不透明串,调用方不得自己构造(构造出来的越界值一律按 `VALIDATION_FAILED` 拒绝)。
753
+ */
754
+ function takeTailPage(all, options, what) {
755
+ const limit = options.limit ?? 50;
756
+ if (!Number.isInteger(limit) || limit <= 0) throw new WebSkillError("VALIDATION_FAILED", `Page limit must be a positive integer, received ${String(limit)}`);
757
+ let end = all.length;
758
+ if (options.cursor !== void 0) {
759
+ end = Number(options.cursor);
760
+ if (!Number.isInteger(end) || end < 0 || end > all.length) throw new WebSkillError("VALIDATION_FAILED", `Invalid ${what} cursor: ${JSON.stringify(options.cursor)}`);
761
+ }
762
+ const start = Math.max(0, end - limit);
763
+ return {
764
+ items: all.slice(start, end),
765
+ ...start > 0 ? { nextCursor: String(start) } : {}
766
+ };
767
+ }
768
+ /**
769
+ * 解析会话文件。缺 `schemaVersion` 视为 0(0.0.1 时代文件,兼容读);
770
+ * 高于当前版本拒绝读,避免新版写的字段被旧版静默丢弃。
771
+ */
772
+ function parseSessionFile(raw, path) {
773
+ let parsed;
774
+ try {
775
+ parsed = JSON.parse(raw);
776
+ } catch (e) {
777
+ throw new WebSkillError("VALIDATION_FAILED", `Session file is not valid JSON: ${path}`, e);
778
+ }
779
+ if (typeof parsed !== "object" || parsed === null) throw new WebSkillError("VALIDATION_FAILED", `Session file is not an object: ${path}`);
780
+ const file = parsed;
781
+ const schemaVersion = typeof file.schemaVersion === "number" ? file.schemaVersion : 0;
782
+ if (schemaVersion > 1) throw new WebSkillError("SESSION_INCOMPATIBLE", `Session file at ${path} has schemaVersion ${schemaVersion}, but this build understands at most ${1}`);
783
+ if (typeof file.id !== "string" || typeof file.createdAt !== "string") throw new WebSkillError("VALIDATION_FAILED", `Session file is missing "id" or "createdAt": ${path}`);
784
+ return {
785
+ schemaVersion,
786
+ id: file.id,
787
+ createdAt: file.createdAt,
788
+ ...typeof file.title === "string" ? { title: file.title } : {},
789
+ ...file.titleLocked === true ? { titleLocked: true } : {},
790
+ ...file.archived === true ? { archived: true } : {},
791
+ ...typeof file.modelId === "string" ? { modelId: file.modelId } : {},
792
+ messages: Array.isArray(file.messages) ? file.messages : [],
793
+ messageCount: Array.isArray(file.messages) ? file.messages.length : 0
794
+ };
795
+ }
796
+ /**
797
+ * `FileSystemProvider` 后端的会话存储:`<root>/<id>.json`。
798
+ *
799
+ * 所有变更操作按 id 串到一条 promise 链上:会话文件是整读整写的,
800
+ * 两个并发 `appendMessages` 若都先读后写,后写的会覆盖先写的那条消息。
801
+ */
802
+ var FsSessionStore = class {
803
+ #root;
804
+ #fs;
805
+ /** id → 该 id 上最后一次变更的完成时点,用于串行化读改写 */
806
+ #writes = /* @__PURE__ */ new Map();
807
+ /** FR-11.4:损坏文件诊断只报一次,避免 list 轮询刷屏 */
808
+ #warnedDataLoss = false;
809
+ constructor(deps) {
810
+ this.#root = deps.root.replace(/\/+$/, "");
811
+ this.#fs = deps.fs;
812
+ }
813
+ #path(id) {
814
+ assertSafePathSegment(id, "session id");
815
+ return resolveInsideRoot(this.#root, `${id}.json`);
816
+ }
817
+ /** 把 mutation 排到该 id 的队尾;前一个失败不阻塞后一个 */
818
+ async #serialize(id, work) {
819
+ const next = (this.#writes.get(id) ?? Promise.resolve()).catch(() => void 0).then(work);
820
+ this.#writes.set(id, next);
821
+ try {
822
+ return await next;
823
+ } finally {
824
+ if (this.#writes.get(id) === next) this.#writes.delete(id);
825
+ }
826
+ }
827
+ async #write(record) {
828
+ const file = {
829
+ schemaVersion: 1,
830
+ id: record.id,
831
+ createdAt: record.createdAt,
832
+ ...record.title !== void 0 ? { title: record.title } : {},
833
+ ...record.titleLocked === true ? { titleLocked: true } : {},
834
+ ...record.archived === true ? { archived: true } : {},
835
+ ...record.modelId !== void 0 ? { modelId: record.modelId } : {},
836
+ messages: record.messages
837
+ };
838
+ await atomicWriteText(this.#fs, this.#path(record.id), JSON.stringify(file, null, 2));
839
+ }
840
+ async #require(id) {
841
+ const record = await this.get(id);
842
+ if (!record) throw new WebSkillError("FS_NOT_FOUND", `Session "${id}" not found under ${this.#root}`);
843
+ return record;
844
+ }
845
+ async list(options = {}) {
846
+ if (!await this.#fs.exists(this.#root)) return {
847
+ items: [],
848
+ source: "missing-root",
849
+ skipped: []
850
+ };
851
+ const metas = [];
852
+ const skipped = [];
853
+ for (const entry of await this.#fs.list(this.#root)) {
854
+ if (entry.type !== "file" || !entry.path.endsWith(".json")) continue;
855
+ let record;
856
+ try {
857
+ record = parseSessionFile(await this.#fs.readText(entry.path), entry.path);
858
+ } catch {
859
+ skipped.push(baseName(entry.path));
860
+ continue;
861
+ }
862
+ if (record.archived === true && options.includeArchived !== true) continue;
863
+ metas.push(toMeta(record));
864
+ }
865
+ if (skipped.length > 0 && !this.#warnedDataLoss) {
866
+ this.#warnedDataLoss = true;
867
+ console.warn(`[webskill] ${skipped.length} session file(s) under ${this.#root} are unreadable (possible data loss from an interrupted write): ${skipped.join(", ")}`);
868
+ }
869
+ metas.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
870
+ return {
871
+ ...takeTailPage(metas, options, "session"),
872
+ source: "ok",
873
+ skipped
874
+ };
875
+ }
876
+ /**
877
+ * 整文件读后切片。磁盘 I/O 复杂度没有改善(备案 D25),
878
+ * 但**跨出端口的记录数**已是常量——这正是分页对上层的意义。
879
+ */
880
+ async listMessages(id, options = {}) {
881
+ return takeTailPage((await this.#require(id)).messages, options, "message");
882
+ }
883
+ async get(id) {
884
+ const path = this.#path(id);
885
+ if (!await this.#fs.exists(path)) return void 0;
886
+ return parseSessionFile(await this.#fs.readText(path), path);
887
+ }
888
+ async create(init = {}) {
889
+ const record = {
890
+ schemaVersion: 1,
891
+ id: init.id ?? newSessionId(),
892
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
893
+ ...init.title !== void 0 ? { title: init.title } : {},
894
+ messages: [],
895
+ messageCount: 0
896
+ };
897
+ return this.#serialize(record.id, async () => {
898
+ await this.#write(record);
899
+ return toMeta(record);
900
+ });
901
+ }
902
+ async appendMessages(id, messages) {
903
+ if (messages.length === 0) return;
904
+ await this.#serialize(id, async () => {
905
+ const record = await this.#require(id);
906
+ record.messages.push(...messages);
907
+ await this.#write(record);
908
+ });
909
+ }
910
+ async replaceMessages(id, messages) {
911
+ await this.#serialize(id, async () => {
912
+ const record = await this.#require(id);
913
+ record.messages = [...messages];
914
+ await this.#write(record);
915
+ });
916
+ }
917
+ async setTitle(id, title, options = {}) {
918
+ await this.#serialize(id, async () => {
919
+ const record = await this.#require(id);
920
+ record.title = title;
921
+ if (options.lock === true) record.titleLocked = true;
922
+ await this.#write(record);
923
+ });
924
+ }
925
+ async setArchived(id, archived) {
926
+ await this.#serialize(id, async () => {
927
+ const record = await this.#require(id);
928
+ record.archived = archived;
929
+ await this.#write(record);
930
+ });
931
+ }
932
+ async setModel(id, modelId) {
933
+ await this.#serialize(id, async () => {
934
+ const record = await this.#require(id);
935
+ if (modelId === void 0) delete record.modelId;
936
+ else record.modelId = modelId;
937
+ await this.#write(record);
938
+ });
939
+ }
940
+ async getMeta(id) {
941
+ const record = await this.get(id);
942
+ return record === void 0 ? void 0 : toMeta(record);
943
+ }
944
+ async delete(id) {
945
+ await this.#serialize(id, async () => {
946
+ const path = this.#path(id);
947
+ if (await this.#fs.exists(path)) await this.#fs.remove(path);
948
+ });
949
+ }
950
+ };
951
+
952
+ //#endregion
953
+ //#region ../runtime/src/engine/toolStep.ts
954
+ /**
955
+ * 按会话分文件的 append-only JSONL 留存。
956
+ *
957
+ * 分文件维度取会话而非 run:读取侧唯一的查询就是「这个会话的全部步骤」,
958
+ * 按 run 分会让一次生成要开 N 个文件。
959
+ */
960
+ var FsToolStepStore = class {
961
+ #root;
962
+ #fs;
963
+ #onError;
964
+ constructor(deps) {
965
+ this.#root = deps.root.replace(/\/+$/, "");
966
+ this.#fs = deps.fs;
967
+ this.#onError = deps.onError ?? ((error, record) => {
968
+ console.warn(`Failed to persist tool step for call "${record.callId}": ${messageOf(error)}`);
969
+ });
970
+ }
971
+ #path(sessionId) {
972
+ assertSafePathSegment(sessionId, "session id");
973
+ return resolveInsideRoot(this.#root, `${sessionId}.jsonl`);
974
+ }
975
+ async append(record) {
976
+ const path = this.#path(record.sessionId);
977
+ try {
978
+ await this.#fs.appendText(path, `${JSON.stringify(record)}\n`);
979
+ } catch (e) {
980
+ this.#onError(e, record);
981
+ }
982
+ }
983
+ async listBySession(sessionId) {
984
+ const path = this.#path(sessionId);
985
+ if (!await this.#fs.exists(path)) return [];
986
+ const raw = await this.#fs.readText(path);
987
+ const records = [];
988
+ for (const line of raw.split("\n")) {
989
+ if (line.trim() === "") continue;
990
+ try {
991
+ records.push(JSON.parse(line));
992
+ } catch {
993
+ continue;
994
+ }
995
+ }
996
+ return records;
997
+ }
998
+ };
999
+
1000
+ //#endregion
5
1001
  //#region src/version.ts
6
1002
  /** Generated by scripts/syncVersionConstant.mjs from packages/sdk/package.json. Do not edit by hand. */
7
1003
  /**
8
1004
  * Version of the published `@webskill/sdk` package, injected at build time.
9
1005
  * @stable
10
1006
  */
11
- const SDK_VERSION = "0.13.0";
1007
+ const SDK_VERSION = "0.15.0";
12
1008
 
13
1009
  //#endregion
14
- export { ALLOWED_TOOLS_EXCLUSION_REASON, ASK_USER_FIELD_TYPES, ASK_USER_INPUT_SCHEMA, ASK_USER_MAX_FIELDS, ASK_USER_TOOL, ASK_USER_TOOL_NAME, ATOMIC_TMP_SUFFIX_PATTERN, AgentLoop, AnthropicClient, BEHAVIOR_RECORDS_KEY, CapabilityApproval, DEFAULT_ARCHIVE_LIMITS, DEFAULT_LOOP_LIMITS, DEFAULT_MAX_DATA_SOURCE_BYTES, DEFAULT_MAX_DOCUMENT_BYTES, DEFAULT_MAX_DOCUMENT_TEXT_BYTES, DEFAULT_USER_PROFILE_LIMITS, EMPTY_USER_PROFILE, EventBus, FS_SESSION_PAGE_SIZE, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FsTrustedKeyStore, FullDisclosureRouter, GoogleGenAiClient, HookRunner, MANIFEST_EXCLUDED_FILES, MAX_TOOL_STEP_ARG_BYTES, MemoryFS, OpenAiCompatibleClient, ProgressiveRouter, READ_LINKED_DOCUMENT_TOOL, READ_LINKED_DOCUMENT_TOOL_NAME, READ_SKILL_FILE_INPUT_SCHEMA, READ_SKILL_FILE_TOOL, READ_SKILL_FILE_TOOL_NAME, RUN_SNAPSHOT_SCHEMA_VERSION, RUN_TRACE_SCHEMA_VERSION, SDK_VERSION, SENSITIVE_ANNOTATION, SESSION_SCHEMA_VERSION, SIGNATURE_SCHEMA_VERSION, SKILLS_LOCKFILE, SKILL_MANIFEST_FILE, SKILL_NAME_MAX_LENGTH, SKILL_NAME_PATTERN, SKILL_PACK_FILE, SKILL_SIGNATURE_FILE, SUPPORTED_DOCUMENT_MIME, SerializingMemoryStore, SkillDiscovery, SkillReader, TEXT_BUDGETED_CONTENT_TYPES, TraceRecorder, UNSUPPORTED_DOCUMENT_MESSAGE, USER_PROFILE_EXPORT_VERSION, USER_PROFILE_KEY, USER_PROFILE_NO_INVENTION_RULE, USER_PROFILE_PROMPT_HEADER, USER_PROFILE_REFINE_PROMPT, WebSkillError, WebSkillRuntime, appendBehaviorRecords, applyUserProfileImport, assertRemoteUrlAllowed, assertSafePathSegment, atomicWriteText, bridgeError, buildCatalog, buildManifest, buildRenderResult, checkDependencyCycles, checkSkillRules, computeDigest, createScriptContext, createWebSkillApi, detectSkillArchiveShape, detectSkillArchiveShapeFromFs, diffUserProfile, escapeXml, exportSkills, exportUserProfile, extractChartSpec, extractSkillCandidate, extractTodoTraceEvents, extractUiSpecEvents, findUnpairedToolCalls, formatSkillScriptManifest, fromVercelResult, fromVercelStreamPart, interruptedToolResult, isAtomicTempPath, isNetworkAllowed, isUnsupportedRunSnapshot, isValidSkillName, jsonRenderer, keyIdOf, listSkillScripts, mergeCatalogEntries, mergeProfileEntries, messageOf, networkPolicyLibSource, networkUrlHost, normalizeErrorCode, normalizePath, normalizeToolContent, normalizeToolError, parseBridgeRequest, parseSkillMarkdown, parseSkillPackManifest, parseUserProfileExport, partsToText, readBehaviorRecords, readProfileEntries, readResponseWithLimit, readSkillSignature, readUserProfile, redactToolStepArgs, refineUserProfile, renderAvailableSkillsXml, renderCatalogJson, renderUserProfileContext, resolveArchiveLimits, resolveInsideRoot, resolveToolName, sampleBehaviorRecords, schemaSourceLabel, schemaToForm, scriptToolName, sealToolCallPairs, signSkill, signaturePayloadBytes, stripArchiveRoot, summarizeRunUsage, summarizeToolCalls, textParts, toLlmToolSpec, toRecordDigests, toVercelToolSpecs, unzipWithLimits, validateSkills, validateUiSpecEvent, validateUiSpecNode, verifyManifest, verifySkillSignature, xmlRenderer };
1010
+ export { ALLOWED_TOOLS_EXCLUSION_REASON, ASK_USER_FIELD_TYPES, ASK_USER_INPUT_SCHEMA, ASK_USER_MAX_FIELDS, ASK_USER_TOOL, ASK_USER_TOOL_NAME, ATOMIC_TMP_SUFFIX_PATTERN, ATTACHMENT_TEXT_LIMIT, AgentLoop, AnthropicClient, BEHAVIOR_RECORDS_KEY, CapabilityApproval, DEFAULT_ARCHIVE_LIMITS, DEFAULT_LOOP_LIMITS, DEFAULT_MAX_DATA_SOURCE_BYTES, DEFAULT_MAX_DOCUMENT_BYTES, DEFAULT_MAX_DOCUMENT_TEXT_BYTES, DEFAULT_MAX_UPLOAD_FILE_BYTES, DEFAULT_USER_PROFILE_LIMITS, DOCX_MIME, EMPTY_USER_PROFILE, EventBus, FILE_MIME_TYPES, FS_SESSION_PAGE_SIZE, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FsTrustedKeyStore, FullDisclosureRouter, GoogleGenAiClient, HookRunner, IMAGE_MIME_TYPES, MANIFEST_EXCLUDED_FILES, MAX_TOOL_STEP_ARG_BYTES, MemoryFS, OpenAiCompatibleClient, ProgressiveRouter, READ_LINKED_DOCUMENT_TOOL, READ_LINKED_DOCUMENT_TOOL_NAME, READ_SKILL_FILE_INPUT_SCHEMA, READ_SKILL_FILE_TOOL, READ_SKILL_FILE_TOOL_NAME, RUN_SNAPSHOT_SCHEMA_VERSION, RUN_TRACE_SCHEMA_VERSION, SDK_VERSION, SENSITIVE_ANNOTATION, SESSION_SCHEMA_VERSION, SIGNATURE_SCHEMA_VERSION, SKILLS_LOCKFILE, SKILL_MANIFEST_FILE, SKILL_NAME_MAX_LENGTH, SKILL_NAME_PATTERN, SKILL_PACK_FILE, SKILL_SIGNATURE_FILE, SUPPORTED_DOCUMENT_MIME, SerializingMemoryStore, SkillDiscovery, SkillReader, TEXT_BUDGETED_CONTENT_TYPES, TEXT_EXTENSIONS, TraceRecorder, UNSUPPORTED_DOCUMENT_MESSAGE, UNTRUSTED_LINE_LIMIT, UPLOAD_FILES_UNAVAILABLE, USER_PROFILE_EXPORT_VERSION, USER_PROFILE_KEY, USER_PROFILE_NO_INVENTION_RULE, USER_PROFILE_PROMPT_HEADER, USER_PROFILE_REFINE_PROMPT, WebSkillError, WebSkillRuntime, XLSX_MIME, appendBehaviorRecords, applyUserProfileImport, assertRemoteUrlAllowed, assertSafePathSegment, atomicWriteText, bridgeError, buildCatalog, buildManifest, buildRenderResult, checkDependencyCycles, checkSkillRules, classifyAttachment, computeDigest, createScriptContext, createWebSkillApi, detectSkillArchiveShape, detectSkillArchiveShapeFromFs, diffUserProfile, escapeXml, exportSkills, exportUserProfile, extractChartSpec, extractSkillCandidate, extractTodoTraceEvents, extractUiSpecEvents, findUnpairedToolCalls, formatAttachmentText, formatSkillScriptManifest, fromVercelResult, fromVercelStreamPart, interruptedToolResult, isAtomicTempPath, isNetworkAllowed, isUnsupportedRunSnapshot, isValidSkillName, jsonRenderer, keyIdOf, listSkillScripts, mergeCatalogEntries, mergeProfileEntries, messageOf, networkPolicyLibSource, networkUrlHost, normalizeErrorCode, normalizePath, normalizeToolContent, normalizeToolError, parseBridgeRequest, parseSkillMarkdown, parseSkillPackManifest, parseUserProfileExport, partsToText, readBehaviorRecords, readProfileEntries, readResponseWithLimit, readSkillSignature, readUserProfile, redactToolStepArgs, refineUserProfile, renderAvailableSkillsXml, renderCatalogJson, renderUserProfileContext, resolveArchiveLimits, resolveInsideRoot, resolveToolName, sampleBehaviorRecords, sanitizeUntrustedLine, schemaSourceLabel, schemaToForm, scriptToolName, sealToolCallPairs, signSkill, signaturePayloadBytes, stripArchiveRoot, summarizeRunUsage, summarizeToolCalls, textParts, toBase64, toLlmToolSpec, toRecordDigests, toVercelToolSpecs, unzipWithLimits, validateSkills, validateUiSpecEvent, validateUiSpecNode, verifyManifest, verifySkillSignature, xmlRenderer };