@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.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 frameSteps, i as frameLabel, n as toActionFrameScopes, r as toFrameScopes, t as PAGE_ACTION_KINDS } from "./types-TUydnxaj.js";
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$5(code, message) {
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$5("VALIDATION_FAILED", items));
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$5("VALIDATION_FAILED", "The \"id\" argument is required for action \"update\""));
242
- if (status !== void 0 && typeof status !== "string") return Promise.resolve(toolError$5("VALIDATION_FAILED", "The \"status\" argument must be a string"));
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$5("VALIDATION_FAILED", "The \"action\" argument must be one of create, update, clear"));
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$5(code, e instanceof Error ? e.message : String(e)));
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$4(code, message) {
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$4("SKILL_GENERATION_VALIDATION_FAILED", draft);
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$4(e instanceof Error && "code" in e ? String(e.code) : "SKILL_GENERATION_VALIDATION_FAILED", e instanceof Error ? e.message : String(e));
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$3(code, message) {
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$3("VALIDATION_FAILED", "The \"todoId\" argument is required and must be a string");
828
- if (typeof task !== "string" || task.trim() === "") return toolError$3("VALIDATION_FAILED", "The \"task\" argument is required and must be a non-empty string");
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$3("VALIDATION_FAILED", "The \"allowedTools\" argument must be an array of tool names");
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$3(e instanceof Error && "code" in e ? String(e.code) : "DELEGATION_FAILED", e instanceof Error ? e.message : String(e));
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
- * **`perceive()` 不接受任何范围参数**——这是「白名单判定不受模型输出影响」
1127
- * (AC-10.8)的实现方式。不是先拿模型给的范围再去校验,而是模型压根没有
1128
- * 表达范围的入口:能被读的区域只由构造时的 `scope` 决定。
1129
- * 唯一的参数是取像预算(`PerceptionCaptureOptions`),它里面没有任何范围字段。
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
- * 唯一的参数,且它表达不了「读哪里」(AC-10.8 的判据由 0.14.0 分册 22 §3 修订)。
1238
- * 值是 SDK 上一次发出的不透明串:白名单仍然只由宿主的 `scope` 决定,
1239
- * 游标只说「接着刚才那处继续」,它扩不出一寸新范围。
1279
+ * 两个参数,且它们都扩不出一寸新范围(AC-10.8 的判据由 0.14.0 分册 22 §3 修订)。
1240
1280
  *
1241
- * **分段关闭时下发的仍是这份 schema,不是空 schema**:两套 schema 会让
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: { cursor: {
1248
- type: "string",
1249
- description: "Opaque cursor from a previous perceive_page result. Omit to read from the start."
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
- function toolError$2(code, message) {
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$2("PERCEPTION_CURSOR_INVALID", CURSOR_REJECTED);
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
- return toolError$2("PERCEPTION_FAILED", `perceive_page failed: ${messageOf(e)}`);
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 watch = await this.#options.downloadWatcher?.open({ ...context?.runId ? { runId: context.runId } : {} });
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
- if (watch) await watch.settle({ timeoutMs: 0 }).catch(() => 0);
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 downloads = watch ? await watch.settle({ timeoutMs: clampDownloadWaitMs(this.#options.downloadWaitMs?.()) }) : 0;
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 outcome = signalled ? {
1954
+ const openedTab = opened === void 0 ? void 0 : await this.#admit(opened);
1955
+ const outcome = {
1889
1956
  ...executed,
1890
- downloadCount: downloads
1891
- } : executed;
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: `Allow the assistant to ${request.action} "${target.name ?? target.role}" ${where}?${scopeNote}`,
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: { ...record }
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: ["ref", "action"],
2124
+ required: ["action"],
2019
2125
  additionalProperties: false
2020
2126
  };
2021
- function toolError$1(code, message) {
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 ref !== "string" || typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return toolError$1("VALIDATION_FAILED", `act_on_page needs a "ref" string and one of ${PAGE_ACTION_KINDS.join(" / ")}.`);
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$1("VALIDATION_FAILED", `act_on_page needs a "value" string when the action is "${action}".`);
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
- const outcome = await policy.act({
2081
- ref,
2082
- action,
2083
- ...typeof value === "string" ? { value } : {}
2084
- }, { ...context?.runId !== void 0 ? { runId: context.runId } : {} });
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: outcome.ok,
2490
+ ok: true,
2087
2491
  content: [{
2088
2492
  type: "json",
2089
- data: { ...outcome }
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 : "PAGE_ACTION_FAILED", messageOf(e));
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 };