@webskill/sdk 0.2.6 → 0.2.8
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/browser.d.ts +13 -4
- package/dist/browser.js +16 -6
- package/dist/catalogComponents-C_V39rbF-BOHveMWa.js +120303 -0
- package/dist/{dist-BXpDDZpR.js → dist-B9VLwOME.js} +464 -29
- package/dist/{dist-DNSG9FqC.js → dist-CtBLBbEz.js} +1186 -123
- package/dist/dist-DxyxmXPU-DGC-dpXf.js +5621 -0
- package/dist/echarts-DhNm2ene.js +78816 -0
- package/dist/governance.d.ts +29 -196
- package/dist/governance.js +38 -199
- package/dist/index-7DVaZJU7.d.ts +597 -0
- package/dist/{index-DJOha4b6.d.ts → index-QrHtAudz.d.ts} +37 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/jsonRenderRegistry-9GrWP_hE-CQY8bT9w.js +2468 -0
- package/dist/mcp.d.ts +5 -3
- package/dist/mcp.js +9 -4
- package/dist/node.d.ts +49 -4
- package/dist/node.js +2064 -4
- package/dist/openUiLibrary-B8-Cvou9-D3RsU2EB.js +3785 -0
- package/dist/rolldown-runtime-BOF7iYI8.js +55 -0
- package/dist/sandboxWorkerEntry.js +12 -3
- package/dist/{index-DRlYzdr2.d.ts → skillVersionStore-B7rGjtMi-BgnQho9v.d.ts} +128 -3
- package/dist/testing.d.ts +1 -1
- package/dist/{types-CKm5G_eQ-krKWW8WV.d.ts → types-AmKCKJn_-BogJPQHU.d.ts} +129 -2
- package/dist/ui-react.d.ts +138 -2
- package/dist/ui-react.js +6935 -3
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +1 -1
- package/dist/ui.d.ts +3 -203
- package/dist/ui.js +3 -3
- package/dist/webskillLitCatalog-CNaUpasU-CfSRvqCZ.js +1601 -0
- package/package.json +2 -1
- package/dist/dist-DWFDb1Ww.js +0 -1896
|
@@ -125,13 +125,17 @@ var OpenAiCompatibleClient = class {
|
|
|
125
125
|
type: "tool-calls",
|
|
126
126
|
toolCalls: [...toolCallsByIndex.entries()].sort(([a], [b]) => a - b).map(([index, acc]) => {
|
|
127
127
|
let args = {};
|
|
128
|
+
let parseError;
|
|
128
129
|
try {
|
|
129
130
|
args = JSON.parse(acc.arguments || "{}");
|
|
130
|
-
} catch {
|
|
131
|
+
} catch (e) {
|
|
132
|
+
parseError = e instanceof Error ? e.message : String(e);
|
|
133
|
+
}
|
|
131
134
|
return {
|
|
132
135
|
id: acc.id || `call-${index}`,
|
|
133
136
|
name: acc.name,
|
|
134
|
-
arguments: args
|
|
137
|
+
arguments: args,
|
|
138
|
+
...parseError ? { argumentsParseError: parseError } : {}
|
|
135
139
|
};
|
|
136
140
|
})
|
|
137
141
|
};
|
|
@@ -358,13 +362,17 @@ var AnthropicClient = class {
|
|
|
358
362
|
type: "tool-calls",
|
|
359
363
|
toolCalls: [...toolCallsByIndex.entries()].sort(([a], [b]) => a - b).map(([index, acc]) => {
|
|
360
364
|
let args = {};
|
|
365
|
+
let parseError;
|
|
361
366
|
try {
|
|
362
367
|
args = JSON.parse(acc.arguments || "{}");
|
|
363
|
-
} catch {
|
|
368
|
+
} catch (e) {
|
|
369
|
+
parseError = e instanceof Error ? e.message : String(e);
|
|
370
|
+
}
|
|
364
371
|
return {
|
|
365
372
|
id: acc.id || `call-${index}`,
|
|
366
373
|
name: acc.name,
|
|
367
|
-
arguments: args
|
|
374
|
+
arguments: args,
|
|
375
|
+
...parseError ? { argumentsParseError: parseError } : {}
|
|
368
376
|
};
|
|
369
377
|
})
|
|
370
378
|
};
|
|
@@ -875,22 +883,22 @@ function createScriptContext(deps) {
|
|
|
875
883
|
...onWarning ? { onWarning } : {}
|
|
876
884
|
};
|
|
877
885
|
}
|
|
878
|
-
const isRecord$
|
|
886
|
+
const isRecord$2 = (v) => typeof v === "object" && v !== null;
|
|
879
887
|
/**
|
|
880
888
|
* $chart 约定的形状校验:JSON content 的 data 含 $chart 键且形状合法 → ChartSpec;
|
|
881
889
|
* 任何畸形(kind 非法 / labels 非字符串数组 / series 项缺数值 data)→ undefined(忽略不炸)。
|
|
882
890
|
*/
|
|
883
891
|
function extractChartSpec(data) {
|
|
884
|
-
if (!isRecord$
|
|
892
|
+
if (!isRecord$2(data)) return void 0;
|
|
885
893
|
const raw = data["$chart"];
|
|
886
|
-
if (!isRecord$
|
|
894
|
+
if (!isRecord$2(raw)) return void 0;
|
|
887
895
|
const { kind, labels, series } = raw;
|
|
888
896
|
if (kind !== "bar" && kind !== "line" && kind !== "pie") return void 0;
|
|
889
897
|
if (!Array.isArray(labels) || !labels.every((l) => typeof l === "string")) return void 0;
|
|
890
898
|
if (!Array.isArray(series)) return void 0;
|
|
891
899
|
const validSeries = [];
|
|
892
900
|
for (const item of series) {
|
|
893
|
-
if (!isRecord$
|
|
901
|
+
if (!isRecord$2(item) || !Array.isArray(item["data"]) || !item["data"].every((n) => typeof n === "number")) return;
|
|
894
902
|
const name = item["name"];
|
|
895
903
|
validSeries.push({
|
|
896
904
|
...typeof name === "string" ? { name } : {},
|
|
@@ -928,6 +936,212 @@ function buildRenderResult(run, output, renderBlocks = []) {
|
|
|
928
936
|
artifacts: run.artifacts
|
|
929
937
|
};
|
|
930
938
|
}
|
|
939
|
+
const MAX_SURFACE_BYTES = 256 * 1024;
|
|
940
|
+
const MAX_ACTIONS = 32;
|
|
941
|
+
const MAX_FORM_FIELDS = 64;
|
|
942
|
+
const MAX_OPTIONS = 256;
|
|
943
|
+
const MAX_TABLE_COLUMNS = 128;
|
|
944
|
+
const MAX_TABLE_ROWS = 1e4;
|
|
945
|
+
const MAX_CHART_POINTS = 2e4;
|
|
946
|
+
const MAX_JSON_DEPTH = 16;
|
|
947
|
+
const MAX_PATCH_OPERATIONS = 128;
|
|
948
|
+
const actionIntents = /* @__PURE__ */ new Set([
|
|
949
|
+
"submit",
|
|
950
|
+
"cancel",
|
|
951
|
+
"select",
|
|
952
|
+
"download",
|
|
953
|
+
"refresh"
|
|
954
|
+
]);
|
|
955
|
+
const fieldTypes = /* @__PURE__ */ new Set([
|
|
956
|
+
"text",
|
|
957
|
+
"number",
|
|
958
|
+
"date",
|
|
959
|
+
"textarea",
|
|
960
|
+
"select",
|
|
961
|
+
"multi-select",
|
|
962
|
+
"toggle",
|
|
963
|
+
"file"
|
|
964
|
+
]);
|
|
965
|
+
function isRecord$1(value) {
|
|
966
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
967
|
+
}
|
|
968
|
+
function reject(message) {
|
|
969
|
+
throw new WebSkillError("VALIDATION_FAILED", message);
|
|
970
|
+
}
|
|
971
|
+
function requireString(value, name) {
|
|
972
|
+
if (typeof value !== "string" || value.trim() === "") reject(`${name} must be a non-empty string`);
|
|
973
|
+
}
|
|
974
|
+
function requireNonNegativeInteger(value, name) {
|
|
975
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) reject(`${name} must be a non-negative integer`);
|
|
976
|
+
}
|
|
977
|
+
function isJsonValue(value, depth = 0) {
|
|
978
|
+
if (depth > MAX_JSON_DEPTH || value === void 0 || typeof value === "function" || typeof value === "symbol") return false;
|
|
979
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
980
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
981
|
+
if (Array.isArray(value)) return value.every((item) => isJsonValue(item, depth + 1));
|
|
982
|
+
if (!isRecord$1(value)) return false;
|
|
983
|
+
return Object.keys(value).every((key) => key !== "__proto__" && key !== "constructor" && isJsonValue(value[key], depth + 1));
|
|
984
|
+
}
|
|
985
|
+
function assertActions(value) {
|
|
986
|
+
if (value === void 0) return;
|
|
987
|
+
if (!Array.isArray(value) || value.length > MAX_ACTIONS) reject(`Surface actions must contain at most ${MAX_ACTIONS} items`);
|
|
988
|
+
for (const action of value) {
|
|
989
|
+
if (!isRecord$1(action)) reject("A surface action must be an object");
|
|
990
|
+
requireString(action["id"], "Surface action ID");
|
|
991
|
+
requireString(action["label"], "Surface action label");
|
|
992
|
+
if (typeof action["intent"] !== "string" || !actionIntents.has(action["intent"])) reject("Surface action intent is invalid");
|
|
993
|
+
if (action["disabled"] !== void 0 && typeof action["disabled"] !== "boolean") reject("Surface action disabled must be a boolean");
|
|
994
|
+
if (action["awaitResponse"] !== void 0 && typeof action["awaitResponse"] !== "boolean") reject("Surface action awaitResponse must be a boolean");
|
|
995
|
+
if (action["nonce"] !== void 0 && (typeof action["nonce"] !== "string" || action["nonce"] === "")) reject("Surface action nonce must be a non-empty string");
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
function assertForm(surface) {
|
|
999
|
+
if (!Array.isArray(surface["fields"]) || surface["fields"].length > MAX_FORM_FIELDS) reject(`A form surface must contain at most ${MAX_FORM_FIELDS} fields`);
|
|
1000
|
+
for (const field of surface["fields"]) {
|
|
1001
|
+
if (!isRecord$1(field)) reject("A form field must be an object");
|
|
1002
|
+
requireString(field["name"], "Form field name");
|
|
1003
|
+
requireString(field["label"], "Form field label");
|
|
1004
|
+
if (typeof field["type"] !== "string" || !fieldTypes.has(field["type"])) reject("Form field type is invalid");
|
|
1005
|
+
if (field["required"] !== void 0 && typeof field["required"] !== "boolean") reject("Form field required must be a boolean");
|
|
1006
|
+
if (field["description"] !== void 0 && typeof field["description"] !== "string") reject("Form field description must be a string");
|
|
1007
|
+
if (field["defaultValue"] !== void 0 && !isJsonValue(field["defaultValue"])) reject("Form field defaultValue must be JSON data");
|
|
1008
|
+
if (field["options"] !== void 0) {
|
|
1009
|
+
if (!Array.isArray(field["options"]) || field["options"].length > MAX_OPTIONS) reject(`Form field options must contain at most ${MAX_OPTIONS} items`);
|
|
1010
|
+
for (const option of field["options"]) {
|
|
1011
|
+
if (!isRecord$1(option)) reject("A form option must be an object");
|
|
1012
|
+
requireString(option["label"], "Form option label");
|
|
1013
|
+
if (!isJsonValue(option["value"])) reject("Form option value must be JSON data");
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
assertActions(surface["actions"]);
|
|
1018
|
+
}
|
|
1019
|
+
function assertChart(surface) {
|
|
1020
|
+
const chart = surface["chart"];
|
|
1021
|
+
if (!isRecord$1(chart)) reject("A chart surface requires a chart object");
|
|
1022
|
+
if (chart["kind"] !== "bar" && chart["kind"] !== "line" && chart["kind"] !== "pie") reject("Chart kind is invalid");
|
|
1023
|
+
if (!Array.isArray(chart["labels"]) || !chart["labels"].every((label) => typeof label === "string")) reject("Chart labels must be an array of strings");
|
|
1024
|
+
if (!Array.isArray(chart["series"])) reject("Chart series must be an array");
|
|
1025
|
+
let points = 0;
|
|
1026
|
+
for (const series of chart["series"]) {
|
|
1027
|
+
if (!isRecord$1(series) || !Array.isArray(series["data"]) || !series["data"].every((point) => typeof point === "number" && Number.isFinite(point))) reject("Chart series data must be finite numbers");
|
|
1028
|
+
if (series["name"] !== void 0 && typeof series["name"] !== "string") reject("Chart series name must be a string");
|
|
1029
|
+
points += series["data"].length;
|
|
1030
|
+
}
|
|
1031
|
+
if (points > MAX_CHART_POINTS) reject(`Chart data exceeds the ${MAX_CHART_POINTS}-point limit`);
|
|
1032
|
+
assertActions(surface["actions"]);
|
|
1033
|
+
}
|
|
1034
|
+
function assertTable(surface) {
|
|
1035
|
+
if (!Array.isArray(surface["columns"]) || surface["columns"].length > MAX_TABLE_COLUMNS || !surface["columns"].every((column) => typeof column === "string")) reject(`Table columns must be strings and contain at most ${MAX_TABLE_COLUMNS} items`);
|
|
1036
|
+
if (!Array.isArray(surface["rows"]) || surface["rows"].length > MAX_TABLE_ROWS) reject(`Table rows must contain at most ${MAX_TABLE_ROWS} items`);
|
|
1037
|
+
for (const row of surface["rows"]) if (!Array.isArray(row) || row.length > surface["columns"].length || !row.every((cell) => isJsonValue(cell))) reject("Table rows must contain JSON cells within the declared column count");
|
|
1038
|
+
assertActions(surface["actions"]);
|
|
1039
|
+
}
|
|
1040
|
+
/** Validates the allowlisted, data-only shape accepted by a UI surface renderer. @experimental */
|
|
1041
|
+
function validateUiSurface(value) {
|
|
1042
|
+
if (!isRecord$1(value)) reject("A UI surface must be an object");
|
|
1043
|
+
requireString(value["id"], "UI surface ID");
|
|
1044
|
+
if (value["title"] !== void 0 && typeof value["title"] !== "string") reject("UI surface title must be a string");
|
|
1045
|
+
switch (value["kind"]) {
|
|
1046
|
+
case "form":
|
|
1047
|
+
assertForm(value);
|
|
1048
|
+
break;
|
|
1049
|
+
case "chart":
|
|
1050
|
+
assertChart(value);
|
|
1051
|
+
break;
|
|
1052
|
+
case "table":
|
|
1053
|
+
assertTable(value);
|
|
1054
|
+
break;
|
|
1055
|
+
case "metric":
|
|
1056
|
+
requireString(value["label"], "Metric label");
|
|
1057
|
+
if (typeof value["value"] !== "string" && (typeof value["value"] !== "number" || !Number.isFinite(value["value"]))) reject("Metric value must be a string or finite number");
|
|
1058
|
+
if (value["trend"] !== void 0 && value["trend"] !== "up" && value["trend"] !== "down" && value["trend"] !== "neutral") reject("Metric trend is invalid");
|
|
1059
|
+
break;
|
|
1060
|
+
case "file": {
|
|
1061
|
+
requireString(value["path"], "File path");
|
|
1062
|
+
if (value["mimeType"] !== void 0 && typeof value["mimeType"] !== "string") reject("File mimeType must be a string");
|
|
1063
|
+
const fileSize = value["size"];
|
|
1064
|
+
if (fileSize !== void 0 && (typeof fileSize !== "number" || !Number.isSafeInteger(fileSize) || fileSize < 0)) reject("File size must be a non-negative integer");
|
|
1065
|
+
assertActions(value["actions"]);
|
|
1066
|
+
break;
|
|
1067
|
+
}
|
|
1068
|
+
case "custom":
|
|
1069
|
+
requireString(value["component"], "Custom surface component");
|
|
1070
|
+
if (!isJsonValue(value["props"])) reject("Custom surface props must be JSON data");
|
|
1071
|
+
assertActions(value["actions"]);
|
|
1072
|
+
break;
|
|
1073
|
+
default: reject("UI surface kind is invalid");
|
|
1074
|
+
}
|
|
1075
|
+
if (!isJsonValue(value)) reject("A UI surface must contain JSON data only");
|
|
1076
|
+
if (JSON.stringify(value).length > MAX_SURFACE_BYTES) reject(`A UI surface exceeds the ${MAX_SURFACE_BYTES}-byte limit`);
|
|
1077
|
+
return structuredClone(value);
|
|
1078
|
+
}
|
|
1079
|
+
function assertPatch(value) {
|
|
1080
|
+
if (!isRecord$1(value)) reject("A surface patch operation must be an object");
|
|
1081
|
+
if (value["op"] !== "replace" && value["op"] !== "merge" && value["op"] !== "append") reject("Surface patch operation is invalid");
|
|
1082
|
+
requireString(value["path"], "Surface patch path");
|
|
1083
|
+
if (!value["path"].startsWith("/")) reject("Surface patch path must be a JSON pointer");
|
|
1084
|
+
if (!isJsonValue(value["value"])) reject("Surface patch value must be JSON data");
|
|
1085
|
+
}
|
|
1086
|
+
/** Validates an individual event in the framework-neutral surface stream. @experimental */
|
|
1087
|
+
function validateUiSurfaceEvent(value) {
|
|
1088
|
+
if (!isRecord$1(value) || typeof value["type"] !== "string") reject("A UI surface event must have a type");
|
|
1089
|
+
if (value["runId"] !== void 0) requireString(value["runId"], "Surface event run ID");
|
|
1090
|
+
switch (value["type"]) {
|
|
1091
|
+
case "open": return {
|
|
1092
|
+
type: "open",
|
|
1093
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
1094
|
+
surface: validateUiSurface(value["surface"])
|
|
1095
|
+
};
|
|
1096
|
+
case "patch":
|
|
1097
|
+
requireString(value["id"], "Surface patch ID");
|
|
1098
|
+
requireNonNegativeInteger(value["revision"], "Surface patch revision");
|
|
1099
|
+
if (!Array.isArray(value["operations"]) || value["operations"].length === 0) reject("A surface patch requires operations");
|
|
1100
|
+
if (value["operations"].length > MAX_PATCH_OPERATIONS) reject(`A surface patch must contain at most ${MAX_PATCH_OPERATIONS} operations`);
|
|
1101
|
+
for (const operation of value["operations"]) assertPatch(operation);
|
|
1102
|
+
return {
|
|
1103
|
+
type: "patch",
|
|
1104
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
1105
|
+
id: value["id"],
|
|
1106
|
+
revision: value["revision"],
|
|
1107
|
+
operations: structuredClone(value["operations"])
|
|
1108
|
+
};
|
|
1109
|
+
case "complete":
|
|
1110
|
+
requireString(value["id"], "Surface completion ID");
|
|
1111
|
+
requireNonNegativeInteger(value["revision"], "Surface completion revision");
|
|
1112
|
+
return {
|
|
1113
|
+
type: "complete",
|
|
1114
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
1115
|
+
id: value["id"],
|
|
1116
|
+
revision: value["revision"]
|
|
1117
|
+
};
|
|
1118
|
+
case "error":
|
|
1119
|
+
requireString(value["id"], "Surface error ID");
|
|
1120
|
+
requireString(value["code"], "Surface error code");
|
|
1121
|
+
requireString(value["message"], "Surface error message");
|
|
1122
|
+
return {
|
|
1123
|
+
type: "error",
|
|
1124
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
1125
|
+
id: value["id"],
|
|
1126
|
+
code: value["code"],
|
|
1127
|
+
message: value["message"]
|
|
1128
|
+
};
|
|
1129
|
+
case "cancel":
|
|
1130
|
+
requireString(value["id"], "Surface cancellation ID");
|
|
1131
|
+
return {
|
|
1132
|
+
type: "cancel",
|
|
1133
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
1134
|
+
id: value["id"]
|
|
1135
|
+
};
|
|
1136
|
+
default: return reject("UI surface event type is invalid");
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
/** Extracts validated surface stream events from structured tool output. @experimental */
|
|
1140
|
+
function extractUiSurfaceEvents(data) {
|
|
1141
|
+
if (!isRecord$1(data) || data["$surface"] === void 0) return [];
|
|
1142
|
+
const raw = data["$surface"];
|
|
1143
|
+
return (Array.isArray(raw) ? raw : [raw]).map((event) => validateUiSurfaceEvent(event));
|
|
1144
|
+
}
|
|
931
1145
|
/**
|
|
932
1146
|
* JsonSchema → 表单模型:按 properties 生成字段,required 标记必填;
|
|
933
1147
|
* providedArgs 已有的值作为 defaultValue 预填(表单只为补齐缺失项服务)。
|
|
@@ -1073,6 +1287,7 @@ var TraceRecorder = class {
|
|
|
1073
1287
|
return [...this.#events];
|
|
1074
1288
|
}
|
|
1075
1289
|
};
|
|
1290
|
+
const MAX_SURFACE_PATCHES_PER_SECOND = 240;
|
|
1076
1291
|
/** 交互终态(取消/超时):从工具执行深处直接终止 run */
|
|
1077
1292
|
var RunTerminated = class extends Error {
|
|
1078
1293
|
outcome;
|
|
@@ -1097,6 +1312,7 @@ const summarizeArgs = (args) => {
|
|
|
1097
1312
|
const json = JSON.stringify(args);
|
|
1098
1313
|
return json.length > 100 ? `${json.slice(0, 100)}…` : json;
|
|
1099
1314
|
};
|
|
1315
|
+
const surfaceActions = (surface) => "actions" in surface ? surface.actions ?? [] : [];
|
|
1100
1316
|
/**
|
|
1101
1317
|
* 多轮 Agent 循环。
|
|
1102
1318
|
* 工具失败一律转为 ToolResult{ok:false} 回喂 LLM 继续循环;
|
|
@@ -1168,9 +1384,14 @@ var AgentLoop = class {
|
|
|
1168
1384
|
toolTimeoutMs: this.#config.toolTimeoutMs,
|
|
1169
1385
|
now,
|
|
1170
1386
|
interactionSeq: 0,
|
|
1387
|
+
surfaceActionSeq: 0,
|
|
1171
1388
|
messages: [],
|
|
1172
1389
|
turn: 0,
|
|
1173
1390
|
renderBlocks: [],
|
|
1391
|
+
surfaceEvents: [],
|
|
1392
|
+
surfacePatchWindowStartedAt: Date.now(),
|
|
1393
|
+
surfacePatchCount: 0,
|
|
1394
|
+
processedSurfaceActionNonces: /* @__PURE__ */ new Set(),
|
|
1174
1395
|
startMs,
|
|
1175
1396
|
pausedMs: 0,
|
|
1176
1397
|
maxTurns: this.#config.maxTurns,
|
|
@@ -1310,6 +1531,7 @@ var AgentLoop = class {
|
|
|
1310
1531
|
toolCallId: call.id,
|
|
1311
1532
|
content: await this.#serializeToolResult(call, result, state)
|
|
1312
1533
|
});
|
|
1534
|
+
await this.#drainSurfaceAction(state);
|
|
1313
1535
|
}
|
|
1314
1536
|
}
|
|
1315
1537
|
} finally {
|
|
@@ -1365,7 +1587,7 @@ var AgentLoop = class {
|
|
|
1365
1587
|
};
|
|
1366
1588
|
}
|
|
1367
1589
|
/** D3 快照写入(含 pendingInteraction 与过期时间;写失败降级 run.warning) */
|
|
1368
|
-
async #saveSnapshot(state,
|
|
1590
|
+
async #saveSnapshot(state, pending) {
|
|
1369
1591
|
const store = this.#deps.snapshotStore;
|
|
1370
1592
|
if (!store) return;
|
|
1371
1593
|
const snapshot = {
|
|
@@ -1378,10 +1600,14 @@ var AgentLoop = class {
|
|
|
1378
1600
|
turn: state.turn,
|
|
1379
1601
|
activeSkillNames: [...state.activated].sort(),
|
|
1380
1602
|
activatedTools: [...state.activatedTools.values()],
|
|
1381
|
-
pendingInteraction:
|
|
1603
|
+
...pending.interaction ? { pendingInteraction: pending.interaction } : {},
|
|
1604
|
+
...pending.surfaceAction ? { pendingSurfaceAction: pending.surfaceAction } : {},
|
|
1382
1605
|
interactionExpiresAt: state.run.interruptExpiresAt,
|
|
1383
1606
|
renderBlocks: [...state.renderBlocks],
|
|
1607
|
+
surfaceEvents: state.surfaceEvents.map((event) => structuredClone(event)),
|
|
1384
1608
|
interactionSeq: state.interactionSeq,
|
|
1609
|
+
surfaceActionSeq: state.surfaceActionSeq,
|
|
1610
|
+
processedSurfaceActionNonces: [...state.processedSurfaceActionNonces],
|
|
1385
1611
|
pausedMs: state.pausedMs,
|
|
1386
1612
|
config: {
|
|
1387
1613
|
maxTurns: state.maxTurns,
|
|
@@ -1426,9 +1652,14 @@ var AgentLoop = class {
|
|
|
1426
1652
|
toolTimeoutMs: snapshot.config.toolTimeoutMs,
|
|
1427
1653
|
now,
|
|
1428
1654
|
interactionSeq: snapshot.interactionSeq ?? 0,
|
|
1655
|
+
surfaceActionSeq: snapshot.surfaceActionSeq ?? 0,
|
|
1429
1656
|
messages: snapshot.messages.map((m) => ({ ...m })),
|
|
1430
1657
|
turn: snapshot.turn,
|
|
1431
1658
|
renderBlocks: (snapshot.renderBlocks ?? []).map((b) => ({ ...b })),
|
|
1659
|
+
surfaceEvents: (snapshot.surfaceEvents ?? []).map((event) => structuredClone(event)),
|
|
1660
|
+
surfacePatchWindowStartedAt: Date.now(),
|
|
1661
|
+
surfacePatchCount: 0,
|
|
1662
|
+
processedSurfaceActionNonces: new Set(snapshot.processedSurfaceActionNonces ?? []),
|
|
1432
1663
|
startMs,
|
|
1433
1664
|
pausedMs: snapshot.pausedMs ?? 0,
|
|
1434
1665
|
maxTurns: snapshot.config.maxTurns,
|
|
@@ -1437,15 +1668,19 @@ var AgentLoop = class {
|
|
|
1437
1668
|
};
|
|
1438
1669
|
this.#controllers.set(runId, state.controller);
|
|
1439
1670
|
if (this.#deps.hooks && !this.#deps.hooks.onWarning) this.#deps.hooks.onWarning = (message) => trace.record("run.warning", { message });
|
|
1671
|
+
const pendingInteraction = snapshot.pendingInteraction;
|
|
1672
|
+
const pendingSurfaceAction = snapshot.pendingSurfaceAction;
|
|
1440
1673
|
trace.record("run.resumed", { data: {
|
|
1441
1674
|
snapshotAt: snapshot.snapshotAt,
|
|
1442
1675
|
turn: snapshot.turn,
|
|
1443
|
-
interactionType:
|
|
1676
|
+
interactionType: pendingSurfaceAction ? "surface-action" : pendingInteraction?.type
|
|
1444
1677
|
} });
|
|
1445
|
-
const pending =
|
|
1678
|
+
const pending = pendingInteraction;
|
|
1446
1679
|
const pendingCall = this.#findPendingToolCall(state.messages);
|
|
1447
1680
|
try {
|
|
1448
|
-
|
|
1681
|
+
await this.#replaySurfaceEvents(state);
|
|
1682
|
+
if (pendingSurfaceAction) await this.#resumeSurfaceAction(state, pendingSurfaceAction);
|
|
1683
|
+
else if ((pending?.type === "form" || pending?.type === "select") && pendingCall) {
|
|
1449
1684
|
const value = await this.#interact(state, pending, {
|
|
1450
1685
|
tool: pendingCall.name,
|
|
1451
1686
|
resumed: true
|
|
@@ -1463,7 +1698,8 @@ var AgentLoop = class {
|
|
|
1463
1698
|
toolCallId: pendingCall.id,
|
|
1464
1699
|
content: await this.#serializeToolResult(pendingCall, result, state)
|
|
1465
1700
|
});
|
|
1466
|
-
|
|
1701
|
+
await this.#drainSurfaceAction(state);
|
|
1702
|
+
} else if (pending?.type === "ask" && pendingCall) {
|
|
1467
1703
|
const value = await this.#interact(state, pending, {
|
|
1468
1704
|
tool: pendingCall.name,
|
|
1469
1705
|
resumed: true
|
|
@@ -1484,6 +1720,7 @@ var AgentLoop = class {
|
|
|
1484
1720
|
toolCallId: pendingCall.id,
|
|
1485
1721
|
content: await this.#serializeToolResult(pendingCall, result, state)
|
|
1486
1722
|
});
|
|
1723
|
+
await this.#drainSurfaceAction(state);
|
|
1487
1724
|
} else if (pendingCall) {
|
|
1488
1725
|
const result = await this.#executeCall(pendingCall, state);
|
|
1489
1726
|
state.messages.push({
|
|
@@ -1491,6 +1728,7 @@ var AgentLoop = class {
|
|
|
1491
1728
|
toolCallId: pendingCall.id,
|
|
1492
1729
|
content: await this.#serializeToolResult(pendingCall, result, state)
|
|
1493
1730
|
});
|
|
1731
|
+
await this.#drainSurfaceAction(state);
|
|
1494
1732
|
}
|
|
1495
1733
|
for (;;) {
|
|
1496
1734
|
const next = this.#findPendingToolCall(state.messages);
|
|
@@ -1501,6 +1739,7 @@ var AgentLoop = class {
|
|
|
1501
1739
|
toolCallId: next.id,
|
|
1502
1740
|
content: await this.#serializeToolResult(next, result, state)
|
|
1503
1741
|
});
|
|
1742
|
+
await this.#drainSurfaceAction(state);
|
|
1504
1743
|
}
|
|
1505
1744
|
} catch (e) {
|
|
1506
1745
|
if (e instanceof RunTerminated) return this.#finish(state, e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
|
|
@@ -1597,7 +1836,7 @@ var AgentLoop = class {
|
|
|
1597
1836
|
const { run } = state;
|
|
1598
1837
|
run.status = "interrupted";
|
|
1599
1838
|
run.interruptExpiresAt = new Date(Date.parse(state.now()) + this.#policy.interactionTimeoutMs).toISOString();
|
|
1600
|
-
await this.#saveSnapshot(state, request);
|
|
1839
|
+
await this.#saveSnapshot(state, { interaction: request });
|
|
1601
1840
|
await this.#lifecycle("interact", state, {
|
|
1602
1841
|
interactionId: request.id,
|
|
1603
1842
|
type: request.type
|
|
@@ -1665,7 +1904,8 @@ var AgentLoop = class {
|
|
|
1665
1904
|
} });
|
|
1666
1905
|
this.#emitTool(state, "started", call);
|
|
1667
1906
|
let result;
|
|
1668
|
-
if (call.
|
|
1907
|
+
if (call.argumentsParseError) result = toolError("VALIDATION_FAILED", `Tool arguments were not valid JSON: ${call.argumentsParseError}`);
|
|
1908
|
+
else if (call.name === "read_skill_file") result = await this.#handleReadSkillFile(call, state);
|
|
1669
1909
|
else if (call.name === "ask_user") result = await this.#handleAskUser(call, state);
|
|
1670
1910
|
else {
|
|
1671
1911
|
const resolution = resolveToolName(call.name, state.activated);
|
|
@@ -1691,6 +1931,14 @@ var AgentLoop = class {
|
|
|
1691
1931
|
type: "chart",
|
|
1692
1932
|
chart
|
|
1693
1933
|
});
|
|
1934
|
+
try {
|
|
1935
|
+
for (const event of extractUiSurfaceEvents(item.data)) await this.#renderSurface(state, event);
|
|
1936
|
+
} catch (e) {
|
|
1937
|
+
state.trace.record("run.warning", {
|
|
1938
|
+
message: `UI surface rejected: ${messageOf(e)}`,
|
|
1939
|
+
data: e instanceof WebSkillError ? { code: e.code } : void 0
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1694
1942
|
}
|
|
1695
1943
|
} else {
|
|
1696
1944
|
state.trace.record("tool.failed", {
|
|
@@ -1711,10 +1959,155 @@ var AgentLoop = class {
|
|
|
1711
1959
|
} });
|
|
1712
1960
|
return result;
|
|
1713
1961
|
}
|
|
1962
|
+
/** Attaches trusted run provenance, then records only events accepted by the configured bridge. */
|
|
1963
|
+
async #renderSurface(state, event) {
|
|
1964
|
+
const bridge = this.#deps.uiBridge;
|
|
1965
|
+
if (!bridge?.renderSurface) {
|
|
1966
|
+
state.trace.record("run.warning", { message: "UiBridge does not support renderSurface; UI surface was not rendered" });
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
const attributed = this.#attributeSurfaceEvent(state, event);
|
|
1970
|
+
if (attributed.type === "patch") this.#consumeSurfacePatchBudget(state);
|
|
1971
|
+
await bridge.renderSurface(attributed);
|
|
1972
|
+
state.surfaceEvents.push(structuredClone(attributed));
|
|
1973
|
+
if (attributed.type === "open") {
|
|
1974
|
+
const waiting = surfaceActions(attributed.surface).filter((action) => action.awaitResponse);
|
|
1975
|
+
if (waiting.length > 1) throw new WebSkillError("VALIDATION_FAILED", "A UI surface can wait for only one action");
|
|
1976
|
+
const action = waiting[0];
|
|
1977
|
+
if (action?.nonce) if (!bridge.requestSurfaceAction) state.trace.record("run.warning", { message: "UiBridge does not support requestSurfaceAction; UI surface action will not pause the run" });
|
|
1978
|
+
else if (state.pendingSurfaceAction) throw new WebSkillError("VALIDATION_FAILED", "Only one UI surface action can be pending at a time");
|
|
1979
|
+
else state.pendingSurfaceAction = {
|
|
1980
|
+
runId: state.runId,
|
|
1981
|
+
surfaceId: attributed.surface.id,
|
|
1982
|
+
actionId: action.id,
|
|
1983
|
+
intent: action.intent,
|
|
1984
|
+
nonce: action.nonce
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
#consumeSurfacePatchBudget(state) {
|
|
1989
|
+
const now = Date.now();
|
|
1990
|
+
if (now - state.surfacePatchWindowStartedAt >= 1e3) {
|
|
1991
|
+
state.surfacePatchWindowStartedAt = now;
|
|
1992
|
+
state.surfacePatchCount = 0;
|
|
1993
|
+
}
|
|
1994
|
+
state.surfacePatchCount += 1;
|
|
1995
|
+
if (state.surfacePatchCount > MAX_SURFACE_PATCHES_PER_SECOND) throw new WebSkillError("VALIDATION_FAILED", `UI surface patch rate exceeds ${MAX_SURFACE_PATCHES_PER_SECOND} events per second`);
|
|
1996
|
+
}
|
|
1997
|
+
/** Assigns unforgeable action nonces after model output has passed structural validation. */
|
|
1998
|
+
#attributeSurfaceEvent(state, event) {
|
|
1999
|
+
const actions = event.type === "open" ? surfaceActions(event.surface) : [];
|
|
2000
|
+
if (event.type !== "open" || actions.length === 0) return {
|
|
2001
|
+
...event,
|
|
2002
|
+
runId: state.runId
|
|
2003
|
+
};
|
|
2004
|
+
return {
|
|
2005
|
+
type: "open",
|
|
2006
|
+
runId: state.runId,
|
|
2007
|
+
surface: {
|
|
2008
|
+
...event.surface,
|
|
2009
|
+
actions: actions.map((action) => ({
|
|
2010
|
+
...action,
|
|
2011
|
+
nonce: `surface-${state.runId}-${++state.surfaceActionSeq}`
|
|
2012
|
+
}))
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
/** Awaits the single action emitted with the most recently persisted tool result. */
|
|
2017
|
+
async #drainSurfaceAction(state) {
|
|
2018
|
+
const request = state.pendingSurfaceAction;
|
|
2019
|
+
if (!request) return;
|
|
2020
|
+
state.pendingSurfaceAction = void 0;
|
|
2021
|
+
await this.#acceptSurfaceAction(state, request);
|
|
2022
|
+
}
|
|
2023
|
+
/** Restores a checkpointed action wait without re-running the tool that emitted its surface. */
|
|
2024
|
+
async #resumeSurfaceAction(state, request) {
|
|
2025
|
+
await this.#acceptSurfaceAction(state, request, true);
|
|
2026
|
+
}
|
|
2027
|
+
async #acceptSurfaceAction(state, request, resumed = false) {
|
|
2028
|
+
const response = await this.#interactSurfaceAction(state, request, resumed);
|
|
2029
|
+
if (response.cancelled) throw new RunTerminated({
|
|
2030
|
+
status: "cancelled",
|
|
2031
|
+
reason: "user-cancelled",
|
|
2032
|
+
message: "Surface action cancelled by user",
|
|
2033
|
+
code: "RUN_CANCELLED"
|
|
2034
|
+
});
|
|
2035
|
+
if (response.runId !== request.runId || response.surfaceId !== request.surfaceId || response.actionId !== request.actionId || response.nonce !== request.nonce) throw new BridgeRequestError("UiBridge returned a surface action response with an invalid capability nonce");
|
|
2036
|
+
if (state.processedSurfaceActionNonces.has(response.nonce)) throw new BridgeRequestError("UiBridge returned a surface action response with an already consumed capability nonce");
|
|
2037
|
+
state.processedSurfaceActionNonces.add(response.nonce);
|
|
2038
|
+
state.messages.push({
|
|
2039
|
+
role: "user",
|
|
2040
|
+
content: JSON.stringify({
|
|
2041
|
+
type: "webskill_surface_action",
|
|
2042
|
+
surfaceId: response.surfaceId,
|
|
2043
|
+
actionId: response.actionId,
|
|
2044
|
+
intent: response.intent,
|
|
2045
|
+
value: response.value ?? null
|
|
2046
|
+
})
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
async #interactSurfaceAction(state, request, resumed) {
|
|
2050
|
+
const bridge = this.#deps.uiBridge;
|
|
2051
|
+
if (!bridge?.requestSurfaceAction) throw new BridgeRequestError("UiBridge does not support requestSurfaceAction");
|
|
2052
|
+
this.#disarmDeadline(state);
|
|
2053
|
+
const waitStartMs = Date.parse(state.now());
|
|
2054
|
+
try {
|
|
2055
|
+
const { run } = state;
|
|
2056
|
+
run.status = "interrupted";
|
|
2057
|
+
run.interruptExpiresAt = new Date(Date.parse(state.now()) + this.#policy.interactionTimeoutMs).toISOString();
|
|
2058
|
+
await this.#saveSnapshot(state, { surfaceAction: request });
|
|
2059
|
+
await this.#lifecycle("interact", state, {
|
|
2060
|
+
surfaceId: request.surfaceId,
|
|
2061
|
+
actionId: request.actionId,
|
|
2062
|
+
nonce: request.nonce,
|
|
2063
|
+
...resumed ? { resumed: true } : {}
|
|
2064
|
+
});
|
|
2065
|
+
state.trace.record("ui.surface-action.requested", { data: {
|
|
2066
|
+
surfaceId: request.surfaceId,
|
|
2067
|
+
actionId: request.actionId,
|
|
2068
|
+
nonce: request.nonce,
|
|
2069
|
+
...resumed ? { resumed: true } : {}
|
|
2070
|
+
} });
|
|
2071
|
+
const response = await this.#withInteractionTimeout(bridge.requestSurfaceAction(request), this.#policy.interactionTimeoutMs, () => bridge.cancelSurfaceAction?.(request.nonce));
|
|
2072
|
+
run.status = "running";
|
|
2073
|
+
run.interruptExpiresAt = void 0;
|
|
2074
|
+
state.trace.record("ui.surface-action.resolved", { data: {
|
|
2075
|
+
surfaceId: request.surfaceId,
|
|
2076
|
+
actionId: request.actionId,
|
|
2077
|
+
nonce: request.nonce
|
|
2078
|
+
} });
|
|
2079
|
+
await this.#lifecycle("execute", state, { surfaceAction: request.actionId });
|
|
2080
|
+
return response;
|
|
2081
|
+
} catch (e) {
|
|
2082
|
+
state.run.status = "running";
|
|
2083
|
+
state.run.interruptExpiresAt = void 0;
|
|
2084
|
+
if (e instanceof WebSkillError && e.code === "RUN_INTERACTION_TIMEOUT") throw new RunTerminated({
|
|
2085
|
+
status: "failed",
|
|
2086
|
+
reason: "interaction-timeout",
|
|
2087
|
+
message: e.message,
|
|
2088
|
+
code: "RUN_INTERACTION_TIMEOUT"
|
|
2089
|
+
});
|
|
2090
|
+
if (e instanceof RunTerminated || e instanceof BridgeRequestError) throw e;
|
|
2091
|
+
throw new BridgeRequestError(messageOf(e));
|
|
2092
|
+
} finally {
|
|
2093
|
+
state.pausedMs += Date.parse(state.now()) - waitStartMs;
|
|
2094
|
+
this.#armDeadline(state);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
/** Replays the persisted stream before an interrupted interaction is rendered again. */
|
|
2098
|
+
async #replaySurfaceEvents(state) {
|
|
2099
|
+
const bridge = this.#deps.uiBridge;
|
|
2100
|
+
if (!bridge?.renderSurface || state.surfaceEvents.length === 0) return;
|
|
2101
|
+
try {
|
|
2102
|
+
for (const event of state.surfaceEvents) await bridge.renderSurface(structuredClone(event));
|
|
2103
|
+
} catch (e) {
|
|
2104
|
+
state.trace.record("run.warning", { message: `Failed to replay UI surfaces: ${messageOf(e)}` });
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
1714
2107
|
/**
|
|
1715
|
-
* 逐工具实时事件(execute 相位,data.type='tool'):chatbot
|
|
2108
|
+
* 逐工具实时事件(execute 相位,data.type='tool'):chatbot 思维链工具行等的 live 状态源;
|
|
1716
2109
|
* 与既有 execute 相位事件({turn})并存,监听方按 data.type 区分。
|
|
1717
|
-
* data.args 为参数摘要(JSON 截断 100
|
|
2110
|
+
* data.args 为参数摘要(JSON 截断 100 字符,展开详情用)。
|
|
1718
2111
|
*/
|
|
1719
2112
|
#emitTool(state, status, call) {
|
|
1720
2113
|
this.#deps.eventBus?.emit({
|
|
@@ -1880,8 +2273,9 @@ var AgentLoop = class {
|
|
|
1880
2273
|
let scriptFiles;
|
|
1881
2274
|
try {
|
|
1882
2275
|
scriptFiles = (await this.#deps.fs.list(`${root}/scripts`)).filter((s) => s.type === "file").map((s) => baseName(s.path));
|
|
1883
|
-
} catch {
|
|
2276
|
+
} catch (e) {
|
|
1884
2277
|
scriptFiles = [];
|
|
2278
|
+
if (!(e instanceof WebSkillError && e.code === "FS_NOT_FOUND")) state.trace.record("run.warning", { message: `Failed to list scripts of skill "${skillName}": ${messageOf(e)}` });
|
|
1885
2279
|
}
|
|
1886
2280
|
for (const file of scriptFiles) {
|
|
1887
2281
|
const match = /^(.*)\.(ts|js)$/.exec(file);
|
|
@@ -2018,10 +2412,11 @@ var AgentLoop = class {
|
|
|
2018
2412
|
#nextInteractionId(state) {
|
|
2019
2413
|
return `int-${++state.interactionSeq}`;
|
|
2020
2414
|
}
|
|
2021
|
-
async #memoryGet(scope, key) {
|
|
2415
|
+
async #memoryGet(scope, key, state) {
|
|
2022
2416
|
try {
|
|
2023
2417
|
return await this.#deps.memory?.get(scope, key);
|
|
2024
|
-
} catch {
|
|
2418
|
+
} catch (e) {
|
|
2419
|
+
state.trace.record("run.warning", { message: `Memory read failed: ${messageOf(e)}` });
|
|
2025
2420
|
return;
|
|
2026
2421
|
}
|
|
2027
2422
|
}
|
|
@@ -2046,7 +2441,7 @@ var AgentLoop = class {
|
|
|
2046
2441
|
}
|
|
2047
2442
|
return;
|
|
2048
2443
|
}
|
|
2049
|
-
await this.#memorySet(scope, key, mutate(await this.#memoryGet(scope, key)), state);
|
|
2444
|
+
await this.#memorySet(scope, key, mutate(await this.#memoryGet(scope, key, state)), state);
|
|
2050
2445
|
}
|
|
2051
2446
|
async #writeActivationMemory(skillName, state) {
|
|
2052
2447
|
if (!this.#deps.memory) return;
|
|
@@ -2578,9 +2973,26 @@ const encode = (s) => encodeURIComponent(s);
|
|
|
2578
2973
|
var FsMemoryStore = class {
|
|
2579
2974
|
#root;
|
|
2580
2975
|
#fs;
|
|
2976
|
+
#onWarning;
|
|
2581
2977
|
constructor(deps) {
|
|
2582
2978
|
this.#root = deps.root.replace(/\/+$/, "");
|
|
2583
2979
|
this.#fs = deps.fs;
|
|
2980
|
+
this.#onWarning = deps.onWarning ?? ((message) => console.warn(message));
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* 0.2.8 G2:损坏条目此前直接 remove——静默销毁用户数据,且对调用方伪装成「没有这条记忆」。
|
|
2984
|
+
* 改为隔离到 .corrupt 并告警:run 照常继续,但数据留存、故障可见。
|
|
2985
|
+
*/
|
|
2986
|
+
async #quarantine(path, error) {
|
|
2987
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2988
|
+
const target = `${path}.corrupt`;
|
|
2989
|
+
try {
|
|
2990
|
+
await this.#fs.rename(path, target);
|
|
2991
|
+
this.#onWarning(`[webskill] Corrupted memory entry quarantined to ${target}: ${reason}`);
|
|
2992
|
+
} catch (renameError) {
|
|
2993
|
+
const detail = renameError instanceof Error ? renameError.message : String(renameError);
|
|
2994
|
+
this.#onWarning(`[webskill] Corrupted memory entry at ${path} could not be quarantined (${detail}): ${reason}`);
|
|
2995
|
+
}
|
|
2584
2996
|
}
|
|
2585
2997
|
#scopeDir(scope) {
|
|
2586
2998
|
return `${this.#root}/${encode(scope)}`;
|
|
@@ -2593,8 +3005,8 @@ var FsMemoryStore = class {
|
|
|
2593
3005
|
if (!await this.#fs.exists(path)) return void 0;
|
|
2594
3006
|
try {
|
|
2595
3007
|
return JSON.parse(await this.#fs.readText(path));
|
|
2596
|
-
} catch {
|
|
2597
|
-
await this.#
|
|
3008
|
+
} catch (e) {
|
|
3009
|
+
await this.#quarantine(path, e);
|
|
2598
3010
|
return;
|
|
2599
3011
|
}
|
|
2600
3012
|
}
|
|
@@ -2618,8 +3030,8 @@ var FsMemoryStore = class {
|
|
|
2618
3030
|
key,
|
|
2619
3031
|
value: JSON.parse(await this.#fs.readText(entry.path))
|
|
2620
3032
|
});
|
|
2621
|
-
} catch {
|
|
2622
|
-
await this.#
|
|
3033
|
+
} catch (e) {
|
|
3034
|
+
await this.#quarantine(entry.path, e);
|
|
2623
3035
|
}
|
|
2624
3036
|
}
|
|
2625
3037
|
return out.sort((a, b) => a.key.localeCompare(b.key));
|
|
@@ -2643,9 +3055,11 @@ const INDEX_FILE = "index.json";
|
|
|
2643
3055
|
var FsArtifactStore = class {
|
|
2644
3056
|
#root;
|
|
2645
3057
|
#fs;
|
|
3058
|
+
#onWarning;
|
|
2646
3059
|
constructor(deps) {
|
|
2647
3060
|
this.#root = deps.root.replace(/\/+$/, "");
|
|
2648
3061
|
this.#fs = deps.fs;
|
|
3062
|
+
this.#onWarning = deps.onWarning ?? ((message) => console.warn(message));
|
|
2649
3063
|
}
|
|
2650
3064
|
async createTextArtifact(input) {
|
|
2651
3065
|
const size = new TextEncoder().encode(input.content).length;
|
|
@@ -2671,8 +3085,15 @@ var FsArtifactStore = class {
|
|
|
2671
3085
|
const raw = await this.#fs.readText(indexPath);
|
|
2672
3086
|
try {
|
|
2673
3087
|
return JSON.parse(raw).artifacts ?? [];
|
|
2674
|
-
} catch {
|
|
2675
|
-
|
|
3088
|
+
} catch (e) {
|
|
3089
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
3090
|
+
try {
|
|
3091
|
+
await this.#fs.rename(indexPath, `${indexPath}.corrupt`);
|
|
3092
|
+
this.#onWarning(`[webskill] Corrupted artifact index for run ${runId} quarantined to ${indexPath}.corrupt: ${reason}`);
|
|
3093
|
+
} catch (renameError) {
|
|
3094
|
+
const detail = renameError instanceof Error ? renameError.message : String(renameError);
|
|
3095
|
+
this.#onWarning(`[webskill] Corrupted artifact index for run ${runId} could not be quarantined (${detail}): ${reason}`);
|
|
3096
|
+
}
|
|
2676
3097
|
return [];
|
|
2677
3098
|
}
|
|
2678
3099
|
}
|
|
@@ -2786,6 +3207,20 @@ function networkUrlHost(url) {
|
|
|
2786
3207
|
return "(unparseable-url)";
|
|
2787
3208
|
}
|
|
2788
3209
|
}
|
|
3210
|
+
/**
|
|
3211
|
+
* 网络策略判定逻辑的可注入源码(单一来源)。
|
|
3212
|
+
*
|
|
3213
|
+
* 0.2.8 C4:此前各注入点直接拼 `isNetworkAllowed.toString()`,依赖**函数名在产物里保持不变**。
|
|
3214
|
+
* SDK 自身不压缩,但消费方一旦跑生产构建,打包器会把导出函数改名(`function Ke(...)`),
|
|
3215
|
+
* 注入后的沙箱里 `isNetworkAllowed` 就是 undefined —— 沙箱内任何 fetch 直接
|
|
3216
|
+
* TOOL_EXECUTION_FAILED,网络白名单形同虚设。dev server 不压缩,所以只在真实产物上暴露。
|
|
3217
|
+
*
|
|
3218
|
+
* 因此改为把函数源码绑定到**固定的变量名**上:`var isNetworkAllowed = function Ke(...) {…};`
|
|
3219
|
+
* ——名字随便压缩,绑定名恒定。两个函数都自包含(不引用模块内其它符号),故可独立绑定。
|
|
3220
|
+
*/
|
|
3221
|
+
function networkPolicyLibSource() {
|
|
3222
|
+
return `var isNetworkAllowed = ${isNetworkAllowed.toString()};\nvar networkUrlHost = ${networkUrlHost.toString()};`;
|
|
3223
|
+
}
|
|
2789
3224
|
/** WebSkillErrorCode 全量白名单(错误码归一用;与 core errors.ts 保持同步) */
|
|
2790
3225
|
const WHITELIST = /* @__PURE__ */ new Set([
|
|
2791
3226
|
"FS_NOT_FOUND",
|
|
@@ -2899,4 +3334,4 @@ var CapabilityApproval = class CapabilityApproval {
|
|
|
2899
3334
|
};
|
|
2900
3335
|
|
|
2901
3336
|
//#endregion
|
|
2902
|
-
export {
|
|
3337
|
+
export { fromVercelStreamPart as A, schemaToForm as B, bridgeError as C, extractChartSpec as D, createWebSkillApi as E, normalizeErrorCode as F, toVercelToolSpecs as H, normalizeToolContent as I, normalizeToolError as L, mergeCatalogEntries as M, networkPolicyLibSource as N, extractUiSurfaceEvents as O, networkUrlHost as P, parseBridgeRequest as R, WebSkillRuntime as S, createScriptContext as T, validateUiSurface as U, toLlmToolSpec as V, validateUiSurfaceEvent as W, READ_SKILL_FILE_TOOL as _, AnthropicClient as a, SerializingMemoryStore as b, FsArtifactStore as c, FullDisclosureRouter as d, GoogleGenAiClient as f, READ_SKILL_FILE_INPUT_SCHEMA as g, ProgressiveRouter as h, AgentLoop as i, isNetworkAllowed as j, fromVercelResult as k, FsMemoryStore as l, OpenAiCompatibleClient as m, ASK_USER_TOOL as n, CapabilityApproval as o, HookRunner as p, ASK_USER_TOOL_NAME as r, EventBus as s, ASK_USER_INPUT_SCHEMA as t, FsRunSnapshotStore as u, READ_SKILL_FILE_TOOL_NAME as v, buildRenderResult as w, TraceRecorder as x, RUN_SNAPSHOT_SCHEMA_VERSION as y, resolveToolName as z };
|