@webskill/chatbot 0.9.0 → 0.11.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/index.js CHANGED
@@ -6,11 +6,11 @@ import { t as yaml } from "./yaml-C888tCbJ.js";
6
6
  import { t as json } from "./json-BOcU6nei.js";
7
7
  import { t as typescript } from "./typescript-DAau_Qyl.js";
8
8
  import { t as jsx$1 } from "./jsx-D2Jv0wvt.js";
9
- import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FullDisclosureRouter, SerializingMemoryStore, SkillDiscovery, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, atomicWriteText, diffUserProfile, exportUserProfile, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
9
+ import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FullDisclosureRouter, SUPPORTED_DOCUMENT_MIME, SerializingMemoryStore, SkillDiscovery, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, atomicWriteText, diffUserProfile, exportUserProfile, formatAttachmentText, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
10
10
  import { BrowserWorkerScriptExecutor, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, deleteMemoryEncryptionKey, openMemoryEncryptionKey, probeChromeBuiltinAvailability, startDictation } from "@webskill/sdk/browser";
11
11
  import { JsonRenderSpecSurface, NativeSpecSurface, OpenUiSpecSurface, ReactBridgeState, SpecInteractionChannel, SurfaceFormTextsProvider, UiSurfaceSnapshotList, probeJsonRenderAvailability } from "@webskill/sdk/ui-react";
12
12
  import { A2UI_SPEC_FORM_PATH, collectFormScopes, createUiCatalogToolSource, fromA2uiSpecAction, fromA2uiSurfaceAction, interactionToFormModel, interactionToUiSpec, loadOpenUiPeers, loadWebSkillLitCatalog, renderMiniChart, shapeInteractionValue, toA2uiSpecMessages, toA2uiSurfaceAction, toVercelToolInvocation, uiCatalog } from "@webskill/sdk/ui";
13
- import { TodoStore, createDelegationToolSource, createPageActionToolSource, createPagePerceptionToolSource, createSkillGenerationToolSource, createTodoToolSource, withDelegationOrigin } from "@webskill/sdk/agent";
13
+ import { DownloadedFilePolicy, PAGE_ACTION_KINDS, TodoStore, createDelegationToolSource, createDownloadedFileToolSource, createPageActionToolSource, createPagePerceptionToolSource, createSkillGenerationToolSource, createTodoToolSource, withDelegationOrigin } from "@webskill/sdk/agent";
14
14
  import * as React$1 from "react";
