@webskill/sdk 0.15.0 → 0.16.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 +3 -3
- package/dist/agent.js +466 -66
- package/dist/browser.d.ts +140 -7
- package/dist/browser.js +326 -33
- package/dist/{eventTypes-Y07N8IAC.js → eventTypes-CwciaqCU.js} +23 -0
- package/dist/governance.d.ts +26 -3
- package/dist/governance.js +1 -1
- package/dist/{index-CQYo4tcT.d.ts → index-BLfnO7sz.d.ts} +324 -16
- package/dist/{index-Oc3H89TJ.d.ts → index-DbVqg2V3.d.ts} +1 -1
- package/dist/{index-CU2md8R1.d.ts → index-DeXgdtHa.d.ts} +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/{miniChart-owdCdaw-.js → miniChart-lauNtZu8.js} +2 -7
- package/dist/node.d.ts +3 -3
- package/dist/node.js +1 -1
- package/dist/{openUiLibrary-CY_ADoe0.js → openUiLibrary-BWEBU9P-.js} +1 -1
- package/dist/{openUiSpecLang-DuOCdw9G.js → openUiSpecLang-BIK6CWlW.js} +1 -1
- package/dist/{skillVersionStore-B24Jjjry-C4zomhMZ.d.ts → skillVersionStore-B24Jjjry-CsbzF7Oe.d.ts} +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{types-TUydnxaj.js → types-BuacBov_.js} +14 -3
- package/dist/{types-C3V6lAeO-BIf7UB2e.d.ts → types-C3V6lAeO-BUpcRLER.d.ts} +11 -0
- package/dist/ui-react.d.ts +9 -3
- package/dist/ui-react.js +15 -4
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +2 -2
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +4 -4
- package/dist/{webskillLitCatalog-DSqVFQjK.js → webskillLitCatalog-poL1qhBp.js} +2 -2
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -3,7 +3,7 @@ import { b as parseSkillMarkdown, y as isValidSkillName } from "./skill-CAJMsLod
|
|
|
3
3
|
import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
|
|
4
4
|
import { c as formatAttachmentText, l as UNTRUSTED_LINE_LIMIT, o as XLSX_MIME, s as classifyAttachment, u as sanitizeUntrustedLine } from "./kind-DxgS8LM-.js";
|
|
5
5
|
import { o as toBase64, s as DEFAULT_MAX_DATA_SOURCE_BYTES } from "./linkedDocument-xNXF-z8n.js";
|
|
6
|
-
import { a as
|
|
6
|
+
import { a as frameLabel, i as toFrameScopes, n as PAGE_DOCUMENT_REF, o as frameSteps, r as toActionFrameScopes, t as PAGE_ACTION_KINDS } from "./types-BuacBov_.js";
|
|
7
7
|
|
|
8
8
|
//#region ../agent/src/todo/store.ts
|
|
9
9
|
const STATUSES = [
|
|
@@ -182,7 +182,7 @@ const INPUT_SCHEMA$4 = {
|
|
|
182
182
|
required: ["action"],
|
|
183
183
|
additionalProperties: false
|
|
184
184
|
};
|
|
185
|
-
function toolError$
|
|
185
|
+
function toolError$6(code, message) {
|
|
186
186
|
return {
|
|
187
187
|
ok: false,
|
|
188
188
|
content: [],
|
|
@@ -231,15 +231,15 @@ function createTodoToolSource(options = {}) {
|
|
|
231
231
|
try {
|
|
232
232
|
if (action === "create") {
|
|
233
233
|
const items = parseItems(args["items"]);
|
|
234
|
-
if (typeof items === "string") return Promise.resolve(toolError$
|
|
234
|
+
if (typeof items === "string") return Promise.resolve(toolError$6("VALIDATION_FAILED", items));
|
|
235
235
|
const list = store.create(items);
|
|
236
236
|
return Promise.resolve(ok(events, progressText(list.items)));
|
|
237
237
|
}
|
|
238
238
|
if (action === "update") {
|
|
239
239
|
const id = args["id"];
|
|
240
240
|
const status = args["status"];
|
|
241
|
-
if (typeof id !== "string") return Promise.resolve(toolError$
|
|
242
|
-
if (status !== void 0 && typeof status !== "string") return Promise.resolve(toolError$
|
|
241
|
+
if (typeof id !== "string") return Promise.resolve(toolError$6("VALIDATION_FAILED", "The \"id\" argument is required for action \"update\""));
|
|
242
|
+
if (status !== void 0 && typeof status !== "string") return Promise.resolve(toolError$6("VALIDATION_FAILED", "The \"status\" argument must be a string"));
|
|
243
243
|
const title = args["title"];
|
|
244
244
|
const list = store.update(id, {
|
|
245
245
|
...status !== void 0 ? { status } : {},
|
|
@@ -251,10 +251,10 @@ function createTodoToolSource(options = {}) {
|
|
|
251
251
|
store.clear();
|
|
252
252
|
return Promise.resolve(ok(events, progressText([])));
|
|
253
253
|
}
|
|
254
|
-
return Promise.resolve(toolError$
|
|
254
|
+
return Promise.resolve(toolError$6("VALIDATION_FAILED", "The \"action\" argument must be one of create, update, clear"));
|
|
255
255
|
} catch (e) {
|
|
256
256
|
const code = e instanceof Error && "code" in e ? String(e.code) : "VALIDATION_FAILED";
|
|
257
|
-
return Promise.resolve(toolError$
|
|
257
|
+
return Promise.resolve(toolError$6(code, e instanceof Error ? e.message : String(e)));
|
|
258
258
|
} finally {
|
|
259
259
|
unsubscribe();
|
|
260
260
|
}
|
|
@@ -557,7 +557,7 @@ const INPUT_SCHEMA$3 = {
|
|
|
557
557
|
],
|
|
558
558
|
additionalProperties: false
|
|
559
559
|
};
|
|
560
|
-
function toolError$
|
|
560
|
+
function toolError$5(code, message) {
|
|
561
561
|
return {
|
|
562
562
|
ok: false,
|
|
563
563
|
content: [],
|
|
@@ -626,7 +626,7 @@ function createSkillGenerationToolSource(options) {
|
|
|
626
626
|
const generator = new SkillGenerator(options);
|
|
627
627
|
const call = async (_name, args) => {
|
|
628
628
|
const draft = readDraft(args);
|
|
629
|
-
if (typeof draft === "string") return toolError$
|
|
629
|
+
if (typeof draft === "string") return toolError$5("SKILL_GENERATION_VALIDATION_FAILED", draft);
|
|
630
630
|
try {
|
|
631
631
|
const outcome = await generator.generate(draft);
|
|
632
632
|
if (outcome.status === "declined") return {
|
|
@@ -650,7 +650,7 @@ function createSkillGenerationToolSource(options) {
|
|
|
650
650
|
}]
|
|
651
651
|
};
|
|
652
652
|
} catch (e) {
|
|
653
|
-
return toolError$
|
|
653
|
+
return toolError$5(e instanceof Error && "code" in e ? String(e.code) : "SKILL_GENERATION_VALIDATION_FAILED", e instanceof Error ? e.message : String(e));
|
|
654
654
|
}
|
|
655
655
|
};
|
|
656
656
|
return {
|
|
@@ -789,7 +789,7 @@ const INPUT_SCHEMA$2 = {
|
|
|
789
789
|
required: ["todoId", "task"],
|
|
790
790
|
additionalProperties: false
|
|
791
791
|
};
|
|
792
|
-
function toolError$
|
|
792
|
+
function toolError$4(code, message) {
|
|
793
793
|
return {
|
|
794
794
|
ok: false,
|
|
795
795
|
content: [],
|
|
@@ -824,12 +824,12 @@ function createDelegationToolSource(options) {
|
|
|
824
824
|
const call = async (_name, args) => {
|
|
825
825
|
const todoId = args["todoId"];
|
|
826
826
|
const task = args["task"];
|
|
827
|
-
if (typeof todoId !== "string" || todoId === "") return toolError$
|
|
828
|
-
if (typeof task !== "string" || task.trim() === "") return toolError$
|
|
827
|
+
if (typeof todoId !== "string" || todoId === "") return toolError$4("VALIDATION_FAILED", "The \"todoId\" argument is required and must be a string");
|
|
828
|
+
if (typeof task !== "string" || task.trim() === "") return toolError$4("VALIDATION_FAILED", "The \"task\" argument is required and must be a non-empty string");
|
|
829
829
|
const rawTools = args["allowedTools"];
|
|
830
830
|
let allowedTools;
|
|
831
831
|
if (rawTools !== void 0) {
|
|
832
|
-
if (!Array.isArray(rawTools) || rawTools.some((entry) => typeof entry !== "string")) return toolError$
|
|
832
|
+
if (!Array.isArray(rawTools) || rawTools.some((entry) => typeof entry !== "string")) return toolError$4("VALIDATION_FAILED", "The \"allowedTools\" argument must be an array of tool names");
|
|
833
833
|
allowedTools = rawTools;
|
|
834
834
|
}
|
|
835
835
|
try {
|
|
@@ -861,7 +861,7 @@ function createDelegationToolSource(options) {
|
|
|
861
861
|
}]
|
|
862
862
|
};
|
|
863
863
|
} catch (e) {
|
|
864
|
-
return toolError$
|
|
864
|
+
return toolError$4(e instanceof Error && "code" in e ? String(e.code) : "DELEGATION_FAILED", e instanceof Error ? e.message : String(e));
|
|
865
865
|
}
|
|
866
866
|
};
|
|
867
867
|
return {
|
|
@@ -1123,10 +1123,12 @@ function tailOf(nodes) {
|
|
|
1123
1123
|
/**
|
|
1124
1124
|
* 页面只读感知策略(需求 10)。
|
|
1125
1125
|
*
|
|
1126
|
-
*
|
|
1127
|
-
* (AC-10.8
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1126
|
+
* **模型没有扩大可读范围的入口**——这是「白名单判定不受模型输出影响」
|
|
1127
|
+
* (AC-10.8)的实现方式:能被读的区域只由构造时的 `scope` 决定。
|
|
1128
|
+
* `perceive()` 的第二个参数 `subtree` 只能**缩小**读取范围(分册 14):
|
|
1129
|
+
* 它走的是同一个遍历器、同一套 scope 判定,只换一个根节点,
|
|
1130
|
+
* 因此「带 `ref` 的结果是不带它时的子集」是个结构事实,而不是事后过滤的结果。
|
|
1131
|
+
* 第一个参数仍旧只是取像预算(`PerceptionCaptureOptions`),它里面没有任何范围字段。
|
|
1130
1132
|
*
|
|
1131
1133
|
* 本类**没有**任何点击 / 输入 / 提交 / 导航 / 滚动方法(FR-10.7)。
|
|
1132
1134
|
* @experimental
|
|
@@ -1154,11 +1156,11 @@ var PagePerceptionPolicy = class {
|
|
|
1154
1156
|
this.#listeners.add(listener);
|
|
1155
1157
|
return () => this.#listeners.delete(listener);
|
|
1156
1158
|
}
|
|
1157
|
-
async perceive(capture) {
|
|
1159
|
+
async perceive(capture, subtree) {
|
|
1158
1160
|
if (!this.enabled) throw new WebSkillError("PERCEPTION_NOT_ENABLED", "Page perception is not enabled: the host declared no readable regions.");
|
|
1159
1161
|
const scope = this.#options.scope;
|
|
1160
1162
|
const frames = toFrameScopes(scope);
|
|
1161
|
-
const raw = await this.#options.reader.read(scope, capture);
|
|
1163
|
+
const raw = subtree === void 0 ? await this.#options.reader.read(scope, capture) : await this.#readSubtree(scope, subtree.ref, capture);
|
|
1162
1164
|
const result = Array.isArray(raw) ? { nodes: raw } : raw;
|
|
1163
1165
|
const nodes = result.nodes;
|
|
1164
1166
|
for (const note of result.frameNotes ?? []) this.#options.onWarning?.(note.message);
|
|
@@ -1171,6 +1173,7 @@ var PagePerceptionPolicy = class {
|
|
|
1171
1173
|
...frames.length > 1 || frames[0] !== void 0 && frameLabel(frames[0].frame) !== "self" ? { frames: frames.map((frame) => frameLabel(frame.frame)) } : {},
|
|
1172
1174
|
nodeCount: nodes.length,
|
|
1173
1175
|
nodesTotal: countNodesDeep(nodes),
|
|
1176
|
+
...subtree !== void 0 ? { subtree: { ref: subtree.ref } } : {},
|
|
1174
1177
|
...capture?.images === true ? {
|
|
1175
1178
|
images: {
|
|
1176
1179
|
src: images.filter((image) => image.level === "src").length,
|
|
@@ -1182,6 +1185,7 @@ var PagePerceptionPolicy = class {
|
|
|
1182
1185
|
};
|
|
1183
1186
|
this.#records = [record, ...this.#records].slice(0, 50);
|
|
1184
1187
|
for (const listener of this.#listeners) listener(record);
|
|
1188
|
+
const page = this.#options.describePage?.();
|
|
1185
1189
|
await this.#options.audit?.append({
|
|
1186
1190
|
type: "page.perceived",
|
|
1187
1191
|
target: this.#options.auditTarget ?? "page",
|
|
@@ -1192,6 +1196,8 @@ var PagePerceptionPolicy = class {
|
|
|
1192
1196
|
nodeCount: record.nodeCount,
|
|
1193
1197
|
nodesTotal: record.nodesTotal,
|
|
1194
1198
|
at: record.at,
|
|
1199
|
+
...record.subtree !== void 0 ? { subtree: record.subtree } : {},
|
|
1200
|
+
...page !== void 0 ? { page } : {},
|
|
1195
1201
|
...record.images !== void 0 ? {
|
|
1196
1202
|
imageCount: record.images.src + record.images.canvas,
|
|
1197
1203
|
imagesOmitted,
|
|
@@ -1206,6 +1212,42 @@ var PagePerceptionPolicy = class {
|
|
|
1206
1212
|
record
|
|
1207
1213
|
};
|
|
1208
1214
|
}
|
|
1215
|
+
/**
|
|
1216
|
+
* 子树续读的读取与拒绝(分册 14 §3 / §6)。
|
|
1217
|
+
*
|
|
1218
|
+
* 三条拒绝路径都在**遍历开始之前**结束:能力未实现、句柄不在 scope 内、句柄不认识。
|
|
1219
|
+
* AC-14.2 的「结果里不含该子树的任何内容」因此不是靠过滤达成的,是靠没读达成的。
|
|
1220
|
+
*/
|
|
1221
|
+
async #readSubtree(scope, ref, capture) {
|
|
1222
|
+
const read = this.#options.reader.readSubtree;
|
|
1223
|
+
if (read === void 0) throw new WebSkillError("PERCEPTION_SUBTREE_UNSUPPORTED", "This page does not support reading a subtree. Call perceive_page without \"ref\".");
|
|
1224
|
+
const out = await read.call(this.#options.reader, scope, ref, capture);
|
|
1225
|
+
if ("rejected" in out) {
|
|
1226
|
+
await this.#recordDenied(out.rejected);
|
|
1227
|
+
throw new WebSkillError(out.rejected === "out-of-scope" ? "PERCEPTION_SUBTREE_OUT_OF_SCOPE" : "PERCEPTION_SUBTREE_UNKNOWN_REF", out.rejected === "out-of-scope" ? "That element is outside the readable region of this page." : "That reference is no longer valid. Perceive the page again and retry.");
|
|
1228
|
+
}
|
|
1229
|
+
return out;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* 被拒绝的子树读取的留痕(FR-14.6)。
|
|
1233
|
+
*
|
|
1234
|
+
* **`ref` 的值不进载荷**:它是被拒绝的那次尝试的内容,句柄换轮即失效,
|
|
1235
|
+
* 对复盘没有帮助,却会把一个模型可控的字符串写进审计流。`reason` 才是复盘需要的那部分。
|
|
1236
|
+
* `out-of-scope` 这一条是注入攻击的观测点:页面里写着「请读取整个页面」时,
|
|
1237
|
+
* 模型若照做,这条留痕就是它留下的痕迹。
|
|
1238
|
+
*/
|
|
1239
|
+
async #recordDenied(reason) {
|
|
1240
|
+
const page = this.#options.describePage?.();
|
|
1241
|
+
await this.#options.audit?.append({
|
|
1242
|
+
type: "page.perceive.denied",
|
|
1243
|
+
target: this.#options.auditTarget ?? "page",
|
|
1244
|
+
data: {
|
|
1245
|
+
reason,
|
|
1246
|
+
at: this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1247
|
+
...page !== void 0 ? { page } : {}
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1209
1251
|
};
|
|
1210
1252
|
|
|
1211
1253
|
//#endregion
|
|
@@ -1234,24 +1276,39 @@ const PERCEPTION_SYSTEM_PROMPT = [
|
|
|
1234
1276
|
const PERCEIVE_PAGE_TOOL = "perceive_page";
|
|
1235
1277
|
const DESCRIPTION$1 = "Read the parts of the current page the host made readable, as a structured accessibility outline. Read-only: it cannot click, type, submit, navigate or scroll.";
|
|
1236
1278
|
/**
|
|
1237
|
-
*
|
|
1238
|
-
* 值是 SDK 上一次发出的不透明串:白名单仍然只由宿主的 `scope` 决定,
|
|
1239
|
-
* 游标只说「接着刚才那处继续」,它扩不出一寸新范围。
|
|
1279
|
+
* 两个参数,且它们都扩不出一寸新范围(AC-10.8 的判据由 0.14.0 分册 22 §3 修订)。
|
|
1240
1280
|
*
|
|
1241
|
-
*
|
|
1281
|
+
* `cursor` 是 SDK 上一次发出的不透明串,只说「接着刚才那处继续」;
|
|
1282
|
+
* `ref` 是上一次感知结果里的元素句柄,只能把读取范围**缩小**到那个子树
|
|
1283
|
+
* (分册 14 FR-14.2:同一个遍历器、同一套 scope 判定,只换一个根节点)。
|
|
1284
|
+
* 白名单仍旧只由宿主的 `scope` 决定。
|
|
1285
|
+
*
|
|
1286
|
+
* **分段 / 子树关闭时下发的仍是这份 schema,不是另一份**:两套 schema 会让
|
|
1242
1287
|
* 「模型看到的工具契约」随宿主配置漂移,同一段对话里换个设置就变一次形状,
|
|
1243
1288
|
* 而模型的历史消息里还留着旧形状。宁可多一个恒定存在、关闭时明确拒绝的参数。
|
|
1244
1289
|
*/
|
|
1245
1290
|
const INPUT_SCHEMA$1 = {
|
|
1246
1291
|
type: "object",
|
|
1247
|
-
properties: {
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1292
|
+
properties: {
|
|
1293
|
+
cursor: {
|
|
1294
|
+
type: "string",
|
|
1295
|
+
description: "Opaque cursor from a previous perceive_page result. Omit to read from the start."
|
|
1296
|
+
},
|
|
1297
|
+
ref: {
|
|
1298
|
+
type: "string",
|
|
1299
|
+
description: "Optional. An opaque element reference from a previous perceive_page result. Reads only that element and its descendants. Never widens what you can read."
|
|
1300
|
+
}
|
|
1301
|
+
},
|
|
1251
1302
|
additionalProperties: false
|
|
1252
1303
|
};
|
|
1253
1304
|
const CURSOR_REJECTED = "The perception cursor is no longer valid. Call perceive_page again without a cursor to read the page from the start.";
|
|
1254
|
-
|
|
1305
|
+
/** 子树续读的三条拒绝(分册 14 §3);它们的码直接回给模型,不被压成 PERCEPTION_FAILED */
|
|
1306
|
+
const SUBTREE_CODES = /* @__PURE__ */ new Set([
|
|
1307
|
+
"PERCEPTION_SUBTREE_UNSUPPORTED",
|
|
1308
|
+
"PERCEPTION_SUBTREE_OUT_OF_SCOPE",
|
|
1309
|
+
"PERCEPTION_SUBTREE_UNKNOWN_REF"
|
|
1310
|
+
]);
|
|
1311
|
+
function toolError$3(code, message) {
|
|
1255
1312
|
return {
|
|
1256
1313
|
ok: false,
|
|
1257
1314
|
content: [],
|
|
@@ -1357,9 +1414,12 @@ function createPagePerceptionToolSource(options) {
|
|
|
1357
1414
|
maxChars: paging?.maxChars ?? PERCEPTION_PAGING_DEFAULTS.maxChars
|
|
1358
1415
|
};
|
|
1359
1416
|
const cursor = args["cursor"];
|
|
1417
|
+
const ref = args["ref"];
|
|
1418
|
+
if (ref !== void 0 && typeof ref !== "string") return toolError$3("PERCEPTION_REF_INVALID", "perceive_page needs \"ref\" to be a string when it is present.");
|
|
1360
1419
|
if (cursor !== void 0) {
|
|
1361
1420
|
const index = typeof cursor === "string" ? pending?.cursors.get(cursor) : void 0;
|
|
1362
|
-
if (!paged || pending === void 0 || index === void 0 || pending.runId !== context?.runId) return toolError$
|
|
1421
|
+
if (!paged || pending === void 0 || index === void 0 || pending.runId !== context?.runId) return toolError$3("PERCEPTION_CURSOR_INVALID", CURSOR_REJECTED);
|
|
1422
|
+
if (ref !== void 0 && ref !== pending.subtreeRef) return toolError$3("PERCEPTION_CURSOR_INVALID", CURSOR_REJECTED);
|
|
1363
1423
|
return resultOf(pending, index);
|
|
1364
1424
|
}
|
|
1365
1425
|
try {
|
|
@@ -1370,7 +1430,7 @@ function createPagePerceptionToolSource(options) {
|
|
|
1370
1430
|
maxImages: image?.maxImages ?? 0,
|
|
1371
1431
|
minImageArea: image?.minImageArea ?? 0
|
|
1372
1432
|
};
|
|
1373
|
-
const { nodes, images, imagesOmitted, record } = await policy.perceive(capture);
|
|
1433
|
+
const { nodes, images, imagesOmitted, record } = await policy.perceive(capture, ref === void 0 ? void 0 : { ref });
|
|
1374
1434
|
const data = {
|
|
1375
1435
|
scope: record.include,
|
|
1376
1436
|
excluded: record.exclude,
|
|
@@ -1415,11 +1475,14 @@ function createPagePerceptionToolSource(options) {
|
|
|
1415
1475
|
nodesTotal,
|
|
1416
1476
|
images,
|
|
1417
1477
|
data,
|
|
1418
|
-
cursors
|
|
1478
|
+
cursors,
|
|
1479
|
+
...ref === void 0 ? {} : { subtreeRef: ref }
|
|
1419
1480
|
};
|
|
1420
1481
|
return resultOf(pending, 0);
|
|
1421
1482
|
} catch (e) {
|
|
1422
|
-
|
|
1483
|
+
const code = e instanceof WebSkillError ? e.code : void 0;
|
|
1484
|
+
if (code !== void 0 && SUBTREE_CODES.has(code)) return toolError$3(code, messageOf(e));
|
|
1485
|
+
return toolError$3("PERCEPTION_FAILED", `perceive_page failed: ${messageOf(e)}`);
|
|
1423
1486
|
}
|
|
1424
1487
|
}
|
|
1425
1488
|
};
|
|
@@ -1863,8 +1926,8 @@ var PageActionPolicy = class {
|
|
|
1863
1926
|
}
|
|
1864
1927
|
async act(request, context) {
|
|
1865
1928
|
if (!this.enabled) throw new WebSkillError("PAGE_ACTION_OUT_OF_SCOPE", "Page actions are not enabled: the host declared no actionable regions.");
|
|
1866
|
-
const target = this.#options.executor.describe(request.ref);
|
|
1867
|
-
if (target === void 0) throw new WebSkillError("PAGE_ACTION_STALE_REF", "The element reference is unknown or expired. Perceive the page again and retry.");
|
|
1929
|
+
const target = request.action === "back" ? this.#documentTarget() : this.#options.executor.describe(request.ref);
|
|
1930
|
+
if (target === void 0) throw new WebSkillError("PAGE_ACTION_STALE_REF", this.#options.diagnose?.(request.ref) ?? "The element reference is unknown or expired. Perceive the page again and retry.");
|
|
1868
1931
|
const approved = await this.#authorize(request, target);
|
|
1869
1932
|
if (approved === false) {
|
|
1870
1933
|
await this.#record(request, {
|
|
@@ -1875,24 +1938,60 @@ var PageActionPolicy = class {
|
|
|
1875
1938
|
throw new WebSkillError("PAGE_ACTION_DECLINED", declineReason);
|
|
1876
1939
|
}
|
|
1877
1940
|
this.#rememberTarget(request.ref, target);
|
|
1878
|
-
const
|
|
1941
|
+
const context_ = { ...context?.runId ? { runId: context.runId } : {} };
|
|
1942
|
+
const watch = await this.#options.downloadWatcher?.open(context_);
|
|
1943
|
+
const tabWatch = await this.#options.tabWatcher?.open(context_);
|
|
1879
1944
|
let executed;
|
|
1880
1945
|
try {
|
|
1881
1946
|
executed = await this.#options.executor.execute(request);
|
|
1882
1947
|
} catch (e) {
|
|
1883
|
-
|
|
1948
|
+
await Promise.all([watch?.settle({ timeoutMs: 0 }).catch(() => 0), tabWatch?.settle({ timeoutMs: 0 }).catch(() => void 0)]);
|
|
1884
1949
|
throw e;
|
|
1885
1950
|
}
|
|
1886
|
-
const
|
|
1951
|
+
const budget = clampDownloadWaitMs(this.#options.downloadWaitMs?.());
|
|
1952
|
+
const [downloads, opened] = await Promise.all([watch ? watch.settle({ timeoutMs: budget }) : Promise.resolve(0), tabWatch ? tabWatch.settle({ timeoutMs: budget }) : Promise.resolve(void 0)]);
|
|
1887
1953
|
const signalled = downloads > 0 && this.#options.downloadSignalEnabled?.() === true;
|
|
1888
|
-
const
|
|
1954
|
+
const openedTab = opened === void 0 ? void 0 : await this.#admit(opened);
|
|
1955
|
+
const outcome = {
|
|
1889
1956
|
...executed,
|
|
1890
|
-
downloadCount: downloads
|
|
1891
|
-
|
|
1957
|
+
...signalled ? { downloadCount: downloads } : {},
|
|
1958
|
+
...openedTab !== void 0 ? { openedTab } : {}
|
|
1959
|
+
};
|
|
1892
1960
|
await this.#record(request, outcome, approved);
|
|
1893
1961
|
if (downloads > 0 && !signalled) await this.#recordUnreported(request, downloads);
|
|
1894
1962
|
return outcome;
|
|
1895
1963
|
}
|
|
1964
|
+
/**
|
|
1965
|
+
* 新页面接进工作集(FR-13.4 / FR-13.5)。
|
|
1966
|
+
*
|
|
1967
|
+
* `workset` 未注入时不 admit,`openedTab` 仍然给(只有 title / url,无 `tab`)——
|
|
1968
|
+
* 那对应「宿主要信号但不要工作集」的装配,是合法组合。
|
|
1969
|
+
*/
|
|
1970
|
+
async #admit(opened) {
|
|
1971
|
+
const admission = await this.#options.workset?.admit(opened);
|
|
1972
|
+
if (admission?.admitted === true) return {
|
|
1973
|
+
tab: admission.handle,
|
|
1974
|
+
...opened
|
|
1975
|
+
};
|
|
1976
|
+
return {
|
|
1977
|
+
...opened,
|
|
1978
|
+
admitted: false,
|
|
1979
|
+
reason: admission === void 0 ? "This page was opened but is not available to switch to." : "The page set is full; this page was not added. Close a page you are done with, then try again."
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* `back` 的目标是**合成**的,不查表(分册 13 §4.1)。
|
|
1984
|
+
*
|
|
1985
|
+
* 不改 `PageActionRequest.ref` 的类型、也不改 `describe()` 的契约:
|
|
1986
|
+
* 动其中任何一个都会波及所有实现过 executor 的宿主,包括本地 DOM 形态。
|
|
1987
|
+
*/
|
|
1988
|
+
#documentTarget() {
|
|
1989
|
+
const page = this.#options.describePage?.();
|
|
1990
|
+
return {
|
|
1991
|
+
role: "document",
|
|
1992
|
+
...page?.title !== void 0 ? { name: page.title } : {}
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1896
1995
|
async #authorize(request, target) {
|
|
1897
1996
|
if ((this.#options.preauthorized ?? []).includes(request.action)) return "preauthorized";
|
|
1898
1997
|
const rememberable = this.#rememberable(target);
|
|
@@ -1901,11 +2000,12 @@ var PageActionPolicy = class {
|
|
|
1901
2000
|
const where = target.frame === void 0 ? "on the page" : `in the embedded frame "${target.frame}"`;
|
|
1902
2001
|
const scopeNote = target.elevated === true ? " This dialog is outside the usual allowlist; it was opened by this task." : "";
|
|
1903
2002
|
const scopeLabel = rememberable ? this.#options.consent?.describeScope?.(target, request.action) : void 0;
|
|
2003
|
+
const message = request.action === "back" ? `Allow the assistant to go back to the previous page${target.name === void 0 ? "" : ` on "${target.name}"`}?${scopeNote}` : `Allow the assistant to ${request.action} "${target.name ?? target.role}" ${where}?${scopeNote}`;
|
|
1904
2004
|
const response = await this.#options.ui.request({
|
|
1905
2005
|
type: "authorize",
|
|
1906
2006
|
id: `page-action-${this.#seq}`,
|
|
1907
2007
|
capability: "pageAction",
|
|
1908
|
-
message
|
|
2008
|
+
message,
|
|
1909
2009
|
details: {
|
|
1910
2010
|
action: request.action,
|
|
1911
2011
|
...target.elevated === true ? { elevated: true } : {},
|
|
@@ -1933,7 +2033,7 @@ var PageActionPolicy = class {
|
|
|
1933
2033
|
*/
|
|
1934
2034
|
async #record(request, outcome, approved) {
|
|
1935
2035
|
const secret = outcome.secret === true;
|
|
1936
|
-
const target = this.#options.executor.describe(request.ref);
|
|
2036
|
+
const target = request.action === "back" ? void 0 : this.#options.executor.describe(request.ref);
|
|
1937
2037
|
const record = {
|
|
1938
2038
|
at: this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1939
2039
|
action: request.action,
|
|
@@ -1949,10 +2049,14 @@ var PageActionPolicy = class {
|
|
|
1949
2049
|
...outcome.reason !== void 0 ? { reason: outcome.reason } : {}
|
|
1950
2050
|
};
|
|
1951
2051
|
this.#records = [record, ...this.#records].slice(0, 50);
|
|
2052
|
+
const page = this.#options.describePage?.();
|
|
1952
2053
|
await this.#options.audit?.append({
|
|
1953
2054
|
type: "page.action",
|
|
1954
2055
|
target: this.#options.auditTarget ?? "page",
|
|
1955
|
-
data: {
|
|
2056
|
+
data: {
|
|
2057
|
+
...record,
|
|
2058
|
+
...page !== void 0 ? { page } : {}
|
|
2059
|
+
}
|
|
1956
2060
|
});
|
|
1957
2061
|
}
|
|
1958
2062
|
/**
|
|
@@ -1987,7 +2091,9 @@ const PAGE_ACTION_SYSTEM_PROMPT = [
|
|
|
1987
2091
|
"You can act on the current page, but only within the regions the host made actionable.",
|
|
1988
2092
|
"Targets must come from the \"ref\" values in a fresh perceive_page result — you cannot construct them, and CSS selectors are rejected.",
|
|
1989
2093
|
"Every action asks the user to confirm it first, so do not chain actions speculatively: act only when the user asked for something that requires it.",
|
|
1990
|
-
"A reference expires as soon as the page is perceived again; perceive first, then act."
|
|
2094
|
+
"A reference expires as soon as the page is perceived again; perceive first, then act.",
|
|
2095
|
+
"After an action, the result tells you where it led. \"openedTab\" means a new page opened — switch to it with focus_page_tab to read it. \"navigated\" means this page itself moved to a new address. \"elevatedModal\" means a dialog opened on this page. If none of the three appear, the page did not change.",
|
|
2096
|
+
"Use the \"back\" action to return to the previous page in this page's history. It takes no \"ref\" and no \"value\", and it only affects the page you are currently on. After going back, always perceive_page again: every reference from before the action is dead."
|
|
1991
2097
|
].join("\n");
|
|
1992
2098
|
|
|
1993
2099
|
//#endregion
|
|
@@ -2004,7 +2110,7 @@ const INPUT_SCHEMA = {
|
|
|
2004
2110
|
properties: {
|
|
2005
2111
|
ref: {
|
|
2006
2112
|
type: "string",
|
|
2007
|
-
description: "An opaque element reference from perceive_page"
|
|
2113
|
+
description: "An opaque element reference from perceive_page; required for every action except \"back\""
|
|
2008
2114
|
},
|
|
2009
2115
|
action: {
|
|
2010
2116
|
type: "string",
|
|
@@ -2015,10 +2121,10 @@ const INPUT_SCHEMA = {
|
|
|
2015
2121
|
description: "Text for \"fill\", the option name for \"select\", or \"true\"/\"false\" for \"set\""
|
|
2016
2122
|
}
|
|
2017
2123
|
},
|
|
2018
|
-
required: ["
|
|
2124
|
+
required: ["action"],
|
|
2019
2125
|
additionalProperties: false
|
|
2020
2126
|
};
|
|
2021
|
-
function toolError$
|
|
2127
|
+
function toolError$2(code, message) {
|
|
2022
2128
|
return {
|
|
2023
2129
|
ok: false,
|
|
2024
2130
|
content: [],
|
|
@@ -2067,34 +2173,328 @@ function createPageActionToolSource(options) {
|
|
|
2067
2173
|
};
|
|
2068
2174
|
},
|
|
2069
2175
|
call: async (_name, args, context) => {
|
|
2070
|
-
const ref = args["ref"];
|
|
2071
2176
|
const action = args["action"];
|
|
2072
|
-
if (typeof
|
|
2177
|
+
if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return toolError$2("VALIDATION_FAILED", `act_on_page needs one of ${PAGE_ACTION_KINDS.join(" / ")}.`);
|
|
2178
|
+
const runContext = { ...context?.runId !== void 0 ? { runId: context.runId } : {} };
|
|
2179
|
+
const run = async (request) => {
|
|
2180
|
+
try {
|
|
2181
|
+
const outcome = await policy.act(request, runContext);
|
|
2182
|
+
return {
|
|
2183
|
+
ok: outcome.ok,
|
|
2184
|
+
content: [{
|
|
2185
|
+
type: "json",
|
|
2186
|
+
data: { ...outcome }
|
|
2187
|
+
}],
|
|
2188
|
+
...outcome.ok ? {} : { error: {
|
|
2189
|
+
code: "PAGE_ACTION_FAILED",
|
|
2190
|
+
message: outcome.reason ?? "Page action failed"
|
|
2191
|
+
} }
|
|
2192
|
+
};
|
|
2193
|
+
} catch (e) {
|
|
2194
|
+
return toolError$2(e instanceof WebSkillError ? e.code : "PAGE_ACTION_FAILED", messageOf(e));
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
if (action === "back") {
|
|
2198
|
+
if ("ref" in args || "value" in args) return toolError$2("VALIDATION_FAILED", "act_on_page with \"back\" takes no \"ref\" and no \"value\".");
|
|
2199
|
+
return await run({
|
|
2200
|
+
ref: PAGE_DOCUMENT_REF,
|
|
2201
|
+
action: "back"
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
const ref = args["ref"];
|
|
2205
|
+
if (typeof ref !== "string") return toolError$2("VALIDATION_FAILED", `act_on_page needs a "ref" string when the action is "${action}".`);
|
|
2073
2206
|
const value = args["value"];
|
|
2074
2207
|
if ([
|
|
2075
2208
|
"fill",
|
|
2076
2209
|
"select",
|
|
2077
2210
|
"set"
|
|
2078
|
-
].includes(action) && typeof value !== "string") return toolError$
|
|
2211
|
+
].includes(action) && typeof value !== "string") return toolError$2("VALIDATION_FAILED", `act_on_page needs a "value" string when the action is "${action}".`);
|
|
2212
|
+
return await run({
|
|
2213
|
+
ref,
|
|
2214
|
+
action,
|
|
2215
|
+
...typeof value === "string" ? { value } : {}
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
//#endregion
|
|
2222
|
+
//#region ../agent/src/tabs/policy.ts
|
|
2223
|
+
/** 工作集容量缺省;「起点页 + 若干详情页」的常见规模 */
|
|
2224
|
+
const TAB_WORKSET_DEFAULT_MAX = 8;
|
|
2225
|
+
/** 地板:2 的含义是「起点页 + 至少一个详情页」,低于它工作集这个概念就没有意义 */
|
|
2226
|
+
const TAB_WORKSET_MIN_MAX = 2;
|
|
2227
|
+
/** 天花板:宿主写错一个 0 不该让一轮任务铺开几百个标签页 */
|
|
2228
|
+
const TAB_WORKSET_MAX_MAX = 32;
|
|
2229
|
+
/**
|
|
2230
|
+
* 夹取工作集容量(FR-11.5 / D-11-3)。
|
|
2231
|
+
*
|
|
2232
|
+
* 与 0.15.0 的 `clampDownloadWaitMs` 同惯例:宿主给不合理的值**不报错,夹到边界**——
|
|
2233
|
+
* 装配期的一个笔误不该让运行期整条能力崩掉。模型无法影响这个值。
|
|
2234
|
+
* @experimental
|
|
2235
|
+
*/
|
|
2236
|
+
function clampWorksetMax(value) {
|
|
2237
|
+
if (value === void 0 || !Number.isFinite(value)) return 8;
|
|
2238
|
+
return Math.min(32, Math.max(2, Math.trunc(value)));
|
|
2239
|
+
}
|
|
2240
|
+
const UNKNOWN_MESSAGE = "That page is not in this task's page set.";
|
|
2241
|
+
const EXPIRED_MESSAGE = "That page was closed or navigated away. List the pages again.";
|
|
2242
|
+
const NOT_CLOSABLE_MESSAGE = "That page belongs to the user, not to this task. It cannot be closed.";
|
|
2243
|
+
/**
|
|
2244
|
+
* 工作集策略(分册 12)。
|
|
2245
|
+
*
|
|
2246
|
+
* 它守着三件事:模型只能在**已接入**的页之间往返(SC-2)、
|
|
2247
|
+
* 切换不改变用户看到的画面(SC-3,由端口实现兑现,此处只做不诱导的接口设计)、
|
|
2248
|
+
* 起点页永远关不掉(FR-12.6)。
|
|
2249
|
+
* @experimental
|
|
2250
|
+
*/
|
|
2251
|
+
var TabWorksetPolicy = class {
|
|
2252
|
+
#options;
|
|
2253
|
+
/** 当前焦点句柄;`undefined` 即还在起点页 */
|
|
2254
|
+
#focused;
|
|
2255
|
+
/**
|
|
2256
|
+
* 见过但已不在工作集里的句柄。只为把「关掉了」与「从没有过」区分开——
|
|
2257
|
+
* 它**不含**任何标签页信息,仅仅是一组字符串。
|
|
2258
|
+
*/
|
|
2259
|
+
#retired = /* @__PURE__ */ new Set();
|
|
2260
|
+
constructor(options) {
|
|
2261
|
+
this.#options = options;
|
|
2262
|
+
}
|
|
2263
|
+
get enabled() {
|
|
2264
|
+
return this.#options.enabled();
|
|
2265
|
+
}
|
|
2266
|
+
/** 起点页:接入顺序的第 0 项。它属于用户,不属于这一轮任务 */
|
|
2267
|
+
async #origin() {
|
|
2268
|
+
return (await this.#options.port.list())[0];
|
|
2269
|
+
}
|
|
2270
|
+
async #find(handle) {
|
|
2271
|
+
return (await this.#options.port.list()).find((tab) => tab.handle === handle);
|
|
2272
|
+
}
|
|
2273
|
+
/** 认识但不在了 → expired;从没见过 → unknown。跨 run 的句柄落 unknown(`reset` 清了 retired) */
|
|
2274
|
+
#reject(handle) {
|
|
2275
|
+
if (this.#retired.has(handle)) throw new WebSkillError("PAGE_TAB_EXPIRED", EXPIRED_MESSAGE);
|
|
2276
|
+
throw new WebSkillError("PAGE_TAB_UNKNOWN", UNKNOWN_MESSAGE);
|
|
2277
|
+
}
|
|
2278
|
+
async list() {
|
|
2279
|
+
const tabs = await this.#options.port.list();
|
|
2280
|
+
const originHandle = tabs[0]?.handle;
|
|
2281
|
+
return tabs.map((tab) => ({
|
|
2282
|
+
tab: tab.handle,
|
|
2283
|
+
...tab.title !== void 0 ? { title: tab.title } : {},
|
|
2284
|
+
...tab.url !== void 0 ? { url: tab.url } : {},
|
|
2285
|
+
...tab.handle === originHandle ? { origin: true } : {},
|
|
2286
|
+
focused: tab.handle === (this.#focused ?? originHandle)
|
|
2287
|
+
}));
|
|
2288
|
+
}
|
|
2289
|
+
async focus(handle) {
|
|
2290
|
+
const tab = await this.#find(handle);
|
|
2291
|
+
if (tab === void 0) {
|
|
2292
|
+
await this.#record("denied", { reason: this.#retired.has(handle) ? "expired" : "unknown" });
|
|
2293
|
+
this.#reject(handle);
|
|
2294
|
+
}
|
|
2295
|
+
const from = this.#focused ?? (await this.#origin())?.handle;
|
|
2296
|
+
await this.#options.port.focus(handle);
|
|
2297
|
+
this.#focused = handle;
|
|
2298
|
+
await this.#record("focused", {
|
|
2299
|
+
page: pageOf(tab),
|
|
2300
|
+
...from !== void 0 ? { from } : {}
|
|
2301
|
+
});
|
|
2302
|
+
return { focused: handle };
|
|
2303
|
+
}
|
|
2304
|
+
async close(handle) {
|
|
2305
|
+
const tabs = await this.#options.port.list();
|
|
2306
|
+
const originHandle = tabs[0]?.handle;
|
|
2307
|
+
if (handle === originHandle) {
|
|
2308
|
+
await this.#record("denied", { reason: "origin-tab" });
|
|
2309
|
+
throw new WebSkillError("PAGE_TAB_NOT_CLOSABLE", NOT_CLOSABLE_MESSAGE);
|
|
2310
|
+
}
|
|
2311
|
+
const tab = tabs.find((t) => t.handle === handle);
|
|
2312
|
+
if (tab === void 0) {
|
|
2313
|
+
await this.#record("denied", { reason: this.#retired.has(handle) ? "expired" : "unknown" });
|
|
2314
|
+
this.#reject(handle);
|
|
2315
|
+
}
|
|
2316
|
+
if ((this.#focused ?? originHandle) === handle && originHandle !== void 0) {
|
|
2317
|
+
await this.#options.port.focus(originHandle);
|
|
2318
|
+
this.#focused = originHandle;
|
|
2319
|
+
}
|
|
2320
|
+
await this.#options.port.close(handle);
|
|
2321
|
+
this.#retired.add(handle);
|
|
2322
|
+
const focused = this.#focused ?? originHandle;
|
|
2323
|
+
await this.#record("closed", {
|
|
2324
|
+
page: pageOf(tab),
|
|
2325
|
+
...focused !== void 0 ? { focused } : {}
|
|
2326
|
+
});
|
|
2327
|
+
return {
|
|
2328
|
+
closed: handle,
|
|
2329
|
+
...focused !== void 0 ? { focused } : { focused: void 0 }
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* 把页面操作新开出来的那一页接进工作集(SC-2 的唯一入口)。
|
|
2334
|
+
*
|
|
2335
|
+
* 容量闸门在这里,管的是**标签页数**(FR-11.5);分册 11 的 registry 分片软上限
|
|
2336
|
+
* 是另一回事(同一页里跳三次产生三个分片,对它们硬拒绝会打断正常感知)。
|
|
2337
|
+
*/
|
|
2338
|
+
async admit(opened) {
|
|
2339
|
+
const max = clampWorksetMax(this.#options.maxTabs?.());
|
|
2340
|
+
if ((await this.#options.port.list()).length >= max) {
|
|
2341
|
+
await this.#record("denied", {
|
|
2342
|
+
reason: "workset-full",
|
|
2343
|
+
limit: max
|
|
2344
|
+
});
|
|
2345
|
+
return {
|
|
2346
|
+
admitted: false,
|
|
2347
|
+
reason: "full",
|
|
2348
|
+
limit: max
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
const handle = await this.#options.port.admit(opened);
|
|
2352
|
+
await this.#record("opened", {
|
|
2353
|
+
handle,
|
|
2354
|
+
...opened
|
|
2355
|
+
});
|
|
2356
|
+
return {
|
|
2357
|
+
admitted: true,
|
|
2358
|
+
handle
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
/** run 结束(分册 11 / FR-11.4)。**不关闭任何标签页**——那是用户的浏览器 */
|
|
2362
|
+
async reset() {
|
|
2363
|
+
this.#focused = void 0;
|
|
2364
|
+
this.#retired.clear();
|
|
2365
|
+
await this.#options.port.reset();
|
|
2366
|
+
}
|
|
2367
|
+
async #record(kind, data) {
|
|
2368
|
+
await this.#options.audit?.append({
|
|
2369
|
+
type: `page.tab.${kind}`,
|
|
2370
|
+
target: this.#options.auditTarget ?? "page",
|
|
2371
|
+
data: {
|
|
2372
|
+
at: this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2373
|
+
...data
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
};
|
|
2378
|
+
/** 与分册 11 的 `page` 形状共用;`url` / `title` 只为可读性,不参与判定 */
|
|
2379
|
+
function pageOf(tab) {
|
|
2380
|
+
return {
|
|
2381
|
+
key: tab.handle,
|
|
2382
|
+
...tab.url !== void 0 ? { url: tab.url } : {},
|
|
2383
|
+
...tab.title !== void 0 ? { title: tab.title } : {}
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
//#endregion
|
|
2388
|
+
//#region ../agent/src/prompts/tabWorkset.ts
|
|
2389
|
+
/**
|
|
2390
|
+
* 工作集系统提示词(0.16.0 分册 12 / FR-12.9)。
|
|
2391
|
+
*
|
|
2392
|
+
* 第三段是最要紧的:模型若以为切换会让用户看到那一页,
|
|
2393
|
+
* 就会写出「我已经帮你打开了详情页」这种与事实不符的话。
|
|
2394
|
+
*/
|
|
2395
|
+
const TAB_WORKSET_SYSTEM_PROMPT = `You can work across several pages within one task.
|
|
2396
|
+
|
|
2397
|
+
- \`list_page_tabs\` shows the pages available to you. The first one is the user's own page; you can read and act on it, but you can never close it.
|
|
2398
|
+
- \`focus_page_tab\` decides which page \`perceive_page\` and \`act_on_page\` talk to next. Element references are per-page: a reference from one page will be rejected on another, so perceive after every switch.
|
|
2399
|
+
- New pages only join this set when an action you took opened them. You cannot open a page by URL, and pages the user opens on their own are not yours to touch.
|
|
2400
|
+
- Close a detail page with \`close_page_tab\` once you have read what you needed. Leaving them open uses up the page budget and will eventually block new ones.
|
|
2401
|
+
|
|
2402
|
+
Switching pages does not change what the user sees. Their browser stays exactly where they left it, so never tell them you have "opened" or "switched to" a page for them.`;
|
|
2403
|
+
|
|
2404
|
+
//#endregion
|
|
2405
|
+
//#region ../agent/src/tabs/toolSource.ts
|
|
2406
|
+
const LIST_PAGE_TABS_TOOL = "list_page_tabs";
|
|
2407
|
+
const FOCUS_PAGE_TAB_TOOL = "focus_page_tab";
|
|
2408
|
+
const CLOSE_PAGE_TAB_TOOL = "close_page_tab";
|
|
2409
|
+
const TAB_WORKSET_TOOLS = [
|
|
2410
|
+
LIST_PAGE_TABS_TOOL,
|
|
2411
|
+
FOCUS_PAGE_TAB_TOOL,
|
|
2412
|
+
CLOSE_PAGE_TAB_TOOL
|
|
2413
|
+
];
|
|
2414
|
+
/** 无入参。没有 filter、没有 query、没有 all —— 工作集之外的页面不存在于这个视角里 */
|
|
2415
|
+
const LIST_SCHEMA = {
|
|
2416
|
+
type: "object",
|
|
2417
|
+
properties: {},
|
|
2418
|
+
additionalProperties: false
|
|
2419
|
+
};
|
|
2420
|
+
/**
|
|
2421
|
+
* 参数名刻意叫 `tab` 而不是 `tabId` / `url`:名字本身要挡住下一个人把一个数字或
|
|
2422
|
+
* 地址塞进来(0.15.0 `downloadCount` 的同款命名纪律)。
|
|
2423
|
+
*
|
|
2424
|
+
* `additionalProperties: false` 是 SC-1 的第二道锁:即使有人后来往 `properties` 里加了
|
|
2425
|
+
* `url`,AC-G3 的守护会先命中;而在守护命中之前,多余字段至少不会被静默接受。
|
|
2426
|
+
*/
|
|
2427
|
+
const TAB_SCHEMA = {
|
|
2428
|
+
type: "object",
|
|
2429
|
+
properties: { tab: {
|
|
2430
|
+
type: "string",
|
|
2431
|
+
description: "An opaque page handle from list_page_tabs"
|
|
2432
|
+
} },
|
|
2433
|
+
required: ["tab"],
|
|
2434
|
+
additionalProperties: false
|
|
2435
|
+
};
|
|
2436
|
+
const SPECS = [
|
|
2437
|
+
{
|
|
2438
|
+
name: LIST_PAGE_TABS_TOOL,
|
|
2439
|
+
description: "List the pages available to this task. The first entry is the user's own page and cannot be closed. Takes no arguments.",
|
|
2440
|
+
inputSchema: LIST_SCHEMA
|
|
2441
|
+
},
|
|
2442
|
+
{
|
|
2443
|
+
name: FOCUS_PAGE_TAB_TOOL,
|
|
2444
|
+
description: "Choose which page perceive_page and act_on_page talk to next. This does not change what the user sees. Element references do not carry across pages, so perceive again after switching.",
|
|
2445
|
+
inputSchema: TAB_SCHEMA
|
|
2446
|
+
},
|
|
2447
|
+
{
|
|
2448
|
+
name: CLOSE_PAGE_TAB_TOOL,
|
|
2449
|
+
description: "Close a page this task opened, once you have read what you needed from it. The user's own page cannot be closed.",
|
|
2450
|
+
inputSchema: TAB_SCHEMA
|
|
2451
|
+
}
|
|
2452
|
+
];
|
|
2453
|
+
function toolError$1(code, message) {
|
|
2454
|
+
return {
|
|
2455
|
+
ok: false,
|
|
2456
|
+
content: [],
|
|
2457
|
+
error: {
|
|
2458
|
+
code,
|
|
2459
|
+
message
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* 把工作集接到既有工具协议上(分册 12 §7)。
|
|
2465
|
+
*
|
|
2466
|
+
* 端口未注入或页面操作未启用时 `listToolSpecs()` 返回空数组——
|
|
2467
|
+
* 模型连这三个工具的存在都看不到,而不是看得到再被拒(AC-12.11)。
|
|
2468
|
+
* @experimental
|
|
2469
|
+
*/
|
|
2470
|
+
function createTabWorksetToolSource(options) {
|
|
2471
|
+
const { policy } = options;
|
|
2472
|
+
return {
|
|
2473
|
+
kind: "page-tabs",
|
|
2474
|
+
listToolSpecs: () => Promise.resolve(policy.enabled ? [...SPECS] : []),
|
|
2475
|
+
systemPrompt: () => Promise.resolve(policy.enabled ? TAB_WORKSET_SYSTEM_PROMPT : void 0),
|
|
2476
|
+
canHandle: (name) => TAB_WORKSET_TOOLS.includes(name),
|
|
2477
|
+
argCaptureTrust: (name) => TAB_WORKSET_TOOLS.includes(name) ? { tier: "reviewed" } : void 0,
|
|
2478
|
+
call: async (name, args) => {
|
|
2079
2479
|
try {
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2480
|
+
if (name === "list_page_tabs") return {
|
|
2481
|
+
ok: true,
|
|
2482
|
+
content: [{
|
|
2483
|
+
type: "json",
|
|
2484
|
+
data: { tabs: await policy.list() }
|
|
2485
|
+
}]
|
|
2486
|
+
};
|
|
2487
|
+
const handle = args["tab"];
|
|
2488
|
+
if (typeof handle !== "string" || handle === "") return toolError$1("VALIDATION_FAILED", `${name} needs a "tab" handle from list_page_tabs.`);
|
|
2085
2489
|
return {
|
|
2086
|
-
ok:
|
|
2490
|
+
ok: true,
|
|
2087
2491
|
content: [{
|
|
2088
2492
|
type: "json",
|
|
2089
|
-
data: { ...
|
|
2090
|
-
}]
|
|
2091
|
-
...outcome.ok ? {} : { error: {
|
|
2092
|
-
code: "PAGE_ACTION_FAILED",
|
|
2093
|
-
message: outcome.reason ?? "Page action failed"
|
|
2094
|
-
} }
|
|
2493
|
+
data: { ...name === "focus_page_tab" ? await policy.focus(handle) : await policy.close(handle) }
|
|
2494
|
+
}]
|
|
2095
2495
|
};
|
|
2096
2496
|
} catch (e) {
|
|
2097
|
-
return toolError$1(e instanceof WebSkillError ? e.code : "
|
|
2497
|
+
return toolError$1(e instanceof WebSkillError ? e.code : "PAGE_TAB_FAILED", messageOf(e));
|
|
2098
2498
|
}
|
|
2099
2499
|
}
|
|
2100
2500
|
};
|
|
@@ -2440,4 +2840,4 @@ function readContent(result) {
|
|
|
2440
2840
|
}
|
|
2441
2841
|
|
|
2442
2842
|
//#endregion
|
|
2443
|
-
export { DEFAULT_MAX_DATA_SOURCE_BYTES, DELEGATE_TASK_TOOL, DELEGATION_SYSTEM_PROMPT, DOWNLOAD_WAIT_DEFAULT_MS, DOWNLOAD_WAIT_MAX_MS, DOWNLOAD_WAIT_MIN_MS, DataSourceCandidatePolicy, DataSourcePolicy, DelegationOrchestrator, DownloadedFilePolicy, GENERATE_SKILL_TOOL, LIST_DOWNLOADED_FILES_TOOL, MANAGE_TODO_TOOL, PAGE_ACTION_KINDS, PAGE_ACTION_SYSTEM_PROMPT, PAGE_ACTION_TOOL, PERCEIVE_PAGE_TOOL, PERCEPTION_PAGING_DEFAULTS, PERCEPTION_SYSTEM_PROMPT, PageActionPolicy, PagePerceptionPolicy, READ_DOWNLOADED_FILE_TOOL, SKILL_GENERATION_SYSTEM_PROMPT, SkillGenerator, TODO_SYSTEM_PROMPT, TodoStore, clampDownloadWaitMs, createDelegationToolSource, createDownloadedFileToolSource, createHttpDataSourceTransport, createPageActionToolSource, createPagePerceptionToolSource, createSkillGenerationToolSource, createTodoToolSource, frameLabel, frameSteps, toActionFrameScopes, toFrameScopes, withDelegationOrigin };
|
|
2843
|
+
export { CLOSE_PAGE_TAB_TOOL, DEFAULT_MAX_DATA_SOURCE_BYTES, DELEGATE_TASK_TOOL, DELEGATION_SYSTEM_PROMPT, DOWNLOAD_WAIT_DEFAULT_MS, DOWNLOAD_WAIT_MAX_MS, DOWNLOAD_WAIT_MIN_MS, DataSourceCandidatePolicy, DataSourcePolicy, DelegationOrchestrator, DownloadedFilePolicy, FOCUS_PAGE_TAB_TOOL, GENERATE_SKILL_TOOL, LIST_DOWNLOADED_FILES_TOOL, LIST_PAGE_TABS_TOOL, MANAGE_TODO_TOOL, PAGE_ACTION_KINDS, PAGE_ACTION_SYSTEM_PROMPT, PAGE_ACTION_TOOL, PAGE_DOCUMENT_REF, PERCEIVE_PAGE_TOOL, PERCEPTION_PAGING_DEFAULTS, PERCEPTION_SYSTEM_PROMPT, PageActionPolicy, PagePerceptionPolicy, READ_DOWNLOADED_FILE_TOOL, SKILL_GENERATION_SYSTEM_PROMPT, SkillGenerator, TAB_WORKSET_DEFAULT_MAX, TAB_WORKSET_MAX_MAX, TAB_WORKSET_MIN_MAX, TAB_WORKSET_SYSTEM_PROMPT, TODO_SYSTEM_PROMPT, TabWorksetPolicy, TodoStore, clampDownloadWaitMs, clampWorksetMax, createDelegationToolSource, createDownloadedFileToolSource, createHttpDataSourceTransport, createPageActionToolSource, createPagePerceptionToolSource, createSkillGenerationToolSource, createTabWorksetToolSource, createTodoToolSource, frameLabel, frameSteps, toActionFrameScopes, toFrameScopes, withDelegationOrigin };
|