@webskill/sdk 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +218 -28
- package/dist/browser.d.ts +177 -4
- package/dist/browser.js +444 -35
- package/dist/{catalogComponents-Dr5dFMAb-Dacibl1e.js → catalogComponents-DfxxfUvn-D55Gbb2l.js} +4016 -1226
- package/dist/{dist-8oQRa8Xz.js → dist-59XlqDuv.js} +93 -6
- package/dist/{dist-DnYG2-eY.js → dist-CJqQsIm9.js} +498 -118
- package/dist/{dist-DusANsrn.js → dist-DmI5SBBF.js} +437 -55
- package/dist/eventTypes-g1BXL6x5-CibcOftR.js +37 -0
- package/dist/governance.d.ts +91 -11
- package/dist/governance.js +194 -52
- package/dist/{index-C-KFAZoF.d.ts → index-K-eewlGL.d.ts} +176 -75
- package/dist/{index-BMocOEi0.d.ts → index-P9J2LTfU.d.ts} +163 -6
- package/dist/{index-BuTpBMzr.d.ts → index-fLskQfAS.d.ts} +156 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/mcp.d.ts +129 -6
- package/dist/mcp.js +235 -28
- package/dist/{memoryArtifactStore-52Zn9npI-BMPYwvoy.js → memoryArtifactStore-52Zn9npI-upv5OWYf.js} +1 -1
- package/dist/node.d.ts +3 -3
- package/dist/node.js +4 -3
- package/dist/{openUiLibrary-Bdrji9qK-DzAxRlTY.js → openUiLibrary-DURlAxjk-CU6AzfSW.js} +3 -3
- package/dist/{skillVersionStore-BzLbzFOL-CxdAFWO2.d.ts → skillVersionStore-Bl-ElD45-gRfSaAby.d.ts} +8 -2
- package/dist/{testing-CYTFqkDm.js → testing-BCUO5gZR.js} +2 -2
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -2
- package/dist/{types-4pg-qp_I-Gq63X8Oa.d.ts → types-B3n0cMZu-BdcqQ35O.d.ts} +74 -7
- package/dist/ui-react.d.ts +16 -7
- package/dist/ui-react.js +159 -108
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +2 -2
- package/dist/ui.d.ts +4 -4
- package/dist/ui.js +3 -3
- package/dist/{webskillLitCatalog-_mugzRHx-B_54vxum.js → webskillLitCatalog-DwTwSBFt-DiXXpNZA.js} +22 -3
- package/package.json +2 -2
|
@@ -1,7 +1,69 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as textParts, i as rejectUnsupportedPart, n as partsToText, o as validateLlmMessages, r as promptText, t as MemoryArtifactStore } from "./memoryArtifactStore-52Zn9npI-
|
|
1
|
+
import { A as messageOf, I as renderAvailableSkillsXml, M as parseSkillMarkdown, W as validateSkills, f as SkillDiscovery, g as assertSafePathSegment, m as WebSkillError, p as SkillReader, v as buildCatalog, z as resolveInsideRoot } from "./dist-59XlqDuv.js";
|
|
2
|
+
import { a as textParts, i as rejectUnsupportedPart, n as partsToText, o as validateLlmMessages, r as promptText, t as MemoryArtifactStore } from "./memoryArtifactStore-52Zn9npI-upv5OWYf.js";
|
|
3
3
|
|
|
4
4
|
//#region ../runtime/dist/index.js
|
|
5
|
+
/** 与正常工具结果同构:模型读到的是「这次调用被中断了」,而不是一个凭空消失的调用 */
|
|
6
|
+
function interruptedToolResult(call, options = {}) {
|
|
7
|
+
const code = options.code ?? "RUN_INTERRUPTED";
|
|
8
|
+
const because = options.reason === void 0 ? "" : ` (${options.reason})`;
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
content: [],
|
|
12
|
+
error: {
|
|
13
|
+
code,
|
|
14
|
+
message: `Tool call "${call.name}" produced no result: the run ended before it finished${because}.`
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** 尚无 `role:'tool'` 应答的工具调用,按消息顺序 */
|
|
19
|
+
function findUnpairedToolCalls(messages) {
|
|
20
|
+
const answered = new Set(messages.filter((m) => m.role === "tool").map((m) => m.toolCallId));
|
|
21
|
+
return messages.filter((m) => m.role === "assistant" && m.toolCalls?.length).flatMap((m) => m.toolCalls ?? []).filter((call) => !answered.has(call.id));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 为每个未应答的工具调用补一条结构化的中断说明。
|
|
25
|
+
*
|
|
26
|
+
* 补齐消息紧跟在该 assistant 已有的 tool 兄弟之后——供应商装配按相邻块分组,
|
|
27
|
+
* 插到序列末尾会让它归属到别的 assistant 消息上。
|
|
28
|
+
* 本函数**不改写入参**,也不写盘(AC-11.8)。
|
|
29
|
+
*/
|
|
30
|
+
function sealToolCallPairs(messages, options = {}) {
|
|
31
|
+
const answered = new Set(messages.filter((m) => m.role === "tool").map((m) => m.toolCallId));
|
|
32
|
+
const code = options.code ?? "RUN_INTERRUPTED";
|
|
33
|
+
const sealed = [];
|
|
34
|
+
const out = [];
|
|
35
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
36
|
+
const message = messages[i];
|
|
37
|
+
out.push(message);
|
|
38
|
+
if (message.role !== "assistant" || !message.toolCalls?.length) continue;
|
|
39
|
+
const missing = message.toolCalls.filter((call) => !answered.has(call.id));
|
|
40
|
+
if (missing.length === 0) continue;
|
|
41
|
+
while (i + 1 < messages.length && messages[i + 1].role === "tool") {
|
|
42
|
+
out.push(messages[i + 1]);
|
|
43
|
+
i += 1;
|
|
44
|
+
}
|
|
45
|
+
for (const call of missing) {
|
|
46
|
+
out.push({
|
|
47
|
+
role: "tool",
|
|
48
|
+
toolCallId: call.id,
|
|
49
|
+
content: textParts(JSON.stringify(interruptedToolResult(call, {
|
|
50
|
+
...options,
|
|
51
|
+
code
|
|
52
|
+
})))
|
|
53
|
+
});
|
|
54
|
+
answered.add(call.id);
|
|
55
|
+
sealed.push({
|
|
56
|
+
callId: call.id,
|
|
57
|
+
toolName: call.name,
|
|
58
|
+
code
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
messages: sealed.length === 0 ? [...messages] : out,
|
|
64
|
+
sealed
|
|
65
|
+
};
|
|
66
|
+
}
|
|
5
67
|
/**
|
|
6
68
|
* $defs 必须与 type / properties 同级,位于每个工具 inputSchema 的根。
|
|
7
69
|
* 已验证:放进 properties.spec 内层会得到同样的 "Unsupported ref: #" ——
|
|
@@ -12,7 +74,7 @@ const DEFS_KEY = "$defs";
|
|
|
12
74
|
const SELF_REF = "#";
|
|
13
75
|
const NODE_NAME = "Node";
|
|
14
76
|
const COMPONENT_PREFIX = "Component_";
|
|
15
|
-
/** 值为「名称 → 子 schema
|
|
77
|
+
/** 值为「名称 → 子 schema」的映射。包内导出:vendorSchema 要用同一套下钻规则(两套会漂移)。 */
|
|
16
78
|
const SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set([
|
|
17
79
|
"properties",
|
|
18
80
|
"patternProperties",
|
|
@@ -41,11 +103,11 @@ const SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
|
41
103
|
"then",
|
|
42
104
|
"else"
|
|
43
105
|
]);
|
|
44
|
-
function isRecord$
|
|
106
|
+
function isRecord$6(value) {
|
|
45
107
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
46
108
|
}
|
|
47
109
|
function isSelfRef(value) {
|
|
48
|
-
return isRecord$
|
|
110
|
+
return isRecord$6(value) && value["$ref"] === SELF_REF;
|
|
49
111
|
}
|
|
50
112
|
/**
|
|
51
113
|
* 按 JSON Schema 关键字下降到子 schema。
|
|
@@ -55,7 +117,7 @@ function isSelfRef(value) {
|
|
|
55
117
|
function forEachSubSchema(node, visit) {
|
|
56
118
|
for (const [key, value] of Object.entries(node)) {
|
|
57
119
|
if (SCHEMA_MAP_KEYS.has(key)) {
|
|
58
|
-
if (isRecord$
|
|
120
|
+
if (isRecord$6(value)) for (const name of Object.keys(value)) visit(value[name], [key, name]);
|
|
59
121
|
continue;
|
|
60
122
|
}
|
|
61
123
|
if (SCHEMA_LIST_KEYS.has(key)) {
|
|
@@ -74,7 +136,7 @@ function pathKey(path) {
|
|
|
74
136
|
* `#` 解析到最近的带 `$id` 的祖先;没有则是文档根。
|
|
75
137
|
*/
|
|
76
138
|
function collectSelfRefRoots(node, path, resourceRoot, out) {
|
|
77
|
-
if (!isRecord$
|
|
139
|
+
if (!isRecord$6(node)) return;
|
|
78
140
|
if (isSelfRef(node)) {
|
|
79
141
|
out.set(pathKey(resourceRoot), resourceRoot);
|
|
80
142
|
return;
|
|
@@ -87,7 +149,7 @@ function collectSelfRefRoots(node, path, resourceRoot, out) {
|
|
|
87
149
|
function getAt(schema, path) {
|
|
88
150
|
let current = schema;
|
|
89
151
|
for (const segment of path) if (Array.isArray(current) && typeof segment === "number") current = current[segment];
|
|
90
|
-
else if (isRecord$
|
|
152
|
+
else if (isRecord$6(current) && typeof segment === "string") current = current[segment];
|
|
91
153
|
else return void 0;
|
|
92
154
|
return current;
|
|
93
155
|
}
|
|
@@ -113,7 +175,7 @@ function setAt(schema, path, replacement) {
|
|
|
113
175
|
* 正是严格解析器无法解析 `#` 的直接原因。
|
|
114
176
|
*/
|
|
115
177
|
function rewriteSelfRefs(node, target, depth) {
|
|
116
|
-
if (!isRecord$
|
|
178
|
+
if (!isRecord$6(node)) return node;
|
|
117
179
|
if (depth > 8) throw new WebSkillError("TOOL_SCHEMA_UNAVAILABLE", `Tool schema nesting exceeds 8 levels; cannot produce a portable form`);
|
|
118
180
|
if (isSelfRef(node)) {
|
|
119
181
|
const rest = {};
|
|
@@ -126,7 +188,7 @@ function rewriteSelfRefs(node, target, depth) {
|
|
|
126
188
|
const result = {};
|
|
127
189
|
for (const [key, value] of Object.entries(node)) {
|
|
128
190
|
if (key === "$id" || key === "$schema") continue;
|
|
129
|
-
if (SCHEMA_MAP_KEYS.has(key) && isRecord$
|
|
191
|
+
if (SCHEMA_MAP_KEYS.has(key) && isRecord$6(value)) {
|
|
130
192
|
const mapped = {};
|
|
131
193
|
for (const [name, child] of Object.entries(value)) mapped[name] = rewriteSelfRefs(child, target, depth + 1);
|
|
132
194
|
result[key] = mapped;
|
|
@@ -164,8 +226,8 @@ function toPortableToolSchema(schema) {
|
|
|
164
226
|
if (roots.size > 1) throw new WebSkillError("TOOL_SCHEMA_UNAVAILABLE", "Tool schema contains self references in more than one schema resource; cannot produce a portable form");
|
|
165
227
|
const rootPath = [...roots.values()][0] ?? [];
|
|
166
228
|
const resource = getAt(schema, rootPath);
|
|
167
|
-
if (!isRecord$
|
|
168
|
-
const existingDefs = isRecord$
|
|
229
|
+
if (!isRecord$6(resource)) return schema;
|
|
230
|
+
const existingDefs = isRecord$6(schema[DEFS_KEY]) ? schema[DEFS_KEY] : void 0;
|
|
169
231
|
const taken = new Set(Object.keys(existingDefs ?? {}));
|
|
170
232
|
const nodeName = uniqueName(NODE_NAME, taken);
|
|
171
233
|
taken.add(nodeName);
|
|
@@ -196,6 +258,136 @@ function toPortableToolSchema(schema) {
|
|
|
196
258
|
}
|
|
197
259
|
};
|
|
198
260
|
}
|
|
261
|
+
/** 各家都认的基础关键字。Google `FunctionDeclaration.parameters` 是其中最窄的一家。 */
|
|
262
|
+
const BASE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
263
|
+
"type",
|
|
264
|
+
"description",
|
|
265
|
+
"title",
|
|
266
|
+
"default",
|
|
267
|
+
"enum",
|
|
268
|
+
"const",
|
|
269
|
+
"properties",
|
|
270
|
+
"required",
|
|
271
|
+
"items",
|
|
272
|
+
"anyOf",
|
|
273
|
+
"oneOf",
|
|
274
|
+
"minimum",
|
|
275
|
+
"maximum",
|
|
276
|
+
"minItems",
|
|
277
|
+
"maxItems",
|
|
278
|
+
"nullable",
|
|
279
|
+
"format"
|
|
280
|
+
]);
|
|
281
|
+
/** 宽松侧:保留 0.6.0 的透传行为,本版不主动收紧(没有实证之前收紧只会制造新的破坏) */
|
|
282
|
+
const PERMISSIVE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
283
|
+
...BASE_KEYWORDS,
|
|
284
|
+
"additionalProperties",
|
|
285
|
+
"patternProperties",
|
|
286
|
+
"dependentSchemas",
|
|
287
|
+
"definitions",
|
|
288
|
+
"$defs",
|
|
289
|
+
"$ref",
|
|
290
|
+
"$schema",
|
|
291
|
+
"$id",
|
|
292
|
+
"allOf",
|
|
293
|
+
"not",
|
|
294
|
+
"if",
|
|
295
|
+
"then",
|
|
296
|
+
"else",
|
|
297
|
+
"prefixItems",
|
|
298
|
+
"additionalItems",
|
|
299
|
+
"unevaluatedItems",
|
|
300
|
+
"unevaluatedProperties",
|
|
301
|
+
"contains",
|
|
302
|
+
"propertyNames",
|
|
303
|
+
"exclusiveMinimum",
|
|
304
|
+
"exclusiveMaximum",
|
|
305
|
+
"multipleOf",
|
|
306
|
+
"minLength",
|
|
307
|
+
"maxLength",
|
|
308
|
+
"pattern",
|
|
309
|
+
"uniqueItems",
|
|
310
|
+
"examples",
|
|
311
|
+
"readOnly",
|
|
312
|
+
"writeOnly",
|
|
313
|
+
"deprecated"
|
|
314
|
+
]);
|
|
315
|
+
const OPENAI_SCHEMA_PROFILE = {
|
|
316
|
+
id: "openai-compatible",
|
|
317
|
+
allowedKeywords: PERMISSIVE_KEYWORDS,
|
|
318
|
+
supportsRefs: true
|
|
319
|
+
};
|
|
320
|
+
const ANTHROPIC_SCHEMA_PROFILE = {
|
|
321
|
+
id: "anthropic",
|
|
322
|
+
allowedKeywords: PERMISSIVE_KEYWORDS,
|
|
323
|
+
supportsRefs: true
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* Google Gen AI 的 `FunctionDeclaration.parameters` 收到 `additionalProperties`
|
|
327
|
+
* 直接回 400 `Unknown name "additionalProperties"`,且不解析 `$ref` / `$defs`。
|
|
328
|
+
*/
|
|
329
|
+
const GOOGLE_SCHEMA_PROFILE = {
|
|
330
|
+
id: "google",
|
|
331
|
+
allowedKeywords: BASE_KEYWORDS,
|
|
332
|
+
supportsRefs: false
|
|
333
|
+
};
|
|
334
|
+
function isRecord$5(value) {
|
|
335
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
336
|
+
}
|
|
337
|
+
/** 只在 schema 位置上找 `$ref`:enum / const / default 里的同名键是字面数据,不是引用 */
|
|
338
|
+
function containsRef(node) {
|
|
339
|
+
if (Array.isArray(node)) return node.some(containsRef);
|
|
340
|
+
if (!isRecord$5(node)) return false;
|
|
341
|
+
if (typeof node["$ref"] === "string") return true;
|
|
342
|
+
for (const [key, value] of Object.entries(node)) {
|
|
343
|
+
if (SCHEMA_MAP_KEYS.has(key)) {
|
|
344
|
+
if (isRecord$5(value) && Object.values(value).some(containsRef)) return true;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (SCHEMA_LIST_KEYS.has(key)) {
|
|
348
|
+
if (Array.isArray(value) && value.some(containsRef)) return true;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (SCHEMA_KEYS.has(key) && containsRef(value)) return true;
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
function sanitizeNode(node, profile) {
|
|
356
|
+
if (Array.isArray(node)) return node.map((item) => sanitizeNode(item, profile));
|
|
357
|
+
if (!isRecord$5(node)) return node;
|
|
358
|
+
const out = {};
|
|
359
|
+
for (const [key, value] of Object.entries(node)) {
|
|
360
|
+
if (!profile.allowedKeywords.has(key)) continue;
|
|
361
|
+
if (SCHEMA_MAP_KEYS.has(key) && isRecord$5(value)) {
|
|
362
|
+
const mapped = {};
|
|
363
|
+
for (const [name, child] of Object.entries(value)) mapped[name] = sanitizeNode(child, profile);
|
|
364
|
+
out[key] = mapped;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (SCHEMA_LIST_KEYS.has(key) && Array.isArray(value)) {
|
|
368
|
+
out[key] = value.map((child) => sanitizeNode(child, profile));
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (SCHEMA_KEYS.has(key)) {
|
|
372
|
+
out[key] = sanitizeNode(value, profile);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
out[key] = value;
|
|
376
|
+
}
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* 按供应商 profile 清洗出站 schema。产出新对象,不修改入参(工具源可能复用同一个实例)。
|
|
381
|
+
*
|
|
382
|
+
* 这是**传输层适配**,只应在出站请求体上调用;不要下沉到 AgentLoop,
|
|
383
|
+
* 否则 trace / eventBus 观测到的 schema 会与技能作者写的不一致。
|
|
384
|
+
*
|
|
385
|
+
* @throws WebSkillError `TOOL_SCHEMA_UNAVAILABLE` — schema 依赖该供应商不支持的引用
|
|
386
|
+
*/
|
|
387
|
+
function sanitizeForVendor(schema, profile, toolName) {
|
|
388
|
+
if (!profile.supportsRefs && containsRef(schema)) throw new WebSkillError("TOOL_SCHEMA_UNAVAILABLE", `Tool "${toolName}" requires $ref support that provider "${profile.id}" does not accept`);
|
|
389
|
+
return sanitizeNode(schema, profile);
|
|
390
|
+
}
|
|
199
391
|
function createSseFrameReader() {
|
|
200
392
|
let buffer = "";
|
|
201
393
|
let data = [];
|
|
@@ -307,7 +499,7 @@ const toOpenAiTools = (tools) => tools.map((tool) => ({
|
|
|
307
499
|
function: {
|
|
308
500
|
name: tool.name,
|
|
309
501
|
...tool.description ? { description: tool.description } : {},
|
|
310
|
-
parameters: toPortableToolSchema(tool.inputSchema)
|
|
502
|
+
parameters: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), OPENAI_SCHEMA_PROFILE, tool.name)
|
|
311
503
|
}
|
|
312
504
|
}));
|
|
313
505
|
const errorMessage$2 = (e) => e instanceof Error ? e.message : String(e);
|
|
@@ -564,7 +756,7 @@ function toAnthropicMessages(messages) {
|
|
|
564
756
|
const toAnthropicTools = (tools) => tools.map((tool) => ({
|
|
565
757
|
name: tool.name,
|
|
566
758
|
...tool.description ? { description: tool.description } : {},
|
|
567
|
-
input_schema: toPortableToolSchema(tool.inputSchema)
|
|
759
|
+
input_schema: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), ANTHROPIC_SCHEMA_PROFILE, tool.name)
|
|
568
760
|
}));
|
|
569
761
|
/** Anthropic Messages API 客户端(零依赖 fetch;Node/浏览器通用) */
|
|
570
762
|
var AnthropicClient = class {
|
|
@@ -807,7 +999,7 @@ function toGenAiContents(messages) {
|
|
|
807
999
|
const toGenAiTools = (tools) => [{ functionDeclarations: tools.map((tool) => ({
|
|
808
1000
|
name: tool.name,
|
|
809
1001
|
...tool.description ? { description: tool.description } : {},
|
|
810
|
-
parameters: toPortableToolSchema(tool.inputSchema)
|
|
1002
|
+
parameters: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), GOOGLE_SCHEMA_PROFILE, tool.name)
|
|
811
1003
|
})) }];
|
|
812
1004
|
/** Google GenAI(generateContent / streamGenerateContent)客户端(零依赖 fetch) */
|
|
813
1005
|
var GoogleGenAiClient = class {
|
|
@@ -1260,22 +1452,22 @@ function createScriptContext(deps) {
|
|
|
1260
1452
|
...onWarning ? { onWarning } : {}
|
|
1261
1453
|
};
|
|
1262
1454
|
}
|
|
1263
|
-
const isRecord$
|
|
1455
|
+
const isRecord$4 = (v) => typeof v === "object" && v !== null;
|
|
1264
1456
|
/**
|
|
1265
1457
|
* $chart 约定的形状校验:JSON content 的 data 含 $chart 键且形状合法 → ChartSpec;
|
|
1266
1458
|
* 任何畸形(kind 非法 / labels 非字符串数组 / series 项缺数值 data)→ undefined(忽略不炸)。
|
|
1267
1459
|
*/
|
|
1268
1460
|
function extractChartSpec(data) {
|
|
1269
|
-
if (!isRecord$
|
|
1461
|
+
if (!isRecord$4(data)) return void 0;
|
|
1270
1462
|
const raw = data["$chart"];
|
|
1271
|
-
if (!isRecord$
|
|
1463
|
+
if (!isRecord$4(raw)) return void 0;
|
|
1272
1464
|
const { kind, labels, series } = raw;
|
|
1273
1465
|
if (kind !== "bar" && kind !== "line" && kind !== "pie") return void 0;
|
|
1274
1466
|
if (!Array.isArray(labels) || !labels.every((l) => typeof l === "string")) return void 0;
|
|
1275
1467
|
if (!Array.isArray(series)) return void 0;
|
|
1276
1468
|
const validSeries = [];
|
|
1277
1469
|
for (const item of series) {
|
|
1278
|
-
if (!isRecord$
|
|
1470
|
+
if (!isRecord$4(item) || !Array.isArray(item["data"]) || !item["data"].every((n) => typeof n === "number")) return;
|
|
1279
1471
|
const name = item["name"];
|
|
1280
1472
|
validSeries.push({
|
|
1281
1473
|
...typeof name === "string" ? { name } : {},
|
|
@@ -1293,13 +1485,18 @@ function extractChartSpec(data) {
|
|
|
1293
1485
|
/**
|
|
1294
1486
|
* 默认的结果渲染构造:run 内收集的 renderBlocks(chart 等)在前,
|
|
1295
1487
|
* LLM 最终输出 → markdown block,run.artifacts → file blocks 在后;summary 取 terminationReason。
|
|
1488
|
+
* 注入 output block 时一并记下它的下标(S7),供消费方按来源去重。
|
|
1296
1489
|
*/
|
|
1297
1490
|
function buildRenderResult(run, output, renderBlocks = []) {
|
|
1298
1491
|
const blocks = [...renderBlocks];
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1492
|
+
let outputBlockIndex;
|
|
1493
|
+
if (output.trim() !== "") {
|
|
1494
|
+
outputBlockIndex = blocks.length;
|
|
1495
|
+
blocks.push({
|
|
1496
|
+
type: "markdown",
|
|
1497
|
+
text: output
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1303
1500
|
for (const artifact of run.artifacts) blocks.push({
|
|
1304
1501
|
type: "file",
|
|
1305
1502
|
path: artifact.path,
|
|
@@ -1310,7 +1507,8 @@ function buildRenderResult(run, output, renderBlocks = []) {
|
|
|
1310
1507
|
runId: run.id,
|
|
1311
1508
|
summary: run.terminationReason,
|
|
1312
1509
|
blocks,
|
|
1313
|
-
artifacts: run.artifacts
|
|
1510
|
+
artifacts: run.artifacts,
|
|
1511
|
+
...outputBlockIndex === void 0 ? {} : { outputBlockIndex }
|
|
1314
1512
|
};
|
|
1315
1513
|
}
|
|
1316
1514
|
const MAX_SURFACE_BYTES = 256 * 1024;
|
|
@@ -1326,7 +1524,7 @@ const actionIntents = /* @__PURE__ */ new Set([
|
|
|
1326
1524
|
"download",
|
|
1327
1525
|
"refresh"
|
|
1328
1526
|
]);
|
|
1329
|
-
function isRecord$
|
|
1527
|
+
function isRecord$3(value) {
|
|
1330
1528
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1331
1529
|
}
|
|
1332
1530
|
function reject(message) {
|
|
@@ -1343,7 +1541,7 @@ function isJsonValue(value, depth = 0) {
|
|
|
1343
1541
|
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1344
1542
|
if (typeof value === "number") return Number.isFinite(value);
|
|
1345
1543
|
if (Array.isArray(value)) return value.every((item) => isJsonValue(item, depth + 1));
|
|
1346
|
-
if (!isRecord$
|
|
1544
|
+
if (!isRecord$3(value)) return false;
|
|
1347
1545
|
return Object.keys(value).every((key) => key !== "__proto__" && key !== "constructor" && isJsonValue(value[key], depth + 1));
|
|
1348
1546
|
}
|
|
1349
1547
|
/**
|
|
@@ -1353,10 +1551,10 @@ function isJsonValue(value, depth = 0) {
|
|
|
1353
1551
|
function assertNode(value, path, depth, counter) {
|
|
1354
1552
|
if (depth > MAX_NODE_DEPTH) reject(`A UI spec tree must not nest deeper than ${MAX_NODE_DEPTH} levels`);
|
|
1355
1553
|
if (++counter.nodes > MAX_NODES) reject(`A UI spec tree must contain at most ${MAX_NODES} nodes`);
|
|
1356
|
-
if (!isRecord$
|
|
1554
|
+
if (!isRecord$3(value)) reject(`${path} must be an object`);
|
|
1357
1555
|
requireString(value["component"], `${path}.component`);
|
|
1358
1556
|
if (value["id"] !== void 0) requireString(value["id"], `${path}.id`);
|
|
1359
|
-
if (value["props"] !== void 0 && (!isRecord$
|
|
1557
|
+
if (value["props"] !== void 0 && (!isRecord$3(value["props"]) || !isJsonValue(value["props"]))) reject(`${path}.props must be a JSON object`);
|
|
1360
1558
|
const children = value["children"];
|
|
1361
1559
|
if (children === void 0) return;
|
|
1362
1560
|
if (!Array.isArray(children)) reject(`${path}.children must be an array`);
|
|
@@ -1366,7 +1564,7 @@ function assertActions(value) {
|
|
|
1366
1564
|
if (value === void 0) return;
|
|
1367
1565
|
if (!Array.isArray(value) || value.length > MAX_ACTIONS) reject(`Surface actions must contain at most ${MAX_ACTIONS} items`);
|
|
1368
1566
|
for (const action of value) {
|
|
1369
|
-
if (!isRecord$
|
|
1567
|
+
if (!isRecord$3(action)) reject("A surface action must be an object");
|
|
1370
1568
|
requireString(action["id"], "Surface action ID");
|
|
1371
1569
|
const intent = action["intent"];
|
|
1372
1570
|
if (typeof intent !== "string" || !actionIntents.has(intent)) reject("Surface action intent is invalid");
|
|
@@ -1382,7 +1580,7 @@ function validateUiSpecNode(value) {
|
|
|
1382
1580
|
return structuredClone(value);
|
|
1383
1581
|
}
|
|
1384
1582
|
function assertPatch(value) {
|
|
1385
|
-
if (!isRecord$
|
|
1583
|
+
if (!isRecord$3(value)) reject("A surface patch operation must be an object");
|
|
1386
1584
|
if (value["op"] !== "replace" && value["op"] !== "merge" && value["op"] !== "append") reject("Surface patch operation is invalid");
|
|
1387
1585
|
requireString(value["path"], "Surface patch path");
|
|
1388
1586
|
if (!value["path"].startsWith("/")) reject("Surface patch path must be a JSON pointer");
|
|
@@ -1390,7 +1588,7 @@ function assertPatch(value) {
|
|
|
1390
1588
|
}
|
|
1391
1589
|
/** Validates an individual event in the framework-neutral surface stream. @experimental */
|
|
1392
1590
|
function validateUiSpecEvent(value) {
|
|
1393
|
-
if (!isRecord$
|
|
1591
|
+
if (!isRecord$3(value) || typeof value["type"] !== "string") reject("A UI surface event must have a type");
|
|
1394
1592
|
if (value["runId"] !== void 0) requireString(value["runId"], "Surface event run ID");
|
|
1395
1593
|
switch (value["type"]) {
|
|
1396
1594
|
case "open":
|
|
@@ -1448,7 +1646,7 @@ function validateUiSpecEvent(value) {
|
|
|
1448
1646
|
}
|
|
1449
1647
|
/** Extracts validated surface stream events from structured tool output. @experimental */
|
|
1450
1648
|
function extractUiSpecEvents(data) {
|
|
1451
|
-
if (!isRecord$
|
|
1649
|
+
if (!isRecord$3(data) || data["$surface"] === void 0) return [];
|
|
1452
1650
|
const raw = data["$surface"];
|
|
1453
1651
|
return (Array.isArray(raw) ? raw : [raw]).map((event) => validateUiSpecEvent(event));
|
|
1454
1652
|
}
|
|
@@ -1481,7 +1679,9 @@ function schemaToForm(schema, providedArgs) {
|
|
|
1481
1679
|
function mapFieldType(prop) {
|
|
1482
1680
|
if (Array.isArray(prop.enum)) return "select";
|
|
1483
1681
|
switch (prop.type) {
|
|
1484
|
-
case "string":
|
|
1682
|
+
case "string":
|
|
1683
|
+
if (prop["format"] === "binary" || prop["contentEncoding"] === "base64") return "file";
|
|
1684
|
+
return prop["format"] === "password" || prop["writeOnly"] === true ? "password" : "text";
|
|
1485
1685
|
case "number":
|
|
1486
1686
|
case "integer": return "number";
|
|
1487
1687
|
case "boolean": return "boolean";
|
|
@@ -1767,7 +1967,7 @@ var TraceRecorder = class {
|
|
|
1767
1967
|
return [...this.#events];
|
|
1768
1968
|
}
|
|
1769
1969
|
};
|
|
1770
|
-
const isRecord$
|
|
1970
|
+
const isRecord$2 = (v) => typeof v === "object" && v !== null;
|
|
1771
1971
|
/** 工具结果可经 `$todo` 标记记入 trace 的事件类型;其余类型不接受,防止工具源伪造 run.* */
|
|
1772
1972
|
const TODO_TRACE_TYPES = /* @__PURE__ */ new Set([
|
|
1773
1973
|
"todo.created",
|
|
@@ -1782,12 +1982,12 @@ const TODO_TRACE_TYPES = /* @__PURE__ */ new Set([
|
|
|
1782
1982
|
* @experimental
|
|
1783
1983
|
*/
|
|
1784
1984
|
function extractTodoTraceEvents(data) {
|
|
1785
|
-
if (!isRecord$
|
|
1985
|
+
if (!isRecord$2(data) || data["$todo"] === void 0) return [];
|
|
1786
1986
|
const raw = data["$todo"];
|
|
1787
1987
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
1788
1988
|
const events = [];
|
|
1789
1989
|
for (const entry of entries) {
|
|
1790
|
-
if (!isRecord$
|
|
1990
|
+
if (!isRecord$2(entry)) continue;
|
|
1791
1991
|
const { type, ...rest } = entry;
|
|
1792
1992
|
if (typeof type !== "string" || !TODO_TRACE_TYPES.has(type)) continue;
|
|
1793
1993
|
events.push({
|
|
@@ -1797,7 +1997,39 @@ function extractTodoTraceEvents(data) {
|
|
|
1797
1997
|
}
|
|
1798
1998
|
return events;
|
|
1799
1999
|
}
|
|
1800
|
-
const
|
|
2000
|
+
const isRecord$1 = (v) => typeof v === "object" && v !== null;
|
|
2001
|
+
/**
|
|
2002
|
+
* `$skillCandidate` 约定的形状校验:JSON content 的 data 含合法 `$skillCandidate` 键 → 候选载荷。
|
|
2003
|
+
*
|
|
2004
|
+
* 与 `$chart` / `$todo` / `$surface` 同一条既有通道,是第四个。
|
|
2005
|
+
* 候选的生成与存储全部在 `@webskill/agent`,runtime 只认这个形状,畸形忽略不炸。
|
|
2006
|
+
* @experimental
|
|
2007
|
+
*/
|
|
2008
|
+
function extractSkillCandidate(data) {
|
|
2009
|
+
if (!isRecord$1(data)) return void 0;
|
|
2010
|
+
const raw = data["$skillCandidate"];
|
|
2011
|
+
if (!isRecord$1(raw)) return void 0;
|
|
2012
|
+
const { id, name } = raw;
|
|
2013
|
+
if (typeof id !== "string" || id === "" || typeof name !== "string" || name === "") return void 0;
|
|
2014
|
+
return {
|
|
2015
|
+
id,
|
|
2016
|
+
name
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Agent loop 的运行上限默认值。**全仓唯一来源**(S8)。
|
|
2021
|
+
*
|
|
2022
|
+
* 在 0.7.0 之前这三个数字在 `AgentLoop` 的构造与 ui-kit 的 `defaultRuntimeConfig()`
|
|
2023
|
+
* 里各写了一遍。两份值今天恰好相等,所以不出事;一旦其中一处被改,
|
|
2024
|
+
* 「恢复默认值」会恢复到 agent loop 根本不用的值——验收绿、行为错。
|
|
2025
|
+
* @stable
|
|
2026
|
+
*/
|
|
2027
|
+
const DEFAULT_LOOP_LIMITS = {
|
|
2028
|
+
maxTurns: 10,
|
|
2029
|
+
totalTimeoutMs: 12e4,
|
|
2030
|
+
toolTimeoutMs: 3e4
|
|
2031
|
+
};
|
|
2032
|
+
const RUN_SNAPSHOT_SCHEMA_VERSION = 3;
|
|
1801
2033
|
/** @experimental */
|
|
1802
2034
|
function isUnsupportedRunSnapshot(entry) {
|
|
1803
2035
|
return entry.unsupported === true;
|
|
@@ -1842,7 +2074,7 @@ var FsRunSnapshotStore = class {
|
|
|
1842
2074
|
throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${runId}" has an unexpected shape and was deleted`);
|
|
1843
2075
|
}
|
|
1844
2076
|
const schemaVersion = typeof snapshot.schemaVersion === "number" ? snapshot.schemaVersion : 0;
|
|
1845
|
-
if (schemaVersion !==
|
|
2077
|
+
if (schemaVersion !== 3) throw new WebSkillError("RUN_SNAPSHOT_SCHEMA_UNSUPPORTED", `Snapshot for run "${runId}" uses schema version ${schemaVersion}; this runtime reads version 3. The file was kept for read-only inspection.`);
|
|
1846
2078
|
if (snapshot.runId !== runId) {
|
|
1847
2079
|
await this.#fs.remove(path).catch(() => void 0);
|
|
1848
2080
|
throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${runId}" has an unexpected shape and was deleted`);
|
|
@@ -1862,7 +2094,7 @@ var FsRunSnapshotStore = class {
|
|
|
1862
2094
|
try {
|
|
1863
2095
|
const parsed = JSON.parse(await this.#fs.readText(entry.path));
|
|
1864
2096
|
const schemaVersion = typeof parsed.schemaVersion === "number" ? parsed.schemaVersion : 0;
|
|
1865
|
-
if (schemaVersion ===
|
|
2097
|
+
if (schemaVersion === 3) {
|
|
1866
2098
|
out.push(parsed);
|
|
1867
2099
|
continue;
|
|
1868
2100
|
}
|
|
@@ -1975,6 +2207,45 @@ const redactFileValue = (value) => {
|
|
|
1975
2207
|
return rest;
|
|
1976
2208
|
};
|
|
1977
2209
|
/**
|
|
2210
|
+
* 密码字段名集合(FR-23.7)。
|
|
2211
|
+
*
|
|
2212
|
+
* 既有的三处脱敏各有各的判别口径(看 request.type、看值是否标量、看值里有没有 data/mimeType),
|
|
2213
|
+
* 没有一处按**字段类型**判。行为记录那处尤其危险:它的安全假设是「标量是安全的」,
|
|
2214
|
+
* 而密码恰好是字符串标量——假设失效了。
|
|
2215
|
+
*/
|
|
2216
|
+
const passwordFieldNames = (request) => new Set(request.type === "form" ? request.fields.filter((f) => f.type === "password").map((f) => f.name) : []);
|
|
2217
|
+
/** 去掉密码字段的值;不是掩码而是**不存**,掩码存下来仍然泄露长度与存在性 */
|
|
2218
|
+
const withoutPasswords = (value, passwords) => {
|
|
2219
|
+
if (passwords.size === 0 || typeof value !== "object" || value === null || Array.isArray(value)) return value;
|
|
2220
|
+
const out = {};
|
|
2221
|
+
for (const [key, entry] of Object.entries(value)) if (!passwords.has(key)) out[key] = entry;
|
|
2222
|
+
return out;
|
|
2223
|
+
};
|
|
2224
|
+
/**
|
|
2225
|
+
* 触顶失败的结构化细节(FR-21.2)。由终止原因推出上限字段,而不是在每个终止点各写一遍——
|
|
2226
|
+
* 终止点有四个,写四遍就迟早有一处对不上。
|
|
2227
|
+
*/
|
|
2228
|
+
const limitDetails = (state, reason) => {
|
|
2229
|
+
if (reason === "timeout") return {
|
|
2230
|
+
limit: "totalTimeoutMs",
|
|
2231
|
+
value: state.totalTimeoutMs
|
|
2232
|
+
};
|
|
2233
|
+
if (reason === "max-turns") return {
|
|
2234
|
+
limit: "maxTurns",
|
|
2235
|
+
value: state.maxTurns
|
|
2236
|
+
};
|
|
2237
|
+
};
|
|
2238
|
+
/** 补齐消息里带的错误码:能说清「为什么没结果」的,就不要退化成 RUN_INTERRUPTED(设计 11 §2.5) */
|
|
2239
|
+
const sealCodeFor = (reason, errorCode) => {
|
|
2240
|
+
switch (reason) {
|
|
2241
|
+
case "interaction-timeout": return "RUN_INTERACTION_TIMEOUT";
|
|
2242
|
+
case "user-cancelled": return "RUN_CANCELLED";
|
|
2243
|
+
case "timeout": return "RUN_TIMEOUT";
|
|
2244
|
+
case "tool-resolution-exhausted": return "TOOL_RESOLUTION_EXHAUSTED";
|
|
2245
|
+
default: return errorCode ?? "RUN_INTERRUPTED";
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
/**
|
|
1978
2249
|
* 多轮 Agent 循环。
|
|
1979
2250
|
* 工具失败一律转为 ToolResult{ok:false} 回喂 LLM 继续循环;
|
|
1980
2251
|
* 缺参/确认/提问经 UiBridge 行内 await 暂停恢复;取消/超时/LLM 异常/护栏超限才终止 run。
|
|
@@ -1993,9 +2264,9 @@ var AgentLoop = class {
|
|
|
1993
2264
|
artifactStore: deps.artifactStore ?? new MemoryArtifactStore()
|
|
1994
2265
|
};
|
|
1995
2266
|
this.#config = {
|
|
1996
|
-
maxTurns: config.maxTurns ??
|
|
1997
|
-
totalTimeoutMs: config.totalTimeoutMs ??
|
|
1998
|
-
toolTimeoutMs: config.toolTimeoutMs ??
|
|
2267
|
+
maxTurns: config.maxTurns ?? DEFAULT_LOOP_LIMITS.maxTurns,
|
|
2268
|
+
totalTimeoutMs: config.totalTimeoutMs ?? DEFAULT_LOOP_LIMITS.totalTimeoutMs,
|
|
2269
|
+
toolTimeoutMs: config.toolTimeoutMs ?? DEFAULT_LOOP_LIMITS.toolTimeoutMs,
|
|
1999
2270
|
toolResultMaxBytes: config.toolResultMaxBytes ?? 1e5,
|
|
2000
2271
|
paramHistoryLimit: config.paramHistoryLimit ?? 50,
|
|
2001
2272
|
toolCallingDisabled: config.toolCallingDisabled ?? false,
|
|
@@ -2210,6 +2481,7 @@ var AgentLoop = class {
|
|
|
2210
2481
|
return finish("failed", "llm-error", messageOf(e), code);
|
|
2211
2482
|
}
|
|
2212
2483
|
const responseText = partsToText(response.content);
|
|
2484
|
+
if (!llm.stream && responseText !== "") await this.#deps.uiBridge?.onTextDelta?.(state.runId, responseText);
|
|
2213
2485
|
trace.record("llm.response", { data: {
|
|
2214
2486
|
turn,
|
|
2215
2487
|
hasToolCalls: Boolean(response.toolCalls?.length),
|
|
@@ -2279,7 +2551,8 @@ var AgentLoop = class {
|
|
|
2279
2551
|
message: output,
|
|
2280
2552
|
data: {
|
|
2281
2553
|
reason,
|
|
2282
|
-
...errorCode ? { code: errorCode } : {}
|
|
2554
|
+
...errorCode ? { code: errorCode } : {},
|
|
2555
|
+
...limitDetails(state, reason) ?? {}
|
|
2283
2556
|
}
|
|
2284
2557
|
});
|
|
2285
2558
|
const bridge = this.#deps.uiBridge;
|
|
@@ -2303,6 +2576,7 @@ var AgentLoop = class {
|
|
|
2303
2576
|
} catch (e) {
|
|
2304
2577
|
trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf(e)}` });
|
|
2305
2578
|
}
|
|
2579
|
+
await this.#sealToolCalls(state, reason, errorCode);
|
|
2306
2580
|
run.trace = trace.list();
|
|
2307
2581
|
return {
|
|
2308
2582
|
output,
|
|
@@ -2310,12 +2584,45 @@ var AgentLoop = class {
|
|
|
2310
2584
|
messages: state.messages.map((m) => ({ ...m }))
|
|
2311
2585
|
};
|
|
2312
2586
|
}
|
|
2587
|
+
/**
|
|
2588
|
+
* S1 出口守卫(FR-11.1):中断留下的未应答工具调用在这里补齐。
|
|
2589
|
+
*
|
|
2590
|
+
* 放在 `#finish` 而不是四个早退点:它是唯一的汇流出口,写四遍就迟早漏一处。
|
|
2591
|
+
* 补齐产物只进 `LlmMessage[]`(下一轮请求装配用),不进 chatbot 的消息流(AC-11.2)。
|
|
2592
|
+
*/
|
|
2593
|
+
async #sealToolCalls(state, reason, errorCode) {
|
|
2594
|
+
const pending = findUnpairedToolCalls(state.messages);
|
|
2595
|
+
if (pending.length === 0) return;
|
|
2596
|
+
const code = sealCodeFor(reason, errorCode);
|
|
2597
|
+
for (const call of pending) {
|
|
2598
|
+
const result = interruptedToolResult(call, {
|
|
2599
|
+
code,
|
|
2600
|
+
reason
|
|
2601
|
+
});
|
|
2602
|
+
state.messages.push({
|
|
2603
|
+
role: "tool",
|
|
2604
|
+
toolCallId: call.id,
|
|
2605
|
+
content: await this.#toolResultParts(call, result, state)
|
|
2606
|
+
});
|
|
2607
|
+
state.trace.record("run.warning", {
|
|
2608
|
+
message: `Sealed unanswered tool call "${call.name}": the run ended before it produced a result.`,
|
|
2609
|
+
data: {
|
|
2610
|
+
kind: "tool-call-sealed",
|
|
2611
|
+
callId: call.id,
|
|
2612
|
+
toolName: call.name,
|
|
2613
|
+
side: "write",
|
|
2614
|
+
reason,
|
|
2615
|
+
code
|
|
2616
|
+
}
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2313
2620
|
/** D3 快照写入(含 pendingInteraction 与过期时间;写失败降级 run.warning) */
|
|
2314
2621
|
async #saveSnapshot(state, pending) {
|
|
2315
2622
|
const store = this.#deps.snapshotStore;
|
|
2316
2623
|
if (!store) return;
|
|
2317
2624
|
const snapshot = {
|
|
2318
|
-
schemaVersion:
|
|
2625
|
+
schemaVersion: 3,
|
|
2319
2626
|
runId: state.runId,
|
|
2320
2627
|
sessionId: state.run.sessionId,
|
|
2321
2628
|
userPrompt: state.run.userPrompt,
|
|
@@ -2490,6 +2797,15 @@ var AgentLoop = class {
|
|
|
2490
2797
|
throw e;
|
|
2491
2798
|
}
|
|
2492
2799
|
}
|
|
2800
|
+
/**
|
|
2801
|
+
* 工具的技能归属。内置工具与外部工具没有归属,返回 `undefined`——
|
|
2802
|
+
* 拿 `activated[0]` 顶替会把内置工具的失败栽给一个无关技能,比不归因更糟(设计 26 §2.3)。
|
|
2803
|
+
*/
|
|
2804
|
+
#skillOf(call, state) {
|
|
2805
|
+
if (call.name === "read_skill_file" || call.name === "ask_user") return void 0;
|
|
2806
|
+
const resolution = resolveToolName(call.name, state.activated, state.activatedTools.keys());
|
|
2807
|
+
return resolution.kind === "script" ? resolution.skillName : void 0;
|
|
2808
|
+
}
|
|
2493
2809
|
/** 最后一条 assistant 消息里第一个尚无 tool 响应的工具调用(即中断时正在处理的那个) */
|
|
2494
2810
|
#findPendingToolCall(messages) {
|
|
2495
2811
|
const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant" && m.toolCalls?.length);
|
|
@@ -2680,11 +2996,14 @@ var AgentLoop = class {
|
|
|
2680
2996
|
}
|
|
2681
2997
|
async #executeCall(call, state) {
|
|
2682
2998
|
const argsSummary = summarizeArgs(call.arguments);
|
|
2999
|
+
const owner = this.#skillOf(call, state);
|
|
3000
|
+
const attribution = owner === void 0 ? {} : { skillName: owner };
|
|
2683
3001
|
const callStartMs = Date.parse(state.now());
|
|
2684
3002
|
state.trace.record("tool.started", { data: {
|
|
2685
3003
|
name: call.name,
|
|
2686
3004
|
callId: call.id,
|
|
2687
|
-
args: argsSummary
|
|
3005
|
+
args: argsSummary,
|
|
3006
|
+
...attribution
|
|
2688
3007
|
} });
|
|
2689
3008
|
this.#emitTool(state, "started", call);
|
|
2690
3009
|
let result;
|
|
@@ -2710,7 +3029,8 @@ var AgentLoop = class {
|
|
|
2710
3029
|
name: call.name,
|
|
2711
3030
|
callId: call.id,
|
|
2712
3031
|
args: argsSummary,
|
|
2713
|
-
durationMs
|
|
3032
|
+
durationMs,
|
|
3033
|
+
...attribution
|
|
2714
3034
|
} });
|
|
2715
3035
|
this.#emitTool(state, "completed", call);
|
|
2716
3036
|
for (const item of result.content) {
|
|
@@ -2721,6 +3041,12 @@ var AgentLoop = class {
|
|
|
2721
3041
|
chart
|
|
2722
3042
|
});
|
|
2723
3043
|
for (const todo of extractTodoTraceEvents(item.data)) state.trace.record(todo.type, { data: todo.data });
|
|
3044
|
+
const candidate = extractSkillCandidate(item.data);
|
|
3045
|
+
if (candidate) try {
|
|
3046
|
+
await this.#deps.uiBridge?.onSkillCandidate?.(state.runId, candidate);
|
|
3047
|
+
} catch (e) {
|
|
3048
|
+
state.trace.record("run.warning", { message: `Skill candidate was not handed off to the UI: ${messageOf(e)}` });
|
|
3049
|
+
}
|
|
2724
3050
|
try {
|
|
2725
3051
|
for (const event of extractUiSpecEvents(item.data)) await this.#renderSurface(state, event);
|
|
2726
3052
|
} catch (e) {
|
|
@@ -2738,7 +3064,8 @@ var AgentLoop = class {
|
|
|
2738
3064
|
callId: call.id,
|
|
2739
3065
|
code: result.error?.code,
|
|
2740
3066
|
args: argsSummary,
|
|
2741
|
-
durationMs
|
|
3067
|
+
durationMs,
|
|
3068
|
+
...attribution
|
|
2742
3069
|
}
|
|
2743
3070
|
});
|
|
2744
3071
|
this.#emitTool(state, "failed", call, result.error?.code);
|
|
@@ -2905,10 +3232,17 @@ var AgentLoop = class {
|
|
|
2905
3232
|
async #replaySurfaceEvents(state) {
|
|
2906
3233
|
const bridge = this.#deps.uiBridge;
|
|
2907
3234
|
if (!bridge?.renderSurface || state.surfaceEvents.length === 0) return;
|
|
2908
|
-
try {
|
|
2909
|
-
|
|
3235
|
+
for (const event of state.surfaceEvents) try {
|
|
3236
|
+
await bridge.renderSurface(structuredClone(event));
|
|
2910
3237
|
} catch (e) {
|
|
2911
|
-
state.trace.record("
|
|
3238
|
+
state.trace.record("ui.degraded", {
|
|
3239
|
+
message: `Failed to replay UI surface "${event.id}": ${messageOf(e)}`,
|
|
3240
|
+
data: {
|
|
3241
|
+
kind: "replay-failed",
|
|
3242
|
+
surfaceId: event.id,
|
|
3243
|
+
eventType: event.type
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
2912
3246
|
}
|
|
2913
3247
|
}
|
|
2914
3248
|
/**
|
|
@@ -3332,7 +3666,8 @@ var AgentLoop = class {
|
|
|
3332
3666
|
if (fresh.length > 0) result.artifacts = [...result.artifacts ?? [], ...fresh];
|
|
3333
3667
|
}
|
|
3334
3668
|
await this.#bumpSkillStat(skillName, result.ok ? "successes" : "failures", state);
|
|
3335
|
-
if (
|
|
3669
|
+
if (result.ok) await this.#reportSkillSuccess(skillName, state);
|
|
3670
|
+
else await this.#reportSkillFailure(skillName, state, {
|
|
3336
3671
|
code: result.error?.code ?? "TOOL_EXECUTION_FAILED",
|
|
3337
3672
|
message: result.error?.message ?? `Tool "${call.name}" returned a failure result`
|
|
3338
3673
|
});
|
|
@@ -3369,6 +3704,22 @@ var AgentLoop = class {
|
|
|
3369
3704
|
state.trace.record("run.warning", { message: `Skill failure report for "${skillName}" was not recorded: ${messageOf(e)}` });
|
|
3370
3705
|
}
|
|
3371
3706
|
}
|
|
3707
|
+
/**
|
|
3708
|
+
* D-5 技能成功上报:把失败计数从「累计」变成「连续」的那半边信号。
|
|
3709
|
+
* 与失败上报同样是可选 port,未注入即整段跳过。
|
|
3710
|
+
*/
|
|
3711
|
+
async #reportSkillSuccess(skillName, state) {
|
|
3712
|
+
const report = this.#deps.skillOutcomeReporter?.onSkillSucceeded;
|
|
3713
|
+
if (report === void 0) return;
|
|
3714
|
+
try {
|
|
3715
|
+
await report.call(this.#deps.skillOutcomeReporter, {
|
|
3716
|
+
skillName,
|
|
3717
|
+
runId: state.runId
|
|
3718
|
+
});
|
|
3719
|
+
} catch (e) {
|
|
3720
|
+
state.trace.record("run.warning", { message: `Skill success report for "${skillName}" was not recorded: ${messageOf(e)}` });
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3372
3723
|
/** context.confirm 触发点:默认真实询问;auto-approve 直通;无 bridge 降级直通 + warning */
|
|
3373
3724
|
async #confirm(message, state) {
|
|
3374
3725
|
if (this.#policy.confirmations === "auto-approve") return true;
|
|
@@ -3455,7 +3806,8 @@ var AgentLoop = class {
|
|
|
3455
3806
|
if (!this.#deps.memory) return;
|
|
3456
3807
|
const scope = `session:${state.run.sessionId}`;
|
|
3457
3808
|
const limit = this.#config.paramHistoryLimit;
|
|
3458
|
-
const
|
|
3809
|
+
const passwords = passwordFieldNames(request);
|
|
3810
|
+
const recorded = request.type === "file-pick" ? redactFileValue(value) : withoutPasswords(value, passwords);
|
|
3459
3811
|
await this.#memoryMutate(scope, "paramHistory", state, (current) => {
|
|
3460
3812
|
const history = current ?? [];
|
|
3461
3813
|
history.push({
|
|
@@ -3522,6 +3874,7 @@ var AgentLoop = class {
|
|
|
3522
3874
|
else if (request.type === "form" && typeof value === "object" && value !== null) {
|
|
3523
3875
|
const submitted = value;
|
|
3524
3876
|
for (const field of request.fields) {
|
|
3877
|
+
if (field.type === "password") continue;
|
|
3525
3878
|
const next = submitted[field.name];
|
|
3526
3879
|
if (typeof next !== "string" && typeof next !== "number" && typeof next !== "boolean") continue;
|
|
3527
3880
|
if (next === "") continue;
|
|
@@ -3675,6 +4028,7 @@ var WebSkillRuntime = class {
|
|
|
3675
4028
|
if (!this.#catalogCache) await this.discover();
|
|
3676
4029
|
const cache = this.#catalogCache;
|
|
3677
4030
|
if (!cache) throw new Error("discover() did not populate the catalog cache");
|
|
4031
|
+
const sealedHistory = options.history ? sealToolCallPairs(options.history) : void 0;
|
|
3678
4032
|
const providerFailures = [];
|
|
3679
4033
|
const providerEntries = (await Promise.all((this.#deps.skillProviders ?? []).map(async (p) => {
|
|
3680
4034
|
try {
|
|
@@ -3721,11 +4075,25 @@ var WebSkillRuntime = class {
|
|
|
3721
4075
|
userPrompt,
|
|
3722
4076
|
route,
|
|
3723
4077
|
runId,
|
|
3724
|
-
...
|
|
4078
|
+
...sealedHistory ? { history: sealedHistory.messages } : {}
|
|
3725
4079
|
});
|
|
3726
4080
|
} finally {
|
|
3727
4081
|
this.#loops.delete(runId);
|
|
3728
4082
|
}
|
|
4083
|
+
for (const record of sealedHistory?.sealed ?? []) result.run.trace.push({
|
|
4084
|
+
id: `evt-seal-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
4085
|
+
runId: result.run.id,
|
|
4086
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4087
|
+
type: "run.warning",
|
|
4088
|
+
message: `Sealed unanswered tool call "${record.toolName}" carried over from an earlier interrupted run.`,
|
|
4089
|
+
data: {
|
|
4090
|
+
kind: "tool-call-sealed",
|
|
4091
|
+
callId: record.callId,
|
|
4092
|
+
toolName: record.toolName,
|
|
4093
|
+
side: "read",
|
|
4094
|
+
code: record.code
|
|
4095
|
+
}
|
|
4096
|
+
});
|
|
3729
4097
|
for (const failure of providerFailures) result.run.trace.push({
|
|
3730
4098
|
id: `evt-provider-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
3731
4099
|
runId: result.run.id,
|
|
@@ -4221,7 +4589,6 @@ function parseUserProfileExport(raw) {
|
|
|
4221
4589
|
entries
|
|
4222
4590
|
};
|
|
4223
4591
|
}
|
|
4224
|
-
/** 导入前给用户看的差异(FR-19.7):新增哪些、覆盖哪些 @experimental */
|
|
4225
4592
|
function diffUserProfile(current, incoming) {
|
|
4226
4593
|
const byId = new Map(current.entries.map((entry) => [entry.id, entry]));
|
|
4227
4594
|
const added = [];
|
|
@@ -4783,6 +5150,7 @@ const toMeta = (record) => ({
|
|
|
4783
5150
|
...record.title !== void 0 ? { title: record.title } : {},
|
|
4784
5151
|
...record.titleLocked === true ? { titleLocked: true } : {},
|
|
4785
5152
|
...record.archived === true ? { archived: true } : {},
|
|
5153
|
+
...record.modelId !== void 0 ? { modelId: record.modelId } : {},
|
|
4786
5154
|
messageCount: record.messages.length
|
|
4787
5155
|
});
|
|
4788
5156
|
const newSessionId = () => `session-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -4829,6 +5197,7 @@ function parseSessionFile(raw, path) {
|
|
|
4829
5197
|
...typeof file.title === "string" ? { title: file.title } : {},
|
|
4830
5198
|
...file.titleLocked === true ? { titleLocked: true } : {},
|
|
4831
5199
|
...file.archived === true ? { archived: true } : {},
|
|
5200
|
+
...typeof file.modelId === "string" ? { modelId: file.modelId } : {},
|
|
4832
5201
|
messages: Array.isArray(file.messages) ? file.messages : [],
|
|
4833
5202
|
messageCount: Array.isArray(file.messages) ? file.messages.length : 0
|
|
4834
5203
|
};
|
|
@@ -4870,6 +5239,7 @@ var FsSessionStore = class {
|
|
|
4870
5239
|
...record.title !== void 0 ? { title: record.title } : {},
|
|
4871
5240
|
...record.titleLocked === true ? { titleLocked: true } : {},
|
|
4872
5241
|
...record.archived === true ? { archived: true } : {},
|
|
5242
|
+
...record.modelId !== void 0 ? { modelId: record.modelId } : {},
|
|
4873
5243
|
messages: record.messages
|
|
4874
5244
|
};
|
|
4875
5245
|
await this.#fs.writeText(this.#path(record.id), JSON.stringify(file, null, 2));
|
|
@@ -4963,6 +5333,18 @@ var FsSessionStore = class {
|
|
|
4963
5333
|
await this.#write(record);
|
|
4964
5334
|
});
|
|
4965
5335
|
}
|
|
5336
|
+
async setModel(id, modelId) {
|
|
5337
|
+
await this.#serialize(id, async () => {
|
|
5338
|
+
const record = await this.#require(id);
|
|
5339
|
+
if (modelId === void 0) delete record.modelId;
|
|
5340
|
+
else record.modelId = modelId;
|
|
5341
|
+
await this.#write(record);
|
|
5342
|
+
});
|
|
5343
|
+
}
|
|
5344
|
+
async getMeta(id) {
|
|
5345
|
+
const record = await this.get(id);
|
|
5346
|
+
return record === void 0 ? void 0 : toMeta(record);
|
|
5347
|
+
}
|
|
4966
5348
|
async delete(id) {
|
|
4967
5349
|
await this.#serialize(id, async () => {
|
|
4968
5350
|
const path = this.#path(id);
|
|
@@ -4972,4 +5354,4 @@ var FsSessionStore = class {
|
|
|
4972
5354
|
};
|
|
4973
5355
|
|
|
4974
5356
|
//#endregion
|
|
4975
|
-
export {
|
|
5357
|
+
export { fromVercelStreamPart as $, SerializingMemoryStore as A, bridgeError as B, ProgressiveRouter as C, sealToolCallPairs as Ct, RUN_SNAPSHOT_SCHEMA_VERSION as D, toVercelToolSpecs as Dt, READ_SKILL_FILE_TOOL_NAME as E, toRecordDigests as Et, USER_PROFILE_PROMPT_HEADER as F, exportUserProfile as G, createScriptContext as H, USER_PROFILE_REFINE_PROMPT as I, extractTodoTraceEvents as J, extractChartSpec as K, WebSkillRuntime as L, USER_PROFILE_EXPORT_VERSION as M, USER_PROFILE_KEY as N, RUN_TRACE_SCHEMA_VERSION as O, validateUiSpecEvent as Ot, USER_PROFILE_NO_INVENTION_RULE as P, fromVercelResult as Q, appendBehaviorRecords as R, OpenAiCompatibleClient as S, scriptToolName as St, READ_SKILL_FILE_TOOL as T, toLlmToolSpec as Tt, createWebSkillApi as U, buildRenderResult as V, diffUserProfile as W, findUnpairedToolCalls as X, extractUiSpecEvents as Y, formatSkillScriptManifest as Z, FsRunTraceStore as _, renderUserProfileContext as _t, AgentLoop as a, mergeProfileEntries as at, GoogleGenAiClient as b, schemaSourceLabel as bt, CapabilityApproval as c, normalizeErrorCode as ct, EMPTY_USER_PROFILE as d, parseBridgeRequest as dt, interruptedToolResult as et, EventBus as f, parseUserProfileExport as ft, FsRunSnapshotStore as g, refineUserProfile as gt, FsMemoryStore as h, readUserProfile as ht, ASK_USER_TOOL_NAME as i, mergeCatalogEntries as it, TraceRecorder as j, SESSION_SCHEMA_VERSION as k, validateUiSpecNode as kt, DEFAULT_LOOP_LIMITS as l, normalizeToolContent as lt, FsArtifactStore as m, readProfileEntries as mt, ASK_USER_INPUT_SCHEMA as n, isUnsupportedRunSnapshot as nt, AnthropicClient as o, networkPolicyLibSource as ot, FS_SESSION_PAGE_SIZE as p, readBehaviorRecords as pt, extractSkillCandidate as q, ASK_USER_TOOL as r, listSkillScripts as rt, BEHAVIOR_RECORDS_KEY as s, networkUrlHost as st, ALLOWED_TOOLS_EXCLUSION_REASON as t, isNetworkAllowed as tt, DEFAULT_USER_PROFILE_LIMITS as u, normalizeToolError as ut, FsSessionStore as v, resolveToolName as vt, READ_SKILL_FILE_INPUT_SCHEMA as w, summarizeToolCalls as wt, HookRunner as x, schemaToForm as xt, FullDisclosureRouter as y, sampleBehaviorRecords as yt, applyUserProfileImport as z };
|