15
15
  import React, { Children, Component, Fragment, createContext, createElement, forwardRef, isValidElement, memo, useCallback, useContext, useDeferredValue, useEffect, useEffectEvent, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
16
16
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -26,6 +26,63 @@ import { Derived, attachTransformScopes, useClientLookup, useClientResource } fr
26
26
  import { resource, useMemoCache, withKey } from "@assistant-ui/tap";
27
27
  import { POLICY_DENIAL_CODES } from "@webskill/sdk/governance";
28
28
 
29
+ //#region ../ui-kit/src/i18n/localizedText.ts
30
+ /**
31
+ * 键的声明顺序即回退顺序(0.13.0 分册 21 FR-21.2)。
32
+ * 写成 `Record<Locale, …>` 而不是 `['zh','en'] as const satisfies readonly Locale[]`:
33
+ * 后者只校验元素合法、不校验取值穷尽,新增语种时会安静地少一个。
34
+ */
35
+ const FALLBACK_ORDER = {
36
+ zh: null,
37
+ en: null
38
+ };
39
+ /**
40
+ * SDK 支持的语种,声明顺序同时是缺语种时的回退顺序与设置界面里的展示顺序。
41
+ * @experimental
42
+ */
43
+ const SUPPORTED_LOCALES = Object.keys(FALLBACK_ORDER);
44
+ const trimmed = (value) => {
45
+ if (typeof value !== "string") return void 0;
46
+ const text = value.trim();
47
+ return text === "" ? void 0 : text;
48
+ };
49
+ /**
50
+ * 按当前语种解析多语种文案(0.13.0 分册 21 FR-21.2):
51
+ * 当前语种非空 → 用它;否则按 {@link SUPPORTED_LOCALES} 顺序取第一个非空的;
52
+ * 全空返回 `undefined`——调用方据此整条丢弃,返回空串会一路流到渲染层变成空白卡片。
53
+ * @experimental
54
+ */
55
+ function resolveLocalizedText(text, locale) {
56
+ if (typeof text === "string") return trimmed(text);
57
+ if (typeof text !== "object" || text === null) return void 0;
58
+ const preferred = trimmed(text[locale]);
59
+ if (preferred !== void 0) return preferred;
60
+ for (const candidate of SUPPORTED_LOCALES) {
61
+ const value = trimmed(text[candidate]);
62
+ if (value !== void 0) return value;
63
+ }
64
+ }
65
+ /** 字符串简写填满全部语种:「这条不分语种,就这一句」。空白串得到空对象。 */
66
+ function normalizeLocalizedText(value) {
67
+ if (typeof value === "string") {
68
+ const text = trimmed(value);
69
+ if (text === void 0) return {};
70
+ return Object.fromEntries(SUPPORTED_LOCALES.map((locale) => [locale, text]));
71
+ }
72
+ if (typeof value !== "object" || value === null) return {};
73
+ const out = {};
74
+ for (const locale of SUPPORTED_LOCALES) {
75
+ const text = trimmed(value[locale]);
76
+ if (text !== void 0) out[locale] = text;
77
+ }
78
+ return out;
79
+ }
80
+ /** 是否有任何语种填了内容;全空的条目一律丢弃(FR-21.2 规则 3) */
81
+ function hasLocalizedText(text) {
82
+ return SUPPORTED_LOCALES.some((locale) => resolveLocalizedText(text, locale) !== void 0);
83
+ }
84
+
85
+ //#endregion
29
86
  //#region ../ui-kit/src/i18n/createI18n.ts
30
87
  /** 字典工厂:各包定义自己的双语字典(恒等返回,保留字面量类型) */
31
88
  function defineDictionary(dict) {
@@ -947,72 +1004,6 @@ const __iconNode$22 = [
947
1004
  ];
948
1005
  const Mic = createLucideIcon("mic", __iconNode$22);
949
1006
 
950
- //#endregion
951
- //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/monitor-off.mjs
952
- /**
953
- * @license lucide-react v1.27.0 - ISC
954
- *
955
- * This source code is licensed under the ISC license.
956
- * See the LICENSE file in the root directory of this source tree.
957
- */
958
- const __iconNode$21 = [
959
- ["path", {
960
- d: "M12 17v4",
961
- key: "1riwvh"
962
- }],
963
- ["path", {
964
- d: "M17 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 1.184-1.826",
965
- key: "cv7jms"
966
- }],
967
- ["path", {
968
- d: "m2 2 20 20",
969
- key: "1ooewy"
970
- }],
971
- ["path", {
972
- d: "M8 21h8",
973
- key: "1ev6f3"
974
- }],
975
- ["path", {
976
- d: "M8.656 3H20a2 2 0 0 1 2 2v10a2 2 0 0 1-.293 1.042",
977
- key: "z8ni2w"
978
- }]
979
- ];
980
- const MonitorOff = createLucideIcon("monitor-off", __iconNode$21);
981
-
982
- //#endregion
983
- //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/monitor.mjs
984
- /**
985
- * @license lucide-react v1.27.0 - ISC
986
- *
987
- * This source code is licensed under the ISC license.
988
- * See the LICENSE file in the root directory of this source tree.
989
- */
990
- const __iconNode$20 = [
991
- ["rect", {
992
- width: "20",
993
- height: "14",
994
- x: "2",
995
- y: "3",
996
- rx: "2",
997
- key: "48i651"
998
- }],
999
- ["line", {
1000
- x1: "8",
1001
- x2: "16",
1002
- y1: "21",
1003
- y2: "21",
1004
- key: "1svkeh"
1005
- }],
1006
- ["line", {
1007
- x1: "12",
1008
- x2: "12",
1009
- y1: "17",
1010
- y2: "21",
1011
- key: "vw1qmm"
1012
- }]
1013
- ];
1014
- const Monitor = createLucideIcon("monitor", __iconNode$20);
1015
-
1016
1007
  //#endregion
1017
1008
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/octagon-x.mjs
1018
1009
  /**
@@ -1021,7 +1012,7 @@ const Monitor = createLucideIcon("monitor", __iconNode$20);
1021
1012
  * This source code is licensed under the ISC license.
1022
1013
  * See the LICENSE file in the root directory of this source tree.
1023
1014
  */
1024
- const __iconNode$19 = [
1015
+ const __iconNode$21 = [
1025
1016
  ["path", {
1026
1017
  d: "m15 9-6 6",
1027
1018
  key: "1uzhvr"
@@ -1035,7 +1026,7 @@ const __iconNode$19 = [
1035
1026
  key: "z0biqf"
1036
1027
  }]
1037
1028
  ];
1038
- const OctagonX = createLucideIcon("octagon-x", __iconNode$19);
1029
+ const OctagonX = createLucideIcon("octagon-x", __iconNode$21);
1039
1030
 
1040
1031
  //#endregion
1041
1032
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/panel-left.mjs
@@ -1045,7 +1036,7 @@ const OctagonX = createLucideIcon("octagon-x", __iconNode$19);
1045
1036
  * This source code is licensed under the ISC license.
1046
1037
  * See the LICENSE file in the root directory of this source tree.
1047
1038
  */
1048
- const __iconNode$18 = [["rect", {
1039
+ const __iconNode$20 = [["rect", {
1049
1040
  width: "18",
1050
1041
  height: "18",
1051
1042
  x: "3",
@@ -1056,7 +1047,7 @@ const __iconNode$18 = [["rect", {
1056
1047
  d: "M9 3v18",
1057
1048
  key: "fh3hqa"
1058
1049
  }]];
1059
- const PanelLeft = createLucideIcon("panel-left", __iconNode$18);
1050
+ const PanelLeft = createLucideIcon("panel-left", __iconNode$20);
1060
1051
 
1061
1052
  //#endregion
1062
1053
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/paperclip.mjs
@@ -1066,11 +1057,11 @@ const PanelLeft = createLucideIcon("panel-left", __iconNode$18);
1066
1057
  * This source code is licensed under the ISC license.
1067
1058
  * See the LICENSE file in the root directory of this source tree.
1068
1059
  */
1069
- const __iconNode$17 = [["path", {
1060
+ const __iconNode$19 = [["path", {
1070
1061
  d: "m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551",
1071
1062
  key: "1miecu"
1072
1063
  }]];
1073
- const Paperclip = createLucideIcon("paperclip", __iconNode$17);
1064
+ const Paperclip = createLucideIcon("paperclip", __iconNode$19);
1074
1065
 
1075
1066
  //#endregion
1076
1067
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/pencil.mjs
@@ -1080,14 +1071,59 @@ const Paperclip = createLucideIcon("paperclip", __iconNode$17);
1080
1071
  * This source code is licensed under the ISC license.
1081
1072
  * See the LICENSE file in the root directory of this source tree.
1082
1073
  */
1083
- const __iconNode$16 = [["path", {
1074
+ const __iconNode$18 = [["path", {
1084
1075
  d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
1085
1076
  key: "1a8usu"
1086
1077
  }], ["path", {
1087
1078
  d: "m15 5 4 4",
1088
1079
  key: "1mk7zo"
1089
1080
  }]];
1090
- const Pencil = createLucideIcon("pencil", __iconNode$16);
1081
+ const Pencil = createLucideIcon("pencil", __iconNode$18);
1082
+
1083
+ //#endregion
1084
+ //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/pin-off.mjs
1085
+ /**
1086
+ * @license lucide-react v1.27.0 - ISC
1087
+ *
1088
+ * This source code is licensed under the ISC license.
1089
+ * See the LICENSE file in the root directory of this source tree.
1090
+ */
1091
+ const __iconNode$17 = [
1092
+ ["path", {
1093
+ d: "M12 17v5",
1094
+ key: "bb1du9"
1095
+ }],
1096
+ ["path", {
1097
+ d: "M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89",
1098
+ key: "znwnzq"
1099
+ }],
1100
+ ["path", {
1101
+ d: "m2 2 20 20",
1102
+ key: "1ooewy"
1103
+ }],
1104
+ ["path", {
1105
+ d: "M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11",
1106
+ key: "c9qhm2"
1107
+ }]
1108
+ ];
1109
+ const PinOff = createLucideIcon("pin-off", __iconNode$17);
1110
+
1111
+ //#endregion
1112
+ //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/pin.mjs
1113
+ /**
1114
+ * @license lucide-react v1.27.0 - ISC
1115
+ *
1116
+ * This source code is licensed under the ISC license.
1117
+ * See the LICENSE file in the root directory of this source tree.
1118
+ */
1119
+ const __iconNode$16 = [["path", {
1120
+ d: "M12 17v5",
1121
+ key: "bb1du9"
1122
+ }], ["path", {
1123
+ d: "M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z",
1124
+ key: "1nkz8b"
1125
+ }]];
1126
+ const Pin = createLucideIcon("pin", __iconNode$16);
1091
1127
 
1092
1128
  //#endregion
1093
1129
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/play.mjs
@@ -7967,7 +8003,7 @@ const DIALOG_WIDTHS = {
7967
8003
  * 基类锁定 `h` `rounded` `z`(className 覆盖不生效):
7968
8004
  * `max-h` 是视口上限,圆角与层级属 ui-kit 的整体契约。
7969
8005
  */
7970
- function Dialog({ open, onClose, title, description, footer, hideCloseButton = false, className = "", size, children }) {
8006
+ function Dialog({ open, onClose, title, description, footer, hideCloseButton = false, dismissOnOutsideClick = true, className = "", size, children }) {
7971
8007
  const restoreFocusRef = useRef(null);
7972
8008
  const wasOpenRef = useRef(false);
7973
8009
  if (open && !wasOpenRef.current && typeof document !== "undefined") restoreFocusRef.current = document.activeElement;
@@ -7978,6 +8014,7 @@ function Dialog({ open, onClose, title, description, footer, hideCloseButton = f
7978
8014
  children: /* @__PURE__ */ jsxs(DialogPortal, {
7979
8015
  container: usePortalContainer(),
7980
8016
  children: [/* @__PURE__ */ jsx(DialogOverlay, { className: "fixed inset-0 z-[var(--webskill-z-scrim)] bg-scrim" }), /* @__PURE__ */ jsxs(DialogContent, {
8017
+ ...dismissOnOutsideClick ? {} : { onInteractOutside: (event) => event.preventDefault() },
7981
8018
  onCloseAutoFocus: (event) => {
7982
8019
  const source = restoreFocusRef.current;
7983
8020
  if (source instanceof HTMLElement && source.isConnected) {
@@ -12748,7 +12785,7 @@ function merge(definitions, space) {
12748
12785
  * Value that can be used to look up the properly cased property on a
12749
12786
  * `Schema`.
12750
12787
  */
12751
- function normalize$1(value) {
12788
+ function normalize$2(value) {
12752
12789
  return value.toLowerCase();
12753
12790
  }
12754
12791
 
@@ -12899,8 +12936,8 @@ function create(definition) {
12899
12936
  const info = new DefinedInfo(property, definition.transform(definition.attributes || {}, property), value, definition.space);
12900
12937
  if (definition.mustUseProperty && definition.mustUseProperty.includes(property)) info.mustUseProperty = true;
12901
12938
  properties[property] = info;
12902
- normals[normalize$1(property)] = property;
12903
- normals[normalize$1(info.attribute)] = property;
12939
+ normals[normalize$2(property)] = property;
12940
+ normals[normalize$2(info.attribute)] = property;
12904
12941
  }
12905
12942
  return new Schema(properties, normals, definition.space);
12906
12943
  }
@@ -13992,7 +14029,7 @@ const valid = /^data[-\w.:]+$/i;
13992
14029
  * Info.
13993
14030
  */
13994
14031
  function find(schema, value) {
13995
- const normal = normalize$1(value);
14032
+ const normal = normalize$2(value);
13996
14033
  let property = value;
13997
14034
  let Type = Info;
13998
14035
  if (normal in schema.normal) return schema.property[schema.normal[normal]];
@@ -18879,7 +18916,7 @@ function join(...segments) {
18879
18916
  assertPath$1(segments[index]);
18880
18917
  if (segments[index]) joined = joined === void 0 ? segments[index] : joined + "/" + segments[index];
18881
18918
  }
18882
- return joined === void 0 ? "." : normalize(joined);
18919
+ return joined === void 0 ? "." : normalize$1(joined);
18883
18920
  }
18884
18921
  /**
18885
18922
  * Normalize a basic file path.
@@ -18889,7 +18926,7 @@ function join(...segments) {
18889
18926
  * @returns {string}
18890
18927
  * File path.
18891
18928
  */
18892
- function normalize(path) {
18929
+ function normalize$1(path) {
18893
18930
  assertPath$1(path);
18894
18931
  const absolute = path.codePointAt(0) === 47;
18895
18932
  let value = normalizeString(path, !absolute);
@@ -27475,7 +27512,7 @@ function addChild(nodes, value) {
27475
27512
  function parsePrimitive(info, name, value) {
27476
27513
  if (typeof value === "string") {
27477
27514
  if (info.number && value && !Number.isNaN(Number(value))) return Number(value);
27478
- if ((info.boolean || info.overloadedBoolean) && (value === "" || normalize$1(value) === normalize$1(name))) return true;
27515
+ if ((info.boolean || info.overloadedBoolean) && (value === "" || normalize$2(value) === normalize$2(name))) return true;
27479
27516
  }
27480
27517
  return value;
27481
27518
  }
@@ -30298,6 +30335,55 @@ const fadeInUp = {
30298
30335
  }
30299
30336
  };
30300
30337
 
30338
+ //#endregion
30339
+ //#region ../core/src/attachment/kind.ts
30340
+ /** 可作为图片分片外发的 MIME 类型 @experimental */
30341
+ const IMAGE_MIME_TYPES = [
30342
+ "image/png",
30343
+ "image/jpeg",
30344
+ "image/webp",
30345
+ "image/gif"
30346
+ ];
30347
+ /**
30348
+ * 可按文本读取的扩展名。accept 白名单与判定白名单共用这一份——
30349
+ * 0.5.x 两处不一致,`log`/`yaml`/`yml` 能通过判定却在文件选择器里选不到。
30350
+ */
30351
+ const TEXT_EXTENSIONS = [
30352
+ "txt",
30353
+ "md",
30354
+ "csv",
30355
+ "json",
30356
+ "log",
30357
+ "yaml",
30358
+ "yml"
30359
+ ];
30360
+ /** 非文本非图片但仍可整体外发的类型(provider 的 file 分片) @experimental */
30361
+ const FILE_MIME_TYPES = ["application/pdf"];
30362
+ /**
30363
+ * 需要**客户端先抽取成文本**才能外发的类型(FR-23.9 / 0.13.0 FR-12.5)。
30364
+ * 不走直通的 `file`:docx / xlsx 发给 provider 会被当成二进制垃圾。
30365
+ */
30366
+ const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
30367
+ const XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
30368
+ const TEXT_EXTENSION_SET = new Set(TEXT_EXTENSIONS);
30369
+ const IMAGE_MIME_SET = new Set(IMAGE_MIME_TYPES);
30370
+ const FILE_MIME_SET = new Set(FILE_MIME_TYPES);
30371
+ const EXTRACTED_MIME_SET = /* @__PURE__ */ new Set([DOCX_MIME, XLSX_MIME]);
30372
+ const EXTRACTED_EXTENSION_SET = /* @__PURE__ */ new Set(["docx", "xlsx"]);
30373
+ function extensionOf(fileName) {
30374
+ return fileName.split(".").pop()?.toLowerCase() ?? "";
30375
+ }
30376
+ /** 分类集中一处,不散落在各 UI 分支里 @experimental */
30377
+ function classifyAttachment(contentType, fileName) {
30378
+ const type = contentType.toLowerCase();
30379
+ if (IMAGE_MIME_SET.has(type)) return "image";
30380
+ if (EXTRACTED_MIME_SET.has(type)) return "document-text";
30381
+ if (FILE_MIME_SET.has(type)) return "file";
30382
+ if (type.startsWith("text/") || type === "application/json") return "text";
30383
+ if (type === "" && TEXT_EXTENSION_SET.has(extensionOf(fileName))) return "text";
30384
+ if (type === "" && EXTRACTED_EXTENSION_SET.has(extensionOf(fileName))) return "document-text";
30385
+ }
30386
+
30301
30387
  //#endregion
30302
30388
  //#region ../runtime/src/tools/types.ts
30303
30389
  /**
@@ -30348,6 +30434,101 @@ const DEFAULT_LOOP_LIMITS$1 = {
30348
30434
  maxHistoryMessages: 1e3
30349
30435
  };
30350
30436
 
30437
+ //#endregion
30438
+ //#region ../ui-kit/src/runtime-config/quickPrompts.ts
30439
+ /**
30440
+ * 图标表。这张表不进任何包的公开面:宿主只认名字,具体用哪套图标是实现细节。
30441
+ */
30442
+ const ICONS = {
30443
+ chart: ChartColumn,
30444
+ document: FileText,
30445
+ report: ClipboardList,
30446
+ search: Search,
30447
+ list: List,
30448
+ bug: Bug,
30449
+ test: FlaskConical,
30450
+ metric: Gauge,
30451
+ compare: GitCompare,
30452
+ page: AppWindow,
30453
+ run: Play,
30454
+ warn: TriangleAlert,
30455
+ sparkles: Sparkles,
30456
+ settings: Settings
30457
+ };
30458
+ /** 受控枚举的全部取值(console 的图标下拉数据源) */
30459
+ const QUICK_PROMPT_ICON_NAMES = Object.keys(ICONS);
30460
+ /** 空态网格是两列,8 条即四行(FR-17.4 / D-17-6);用户可在 console 里上调 */
30461
+ const DEFAULT_QUICK_PROMPT_LIMIT = 8;
30462
+ /** 可调范围的天花板:20 条已经把欢迎区撑成十行,再多就满屏都是按钮 */
30463
+ const MAX_QUICK_PROMPT_LIMIT = 20;
30464
+ /** 未知名字每个只提示一次,免得每次渲染都刷屏 */
30465
+ const warned$1 = /* @__PURE__ */ new Set();
30466
+ /**
30467
+ * 解析图标名;取值不在枚举内时返回 undefined(按无图标渲染),
30468
+ * 并在控制台提示一次可用取值——宿主拼错时不至于只看到「图标没出来」。
30469
+ */
30470
+ function resolveQuickPromptIcon(icon) {
30471
+ if (icon === void 0) return void 0;
30472
+ const resolved = ICONS[icon];
30473
+ if (resolved !== void 0) return resolved;
30474
+ if (!warned$1.has(icon)) {
30475
+ warned$1.add(icon);
30476
+ console.debug("[webskill] unknown quick prompt icon", {
30477
+ icon,
30478
+ available: Object.keys(ICONS)
30479
+ });
30480
+ }
30481
+ }
30482
+ /** 统一成对象形态:宿主可以只给字符串,该字符串填满全部语种(FR-21.1) */
30483
+ const normalize = (entry) => typeof entry === "string" ? { text: normalizeLocalizedText(entry) } : entry;
30484
+ /**
30485
+ * 拼接动态与静态两批快捷指令(FR-17.4)。
30486
+ *
30487
+ * 动态在前——它是上下文相关的,更容易点到的位置留给它。
30488
+ * 去重**只发生在跨界处**:静态清单里与某条动态指令同文案的会让位;
30489
+ * 静态清单**内部**的重复原样保留(0.12.0 AC-21.6:宿主自定义时文案本就无唯一性约束,
30490
+ * 混排 `['A', {text:'A', icon:'bug'}]` 是合法的两张卡)。
30491
+ *
30492
+ * 0.13.0 分册 21 FR-21.4:去重口径是**按 `locale` 解析后的文案**,
30493
+ * 因此同一对条目在一个语种下会合并、在另一个语种下可能各自保留。
30494
+ * 按 `id` 去重不可行:动态清单传的是 `QuickPrompt`,本来就没有 `id`。
30495
+ *
30496
+ * 结果按 `limit` 截断(默认 {@link DEFAULT_QUICK_PROMPT_LIMIT})。
30497
+ */
30498
+ function mergeQuickPrompts(dynamic, statics, locale, limit = 8) {
30499
+ const resolve = (entry) => {
30500
+ const prompt = normalize(entry);
30501
+ const text = resolveLocalizedText(prompt.text, locale);
30502
+ if (text === void 0) return void 0;
30503
+ return {
30504
+ text,
30505
+ ...prompt.icon !== void 0 ? { icon: prompt.icon } : {}
30506
+ };
30507
+ };
30508
+ const head = [];
30509
+ const claimed = /* @__PURE__ */ new Set();
30510
+ for (const entry of dynamic ?? []) {
30511
+ const prompt = resolve(entry);
30512
+ if (prompt === void 0 || claimed.has(prompt.text)) continue;
30513
+ claimed.add(prompt.text);
30514
+ head.push(prompt);
30515
+ }
30516
+ const tail = [];
30517
+ for (const entry of statics) {
30518
+ const prompt = resolve(entry);
30519
+ if (prompt === void 0 || claimed.has(prompt.text)) continue;
30520
+ tail.push(prompt);
30521
+ }
30522
+ return [...head, ...tail].slice(0, clampQuickPromptLimit(limit));
30523
+ }
30524
+ /** 存储里的脏值不能让空态变成空白或一堵墙;非正整数一律回默认 */
30525
+ function clampQuickPromptLimit(value) {
30526
+ if (typeof value !== "number" || !Number.isFinite(value)) return 8;
30527
+ const floored = Math.floor(value);
30528
+ if (floored < 1) return 8;
30529
+ return Math.min(floored, 20);
30530
+ }
30531
+
30351
30532
  //#endregion
30352
30533
  //#region ../ui-kit/src/runtime-config/types.ts
30353
30534
  /**
@@ -30402,11 +30583,13 @@ function defaultRuntimeConfig() {
30402
30583
  fetchData: false
30403
30584
  },
30404
30585
  maxDataSourceBytes: DEFAULT_MAX_DATA_SOURCE_BYTES,
30586
+ dataSources: [],
30405
30587
  remoteUrl: {
30406
30588
  allowHttp: false,
30407
30589
  allowPrivateHosts: false
30408
30590
  },
30409
- typescript: { enabled: false }
30591
+ typescript: { enabled: false },
30592
+ downloadedFiles: false
30410
30593
  },
30411
30594
  llm: { entries: [] },
30412
30595
  agentCapabilities: {
@@ -30427,7 +30610,8 @@ function defaultRuntimeConfig() {
30427
30610
  imageAttachments: false,
30428
30611
  pageImageCapture: false,
30429
30612
  maxImageBytes: 10 * 1024 * 1024,
30430
- maxImagesPerMessage: 5
30613
+ maxImagesPerMessage: 50,
30614
+ minImageArea: 1024
30431
30615
  },
30432
30616
  documentSurface: { enabled: false },
30433
30617
  userProfile: {
@@ -30437,6 +30621,9 @@ function defaultRuntimeConfig() {
30437
30621
  encrypted: true
30438
30622
  },
30439
30623
  skillState: { quarantineThreshold: 5 },
30624
+ quickPrompts: [],
30625
+ quickPromptsSeeded: false,
30626
+ quickPromptLimit: 8,
30440
30627
  dismissedAutoEntries: []
30441
30628
  };
30442
30629
  }
@@ -30508,7 +30695,8 @@ function mergeRuntimeConfigDefaults(partial) {
30508
30695
  typescript: {
30509
30696
  ...d.sandbox.typescript,
30510
30697
  ...p["sandbox"]?.["typescript"] ?? {}
30511
- }
30698
+ },
30699
+ dataSources: readDataSourceEntries(p["sandbox"]?.["dataSources"])
30512
30700
  },
30513
30701
  llm: mergeLlmSelection(p["llm"]),
30514
30702
  agentCapabilities: {
@@ -30521,6 +30709,9 @@ function mergeRuntimeConfigDefaults(partial) {
30521
30709
  documentSurface: mergeDocumentSurface(p["documentSurface"], d.documentSurface),
30522
30710
  userProfile: mergeUserProfile(p["userProfile"], d.userProfile),
30523
30711
  skillState: mergeSkillState(p["skillState"], d.skillState),
30712
+ quickPrompts: readQuickPrompts(p["quickPrompts"]),
30713
+ quickPromptsSeeded: p["quickPromptsSeeded"] === true,
30714
+ quickPromptLimit: clampQuickPromptLimit(p["quickPromptLimit"]),
30524
30715
  dismissedAutoEntries: readStringList$1(p["dismissedAutoEntries"], [])
30525
30716
  };
30526
30717
  }
@@ -30543,6 +30734,58 @@ function readStringList$1(raw, fallback) {
30543
30734
  if (!Array.isArray(raw)) return [...fallback];
30544
30735
  return raw.filter((item) => typeof item === "string");
30545
30736
  }
30737
+ /**
30738
+ * 快捷指令逐条校验(FR-17.2):缺 `id` 或文案全语种皆空的条目**单条丢弃**,
30739
+ * 不因为一条脏数据把整段回退——用户其余的配置不该被连坐。
30740
+ * `icon` 取值不在枚举内时抹掉该字段(渲染侧按无图标处理)。
30741
+ *
30742
+ * 0.13.0 分册 25 FR-25.3:`text: string` 的存量条目一并丢弃,不再升级为语种对象——
30743
+ * 它们在 console 的快捷指令页里没有身份可言,留着就是「看得见、删不掉」。
30744
+ */
30745
+ function readQuickPrompts(raw) {
30746
+ if (!Array.isArray(raw)) return [];
30747
+ const out = [];
30748
+ for (const item of raw) {
30749
+ if (typeof item !== "object" || item === null) continue;
30750
+ const entry = item;
30751
+ const id = typeof entry["id"] === "string" ? entry["id"].trim() : "";
30752
+ const rawText = entry["text"];
30753
+ if (id === "" || typeof rawText !== "object" || rawText === null) continue;
30754
+ const text = normalizeLocalizedText(rawText);
30755
+ if (!hasLocalizedText(text)) continue;
30756
+ const icon = entry["icon"];
30757
+ const valid = typeof icon === "string" && QUICK_PROMPT_ICON_NAMES.includes(icon) ? icon : void 0;
30758
+ out.push({
30759
+ id,
30760
+ text,
30761
+ ...valid !== void 0 ? { icon: valid } : {}
30762
+ });
30763
+ }
30764
+ return out;
30765
+ }
30766
+ /**
30767
+ * 逐条校验用户配的数据源:`id` / `url` 缺失或非字符串即整条丢弃。
30768
+ * 不整段回退,一条脏数据不该连坐其余几条。
30769
+ */
30770
+ function readDataSourceEntries(raw) {
30771
+ if (!Array.isArray(raw)) return [];
30772
+ const out = [];
30773
+ const seen = /* @__PURE__ */ new Set();
30774
+ for (const item of raw) {
30775
+ if (typeof item !== "object" || item === null) continue;
30776
+ const entry = item;
30777
+ const id = typeof entry["id"] === "string" ? entry["id"].trim() : "";
30778
+ const url = typeof entry["url"] === "string" ? entry["url"].trim() : "";
30779
+ if (id === "" || url === "" || seen.has(id)) continue;
30780
+ seen.add(id);
30781
+ out.push({
30782
+ id,
30783
+ url,
30784
+ description: typeof entry["description"] === "string" ? entry["description"] : ""
30785
+ });
30786
+ }
30787
+ return out;
30788
+ }
30546
30789
  /** 上限字段非正数时回退默认值:0 或负数会把整条通道变成永远拒收 */
30547
30790
  function mergeMultimodal(raw, d) {
30548
30791
  const p = typeof raw === "object" && raw !== null ? raw : {};
@@ -30551,9 +30794,14 @@ function mergeMultimodal(raw, d) {
30551
30794
  imageAttachments: typeof p["imageAttachments"] === "boolean" ? p["imageAttachments"] : d.imageAttachments,
30552
30795
  pageImageCapture: typeof p["pageImageCapture"] === "boolean" ? p["pageImageCapture"] : d.pageImageCapture,
30553
30796
  maxImageBytes: positive(p["maxImageBytes"], d.maxImageBytes),
30554
- maxImagesPerMessage: positive(p["maxImagesPerMessage"], d.maxImagesPerMessage)
30797
+ maxImagesPerMessage: positive(p["maxImagesPerMessage"], d.maxImagesPerMessage),
30798
+ minImageArea: nonNegative(p["minImageArea"], d.minImageArea)
30555
30799
  };
30556
30800
  }
30801
+ /** 非有限数 / 负数回退默认值,`0` 原样保留 */
30802
+ function nonNegative(value, fallback) {
30803
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
30804
+ }
30557
30805
  /**
30558
30806
  * CSP 白名单只收字符串数组;存储被污染时回退默认值(空),
30559
30807
  * **不能**把非法值原样带进 CSP —— `viewerCspHeader` 会抛,等于整条投放面瘫掉。
@@ -31292,6 +31540,9 @@ const chatbotDictionary = defineDictionary({
31292
31540
  "app.title": "WebSkill Chat",
31293
31541
  "header.sessions": "Sessions",
31294
31542
  "header.settings": "Settings",
31543
+ "header.dock": "Dock to page",
31544
+ "header.undock": "Undock from page",
31545
+ "header.close": "Close chat",
31295
31546
  "header.console": "Open Console",
31296
31547
  "header.theme.light": "Switch to light theme",
31297
31548
  "header.theme.dark": "Switch to dark theme",
@@ -31337,10 +31588,6 @@ const chatbotDictionary = defineDictionary({
31337
31588
  "welcome.capability.transparency.title": "Run transparency",
31338
31589
  "welcome.capability.transparency.description": "Routing, skill activation, tool calls and results are visible live, step by step.",
31339
31590
  "welcome.quickPrompts": "Try an example",
31340
- "welcome.prompt.1": "What skills are installed in this workspace?",
31341
- "welcome.prompt.2": "Draft a short product update announcement for me",
31342
- "welcome.prompt.3": "Show me a bar chart of this quarter’s sales",
31343
- "welcome.prompt.4": "Explain how WebSkill runs skill scripts safely",
31344
31591
  "message.copy": "Copy",
31345
31592
  "message.copied": "Copied",
31346
31593
  "message.retry": "Retry",
@@ -31426,11 +31673,20 @@ const chatbotDictionary = defineDictionary({
31426
31673
  "interaction.pageAction.click": "Allow the assistant to click “{target}”?",
31427
31674
  "interaction.pageAction.fill": "Allow the assistant to fill “{target}” with:",
31428
31675
  "interaction.pageAction.submit": "Allow the assistant to submit “{target}”?",
31676
+ "interaction.pageAction.select": "Allow the assistant to select an option in “{target}”?",
31677
+ "interaction.pageAction.set": "Allow the assistant to toggle “{target}”?",
31678
+ "interaction.pageAction.attach": "Allow the assistant to attach a file to “{target}”?",
31429
31679
  "interaction.pageAction.role.click": "this control",
31430
31680
  "interaction.pageAction.role.fill": "this field",
31431
31681
  "interaction.pageAction.role.submit": "this form",
31682
+ "interaction.pageAction.role.select": "this list",
31683
+ "interaction.pageAction.role.set": "this switch",
31684
+ "interaction.pageAction.role.attach": "this upload field",
31685
+ "interaction.pageAction.remember": "Don’t ask again for this kind of action",
31432
31686
  "interaction.pageAction.frame": "The target is in the embedded frame \"{frame}\".",
31433
31687
  "interaction.pageAction.elevated": "This dialog is outside the usual allowlist; it was opened by this task.",
31688
+ "interaction.downloadedFile.file": "{name} · {size}",
31689
+ "interaction.downloadedFile.remember": "Don’t ask again for this kind of access",
31434
31690
  "interaction.traceEvidence.title": "Steps this skill claims",
31435
31691
  "interaction.traceEvidence.columnDraft": "In the skill",
31436
31692
  "interaction.traceEvidence.columnTrace": "Actually run in this session",
@@ -31495,9 +31751,6 @@ const chatbotDictionary = defineDictionary({
31495
31751
  "composer.noModel.description": "A large language model has not been configured yet. Configure one on the model settings page before chatting.",
31496
31752
  "composer.noModel.configure": "Configure model",
31497
31753
  "capability.group": "Model capabilities",
31498
- "composer.pageCapture": "Read the page view",
31499
- "composer.pageCapture.disabled.setting": "page image capture is off in Settings → Multimodal",
31500
- "composer.pageCapture.disabled.model": "the selected model does not accept images",
31501
31754
  "composer.addAttachment.imagesOff": "images are off in Settings → Multimodal",
31502
31755
  "composer.addAttachment.modelNoImages": "the selected model does not accept images",
31503
31756
  "attachment.imagesDropped": "Only {limit} images can be sent per message. Not sent: {names}",
@@ -31648,6 +31901,9 @@ const chatbotDictionary = defineDictionary({
31648
31901
  "app.title": "WebSkill Chat",
31649
31902
  "header.sessions": "会话",
31650
31903
  "header.settings": "设置",
31904
+ "header.dock": "固定到页面",
31905
+ "header.undock": "取消固定",
31906
+ "header.close": "关闭对话框",
31651
31907
  "header.console": "打开 Console",
31652
31908
  "header.theme.light": "切换到亮色主题",
31653
31909
  "header.theme.dark": "切换到暗色主题",
@@ -31693,10 +31949,6 @@ const chatbotDictionary = defineDictionary({
31693
31949
  "welcome.capability.transparency.title": "运行透明",
31694
31950
  "welcome.capability.transparency.description": "路由、技能激活、工具调用与结果,逐步实时可见。",
31695
31951
  "welcome.quickPrompts": "试试这些示例",
31696
- "welcome.prompt.1": "这个工作区里安装了哪些技能?",
31697
- "welcome.prompt.2": "帮我起草一份简短的产品更新公告",
31698
- "welcome.prompt.3": "给我展示本季度销售额的柱状图",
31699
- "welcome.prompt.4": "解释一下 WebSkill 如何安全地运行技能脚本",
31700
31952
  "message.copy": "复制",
31701
31953
  "message.copied": "已复制",
31702
31954
  "message.edit": "编辑消息",
@@ -31782,11 +32034,20 @@ const chatbotDictionary = defineDictionary({
31782
32034
  "interaction.pageAction.click": "允许助手点击「{target}」吗?",
31783
32035
  "interaction.pageAction.fill": "允许助手在「{target}」中填入:",
31784
32036
  "interaction.pageAction.submit": "允许助手提交「{target}」吗?",
32037
+ "interaction.pageAction.select": "允许助手在「{target}」中选择选项吗?",
32038
+ "interaction.pageAction.set": "允许助手切换「{target}」吗?",
32039
+ "interaction.pageAction.attach": "允许助手向「{target}」附加文件吗?",
31785
32040
  "interaction.pageAction.role.click": "这个控件",
31786
32041
  "interaction.pageAction.role.fill": "这个输入框",
31787
32042
  "interaction.pageAction.role.submit": "这个表单",
32043
+ "interaction.pageAction.role.select": "这个选择框",
32044
+ "interaction.pageAction.role.set": "这个开关",
32045
+ "interaction.pageAction.role.attach": "这个上传控件",
32046
+ "interaction.pageAction.remember": "以后不再询问此类操作",
31788
32047
  "interaction.pageAction.frame": "目标位于嵌入帧「{frame}」中。",
31789
32048
  "interaction.pageAction.elevated": "该对话框不在常规允许清单内,是本次任务打开的。",
32049
+ "interaction.downloadedFile.file": "{name} · {size}",
32050
+ "interaction.downloadedFile.remember": "以后不再询问此类访问",
31790
32051
  "interaction.traceEvidence.title": "该技能声称的步骤",
31791
32052
  "interaction.traceEvidence.columnDraft": "技能里写的",
31792
32053
  "interaction.traceEvidence.columnTrace": "本会话实际执行的",
@@ -31851,9 +32112,6 @@ const chatbotDictionary = defineDictionary({
31851
32112
  "composer.noModel.description": "大模型尚未配置,请先前往大模型配置页面完成配置,再开始对话。",
31852
32113
  "composer.noModel.configure": "配置大模型",
31853
32114
  "capability.group": "模型能力",
31854
- "composer.pageCapture": "读取页面画面",
31855
- "composer.pageCapture.disabled.setting": "页面图像抓取已在「设置 → 多模态」中关闭",
31856
- "composer.pageCapture.disabled.model": "当前模型不接受图片",
31857
32115
  "composer.addAttachment.imagesOff": "图片已在「设置 → 多模态」中关闭",
31858
32116
  "composer.addAttachment.modelNoImages": "当前模型不接受图片",
31859
32117
  "attachment.imagesDropped": "每条消息最多发送 {limit} 张图片。未发送:{names}",
@@ -32162,8 +32420,6 @@ function redactInteractionValues(value, request) {
32162
32420
  }
32163
32421
  return out;
32164
32422
  }
32165
- /** 注入模型的单个附件正文上限(超出截断并标注) */
32166
- const ATTACHMENT_TEXT_LIMIT = 32 * 1024;
32167
32423
  /**
32168
32424
  * 行为记录与画像归属的 user 作用域标识。chatbot 是单人本地应用,没有账号体系,
32169
32425
  * 固定值即可;它只决定 memory 里的作用域名,不参与任何鉴权。
@@ -32613,6 +32869,17 @@ var ChatEngine = class {
32613
32869
  this.#maxHistoryMessages = void 0;
32614
32870
  }
32615
32871
  /**
32872
+ * 技能仓变更后调用(安装/卸载/发布):作废 runtime 缓存的技能目录,下次 send 重新扫描。
32873
+ *
32874
+ * 比 `reloadConfig()` 轻,因为它不清 `#handles`——那会连带丢掉每个会话的模型上下文。
32875
+ * 装技能的界面与对话不在同一个页面时(如扩展的 options 页 vs side panel),
32876
+ * 宿主必须自己把变更通知过来,否则新技能要等页面重载才出现在 catalog 里。
32877
+ * @experimental
32878
+ */
32879
+ invalidateSkills() {
32880
+ this.#ready?.then((runtime) => runtime.invalidate(), () => void 0);
32881
+ }
32882
+ /**
32616
32883
  * 会话列表页。归档会话也返回:UI 自己分区展示。
32617
32884
  * `limit` 不给默认值——缺省属于 `SessionStore` 实现,在这里兜底就等于只下推了一半。
32618
32885
  */
@@ -32787,7 +33054,7 @@ var ChatEngine = class {
32787
33054
  const rc = await this.#runtimeConfigStore()?.load();
32788
33055
  if (!(rc?.multimodal.imageAttachments ?? false)) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "Image attachments are turned off. Enable them in settings before sending images.");
32789
33056
  if (!entryCapabilities(pickLlmEntry(rc, this.#selectedModelId)).image) throw new WebSkillError("MODEL_IMAGE_UNSUPPORTED", "Current model does not support image input. Switch model or remove the image.");
32790
- const limit = rc?.multimodal.maxImagesPerMessage ?? 5;
33057
+ const limit = rc?.multimodal.maxImagesPerMessage ?? defaultRuntimeConfig().multimodal.maxImagesPerMessage;
32791
33058
  if (images.length <= limit) return { accepted: attachments };
32792
33059
  const dropped = images.slice(limit);
32793
33060
  const droppedIds = new Set(dropped.map((a) => a.id));
@@ -32811,11 +33078,14 @@ var ChatEngine = class {
32811
33078
  if (!await this.#adapter.storage.exists(path)) continue;
32812
33079
  if (attachment.kind === "text" || attachment.kind === "document-text") {
32813
33080
  const body = await this.#adapter.storage.readText(path);
32814
- const clipped = body.length > ATTACHMENT_TEXT_LIMIT ? `${body.slice(0, ATTACHMENT_TEXT_LIMIT)}\n[truncated]` : body;
32815
- const label = attachment.kind === "document-text" ? `${attachment.name} (${attachment.contentType}, body text only)` : `${attachment.name} (${attachment.contentType})`;
32816
33081
  parts.push({
32817
33082
  type: "text",
32818
- text: `--- Attachment: ${label} ---\n${clipped}`
33083
+ text: formatAttachmentText({
33084
+ name: attachment.name,
33085
+ contentType: attachment.contentType,
33086
+ body,
33087
+ kind: attachment.kind
33088
+ })
32819
33089
  });
32820
33090
  continue;
32821
33091
  }
@@ -33165,16 +33435,30 @@ var ChatEngine = class {
33165
33435
  })] : [],
33166
33436
  ...pagePerception ? [createPagePerceptionToolSource({
33167
33437
  policy: pagePerception,
33438
+ ...this.#adapter.pagePerceptionPaging === void 0 ? {} : { paging: () => this.#adapter.pagePerceptionPaging },
33168
33439
  imageCapture: async () => {
33169
33440
  const live = await this.#runtimeConfigStore()?.load();
33170
33441
  return {
33171
33442
  enabled: (live?.multimodal.pageImageCapture ?? false) && entryCapabilities(pickLlmEntry(live, this.#selectedModelId)).image,
33172
33443
  maxImageBytes: live?.multimodal.maxImageBytes ?? 0,
33173
- maxImages: live?.multimodal.maxImagesPerMessage ?? 0
33444
+ maxImages: live?.multimodal.maxImagesPerMessage ?? 0,
33445
+ minImageArea: live?.multimodal.minImageArea ?? 0
33174
33446
  };
33175
33447
  }
33176
33448
  })] : [],
33177
- ...this.#adapter.pageActions ? [createPageActionToolSource({ policy: this.#adapter.pageActions })] : []
33449
+ ...this.#adapter.pageActions ? [createPageActionToolSource({ policy: this.#adapter.pageActions })] : [],
33450
+ ...this.#adapter.downloads ? [createDownloadedFileToolSource({ policy: new DownloadedFilePolicy({
33451
+ reader: this.#adapter.downloads.reader,
33452
+ ui: this.#uiBridge,
33453
+ capabilityEnabled: () => rc?.sandbox.downloadedFiles === true,
33454
+ imageCapable: async () => {
33455
+ return entryCapabilities(pickLlmEntry(await this.#runtimeConfigStore()?.load(), this.#selectedModelId)).image;
33456
+ },
33457
+ ...this.#adapter.downloads.consent ? { consent: this.#adapter.downloads.consent } : {},
33458
+ ...this.#adapter.docxExtractor ? { docxExtractor: this.#adapter.docxExtractor } : {},
33459
+ ...this.#adapter.xlsxExtractor ? { xlsxExtractor: this.#adapter.xlsxExtractor } : {},
33460
+ ...this.#adapter.documentAudit ? { audit: this.#adapter.documentAudit } : {}
33461
+ }) })] : []
33178
33462
  ];
33179
33463
  const deps = {
33180
33464
  fs,
@@ -33209,8 +33493,11 @@ var ChatEngine = class {
33209
33493
  ...Object.keys(loopConfig).length > 0 ? { config: loopConfig } : {},
33210
33494
  ...model ? { model } : {},
33211
33495
  ...this.#options.fetchData ? { fetchData: this.#options.fetchData } : {},
33496
+ ...this.#options.dataSources ? { dataSources: this.#options.dataSources } : {},
33212
33497
  ...this.#adapter.linkedDocuments ? { linkedDocuments: this.#adapter.linkedDocuments } : {},
33213
33498
  ...this.#adapter.docxExtractor ? { docxExtractor: this.#adapter.docxExtractor } : {},
33499
+ ...this.#adapter.xlsxExtractor ? { xlsxExtractor: this.#adapter.xlsxExtractor } : {},
33500
+ ...this.#adapter.pdfExtractor ? { pdfExtractor: this.#adapter.pdfExtractor } : {},
33214
33501
  ...this.#adapter.documentAudit ? { documentAudit: this.#adapter.documentAudit } : {},
33215
33502
  ...toolStepStore ? { toolSteps: toolStepStore } : {}
33216
33503
  };
@@ -34129,47 +34416,6 @@ function useWebSkillAui({ messages, live, sessions, currentSessionId, sessionsLo
34129
34416
  }) });
34130
34417
  }
34131
34418
 
34132
- //#endregion
34133
- //#region src/react/quickPromptIcons.ts
34134
- /**
34135
- * 快捷指令图标表(0.12.0 分册 21)。
34136
- * 这张表不导出到包外:宿主只认名字,具体用哪套图标是实现细节。
34137
- */
34138
- const ICONS = {
34139
- chart: ChartColumn,
34140
- document: FileText,
34141
- report: ClipboardList,
34142
- search: Search,
34143
- list: List,
34144
- bug: Bug,
34145
- test: FlaskConical,
34146
- metric: Gauge,
34147
- compare: GitCompare,
34148
- page: AppWindow,
34149
- run: Play,
34150
- warn: TriangleAlert,
34151
- sparkles: Sparkles,
34152
- settings: Settings
34153
- };
34154
- /** 未知名字每个只提示一次,免得每次渲染都刷屏 */
34155
- const warned$1 = /* @__PURE__ */ new Set();
34156
- /**
34157
- * 解析图标名;取值不在枚举内时返回 undefined(按无图标渲染),
34158
- * 并在控制台提示一次可用取值——宿主拼错时不至于只看到「图标没出来」。
34159
- */
34160
- function resolveQuickPromptIcon(icon) {
34161
- if (icon === void 0) return void 0;
34162
- const resolved = ICONS[icon];
34163
- if (resolved !== void 0) return resolved;
34164
- if (!warned$1.has(icon)) {
34165
- warned$1.add(icon);
34166
- console.debug("[webskill] unknown quick prompt icon", {
34167
- icon,
34168
- available: Object.keys(ICONS)
34169
- });
34170
- }
34171
- }
34172
-
34173
34419
  //#endregion
34174
34420
  //#region src/react/AssistantUiShell.tsx
34175
34421
  /**
@@ -34411,63 +34657,94 @@ function AssistantUiSessionList({ className = "", onNavigate, onCollapse, hasEar
34411
34657
  });
34412
34658
  }
34413
34659
  /** Minimal Base toolbar: workspace controls stay secondary to the thread itself. */
34414
- function AssistantUiThreadToolbar({ title, onOpenSessions, onCreateSession, onOpenSettings }) {
34660
+ function AssistantUiThreadToolbar({ title, onOpenSessions, onCreateSession, onOpenSettings, docked, onDock, onClose }) {
34415
34661
  const t = useT();
34662
+ const dockLabel = docked === true ? t("header.undock") : t("header.dock");
34416
34663
  return /* @__PURE__ */ jsxs("header", {
34417
34664
  className: "flex h-14 shrink-0 items-center justify-between bg-background px-3",
34418
34665
  children: [/* @__PURE__ */ jsxs("div", {
34419
34666
  className: "flex min-w-0 items-center gap-1",
34420
- children: [/* @__PURE__ */ jsx("button", {
34421
- type: "button",
34422
- "aria-label": t("session.expand"),
34423
- title: t("session.expand"),
34424
- "data-testid": "chatbot-sessions-button",
34425
- onClick: onOpenSessions,
34426
- className: "webskill-aui-sessions-button inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34427
- children: /* @__PURE__ */ jsx(PanelLeft, {
34428
- className: "size-4",
34429
- "aria-hidden": true
34430
- })
34431
- }), title !== void 0 ? /* @__PURE__ */ jsx("span", {
34432
- className: "truncate px-2 text-sm font-medium text-ink",
34433
- children: title
34434
- }) : null]
34667
+ children: [
34668
+ /* @__PURE__ */ jsx("button", {
34669
+ type: "button",
34670
+ "aria-label": t("session.expand"),
34671
+ title: t("session.expand"),
34672
+ "data-testid": "chatbot-sessions-button",
34673
+ onClick: onOpenSessions,
34674
+ className: "webskill-aui-sessions-button inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34675
+ children: /* @__PURE__ */ jsx(PanelLeft, {
34676
+ className: "size-4",
34677
+ "aria-hidden": true
34678
+ })
34679
+ }),
34680
+ onDock ? /* @__PURE__ */ jsx("button", {
34681
+ type: "button",
34682
+ "data-testid": "header-dock",
34683
+ "aria-label": dockLabel,
34684
+ "aria-pressed": docked === true,
34685
+ title: dockLabel,
34686
+ onClick: () => onDock(docked !== true),
34687
+ className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34688
+ children: docked === true ? /* @__PURE__ */ jsx(PinOff, {
34689
+ className: "size-4",
34690
+ "aria-hidden": true
34691
+ }) : /* @__PURE__ */ jsx(Pin, {
34692
+ className: "size-4",
34693
+ "aria-hidden": true
34694
+ })
34695
+ }) : null,
34696
+ title !== void 0 ? /* @__PURE__ */ jsx("span", {
34697
+ className: "truncate px-2 text-sm font-medium text-ink",
34698
+ children: title
34699
+ }) : null
34700
+ ]
34435
34701
  }), /* @__PURE__ */ jsxs("div", {
34436
34702
  className: "flex items-center gap-1",
34437
- children: [/* @__PURE__ */ jsx("button", {
34438
- type: "button",
34439
- "data-testid": "header-new-chat",
34440
- "aria-label": t("session.new"),
34441
- title: t("session.new"),
34442
- onClick: onCreateSession,
34443
- className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34444
- children: /* @__PURE__ */ jsx(Plus, {
34445
- className: "size-4",
34446
- "aria-hidden": true
34447
- })
34448
- }), onOpenSettings ? /* @__PURE__ */ jsx("button", {
34449
- type: "button",
34450
- "data-testid": "header-settings",
34451
- "aria-label": t("header.settings"),
34452
- title: t("header.settings"),
34453
- onClick: onOpenSettings,
34454
- className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34455
- children: /* @__PURE__ */ jsx(Settings, {
34456
- className: "size-4",
34457
- "aria-hidden": true
34458
- })
34459
- }) : null]
34703
+ children: [
34704
+ /* @__PURE__ */ jsx("button", {
34705
+ type: "button",
34706
+ "data-testid": "header-new-chat",
34707
+ "aria-label": t("session.new"),
34708
+ title: t("session.new"),
34709
+ onClick: onCreateSession,
34710
+ className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34711
+ children: /* @__PURE__ */ jsx(Plus, {
34712
+ className: "size-4",
34713
+ "aria-hidden": true
34714
+ })
34715
+ }),
34716
+ onOpenSettings ? /* @__PURE__ */ jsx("button", {
34717
+ type: "button",
34718
+ "data-testid": "header-settings",
34719
+ "aria-label": t("header.settings"),
34720
+ title: t("header.settings"),
34721
+ onClick: onOpenSettings,
34722
+ className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34723
+ children: /* @__PURE__ */ jsx(Settings, {
34724
+ className: "size-4",
34725
+ "aria-hidden": true
34726
+ })
34727
+ }) : null,
34728
+ onClose ? /* @__PURE__ */ jsx("button", {
34729
+ type: "button",
34730
+ "data-testid": "header-close",
34731
+ "aria-label": t("header.close"),
34732
+ title: t("header.close"),
34733
+ onClick: onClose,
34734
+ className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34735
+ children: /* @__PURE__ */ jsx(X, {
34736
+ className: "size-4",
34737
+ "aria-hidden": true
34738
+ })
34739
+ }) : null
34740
+ ]
34460
34741
  })]
34461
34742
  });
34462
34743
  }
34463
- function AssistantUiEmptyState({ title, prompts, disabled, onPrompt }) {
34744
+ function AssistantUiEmptyState({ title, prompts, dynamicPrompts, limit, disabled, onPrompt }) {
34464
34745
  const t = useT();
34465
- const starterPrompts = prompts ?? [
34466
- t("welcome.prompt.1"),
34467
- t("welcome.prompt.2"),
34468
- t("welcome.prompt.3"),
34469
- t("welcome.prompt.4")
34470
- ];
34746
+ const locale = useLocale();
34747
+ const starterPrompts = mergeQuickPrompts(dynamicPrompts, prompts ?? [], locale, limit);
34471
34748
  return /* @__PURE__ */ jsx("div", {
34472
34749
  "data-testid": "assistant-ui-empty-state",
34473
34750
  className: "flex min-h-48 flex-col justify-center py-8 sm:min-h-72 sm:py-10",
@@ -34478,8 +34755,7 @@ function AssistantUiEmptyState({ title, prompts, disabled, onPrompt }) {
34478
34755
  children: title ?? t("welcome.assistantTitle")
34479
34756
  }), starterPrompts.length > 0 ? /* @__PURE__ */ jsx("div", {
34480
34757
  className: "grid w-full grid-cols-1 gap-2 pt-3 sm:grid-cols-2",
34481
- children: starterPrompts.map((entry, index) => {
34482
- const item = typeof entry === "string" ? { text: entry } : entry;
34758
+ children: starterPrompts.map((item, index) => {
34483
34759
  const Icon = resolveQuickPromptIcon(item.icon);
34484
34760
  return /* @__PURE__ */ jsxs("button", {
34485
34761
  type: "button",
@@ -34496,6 +34772,11 @@ function AssistantUiEmptyState({ title, prompts, disabled, onPrompt }) {
34496
34772
  })
34497
34773
  });
34498
34774
  }
34775
+ /**
34776
+ * 对话进行中的动态快捷指令条(0.13.0 FR-17.6)已废止:
34777
+ * 每轮对话结束都在输入区上方冒出一条横向滚动的胶囊带,构成持续干扰。
34778
+ * `ChatbotConfig.dynamicQuickPrompts` 保留,但只在空态(`AssistantUiEmptyState`)生效。
34779
+ */
34499
34780
 
34500
34781
  //#endregion
34501
34782
  //#region src/react/format.ts
@@ -34649,12 +34930,10 @@ const DEFAULT_DICTATION_LANG = {
34649
34930
  zh: "zh-CN"
34650
34931
  };
34651
34932
  /**
34652
- * 三个图标共用一套外观规则(FR-28.4):明暗只由 supported 决定,
34653
- * 交互可供性由 interactive 决定——能力是状态,取像是动作。
34933
+ * 两个图标共用一套外观(FR-28.4):明暗只由 supported 决定。
34654
34934
  */
34655
- function CAPABILITY_ICON_CLASS(supported, interactive) {
34656
- const base = `inline-flex size-8 shrink-0 items-center justify-center rounded-full ${supported ? "text-ink" : "text-muted"}`;
34657
- return interactive ? `${base} transition-colors hover:bg-accent hover:text-ink disabled:cursor-not-allowed disabled:hover:bg-transparent` : base;
34935
+ function CAPABILITY_ICON_CLASS(supported) {
34936
+ return `inline-flex size-8 shrink-0 items-center justify-center rounded-full ${supported ? "text-ink" : "text-muted"}`;
34658
34937
  }
34659
34938
  function AttachmentMeta({ info }) {
34660
34939
  const t = useT();
@@ -34714,16 +34993,14 @@ function ModelMenuButton({ models }) {
34714
34993
  }, option.id))
34715
34994
  })] });
34716
34995
  }
34717
- function AssistantUiComposer({ running, disabled = false, waitTarget, placeholder, attachments = false, attachmentInfo, attachmentImages, centered = false, models, noModel, dictationLang, pageCapture, capabilities }) {
34996
+ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholder, attachments = false, attachmentInfo, attachmentImages, centered = false, models, noModel, dictationLang, capabilities }) {
34718
34997
  const t = useT();
34719
34998
  const locale = useLocale();
34720
34999
  const dictation = useComposerDictation(dictationLang ?? DEFAULT_DICTATION_LANG[locale]);
34721
35000
  const dictationReason = dictation.availability.available ? void 0 : t(`composer.dictate.${dictation.availability.reason}`);
34722
35001
  const waitReason = disabled ? t("interaction.wait", { target: waitTarget ?? t("interaction.wait.fallback") }) : void 0;
34723
35002
  const attachmentReason = attachmentImages === void 0 || attachmentImages.enabled && attachmentImages.imageCapable ? void 0 : attachmentImages.enabled ? t("composer.addAttachment.modelNoImages") : t("composer.addAttachment.imagesOff");
34724
- const pageCaptureReason = pageCapture === void 0 || pageCapture.enabled && pageCapture.imageCapable ? void 0 : pageCapture.enabled ? t("composer.pageCapture.disabled.model") : t("composer.pageCapture.disabled.setting");
34725
35003
  const plainChatHintId = useId();
34726
- const pageCaptureHintId = useId();
34727
35004
  return /* @__PURE__ */ jsx(ComposerPrimitive.Root, {
34728
35005
  "data-testid": "assistant-ui-composer",
34729
35006
  "data-centered": centered,
@@ -34823,7 +35100,7 @@ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholde
34823
35100
  "aria-label": t(capabilities.tools ? "capability.tools.on" : "capability.tools.off"),
34824
35101
  title: capabilities.tools ? t("capability.tools.on") : `${t("capability.tools.off")} — ${t("settings.capability.plainChat")}`,
34825
35102
  ...capabilities.tools ? {} : { "aria-describedby": plainChatHintId },
34826
- className: CAPABILITY_ICON_CLASS(capabilities.tools, false),
35103
+ className: CAPABILITY_ICON_CLASS(capabilities.tools),
34827
35104
  children: capabilities.tools ? /* @__PURE__ */ jsx(Wrench, {
34828
35105
  className: "size-4",
34829
35106
  "aria-hidden": true
@@ -34842,7 +35119,7 @@ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholde
34842
35119
  "data-supported": capabilities.image,
34843
35120
  "aria-label": t(capabilities.image ? "capability.images.on" : "capability.images.off"),
34844
35121
  title: t(capabilities.image ? "capability.images.on" : "capability.images.off"),
34845
- className: CAPABILITY_ICON_CLASS(capabilities.image, false),
35122
+ className: CAPABILITY_ICON_CLASS(capabilities.image),
34846
35123
  children: capabilities.image ? /* @__PURE__ */ jsx(Image, {
34847
35124
  className: "size-4",
34848
35125
  "aria-hidden": true
@@ -34852,29 +35129,6 @@ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholde
34852
35129
  })
34853
35130
  })
34854
35131
  ]
34855
- }) : null,
34856
- pageCapture ? /* @__PURE__ */ jsx("button", {
34857
- type: "button",
34858
- "data-testid": "capability-page-capture",
34859
- "data-supported": pageCaptureReason === void 0,
34860
- "aria-label": t("composer.pageCapture"),
34861
- title: pageCaptureReason === void 0 ? t("composer.pageCapture") : `${t("composer.pageCapture")} — ${pageCaptureReason}`,
34862
- disabled: pageCaptureReason !== void 0,
34863
- ...pageCaptureReason !== void 0 ? { "aria-describedby": pageCaptureHintId } : {},
34864
- onClick: pageCapture.onSelect,
34865
- className: CAPABILITY_ICON_CLASS(pageCaptureReason === void 0, true),
34866
- children: pageCaptureReason === void 0 ? /* @__PURE__ */ jsx(Monitor, {
34867
- className: "size-4",
34868
- "aria-hidden": true
34869
- }) : /* @__PURE__ */ jsx(MonitorOff, {
34870
- className: "size-4",
34871
- "aria-hidden": true
34872
- })
34873
- }) : null,
34874
- pageCapture && pageCaptureReason !== void 0 ? /* @__PURE__ */ jsx("span", {
34875
- id: pageCaptureHintId,
34876
- className: "sr-only",
34877
- children: pageCaptureReason
34878
35132
  }) : null
34879
35133
  ]
34880
35134
  }), /* @__PURE__ */ jsxs("div", {
@@ -46537,52 +46791,6 @@ function AssistantUiMessage(props) {
46537
46791
 
46538
46792
  //#endregion
46539
46793
  //#region src/core/attachmentKind.ts
46540
- /** 可作为图片分片外发的 MIME 类型 @experimental */
46541
- const IMAGE_MIME_TYPES = [
46542
- "image/png",
46543
- "image/jpeg",
46544
- "image/webp",
46545
- "image/gif"
46546
- ];
46547
- /**
46548
- * 可按文本读取的扩展名。accept 白名单与判定白名单共用这一份——
46549
- * 0.5.x 两处不一致,`log`/`yaml`/`yml` 能通过判定却在文件选择器里选不到。
46550
- */
46551
- const TEXT_EXTENSIONS = [
46552
- "txt",
46553
- "md",
46554
- "csv",
46555
- "json",
46556
- "log",
46557
- "yaml",
46558
- "yml"
46559
- ];
46560
- const TEXT_EXTENSION_SET = new Set(TEXT_EXTENSIONS);
46561
- const IMAGE_MIME_SET = new Set(IMAGE_MIME_TYPES);
46562
- /** 非文本非图片但仍可整体外发的类型(provider 的 file 分片) */
46563
- const FILE_MIME_TYPES = ["application/pdf"];
46564
- const FILE_MIME_SET = new Set(FILE_MIME_TYPES);
46565
- /**
46566
- * 需要**客户端先抽取成文本**才能外发的类型(FR-23.9)。
46567
- * 不走直通的 `file`:docx 发给 provider 会被当成二进制垃圾。
46568
- */
46569
- const EXTRACTED_MIME_TYPES = ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"];
46570
- const EXTRACTED_MIME_SET = new Set(EXTRACTED_MIME_TYPES);
46571
- const EXTRACTED_EXTENSIONS = ["docx"];
46572
- const EXTRACTED_EXTENSION_SET = new Set(EXTRACTED_EXTENSIONS);
46573
- function extensionOf(fileName) {
46574
- return fileName.split(".").pop()?.toLowerCase() ?? "";
46575
- }
46576
- /** 分类集中一处,不散落在各 UI 分支里 @experimental */
46577
- function classifyAttachment(contentType, fileName) {
46578
- const type = contentType.toLowerCase();
46579
- if (IMAGE_MIME_SET.has(type)) return "image";
46580
- if (EXTRACTED_MIME_SET.has(type)) return "document-text";
46581
- if (FILE_MIME_SET.has(type)) return "file";
46582
- if (type.startsWith("text/") || type === "application/json") return "text";
46583
- if (type === "" && TEXT_EXTENSION_SET.has(extensionOf(fileName))) return "text";
46584
- if (type === "" && EXTRACTED_EXTENSION_SET.has(extensionOf(fileName))) return "document-text";
46585
- }
46586
46794
  const TEXT_ACCEPT = [
46587
46795
  "text/plain",
46588
46796
  "text/markdown",
@@ -46598,7 +46806,8 @@ function attachmentAccept(options) {
46598
46806
  const parts = [...TEXT_ACCEPT, ...TEXT_EXTENSIONS.map((ext) => `.${ext}`)];
46599
46807
  if (options.images) parts.push(...IMAGE_MIME_TYPES);
46600
46808
  if (options.files) parts.push(...FILE_MIME_TYPES);
46601
- if (options.documents) parts.push(...EXTRACTED_MIME_TYPES, ...EXTRACTED_EXTENSIONS.map((ext) => `.${ext}`));
46809
+ if (options.documents) parts.push(DOCX_MIME, ".docx");
46810
+ if (options.spreadsheets) parts.push(XLSX_MIME, ".xlsx");
46602
46811
  return parts.join(",");
46603
46812
  }
46604
46813
 
@@ -46607,17 +46816,19 @@ function attachmentAccept(options) {
46607
46816
  /** 非图片附件无法压缩,沿用固定上限;图片走 `RuntimeMultimodalConfig.maxImageBytes` */
46608
46817
  const MAX_BYTES = 10 * 1024 * 1024;
46609
46818
  const MAX_TEXT_CHARS = 32 * 1024;
46610
- const DEFAULT_MULTIMODAL = {
46611
- imageAttachments: false,
46612
- pageImageCapture: false,
46613
- maxImageBytes: MAX_BYTES,
46614
- maxImagesPerMessage: 5
46615
- };
46819
+ /** 0.13.0 FR-13.3:兜底值必须与 SDK 默认配置同源,各自写一份字面量必然静默漂移 */
46820
+ const DEFAULT_MULTIMODAL = defaultRuntimeConfig().multimodal;
46616
46821
  /** 去掉路径分隔符与 `..`,防止写出附件目录 */
46617
46822
  function safeName(name) {
46618
46823
  const cleaned = (name.split(/[\\/]/).pop() ?? "attachment").replace(/[^\w.\- ]+/g, "_").replace(/^\.+/, "");
46619
46824
  return cleaned === "" ? "attachment" : cleaned.slice(0, 80);
46620
46825
  }
46826
+ /** 两种待抽取格式共用 `document-text` 这一种 kind,选抽取器时要按格式再分一次 */
46827
+ function isSpreadsheet(file) {
46828
+ const type = file.type.toLowerCase();
46829
+ if (type !== "") return type === SUPPORTED_DOCUMENT_MIME.xlsx;
46830
+ return file.name.toLowerCase().endsWith(".xlsx");
46831
+ }
46621
46832
  /**
46622
46833
  * 附件落盘到 `<attachmentsRoot>/<sessionId>/`,`ChatEngine.send` 再按路径读回构造 `LlmContentPart[]`。
46623
46834
  * UI 只持有元数据,正文的唯一真相是存储。
@@ -46634,7 +46845,8 @@ var WebSkillAttachmentAdapter = class {
46634
46845
  return attachmentAccept({
46635
46846
  images: this.#imagesAllowed(),
46636
46847
  files: true,
46637
- documents: this.#options.docxExtractor !== void 0
46848
+ documents: this.#options.docxExtractor !== void 0,
46849
+ spreadsheets: this.#options.xlsxExtractor !== void 0
46638
46850
  });
46639
46851
  }
46640
46852
  #multimodal() {
@@ -46643,13 +46855,16 @@ var WebSkillAttachmentAdapter = class {
46643
46855
  #imagesAllowed() {
46644
46856
  return this.#multimodal().imageAttachments && (this.#options.imageCapable?.() ?? false);
46645
46857
  }
46858
+ #extractorFor(file) {
46859
+ return isSpreadsheet(file) ? this.#options.xlsxExtractor : this.#options.docxExtractor;
46860
+ }
46646
46861
  async add({ file }) {
46647
46862
  const kind = classifyAttachment(file.type, file.name);
46648
46863
  if (kind === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", `Attachment "${file.name}" has an unsupported type "${file.type === "" ? "unknown" : file.type}"`);
46649
46864
  if (kind === "image") {
46650
46865
  if (!this.#multimodal().imageAttachments) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "Image attachments are turned off. Enable them in settings before sending images.");
46651
46866
  if (!(this.#options.imageCapable?.() ?? false)) throw new WebSkillError("MODEL_IMAGE_UNSUPPORTED", "Current model does not support image input. Switch model or remove the image.");
46652
- } else if (kind === "document-text" && this.#options.docxExtractor === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "Word documents are not supported in this environment: no docx extractor is wired up.");
46867
+ } else if (kind === "document-text" && this.#extractorFor(file) === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", `${isSpreadsheet(file) ? "Excel workbooks" : "Word documents"} are not supported in this environment: no ${isSpreadsheet(file) ? "xlsx" : "docx"} extractor is wired up.`);
46653
46868
  else if (file.size > MAX_BYTES) throw new WebSkillError("ATTACHMENT_TOO_LARGE", `Attachment "${file.name}" is ${Math.round(file.size / 1024 / 1024)} MB, which exceeds the 10 MB limit`);
46654
46869
  const id = `att-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
46655
46870
  const relative = `${this.#options.sessionId() ?? "pending"}/${id}-${safeName(file.name)}`;
@@ -46692,8 +46907,8 @@ var WebSkillAttachmentAdapter = class {
46692
46907
  };
46693
46908
  }
46694
46909
  if (kind === "document-text") {
46695
- const extract = this.#options.docxExtractor;
46696
- if (extract === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "No docx extractor is wired up in this environment.");
46910
+ const extract = this.#extractorFor(file);
46911
+ if (extract === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "No document text extractor is wired up in this environment.");
46697
46912
  await this.#options.storage.writeText(path, await extract(new Uint8Array(await file.arrayBuffer())));
46698
46913
  return {
46699
46914
  ...base,
@@ -47097,18 +47312,43 @@ function pageActionDetails(details) {
47097
47312
  const record = details;
47098
47313
  const action = record["action"];
47099
47314
  const target = record["target"];
47100
- if (action !== "click" && action !== "fill" && action !== "submit") return void 0;
47315
+ if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return void 0;
47101
47316
  if (typeof target !== "object" || target === null) return void 0;
47102
47317
  const { role, name, frame } = target;
47103
47318
  if (typeof role !== "string") return void 0;
47104
47319
  const value = record["value"];
47320
+ const rememberLabel = record["rememberLabel"];
47105
47321
  return {
47106
47322
  action,
47107
47323
  role,
47108
47324
  ...typeof name === "string" ? { name } : {},
47109
47325
  ...typeof value === "string" ? { value } : {},
47110
47326
  ...typeof frame === "string" ? { frame } : {},
47111
- ...record["elevated"] === true ? { elevated: true } : {}
47327
+ ...record["elevated"] === true ? { elevated: true } : {},
47328
+ ...record["rememberable"] === true ? { rememberable: true } : {},
47329
+ ...typeof rememberLabel === "string" ? { rememberLabel } : {}
47330
+ };
47331
+ }
47332
+ /**
47333
+ * 下载文件确认卡的载荷(0.14.0 分册 20)。与 `pageActionDetails` 同一条规矩:
47334
+ * 形状不对就整段不渲染,退回通用授权展示,不猜。
47335
+ */
47336
+ function downloadedFileDetails(details) {
47337
+ if (typeof details !== "object" || details === null) return void 0;
47338
+ const record = details;
47339
+ const action = record["action"];
47340
+ if (action !== "list" && action !== "read") return void 0;
47341
+ const file = record["file"];
47342
+ const rememberLabel = record["rememberLabel"];
47343
+ const named = typeof file === "object" && file !== null ? file : void 0;
47344
+ return {
47345
+ action,
47346
+ ...typeof named?.["name"] === "string" && typeof named["size"] === "number" ? { file: {
47347
+ name: named["name"],
47348
+ size: named["size"]
47349
+ } } : {},
47350
+ ...record["rememberable"] === true ? { rememberable: true } : {},
47351
+ ...typeof rememberLabel === "string" ? { rememberLabel } : {}
47112
47352
  };
47113
47353
  }
47114
47354
  /**
@@ -47141,8 +47381,10 @@ function traceEvidenceDetails(details) {
47141
47381
  }
47142
47382
  function ActiveInteractionCard({ request, bridge }) {
47143
47383
  const t = useT();
47384
+ const [remember, setRemember] = useState(false);
47144
47385
  const isAuthorize = request.type === "authorize";
47145
47386
  const pageAction = isAuthorize && request.capability === "pageAction" ? pageActionDetails(request.details) : void 0;
47387
+ const downloadedFile = isAuthorize && request.capability === "readDownloadedFile" ? downloadedFileDetails(request.details) : void 0;
47146
47388
  const traceEvidence = isAuthorize ? traceEvidenceDetails(request.details) : void 0;
47147
47389
  const spec = interactionToUiSpec(request.type === "authorize" && pageAction !== void 0 ? {
47148
47390
  ...request,
@@ -47170,7 +47412,8 @@ function ActiveInteractionCard({ request, bridge }) {
47170
47412
  const value = shapeInteractionValue(interactionToFormModel(request), event.value ?? {});
47171
47413
  bridge.resolve({
47172
47414
  id: request.id,
47173
- value
47415
+ value,
47416
+ ...remember ? { remembered: true } : {}
47174
47417
  });
47175
47418
  };
47176
47419
  return /* @__PURE__ */ jsxs(motion.div, {
@@ -47246,8 +47489,33 @@ function ActiveInteractionCard({ request, bridge }) {
47246
47489
  "data-testid": "interaction-page-action-elevated",
47247
47490
  className: "text-sm font-medium text-warning",
47248
47491
  children: t("interaction.pageAction.elevated")
47492
+ }) : null,
47493
+ pageAction.rememberable === true ? /* @__PURE__ */ jsxs("label", {
47494
+ className: "flex items-center gap-2 text-sm text-ink",
47495
+ children: [/* @__PURE__ */ jsx("input", {
47496
+ type: "checkbox",
47497
+ "data-testid": "interaction-page-action-remember",
47498
+ checked: remember,
47499
+ onChange: (event) => setRemember(event.currentTarget.checked)
47500
+ }), pageAction.rememberLabel ?? t("interaction.pageAction.remember")]
47249
47501
  }) : null
47250
47502
  ] }) : null,
47503
+ downloadedFile ? /* @__PURE__ */ jsxs(Fragment$1, { children: [downloadedFile.file ? /* @__PURE__ */ jsx("p", {
47504
+ "data-testid": "interaction-downloaded-file",
47505
+ className: "text-sm text-ink",
47506
+ children: t("interaction.downloadedFile.file", {
47507
+ name: downloadedFile.file.name,
47508
+ size: formatBytes(downloadedFile.file.size)
47509
+ })
47510
+ }) : null, downloadedFile.rememberable === true ? /* @__PURE__ */ jsxs("label", {
47511
+ className: "flex items-center gap-2 text-sm text-ink",
47512
+ children: [/* @__PURE__ */ jsx("input", {
47513
+ type: "checkbox",
47514
+ "data-testid": "interaction-downloaded-file-remember",
47515
+ checked: remember,
47516
+ onChange: (event) => setRemember(event.currentTarget.checked)
47517
+ }), downloadedFile.rememberLabel ?? t("interaction.downloadedFile.remember")]
47518
+ }) : null] }) : null,
47251
47519
  traceEvidence ? /* @__PURE__ */ jsxs("div", {
47252
47520
  "data-testid": "interaction-trace-evidence",
47253
47521
  children: [/* @__PURE__ */ jsx("div", {
@@ -47666,8 +47934,35 @@ function LocalizedUndoToastProvider({ children }) {
47666
47934
  });
47667
47935
  }
47668
47936
  const toErrorMessage = (e) => e instanceof Error ? e.message : String(e);
47669
- /** 「读取页面画面」菜单项发出的提示词:说清要的是画面,模型才会走取像而非纯文本抽取 */
47670
- const PAGE_CAPTURE_PROMPT = "Read the current page view, including the images on it, and tell me what you see.";
47937
+ /** console 的快捷指令条目同款 id:时间戳 + 随机后缀 */
47938
+ const newQuickPromptId = () => `qp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
47939
+ /**
47940
+ * 宿主种子只注入一次(0.13.0 分册 21 FR-21.6)。
47941
+ * 写盘会触发 subscribe 再跑一遍装载,届时 `quickPromptsSeeded` 已为 true,不会二次写入。
47942
+ * 宿主没传种子(`undefined`)时什么都不做——不该为「宿主没配」产生一次持久化;
47943
+ * 传空数组是「本应用不要快捷指令」,照样落盘打标记(FR-25.4)。
47944
+ * 返回已注入的条目(可能是空数组),调用方要立刻拿去更新界面:写盘是 fire-and-forget。
47945
+ */
47946
+ function seedQuickPrompts(store, config, seed) {
47947
+ if (config.quickPromptsSeeded || seed === void 0) return void 0;
47948
+ const entries = [];
47949
+ for (const item of seed) {
47950
+ const text = normalizeLocalizedText(typeof item === "string" ? item : item.text);
47951
+ if (Object.keys(text).length === 0) continue;
47952
+ const icon = typeof item === "string" ? void 0 : item.icon;
47953
+ entries.push({
47954
+ id: newQuickPromptId(),
47955
+ text,
47956
+ ...icon !== void 0 ? { icon } : {}
47957
+ });
47958
+ }
47959
+ store.save({
47960
+ ...config,
47961
+ quickPrompts: entries,
47962
+ quickPromptsSeeded: true
47963
+ }).catch(() => void 0);
47964
+ return entries;
47965
+ }
47671
47966
  /** 读 artifact 存储并触发浏览器下载(Blob + a[download]) */
47672
47967
  async function downloadArtifactFile(storage, chatRoot, file) {
47673
47968
  const fullPath = `${chatRoot}/artifacts/${file.runId ?? ""}/${file.path}`;
@@ -47689,7 +47984,7 @@ async function downloadArtifactFile(storage, chatRoot, file) {
47689
47984
  * presentation shell and accessible thread primitives.
47690
47985
  * @stable
47691
47986
  */
47692
- function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, surfaceRegistry, layout = "auto", sessionList = "auto", onOpenSettings, onEngineReady }) {
47987
+ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, surfaceRegistry, layout = "auto", sessionList = "auto", onOpenSettings, docked, onDock, onClose, onEngineReady }) {
47693
47988
  const engine = useMemo(() => new ChatEngine(adapter, {
47694
47989
  ...config?.chatRoot ? { chatRoot: config.chatRoot } : {},
47695
47990
  ...config?.llm ? { llm: config.llm } : {},
@@ -47700,6 +47995,8 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47700
47995
  ...config?.governance ? { governance: config.governance } : {},
47701
47996
  ...config?.skillCandidates ? { skillCandidates: config.skillCandidates } : {},
47702
47997
  ...config?.fetchData ? { fetchData: config.fetchData } : {},
47998
+ ...config?.dataSources ? { dataSources: config.dataSources } : {},
47999
+ ...config?.executorFactory ? { executorFactory: config.executorFactory } : {},
47703
48000
  ...rendererProp ? { renderer: rendererProp } : {}
47704
48001
  }), [
47705
48002
  adapter,
@@ -47712,6 +48009,8 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47712
48009
  config?.governance,
47713
48010
  config?.skillCandidates,
47714
48011
  config?.fetchData,
48012
+ config?.dataSources,
48013
+ config?.executorFactory,
47715
48014
  rendererProp
47716
48015
  ]);
47717
48016
  useEffect(() => {
@@ -47770,6 +48069,29 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47770
48069
  });
47771
48070
  /** 当前进行中 run 的 part 累积器(流式正文 / 工具 / 生命周期步进的唯一来源) */
47772
48071
  const [live, setLive] = useState();
48072
+ /** RuntimeConfig 里快捷指令的镜像(FR-21.5):console 改完经 subscribe 回来 */
48073
+ const [storedQuickPrompts, setStoredQuickPrompts] = useState([]);
48074
+ /** 空态与动态指令条最多展示多少条(console 可改) */
48075
+ const [quickPromptLimit, setQuickPromptLimit] = useState(8);
48076
+ /** 种子的 ref 镜像:props 每次渲染都是新数组,进 effect 依赖表会让配置订阅反复重建 */
48077
+ const seedRef = useRef(config?.quickPrompts);
48078
+ seedRef.current = config?.quickPrompts;
48079
+ /**
48080
+ * 静态清单(FR-21.6 / FR-25.2):存储里有什么就是什么。
48081
+ * 未接配置存储的宿主用 props;接了存储就以存储为准,两者皆空即一张卡都不渲染——
48082
+ * 内置示例已下线(分册 25),chatbot 显示的每一条都要在 console 的快捷指令页里管得到。
48083
+ */
48084
+ const staticQuickPrompts = useMemo(() => {
48085
+ if (storedQuickPrompts.length > 0) return storedQuickPrompts.map(({ text, icon }) => ({
48086
+ text,
48087
+ ...icon !== void 0 ? { icon } : {}
48088
+ }));
48089
+ return configStore === void 0 ? config?.quickPrompts : [];
48090
+ }, [
48091
+ storedQuickPrompts,
48092
+ config?.quickPrompts,
48093
+ configStore
48094
+ ]);
47773
48095
  const [interactionPending, setInteractionPending] = useState(false);
47774
48096
  /** 等待对象名称(interaction-requested 携带):等待指示器要说清在等什么 */
47775
48097
  const [interactionLabel, setInteractionLabel] = useState();
@@ -47823,7 +48145,8 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47823
48145
  sessionId: () => sessionRef.current,
47824
48146
  multimodal: () => multimodalRef.current,
47825
48147
  imageCapable: () => imageCapableRef.current,
47826
- ...adapter.docxExtractor ? { docxExtractor: adapter.docxExtractor } : {}
48148
+ ...adapter.docxExtractor ? { docxExtractor: adapter.docxExtractor } : {},
48149
+ ...adapter.xlsxExtractor ? { xlsxExtractor: adapter.xlsxExtractor } : {}
47827
48150
  }), [adapter, engine]);
47828
48151
  useEffect(() => {
47829
48152
  let cancelled = false;
@@ -47850,6 +48173,9 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47850
48173
  ...rc.llm.defaultId ? { defaultId: rc.llm.defaultId } : {}
47851
48174
  });
47852
48175
  setLlmSelectionLoaded(true);
48176
+ const seeded = seedQuickPrompts(merged, rc, seedRef.current);
48177
+ setStoredQuickPrompts(seeded ?? rc.quickPrompts);
48178
+ setQuickPromptLimit(rc.quickPromptLimit);
47853
48179
  multimodalRef.current = rc.multimodal;
47854
48180
  setMultimodal(rc.multimodal);
47855
48181
  setLoopLimits({
@@ -47868,7 +48194,9 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47868
48194
  unsubscribe?.();
47869
48195
  };
47870
48196
  }, [configStore, engine]);
48197
+ const hasBuiltinEntry = llmSelection.entries.some((entry) => entry.provider === "chrome-builtin");
47871
48198
  useEffect(() => {
48199
+ if (!hasBuiltinEntry) return void 0;
47872
48200
  let cancelled = false;
47873
48201
  probeChromeBuiltinAvailability().then((result) => {
47874
48202
  if (!cancelled) setChromeBuiltin(result);
@@ -47876,7 +48204,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47876
48204
  return () => {
47877
48205
  cancelled = true;
47878
48206
  };
47879
- }, []);
48207
+ }, [hasBuiltinEntry]);
47880
48208
  /** 选中模型条目 id:能力受限提示、设置面板置灰与附件门槛都以它为准 */
47881
48209
  const selectedModelId = modelId ?? llmSelection.defaultId ?? llmSelection.entries[0]?.id ?? "";
47882
48210
  const selectedEntry = llmSelection.entries.find((entry) => entry.id === selectedModelId);
@@ -48087,6 +48415,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48087
48415
  setCurrentSessionId(id);
48088
48416
  setMessages(history.items);
48089
48417
  setMessagesCursor(history.nextCursor);
48418
+ setError(void 0);
48090
48419
  setModelId(engine.modelId);
48091
48420
  }).catch((e) => setError({ message: toErrorMessage(e) }));
48092
48421
  }, [engine, flushPendingDeletes]);
@@ -48146,6 +48475,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48146
48475
  setCurrentSessionId(meta.id);
48147
48476
  setMessages([]);
48148
48477
  setMessagesCursor(void 0);
48478
+ setError(void 0);
48149
48479
  setModelId(engine.modelId);
48150
48480
  await refreshSessions();
48151
48481
  }).catch((e) => setError({ message: toErrorMessage(e) }));
@@ -48181,6 +48511,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48181
48511
  if (id === currentSessionId) {
48182
48512
  setCurrentSessionId(void 0);
48183
48513
  setMessages([]);
48514
+ setError(void 0);
48184
48515
  }
48185
48516
  await refreshSessions();
48186
48517
  }).catch((e) => setError({ message: toErrorMessage(e) }));
@@ -48246,7 +48577,10 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48246
48577
  ...config?.title ? { title: config.title } : {},
48247
48578
  onOpenSessions: () => setSessionsOpen(true),
48248
48579
  onCreateSession: handleCreate,
48249
- ...onOpenSettings ? { onOpenSettings: () => onOpenSettings() } : {}
48580
+ ...onOpenSettings ? { onOpenSettings: () => onOpenSettings() } : {},
48581
+ ...docked !== void 0 ? { docked } : {},
48582
+ ...onDock ? { onDock } : {},
48583
+ ...onClose ? { onClose } : {}
48250
48584
  }),
48251
48585
  /* @__PURE__ */ jsx(InterruptedBanner, {
48252
48586
  engine,
@@ -48262,7 +48596,9 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48262
48596
  children: [
48263
48597
  messages.length === 0 && !sending ? /* @__PURE__ */ jsx(AssistantUiEmptyState, {
48264
48598
  ...config?.title ? { title: config.title } : {},
48265
- ...config?.quickPrompts ? { prompts: config.quickPrompts } : {},
48599
+ ...staticQuickPrompts ? { prompts: staticQuickPrompts } : {},
48600
+ ...config?.dynamicQuickPrompts ? { dynamicPrompts: config.dynamicQuickPrompts } : {},
48601
+ limit: quickPromptLimit,
48266
48602
  disabled: sending,
48267
48603
  onPrompt: handleSend
48268
48604
  }) : null,
@@ -48343,11 +48679,6 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48343
48679
  enabled: multimodal.imageAttachments,
48344
48680
  imageCapable: modelTakesImages
48345
48681
  },
48346
- pageCapture: {
48347
- enabled: multimodal.pageImageCapture,
48348
- imageCapable: modelTakesImages,
48349
- onSelect: () => handleSend(PAGE_CAPTURE_PROMPT)
48350
- },
48351
48682
  ...dictationLang !== "" ? { dictationLang } : {},
48352
48683
  ...composerModels && !noUsableModel ? { models: composerModels } : {},
48353
48684
  ...noUsableModel ? { noModel: { onConfigure: () => setNoModelGuideOpen(true) } } : {},
@@ -48425,7 +48756,7 @@ function VercelPayloadPreview({ bridge }) {
48425
48756
  * Version of the published `@webskill/chatbot` package, injected at build time.
48426
48757
  * @stable
48427
48758
  */
48428
- const CHATBOT_VERSION = "0.8.0";
48759
+ const CHATBOT_VERSION = "0.11.0";
48429
48760
 
48430
48761
  //#endregion
48431
48762
  export { A2uiSurfaceHost, A2uiSurfaceSnapshotHost, CHATBOT_VERSION, ChatEngine, Chatbot, CompositeUiBridge, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, ErrorCard, InteractionCard, InterruptedBanner, OpenUiSurfaceHost, OpenUiSurfaceSnapshotHost, ResultBlockList, ResultBlocksPro, SkillBadges, SpecInteraction, VercelPayloadPreview, VercelSurfaceHost, VercelSurfaceSnapshotHost, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, isLlmEntryUsable, pickUsableLlmEntry, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };