@webskill/chatbot 0.12.0 → 0.14.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
@@ -7,10 +7,10 @@ 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
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, assertSafePathSegment, atomicWriteText, diffUserProfile, exportUserProfile, formatAttachmentText, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, resolveInsideRoot, sanitizeUntrustedLine, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
10
- import { BrowserWorkerScriptExecutor, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, deleteMemoryEncryptionKey, openMemoryEncryptionKey, probeChromeBuiltinAvailability, startDictation } from "@webskill/sdk/browser";
10
+ import { BrowserWorkerScriptExecutor, capturePhoto, checkCameraAvailability, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, deleteMemoryEncryptionKey, openCamera, 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 { DownloadedFilePolicy, PAGE_ACTION_KINDS, TodoStore, createDelegationToolSource, createDownloadedFileToolSource, createPageActionToolSource, createPagePerceptionToolSource, createSkillGenerationToolSource, createTodoToolSource, withDelegationOrigin } from "@webskill/sdk/agent";
13
+ import { DownloadedFilePolicy, PAGE_ACTION_KINDS, TodoStore, createDelegationToolSource, createDownloadedFileToolSource, createPageActionToolSource, createPagePerceptionToolSource, createSkillGenerationToolSource, createTabWorksetToolSource, 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";
@@ -254,7 +254,7 @@ const createLucideIcon = (iconName, iconNode) => {
254
254
  * This source code is licensed under the ISC license.
255
255
  * See the LICENSE file in the root directory of this source tree.
256
256
  */
257
- const __iconNode$49 = [
257
+ const __iconNode$50 = [
258
258
  ["rect", {
259
259
  x: "2",
260
260
  y: "4",
@@ -276,7 +276,7 @@ const __iconNode$49 = [
276
276
  key: "1svtjw"
277
277
  }]
278
278
  ];
279
- const AppWindow = createLucideIcon("app-window", __iconNode$49);
279
+ const AppWindow = createLucideIcon("app-window", __iconNode$50);
280
280
 
281
281
  //#endregion
282
282
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/archive-restore.mjs
@@ -286,7 +286,7 @@ const AppWindow = createLucideIcon("app-window", __iconNode$49);
286
286
  * This source code is licensed under the ISC license.
287
287
  * See the LICENSE file in the root directory of this source tree.
288
288
  */
289
- const __iconNode$48 = [
289
+ const __iconNode$49 = [
290
290
  ["rect", {
291
291
  width: "20",
292
292
  height: "5",
@@ -312,7 +312,7 @@ const __iconNode$48 = [
312
312
  key: "192myk"
313
313
  }]
314
314
  ];
315
- const ArchiveRestore = createLucideIcon("archive-restore", __iconNode$48);
315
+ const ArchiveRestore = createLucideIcon("archive-restore", __iconNode$49);
316
316
 
317
317
  //#endregion
318
318
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/archive.mjs
@@ -322,7 +322,7 @@ const ArchiveRestore = createLucideIcon("archive-restore", __iconNode$48);
322
322
  * This source code is licensed under the ISC license.
323
323
  * See the LICENSE file in the root directory of this source tree.
324
324
  */
325
- const __iconNode$47 = [
325
+ const __iconNode$48 = [
326
326
  ["rect", {
327
327
  width: "20",
328
328
  height: "5",
@@ -340,7 +340,7 @@ const __iconNode$47 = [
340
340
  key: "a56b0p"
341
341
  }]
342
342
  ];
343
- const Archive = createLucideIcon("archive", __iconNode$47);
343
+ const Archive = createLucideIcon("archive", __iconNode$48);
344
344
 
345
345
  //#endregion
346
346
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/arrow-up.mjs
@@ -350,14 +350,14 @@ const Archive = createLucideIcon("archive", __iconNode$47);
350
350
  * This source code is licensed under the ISC license.
351
351
  * See the LICENSE file in the root directory of this source tree.
352
352
  */
353
- const __iconNode$46 = [["path", {
353
+ const __iconNode$47 = [["path", {
354
354
  d: "m5 12 7-7 7 7",
355
355
  key: "hav0vg"
356
356
  }], ["path", {
357
357
  d: "M12 19V5",
358
358
  key: "x0mq9r"
359
359
  }]];
360
- const ArrowUp = createLucideIcon("arrow-up", __iconNode$46);
360
+ const ArrowUp = createLucideIcon("arrow-up", __iconNode$47);
361
361
 
362
362
  //#endregion
363
363
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/bug.mjs
@@ -367,7 +367,7 @@ const ArrowUp = createLucideIcon("arrow-up", __iconNode$46);
367
367
  * This source code is licensed under the ISC license.
368
368
  * See the LICENSE file in the root directory of this source tree.
369
369
  */
370
- const __iconNode$45 = [
370
+ const __iconNode$46 = [
371
371
  ["path", {
372
372
  d: "M12 20v-9",
373
373
  key: "1qisl0"
@@ -413,7 +413,26 @@ const __iconNode$45 = [
413
413
  key: "1vgav8"
414
414
  }]
415
415
  ];
416
- const Bug = createLucideIcon("bug", __iconNode$45);
416
+ const Bug = createLucideIcon("bug", __iconNode$46);
417
+
418
+ //#endregion
419
+ //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/camera.mjs
420
+ /**
421
+ * @license lucide-react v1.27.0 - ISC
422
+ *
423
+ * This source code is licensed under the ISC license.
424
+ * See the LICENSE file in the root directory of this source tree.
425
+ */
426
+ const __iconNode$45 = [["path", {
427
+ d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
428
+ key: "18u6gg"
429
+ }], ["circle", {
430
+ cx: "12",
431
+ cy: "13",
432
+ r: "3",
433
+ key: "1vg3eu"
434
+ }]];
435
+ const Camera = createLucideIcon("camera", __iconNode$45);
417
436
 
418
437
  //#endregion
419
438
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/chart-column.mjs
@@ -30291,6 +30310,12 @@ const markdownComponents = {
30291
30310
  /**
30292
30311
  * 流式 Markdown:消费当前 text part 的增量文本,边流边成形(标题/列表/表格/代码块),
30293
30312
  * 终态与流式期是同一个组件、同一份数据,因此 run 结束时不重排。
30313
+ *
30314
+ * `smooth={false}` 是硬性要求,不是口味问题:assistant-ui 以 partIndex 作 React key
30315
+ * (`MessagePartsGrouped`),而运行细节的 part 排在正文之前,每来一次工具调用正文就整体后移一位、
30316
+ * 被卸载重挂。平滑揭示在重挂时从空串重新开始,于是同一段正文会被完整地重打一遍——
30317
+ * 表现为「正文下半段删掉又补回来、高度忽高忽低」。模型本身已按 token 粒度流式返回,
30318
+ * 再叠一层揭示动画只会把结构性重挂放大成可见抖动。守卫见 `e2e-extension/liveBodyMonotonic.spec.ts`。
30294
30319
  */
30295
30320
  const MarkdownText = memo(function MarkdownText({ className }) {
30296
30321
  const [dark, themeRef] = useDarkAncestor();
@@ -30302,6 +30327,7 @@ const MarkdownText = memo(function MarkdownText({ className }) {
30302
30327
  remarkPlugins: [remarkGfm],
30303
30328
  className: cn("aui-md", className),
30304
30329
  components,
30330
+ smooth: false,
30305
30331
  defer: true
30306
30332
  })
30307
30333
  });
@@ -31683,17 +31709,28 @@ const chatbotDictionary = defineDictionary({
31683
31709
  "interaction.pageAction.select": "Allow the assistant to select an option in “{target}”?",
31684
31710
  "interaction.pageAction.set": "Allow the assistant to toggle “{target}”?",
31685
31711
  "interaction.pageAction.attach": "Allow the assistant to attach a file to “{target}”?",
31712
+ "interaction.pageAction.back": "Allow the assistant to go back to the previous page on “{target}”?",
31686
31713
  "interaction.pageAction.role.click": "this control",
31687
31714
  "interaction.pageAction.role.fill": "this field",
31688
31715
  "interaction.pageAction.role.submit": "this form",
31689
31716
  "interaction.pageAction.role.select": "this list",
31690
31717
  "interaction.pageAction.role.set": "this switch",
31691
31718
  "interaction.pageAction.role.attach": "this upload field",
31719
+ "interaction.pageAction.role.back": "this page",
31692
31720
  "interaction.pageAction.remember": "Don’t ask again for this kind of action",
31693
31721
  "interaction.pageAction.frame": "The target is in the embedded frame \"{frame}\".",
31694
31722
  "interaction.pageAction.elevated": "This dialog is outside the usual allowlist; it was opened by this task.",
31723
+ "interaction.downloadedFile.list": "Allow the assistant to see the list of files you recently downloaded?",
31724
+ "interaction.downloadedFile.read": "Allow the assistant to read this file from your downloads?",
31695
31725
  "interaction.downloadedFile.file": "{name} · {size}",
31696
31726
  "interaction.downloadedFile.remember": "Don’t ask again for this kind of access",
31727
+ "interaction.uploadFile.read": "Allow the skill “{skill}” to read the file you uploaded?",
31728
+ "interaction.uploadFile.file": "{name} · {type} · {size}",
31729
+ "interaction.sandbox.readReference": "Allow the skill “{skill}” to read the reference file “{path}”?",
31730
+ "interaction.sandbox.readAsset": "Allow the skill “{skill}” to read the bundled asset “{path}”?",
31731
+ "interaction.sandbox.fetchData": "Allow the skill “{skill}” to fetch the data source “{source}”?",
31732
+ "interaction.sandbox.writeArtifact": "Allow the skill “{skill}” to write the artifact “{path}”?",
31733
+ "interaction.sandbox.confirm": "The skill “{skill}” asks for confirmation:",
31697
31734
  "interaction.traceEvidence.title": "Steps this skill claims",
31698
31735
  "interaction.traceEvidence.columnDraft": "In the skill",
31699
31736
  "interaction.traceEvidence.columnTrace": "Actually run in this session",
@@ -31767,6 +31804,20 @@ const chatbotDictionary = defineDictionary({
31767
31804
  "composer.dictate.recording": "Recording",
31768
31805
  "composer.dictate.api-missing": "This browser does not support speech recognition.",
31769
31806
  "composer.dictate.insecure-context": "Voice input needs a secure connection (https or localhost).",
31807
+ "composer.dictate.denied": "The browser denied microphone access. Allow it in the site permission settings, then try again.",
31808
+ "composer.dictate.denied.handoff": "Microphone access needs to be granted once in a regular tab. Finish it there, then come back and try again.",
31809
+ "composer.dictate.failed": "Voice input stopped unexpectedly. Try again, or type your message.",
31810
+ "composer.camera": "Take a photo",
31811
+ "composer.camera.title": "Take a photo",
31812
+ "composer.camera.description": "The photo is added to the composer as an attachment. It is not sent until you send the message.",
31813
+ "composer.camera.preview": "Camera preview",
31814
+ "composer.camera.shutter": "Capture",
31815
+ "composer.camera.noStorage": "attachments are unavailable in this host",
31816
+ "composer.camera.api-missing": "This browser does not support camera capture.",
31817
+ "composer.camera.insecure-context": "The camera needs a secure connection (https or localhost).",
31818
+ "composer.camera.denied": "The browser denied camera access. Allow it in the site permission settings, then try again.",
31819
+ "composer.camera.denied.handoff": "Camera access needs to be granted once in a regular tab. Finish it there, then come back and try again.",
31820
+ "composer.camera.failed": "The photo could not be taken. Try again, or attach an image file instead.",
31770
31821
  "settings.title": "Settings",
31771
31822
  "settings.group.general": "General",
31772
31823
  "settings.group.models": "Models",
@@ -32045,17 +32096,28 @@ const chatbotDictionary = defineDictionary({
32045
32096
  "interaction.pageAction.select": "允许助手在「{target}」中选择选项吗?",
32046
32097
  "interaction.pageAction.set": "允许助手切换「{target}」吗?",
32047
32098
  "interaction.pageAction.attach": "允许助手向「{target}」附加文件吗?",
32099
+ "interaction.pageAction.back": "允许助手在「{target}」上返回上一页吗?",
32048
32100
  "interaction.pageAction.role.click": "这个控件",
32049
32101
  "interaction.pageAction.role.fill": "这个输入框",
32050
32102
  "interaction.pageAction.role.submit": "这个表单",
32051
32103
  "interaction.pageAction.role.select": "这个选择框",
32052
32104
  "interaction.pageAction.role.set": "这个开关",
32053
32105
  "interaction.pageAction.role.attach": "这个上传控件",
32106
+ "interaction.pageAction.role.back": "这个页面",
32054
32107
  "interaction.pageAction.remember": "以后不再询问此类操作",
32055
32108
  "interaction.pageAction.frame": "目标位于嵌入帧「{frame}」中。",
32056
32109
  "interaction.pageAction.elevated": "该对话框不在常规允许清单内,是本次任务打开的。",
32110
+ "interaction.downloadedFile.list": "允许助手查看你最近下载的文件列表吗?",
32111
+ "interaction.downloadedFile.read": "允许助手读取你下载目录里的这份文件吗?",
32057
32112
  "interaction.downloadedFile.file": "{name} · {size}",
32058
32113
  "interaction.downloadedFile.remember": "以后不再询问此类访问",
32114
+ "interaction.uploadFile.read": "允许技能「{skill}」读取你上传的这份文件吗?",
32115
+ "interaction.uploadFile.file": "{name} · {type} · {size}",
32116
+ "interaction.sandbox.readReference": "允许技能「{skill}」读取参考资料「{path}」吗?",
32117
+ "interaction.sandbox.readAsset": "允许技能「{skill}」读取随附文件「{path}」吗?",
32118
+ "interaction.sandbox.fetchData": "允许技能「{skill}」访问数据源「{source}」吗?",
32119
+ "interaction.sandbox.writeArtifact": "允许技能「{skill}」写出产物「{path}」吗?",
32120
+ "interaction.sandbox.confirm": "技能「{skill}」请求确认:",
32059
32121
  "interaction.traceEvidence.title": "该技能声称的步骤",
32060
32122
  "interaction.traceEvidence.columnDraft": "技能里写的",
32061
32123
  "interaction.traceEvidence.columnTrace": "本会话实际执行的",
@@ -32129,6 +32191,20 @@ const chatbotDictionary = defineDictionary({
32129
32191
  "composer.dictate.recording": "录音中",
32130
32192
  "composer.dictate.api-missing": "当前浏览器不支持语音识别。",
32131
32193
  "composer.dictate.insecure-context": "语音输入需要安全连接(https 或 localhost)。",
32194
+ "composer.dictate.denied": "浏览器拒绝了麦克风。请在站点权限设置里允许麦克风后重试。",
32195
+ "composer.dictate.denied.handoff": "麦克风需要在普通标签页里授权一次。请在那里完成授权,再回到这里重试。",
32196
+ "composer.dictate.failed": "语音输入意外中断。可以再试一次,或直接打字。",
32197
+ "composer.camera": "拍照",
32198
+ "composer.camera.title": "拍照",
32199
+ "composer.camera.description": "拍好的照片会作为附件放进输入框,你点发送才会发出去。",
32200
+ "composer.camera.preview": "摄像头取景",
32201
+ "composer.camera.shutter": "拍照",
32202
+ "composer.camera.noStorage": "当前宿主不支持附件",
32203
+ "composer.camera.api-missing": "当前浏览器不支持调用摄像头。",
32204
+ "composer.camera.insecure-context": "使用摄像头需要安全连接(https 或 localhost)。",
32205
+ "composer.camera.denied": "浏览器拒绝了摄像头。请在站点权限设置里允许摄像头后重试。",
32206
+ "composer.camera.denied.handoff": "摄像头需要在普通标签页里授权一次。请在那里完成授权,再回到这里重试。",
32207
+ "composer.camera.failed": "没能拍下这张照片。可以再试一次,或改为直接添加图片文件。",
32132
32208
  "settings.title": "设置",
32133
32209
  "settings.group.general": "通用",
32134
32210
  "settings.group.models": "模型",
@@ -33102,9 +33178,21 @@ var ChatEngine = class {
33102
33178
  await this.#finishRun(sessionId, result);
33103
33179
  } catch (e) {
33104
33180
  this.#emitError(e);
33181
+ } finally {
33182
+ this.#releaseTabWorkset();
33105
33183
  }
33106
33184
  }
33107
33185
  /**
33186
+ * run 结束就丢掉标签页工作集(0.16.0 FR-11.4 / FR-12.7)。
33187
+ *
33188
+ * 放在 `finally` 而不是 `#finishRun` 里:完成、失败、中断、取消四条路径必须清得一样干净,
33189
+ * 而异常路径根本不经过 `#finishRun`——漏一条就是让上一轮的句柄活到下一轮,
33190
+ * 那正是 SC-2 要堵的口子。它**不关闭任何标签页**:模型开出来的页归用户处置。
33191
+ */
33192
+ #releaseTabWorkset() {
33193
+ Promise.resolve(this.#adapter.pageTabs?.reset()).catch(() => void 0);
33194
+ }
33195
+ /**
33108
33196
  * 本轮触发消息里的附件,包成只对这一次 run 生效的读取端口(0.15.0 分册 17)。
33109
33197
  * 开关关着就返回 undefined → 脚本上下文里那两个成员整个不存在,
33110
33198
  * 与「本轮没传附件」(空数组)分得开。
@@ -33233,6 +33321,8 @@ var ChatEngine = class {
33233
33321
  await this.#finishRun(sessionId, result);
33234
33322
  } catch (e) {
33235
33323
  this.#emitError(e);
33324
+ } finally {
33325
+ this.#releaseTabWorkset();
33236
33326
  }
33237
33327
  }
33238
33328
  async #persistSurfaceDraft(runId, surfaceId, value) {
@@ -33541,6 +33631,7 @@ var ChatEngine = class {
33541
33631
  }
33542
33632
  })] : [],
33543
33633
  ...this.#adapter.pageActions ? [createPageActionToolSource({ policy: this.#adapter.pageActions })] : [],
33634
+ ...this.#adapter.pageTabs ? [createTabWorksetToolSource({ policy: this.#adapter.pageTabs })] : [],
33544
33635
  ...this.#adapter.downloads ? [createDownloadedFileToolSource({ policy: new DownloadedFilePolicy({
33545
33636
  reader: this.#adapter.downloads.reader,
33546
33637
  ui: this.#uiBridge,
@@ -34250,6 +34341,7 @@ function assembleAssistantParts(source) {
34250
34341
  ...source.skills.map(skillPart),
34251
34342
  ...source.perceptions.map(perceptionPart),
34252
34343
  ...source.runDetails.map((item) => item.part),
34344
+ ...source.precedingBody ?? [],
34253
34345
  ...bodyParts(source.contentParts, source.text),
34254
34346
  ...source.interactions.map(interactionPart),
34255
34347
  ...source.candidates.map(skillCandidatePart),
@@ -34272,15 +34364,24 @@ function terminationParts(phases) {
34272
34364
  }
34273
34365
  }];
34274
34366
  }
34367
+ /** 引用在「已落盘」集合里的键;blocks 一个 run 至多一个,无需再分 */
34368
+ const refKey = (part) => part.type === "surface" ? `surface:${part.surfaceId}` : "blocks";
34275
34369
  /**
34276
34370
  * 同 run 的运行细节合并计划(UI-UX5 #7):FR-17.8 的交互切片会让一个 run 产生多条
34277
34371
  * assistant 消息,每条各自组装「运行细节」就是用户看到的两个框(run id 相同)。
34278
34372
  * 这里把相位/技能/工具并入**第一条片段**(表单上方),后续片段与续跑 live 不再出框。
34279
34373
  * 相位本来就是无重叠的增量切片(`#flushed` 水位),直接拼接即全量;
34280
34374
  * live 的相位是全量累积,增量 = 全量减去已落盘数。
34375
+ *
34376
+ * 分册 16:run 未终止时还要把已切片载体本身并进 live 气泡(`hidden`)。
34377
+ * 切片会把写到一半的正文从 live 气泡搬到一个新 id 的消息上,
34378
+ * live 气泡随即被清空再由下一轮填满——每次工具调用都卸载/重建一次 DOM,
34379
+ * 就是用户看到的「正文下半段删掉又补新的进去、高度一会变小一会变大」。
34380
+ * 合并后一个进行中的 run 始终只有一条气泡,内容只增不减;终态消息到达后才恢复切片形态(历史不变)。
34281
34381
  */
34282
34382
  function planRunTrace(messages, live) {
34283
34383
  const byMessage = /* @__PURE__ */ new Map();
34384
+ const hidden = /* @__PURE__ */ new Set();
34284
34385
  let livePlan;
34285
34386
  const byRun = /* @__PURE__ */ new Map();
34286
34387
  for (const message of messages) {
@@ -34321,16 +34422,27 @@ function planRunTrace(messages, live) {
34321
34422
  terminationPhases: message.phases ?? []
34322
34423
  });
34323
34424
  });
34324
- if (liveHere) livePlan = {
34325
- phases: [],
34326
- skills: [],
34327
- runDetails: [],
34328
- settled: false,
34329
- terminationPhases: liveHere.phases
34330
- };
34425
+ if (liveHere) {
34426
+ const refs = /* @__PURE__ */ new Set();
34427
+ for (const message of carriers) for (const part of message.contentParts ?? []) if (part.type !== "text") refs.add(refKey(part));
34428
+ for (const message of carriers) hidden.add(message.id);
34429
+ livePlan = {
34430
+ phases: mergedPhases,
34431
+ skills: mergedSkills,
34432
+ runDetails: mergedRunDetails,
34433
+ settled: false,
34434
+ terminationPhases: liveHere.phases,
34435
+ merged: {
34436
+ carriers,
34437
+ text: carriers.reduce((total, m) => total + (m.text?.length ?? 0), 0),
34438
+ refs
34439
+ }
34440
+ };
34441
+ }
34331
34442
  }
34332
34443
  return {
34333
34444
  byMessage,
34445
+ hidden,
34334
34446
  ...livePlan ? { live: livePlan } : {}
34335
34447
  };
34336
34448
  }
@@ -34383,6 +34495,9 @@ function liveTiming(live) {
34383
34495
  };
34384
34496
  }
34385
34497
  function liveMessage(live, trace) {
34498
+ const from = trace?.merged?.text ?? 0;
34499
+ const flushedRefs = trace?.merged?.refs;
34500
+ const precedingBody = (trace?.merged?.carriers ?? []).flatMap((message) => [...bodyParts(message.contentParts, message.text), ...(message.interactions ?? []).map(interactionPart)]);
34386
34501
  const content = assembleAssistantParts({
34387
34502
  phases: trace?.phases ?? live.phases,
34388
34503
  skills: trace?.skills ?? live.skills,
@@ -34391,8 +34506,9 @@ function liveMessage(live, trace) {
34391
34506
  part: liveToolPart(call),
34392
34507
  at: call.startedAt
34393
34508
  })), live.thinkings),
34394
- text: live.text,
34395
- contentParts: materialize(live.ledger, live.text, 0, { includePending: true }),
34509
+ precedingBody,
34510
+ text: live.text.slice(from),
34511
+ contentParts: materialize(live.ledger, live.text, from, { includePending: true }).filter((part) => part.type === "text" || flushedRefs === void 0 || !flushedRefs.has(refKey(part))),
34396
34512
  interactions: [],
34397
34513
  candidates: [],
34398
34514
  settled: trace?.settled ?? false,
@@ -34427,13 +34543,14 @@ function liveMessage(live, trace) {
34427
34543
  */
34428
34544
  function toExternalMessages(messages, live, cache) {
34429
34545
  const trace = planRunTrace(messages, live);
34430
- const result = messages.map((message) => {
34431
- if (message.role === "user") return userMessage(message);
34546
+ const result = messages.flatMap((message) => {
34547
+ if (message.role === "user") return [userMessage(message)];
34548
+ if (trace.hidden.has(message.id)) return [];
34432
34549
  const override = trace.byMessage.get(message.id);
34433
34550
  const cached = cache?.get(message.id);
34434
34551
  const external = cached !== void 0 && override === void 0 ? cached : assistantMessage(message, override);
34435
34552
  if (override === void 0) cache?.set(message.id, external);
34436
- return external;
34553
+ return [external];
34437
34554
  });
34438
34555
  const settled = messages.some((message) => message.role === "assistant" && message.runId === live?.runId && message.interactions === void 0);
34439
34556
  if (live && !settled) result.push(liveMessage(live, trace.live));
@@ -34955,6 +35072,124 @@ function InteractionWaitBar({ bridge, label, elsewhere = false, panel, onLocate
34955
35072
  });
34956
35073
  }
34957
35074
 
35075
+ //#endregion
35076
+ //#region src/react/CameraCaptureDialog.tsx
35077
+ /**
35078
+ * 取景与快门(分册 14 FR-14.5)。
35079
+ *
35080
+ * 点遮罩不关闭:摄像头开着时误点一下就得重新授权取流,代价与「丢掉表单草稿」同级。
35081
+ * 预览框按 4:3 留位,避免流还没出画时对话框高度跳一下。
35082
+ */
35083
+ function CameraCaptureDialog({ open, busy, videoRef, onCapture, onCancel }) {
35084
+ const t = useT();
35085
+ return /* @__PURE__ */ jsx(Dialog, {
35086
+ open,
35087
+ onClose: onCancel,
35088
+ dismissOnOutsideClick: false,
35089
+ title: t("composer.camera.title"),
35090
+ description: t("composer.camera.description"),
35091
+ size: "lg",
35092
+ footer: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
35093
+ variant: "secondary",
35094
+ onClick: onCancel,
35095
+ children: t("common.cancel")
35096
+ }), /* @__PURE__ */ jsx(Button, {
35097
+ "data-testid": "composer-camera-shutter",
35098
+ icon: /* @__PURE__ */ jsx(Camera, { className: "size-4" }),
35099
+ disabled: busy,
35100
+ onClick: onCapture,
35101
+ children: t("composer.camera.shutter")
35102
+ })] }),
35103
+ children: /* @__PURE__ */ jsx("video", {
35104
+ ref: videoRef,
35105
+ "data-testid": "composer-camera-preview",
35106
+ "aria-label": t("composer.camera.preview"),
35107
+ autoPlay: true,
35108
+ muted: true,
35109
+ playsInline: true,
35110
+ className: "aspect-[4/3] w-full rounded-card bg-subtle object-contain"
35111
+ })
35112
+ });
35113
+ }
35114
+
35115
+ //#endregion
35116
+ //#region src/react/useComposerCamera.ts
35117
+ /**
35118
+ * composer 的拍照接线(分册 14)。照片只作为附件落进输入框,**不发送**——
35119
+ * 与语音同一条口径:拍歪了要能先删掉再重拍,自动发送会把误拍变成不可撤销的操作。
35120
+ */
35121
+ function useComposerCamera(onPermissionDenied) {
35122
+ const aui = useAui();
35123
+ const [availability] = useState(() => checkCameraAvailability());
35124
+ const [previewing, setPreviewing] = useState(false);
35125
+ const [busy, setBusy] = useState(false);
35126
+ const [failure, setFailure] = useState();
35127
+ const sessionRef = useRef(void 0);
35128
+ const videoElementRef = useRef(null);
35129
+ const close = useCallback(() => {
35130
+ sessionRef.current?.close();
35131
+ sessionRef.current = void 0;
35132
+ if (videoElementRef.current !== null) videoElementRef.current.srcObject = null;
35133
+ setPreviewing(false);
35134
+ setBusy(false);
35135
+ }, []);
35136
+ useEffect(() => () => sessionRef.current?.close(), []);
35137
+ /** 侧栏 / popup 这类页面弹不出授权框,只有宿主知道该把用户带到哪儿去(FR-14.7) */
35138
+ const report = useCallback((e) => {
35139
+ const denied = e instanceof WebSkillError && e.code === "CAMERA_PERMISSION_DENIED";
35140
+ setFailure(denied ? "denied" : "failed");
35141
+ if (denied) onPermissionDenied?.();
35142
+ }, [onPermissionDenied]);
35143
+ const videoRef = useCallback((element) => {
35144
+ videoElementRef.current = element;
35145
+ if (element === null) return;
35146
+ const stream = sessionRef.current?.stream;
35147
+ if (stream === void 0) return;
35148
+ element.srcObject = stream;
35149
+ element.play().catch(() => void 0);
35150
+ }, []);
35151
+ return {
35152
+ availability,
35153
+ previewing,
35154
+ busy,
35155
+ start: useCallback(() => {
35156
+ if (!availability.available || busy || previewing) return;
35157
+ setFailure(void 0);
35158
+ setBusy(true);
35159
+ openCamera().then((session) => {
35160
+ sessionRef.current = session;
35161
+ setPreviewing(true);
35162
+ setBusy(false);
35163
+ }).catch((e) => {
35164
+ setBusy(false);
35165
+ report(e);
35166
+ });
35167
+ }, [
35168
+ availability,
35169
+ busy,
35170
+ previewing,
35171
+ report
35172
+ ]),
35173
+ cancel: close,
35174
+ capture: useCallback(() => {
35175
+ const video = videoElementRef.current;
35176
+ if (video === null || busy) return;
35177
+ setBusy(true);
35178
+ capturePhoto(video).then((file) => aui.thread.composer().addAttachment(file)).then(() => close()).catch((e) => {
35179
+ close();
35180
+ report(e);
35181
+ });
35182
+ }, [
35183
+ aui,
35184
+ busy,
35185
+ close,
35186
+ report
35187
+ ]),
35188
+ videoRef,
35189
+ ...failure !== void 0 ? { failure } : {}
35190
+ };
35191
+ }
35192
+
34958
35193
  //#endregion
34959
35194
  //#region src/react/useComposerDictation.ts
34960
35195
  /** 转写片段拼到已有草稿后面:语音只做追加,不覆盖用户已经打好的字 */
@@ -34967,11 +35202,11 @@ function joinText(base, addition) {
34967
35202
  * composer 的语音输入接线(需求 7)。转写结果只写进输入框,**不发送**(FR-7.8)——
34968
35203
  * 语音识别不准是常态,自动发送会把识别错误变成不可撤销的操作。
34969
35204
  */
34970
- function useComposerDictation(lang) {
35205
+ function useComposerDictation(lang, onPermissionDenied) {
34971
35206
  const aui = useAui();
34972
35207
  const [availability] = useState(() => checkDictationAvailability());
34973
35208
  const [recording, setRecording] = useState(false);
34974
- const [error, setError] = useState();
35209
+ const [failure, setFailure] = useState();
34975
35210
  const sessionRef = useRef(void 0);
34976
35211
  /** 本次录音开始时输入框里的内容;中间结果每次都在它后面重画,否则会自我叠加 */
34977
35212
  const baseRef = useRef("");
@@ -34981,6 +35216,12 @@ function useComposerDictation(lang) {
34981
35216
  setRecording(false);
34982
35217
  }, []);
34983
35218
  useEffect(() => () => sessionRef.current?.stop(), []);
35219
+ /** 侧栏 / popup 这类页面弹不出授权框,只有宿主知道该把用户带到哪儿去(FR-10.1) */
35220
+ const report = useCallback((e) => {
35221
+ const denied = e instanceof WebSkillError && e.code === "DICTATION_PERMISSION_DENIED";
35222
+ setFailure(denied ? "denied" : "failed");
35223
+ if (denied) onPermissionDenied?.();
35224
+ }, [onPermissionDenied]);
34984
35225
  return {
34985
35226
  availability,
34986
35227
  recording,
@@ -34992,7 +35233,7 @@ function useComposerDictation(lang) {
34992
35233
  if (!availability.available) return;
34993
35234
  const composer = aui.thread.composer();
34994
35235
  baseRef.current = composer.getState().text;
34995
- setError(void 0);
35236
+ setFailure(void 0);
34996
35237
  try {
34997
35238
  sessionRef.current = startDictation({
34998
35239
  lang,
@@ -35002,21 +35243,22 @@ function useComposerDictation(lang) {
35002
35243
  aui.thread.composer().setText(baseRef.current);
35003
35244
  },
35004
35245
  onError: (e) => {
35005
- setError(messageOf(e));
35246
+ report(e);
35006
35247
  stop();
35007
35248
  }
35008
35249
  });
35009
35250
  setRecording(true);
35010
35251
  } catch (e) {
35011
- setError(messageOf(e));
35252
+ report(e);
35012
35253
  }
35013
35254
  }, [
35014
35255
  aui,
35015
35256
  availability,
35016
35257
  lang,
35258
+ report,
35017
35259
  stop
35018
35260
  ]),
35019
- ...error !== void 0 ? { error } : {}
35261
+ ...failure !== void 0 ? { failure } : {}
35020
35262
  };
35021
35263
  }
35022
35264
 
@@ -35091,13 +35333,22 @@ function ModelMenuButton({ models }) {
35091
35333
  }, option.id))
35092
35334
  })] });
35093
35335
  }
35094
- function AssistantUiComposer({ running, disabled = false, waitTarget, placeholder, attachments = false, attachmentInfo, attachmentImages, centered = false, models, noModel, dictationLang, capabilities }) {
35336
+ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholder, attachments = false, attachmentInfo, attachmentImages, centered = false, models, noModel, dictationLang, onDictationPermissionDenied, onCameraPermissionDenied, capabilities }) {
35095
35337
  const t = useT();
35096
35338
  const locale = useLocale();
35097
- const dictation = useComposerDictation(dictationLang ?? DEFAULT_DICTATION_LANG[locale]);
35339
+ const dictation = useComposerDictation(dictationLang ?? DEFAULT_DICTATION_LANG[locale], onDictationPermissionDenied);
35340
+ const camera = useComposerCamera(onCameraPermissionDenied);
35098
35341
  const dictationReason = dictation.availability.available ? void 0 : t(`composer.dictate.${dictation.availability.reason}`);
35342
+ const dictationFailure = dictation.failure === void 0 ? void 0 : dictation.failure === "denied" && onDictationPermissionDenied !== void 0 ? t("composer.dictate.denied.handoff") : t(`composer.dictate.${dictation.failure}`);
35099
35343
  const waitReason = disabled ? t("interaction.wait", { target: waitTarget ?? t("interaction.wait.fallback") }) : void 0;
35100
35344
  const attachmentReason = attachmentImages === void 0 || attachmentImages.enabled && attachmentImages.imageCapable ? void 0 : attachmentImages.enabled ? t("composer.addAttachment.modelNoImages") : t("composer.addAttachment.imagesOff");
35345
+ /**
35346
+ * 相机的禁用原因(FR-14.2)。三道门缺一不可,且顺序即优先级:
35347
+ * 存不下 → 图片被关 / 模型不收图 → 浏览器不支持。先说最外层那道,
35348
+ * 否则用户会照着「换 https」折腾半天,其实是模型压根不收图。
35349
+ */
35350
+ const cameraReason = !attachments ? t("composer.camera.noStorage") : attachmentReason ?? (camera.availability.available ? void 0 : t(`composer.camera.${camera.availability.reason}`));
35351
+ const cameraFailure = camera.failure === void 0 ? void 0 : camera.failure === "denied" && onCameraPermissionDenied !== void 0 ? t("composer.camera.denied.handoff") : t(`composer.camera.${camera.failure}`);
35101
35352
  const plainChatHintId = useId();
35102
35353
  return /* @__PURE__ */ jsx(ComposerPrimitive.Root, {
35103
35354
  "data-testid": "assistant-ui-composer",
@@ -35231,70 +35482,102 @@ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholde
35231
35482
  ]
35232
35483
  }), /* @__PURE__ */ jsxs("div", {
35233
35484
  className: "flex shrink-0 items-center gap-1",
35234
- children: [/* @__PURE__ */ jsxs("button", {
35235
- type: "button",
35236
- "data-testid": "composer-dictate",
35237
- "data-recording": dictation.recording ? "true" : void 0,
35238
- "aria-pressed": dictation.recording,
35239
- "aria-label": dictation.recording ? t("composer.dictate.stop") : t("composer.dictate"),
35240
- title: dictationReason ?? (dictation.recording ? t("composer.dictate.stop") : t("composer.dictate")),
35241
- disabled: dictationReason !== void 0,
35242
- onClick: dictation.toggle,
35243
- className: `inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${dictation.recording ? "animate-pulse bg-danger/10 text-danger" : "text-muted hover:bg-accent hover:text-ink"}`,
35244
- children: [dictation.recording ? /* @__PURE__ */ jsx(Square, {
35245
- className: "size-3 fill-current",
35246
- "aria-hidden": true
35247
- }) : /* @__PURE__ */ jsx(Mic, {
35248
- className: "size-4",
35249
- "aria-hidden": true
35250
- }), /* @__PURE__ */ jsx("span", {
35251
- className: "sr-only",
35252
- children: dictation.recording ? t("composer.dictate.recording") : t("composer.dictate")
35253
- })]
35254
- }), running ? /* @__PURE__ */ jsxs(ComposerPrimitive.Cancel, {
35255
- "data-testid": "composer-send",
35256
- "aria-label": t("composer.stop"),
35257
- className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full border border-border text-ink transition-colors hover:bg-accent",
35258
- children: [/* @__PURE__ */ jsx(Square, {
35259
- className: "size-3 fill-current",
35260
- "aria-hidden": true
35261
- }), /* @__PURE__ */ jsx("span", {
35262
- className: "sr-only",
35263
- children: t("composer.stop")
35264
- })]
35265
- }) : noModel ? /* @__PURE__ */ jsxs("button", {
35266
- type: "button",
35267
- "data-testid": "composer-send",
35268
- "aria-label": t("composer.send"),
35269
- onClick: noModel.onConfigure,
35270
- className: "webskill-aui-send inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50",
35271
- children: [/* @__PURE__ */ jsx(ArrowUp, {
35272
- className: "size-4",
35273
- "aria-hidden": true
35274
- }), /* @__PURE__ */ jsx("span", {
35275
- className: "sr-only",
35276
- children: t("composer.send")
35277
- })]
35278
- }) : /* @__PURE__ */ jsxs(ComposerPrimitive.Send, {
35279
- "data-testid": "composer-send",
35280
- "aria-label": t("composer.send"),
35281
- className: "webskill-aui-send inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50",
35282
- children: [/* @__PURE__ */ jsx(ArrowUp, {
35283
- className: "size-4",
35284
- "aria-hidden": true
35285
- }), /* @__PURE__ */ jsx("span", {
35286
- className: "sr-only",
35287
- children: t("composer.send")
35288
- })]
35289
- })]
35485
+ children: [
35486
+ /* @__PURE__ */ jsxs("button", {
35487
+ type: "button",
35488
+ "data-testid": "composer-camera",
35489
+ "aria-label": t("composer.camera"),
35490
+ title: cameraReason === void 0 ? t("composer.camera") : `${t("composer.camera")} — ${cameraReason}`,
35491
+ disabled: cameraReason !== void 0 || camera.busy,
35492
+ onClick: camera.start,
35493
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full text-muted transition-colors hover:bg-accent hover:text-ink disabled:cursor-not-allowed disabled:opacity-50",
35494
+ children: [/* @__PURE__ */ jsx(Camera, {
35495
+ className: "size-4",
35496
+ "aria-hidden": true
35497
+ }), /* @__PURE__ */ jsx("span", {
35498
+ className: "sr-only",
35499
+ children: t("composer.camera")
35500
+ })]
35501
+ }),
35502
+ /* @__PURE__ */ jsxs("button", {
35503
+ type: "button",
35504
+ "data-testid": "composer-dictate",
35505
+ "data-recording": dictation.recording ? "true" : void 0,
35506
+ "aria-pressed": dictation.recording,
35507
+ "aria-label": dictation.recording ? t("composer.dictate.stop") : t("composer.dictate"),
35508
+ title: dictationReason ?? (dictation.recording ? t("composer.dictate.stop") : t("composer.dictate")),
35509
+ disabled: dictationReason !== void 0,
35510
+ onClick: dictation.toggle,
35511
+ className: `inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${dictation.recording ? "animate-pulse bg-danger/10 text-danger" : "text-muted hover:bg-accent hover:text-ink"}`,
35512
+ children: [dictation.recording ? /* @__PURE__ */ jsx(Square, {
35513
+ className: "size-3 fill-current",
35514
+ "aria-hidden": true
35515
+ }) : /* @__PURE__ */ jsx(Mic, {
35516
+ className: "size-4",
35517
+ "aria-hidden": true
35518
+ }), /* @__PURE__ */ jsx("span", {
35519
+ className: "sr-only",
35520
+ children: dictation.recording ? t("composer.dictate.recording") : t("composer.dictate")
35521
+ })]
35522
+ }),
35523
+ running ? /* @__PURE__ */ jsxs(ComposerPrimitive.Cancel, {
35524
+ "data-testid": "composer-send",
35525
+ "aria-label": t("composer.stop"),
35526
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full border border-border text-ink transition-colors hover:bg-accent",
35527
+ children: [/* @__PURE__ */ jsx(Square, {
35528
+ className: "size-3 fill-current",
35529
+ "aria-hidden": true
35530
+ }), /* @__PURE__ */ jsx("span", {
35531
+ className: "sr-only",
35532
+ children: t("composer.stop")
35533
+ })]
35534
+ }) : noModel ? /* @__PURE__ */ jsxs("button", {
35535
+ type: "button",
35536
+ "data-testid": "composer-send",
35537
+ "aria-label": t("composer.send"),
35538
+ onClick: noModel.onConfigure,
35539
+ className: "webskill-aui-send inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50",
35540
+ children: [/* @__PURE__ */ jsx(ArrowUp, {
35541
+ className: "size-4",
35542
+ "aria-hidden": true
35543
+ }), /* @__PURE__ */ jsx("span", {
35544
+ className: "sr-only",
35545
+ children: t("composer.send")
35546
+ })]
35547
+ }) : /* @__PURE__ */ jsxs(ComposerPrimitive.Send, {
35548
+ "data-testid": "composer-send",
35549
+ "aria-label": t("composer.send"),
35550
+ className: "webskill-aui-send inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50",
35551
+ children: [/* @__PURE__ */ jsx(ArrowUp, {
35552
+ className: "size-4",
35553
+ "aria-hidden": true
35554
+ }), /* @__PURE__ */ jsx("span", {
35555
+ className: "sr-only",
35556
+ children: t("composer.send")
35557
+ })]
35558
+ })
35559
+ ]
35290
35560
  })]
35291
35561
  }),
35292
- dictation.error !== void 0 ? /* @__PURE__ */ jsx("p", {
35562
+ dictationFailure !== void 0 ? /* @__PURE__ */ jsx("p", {
35293
35563
  "data-testid": "composer-dictate-error",
35294
35564
  role: "alert",
35295
35565
  className: "text-xs text-danger",
35296
- children: dictation.error
35297
- }) : null
35566
+ children: dictationFailure
35567
+ }) : null,
35568
+ cameraFailure !== void 0 ? /* @__PURE__ */ jsx("p", {
35569
+ "data-testid": "composer-camera-error",
35570
+ role: "alert",
35571
+ className: "text-xs text-danger",
35572
+ children: cameraFailure
35573
+ }) : null,
35574
+ /* @__PURE__ */ jsx(CameraCaptureDialog, {
35575
+ open: camera.previewing,
35576
+ busy: camera.busy,
35577
+ videoRef: camera.videoRef,
35578
+ onCapture: camera.capture,
35579
+ onCancel: camera.cancel
35580
+ })
35298
35581
  ]
35299
35582
  })
35300
35583
  });
@@ -45858,8 +46141,155 @@ function InlineBlocks() {
45858
46141
  });
45859
46142
  }
45860
46143
 
46144
+ //#endregion
46145
+ //#region src/react/authorizeDetails.ts
46146
+ /** 页面操作确认卡的载荷;形状不对就整段不渲染,退回通用授权展示 */
46147
+ function pageActionDetails(details) {
46148
+ if (typeof details !== "object" || details === null) return void 0;
46149
+ const record = details;
46150
+ const action = record["action"];
46151
+ const target = record["target"];
46152
+ if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return void 0;
46153
+ if (typeof target !== "object" || target === null) return void 0;
46154
+ const { role, name, frame } = target;
46155
+ if (typeof role !== "string") return void 0;
46156
+ const value = record["value"];
46157
+ const rememberLabel = record["rememberLabel"];
46158
+ return {
46159
+ action,
46160
+ role,
46161
+ ...typeof name === "string" ? { name } : {},
46162
+ ...typeof value === "string" ? { value } : {},
46163
+ ...typeof frame === "string" ? { frame } : {},
46164
+ ...record["elevated"] === true ? { elevated: true } : {},
46165
+ ...record["rememberable"] === true ? { rememberable: true } : {},
46166
+ ...typeof rememberLabel === "string" ? { rememberLabel } : {}
46167
+ };
46168
+ }
46169
+ /**
46170
+ * 下载文件确认卡的载荷(0.14.0 分册 20)。与 `pageActionDetails` 同一条规矩:
46171
+ * 形状不对就整段不渲染,退回通用授权展示,不猜。
46172
+ */
46173
+ function downloadedFileDetails(details) {
46174
+ if (typeof details !== "object" || details === null) return void 0;
46175
+ const record = details;
46176
+ const action = record["action"];
46177
+ if (action !== "list" && action !== "read") return void 0;
46178
+ const file = record["file"];
46179
+ const rememberLabel = record["rememberLabel"];
46180
+ const named = typeof file === "object" && file !== null ? file : void 0;
46181
+ return {
46182
+ action,
46183
+ ...typeof named?.["name"] === "string" && typeof named["size"] === "number" ? { file: {
46184
+ name: named["name"],
46185
+ size: named["size"]
46186
+ } } : {},
46187
+ ...record["rememberable"] === true ? { rememberable: true } : {},
46188
+ ...typeof rememberLabel === "string" ? { rememberLabel } : {}
46189
+ };
46190
+ }
46191
+ /** 上传文件确认卡的载荷(0.15.0 分册 17,0.17.0 分册 19 起结构化) */
46192
+ function uploadFileDetails(details) {
46193
+ if (typeof details !== "object" || details === null) return void 0;
46194
+ const record = details;
46195
+ const skill = record["skill"];
46196
+ if (typeof skill !== "string") return void 0;
46197
+ const file = record["file"];
46198
+ const named = typeof file === "object" && file !== null ? file : void 0;
46199
+ return {
46200
+ skill,
46201
+ ...typeof named?.["name"] === "string" && typeof named["contentType"] === "string" && typeof named["size"] === "number" ? { file: {
46202
+ name: named["name"],
46203
+ contentType: named["contentType"],
46204
+ size: named["size"]
46205
+ } } : {}
46206
+ };
46207
+ }
46208
+ /** 五个沙箱能力闸门的共用载荷(`gate()` 统一注入 `skill`,其余按能力各带一项) */
46209
+ function sandboxDetails(details) {
46210
+ if (typeof details !== "object" || details === null) return void 0;
46211
+ const record = details;
46212
+ const skill = record["skill"];
46213
+ if (typeof skill !== "string") return void 0;
46214
+ const out = { skill };
46215
+ for (const key of [
46216
+ "path",
46217
+ "sourceId",
46218
+ "question"
46219
+ ]) {
46220
+ const value = record[key];
46221
+ if (typeof value === "string") out[key] = value;
46222
+ }
46223
+ return out;
46224
+ }
46225
+ /**
46226
+ * 授权卡正文的本地化文案(分册 19 FR-19.2 / FR-19.6)。
46227
+ *
46228
+ * 活卡、只读回看、非 native 三档的 spec 通道读的是同一个 `request`,因此这份映射
46229
+ * 只能有一份:写进任一视图都会让另外两个继续显示 runtime 的英文原句——
46230
+ * 0.12.0 分册 23 与 0.16.0 分册 15 各犯过一次。
46231
+ * 认不得的能力或形状不对的载荷返回 undefined,正文回落到 `request.message`(FR-19.5)。
46232
+ */
46233
+ function localizedAuthorizeMessage(request, t) {
46234
+ if (request.type !== "authorize") return void 0;
46235
+ if (request.capability === "readUploadFile") {
46236
+ const details = uploadFileDetails(request.details);
46237
+ if (details === void 0) return void 0;
46238
+ const head = t("interaction.uploadFile.read", { skill: details.skill });
46239
+ if (details.file === void 0) return head;
46240
+ return `${head} ${t("interaction.uploadFile.file", {
46241
+ name: details.file.name,
46242
+ type: details.file.contentType,
46243
+ size: formatBytes(details.file.size)
46244
+ })}`;
46245
+ }
46246
+ if (request.capability === "readReference" || request.capability === "readAsset" || request.capability === "writeArtifact" || request.capability === "fetchData" || request.capability === "confirm") {
46247
+ const details = sandboxDetails(request.details);
46248
+ if (details === void 0) return void 0;
46249
+ if (request.capability === "fetchData") {
46250
+ if (details["sourceId"] === void 0) return void 0;
46251
+ return t("interaction.sandbox.fetchData", {
46252
+ skill: details["skill"],
46253
+ source: details["sourceId"]
46254
+ });
46255
+ }
46256
+ if (request.capability === "confirm") {
46257
+ const head = t("interaction.sandbox.confirm", { skill: details["skill"] });
46258
+ return details["question"] === void 0 ? head : `${head} ${details["question"]}`;
46259
+ }
46260
+ if (details["path"] === void 0) return void 0;
46261
+ return t(`interaction.sandbox.${request.capability}`, {
46262
+ skill: details["skill"],
46263
+ path: details["path"]
46264
+ });
46265
+ }
46266
+ if (request.capability === "pageAction") {
46267
+ const details = pageActionDetails(request.details);
46268
+ if (details === void 0) return void 0;
46269
+ return t(`interaction.pageAction.${details.action}`, { target: details.name ?? t(`interaction.pageAction.role.${details.action}`) });
46270
+ }
46271
+ if (request.capability === "readDownloadedFile") {
46272
+ const details = downloadedFileDetails(request.details);
46273
+ if (details === void 0) return void 0;
46274
+ const head = t(`interaction.downloadedFile.${details.action}`);
46275
+ if (details.file === void 0) return head;
46276
+ return `${head} ${t("interaction.downloadedFile.file", {
46277
+ name: details.file.name,
46278
+ size: formatBytes(details.file.size)
46279
+ })}`;
46280
+ }
46281
+ }
46282
+
45861
46283
  //#endregion
45862
46284
  //#region src/react/interactionLabels.ts
46285
+ /**
46286
+ * 这两类的次要按钮是「不同意」而非「中止这次运行」(UI-UX8 D14):
46287
+ * 报成 cancelled 会让运行整个结束,工具拿不到否决结果,也就不会留下否决的审计。
46288
+ * native 卡与 spec 三档共用本判定,不得各写一份(分册 15 缺陷 C 就是两份逐渐走歧)。
46289
+ */
46290
+ function declinesOnCancel(request) {
46291
+ return request.type === "confirm" || request.type === "authorize";
46292
+ }
45863
46293
  /** 交互类型 → 标题字典键。徽章与声明树共用,避免两处各写一份映射。 */
45864
46294
  function interactionTitleKey(request) {
45865
46295
  return request.type === "file-pick" ? "interaction.filePick.title" : `interaction.${request.type}.title`;
@@ -45869,10 +46299,12 @@ function interactionTitleKey(request) {
45869
46299
  * 四档共用本函数,切换渲染档不会让同一个请求出现两套文案。
45870
46300
  */
45871
46301
  function interactionLabels(request, t) {
46302
+ const message = localizedAuthorizeMessage(request, t);
45872
46303
  return {
45873
46304
  title: request.type === "form" && request.title ? request.title : t(interactionTitleKey(request)),
45874
46305
  submitLabel: t(request.type === "authorize" ? "interaction.allow" : request.type === "confirm" ? "interaction.confirm" : "interaction.submit"),
45875
- cancelLabel: t(request.type === "authorize" ? "interaction.deny" : "interaction.cancel")
46306
+ cancelLabel: t(request.type === "authorize" ? "interaction.deny" : "interaction.cancel"),
46307
+ ...message !== void 0 ? { message } : {}
45876
46308
  };
45877
46309
  }
45878
46310
 
@@ -47410,51 +47842,6 @@ function fileToBase64(file) {
47410
47842
  reader.readAsDataURL(file);
47411
47843
  });
47412
47844
  }
47413
- /** 页面操作确认卡的载荷;形状不对就整段不渲染,退回通用授权展示 */
47414
- function pageActionDetails(details) {
47415
- if (typeof details !== "object" || details === null) return void 0;
47416
- const record = details;
47417
- const action = record["action"];
47418
- const target = record["target"];
47419
- if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return void 0;
47420
- if (typeof target !== "object" || target === null) return void 0;
47421
- const { role, name, frame } = target;
47422
- if (typeof role !== "string") return void 0;
47423
- const value = record["value"];
47424
- const rememberLabel = record["rememberLabel"];
47425
- return {
47426
- action,
47427
- role,
47428
- ...typeof name === "string" ? { name } : {},
47429
- ...typeof value === "string" ? { value } : {},
47430
- ...typeof frame === "string" ? { frame } : {},
47431
- ...record["elevated"] === true ? { elevated: true } : {},
47432
- ...record["rememberable"] === true ? { rememberable: true } : {},
47433
- ...typeof rememberLabel === "string" ? { rememberLabel } : {}
47434
- };
47435
- }
47436
- /**
47437
- * 下载文件确认卡的载荷(0.14.0 分册 20)。与 `pageActionDetails` 同一条规矩:
47438
- * 形状不对就整段不渲染,退回通用授权展示,不猜。
47439
- */
47440
- function downloadedFileDetails(details) {
47441
- if (typeof details !== "object" || details === null) return void 0;
47442
- const record = details;
47443
- const action = record["action"];
47444
- if (action !== "list" && action !== "read") return void 0;
47445
- const file = record["file"];
47446
- const rememberLabel = record["rememberLabel"];
47447
- const named = typeof file === "object" && file !== null ? file : void 0;
47448
- return {
47449
- action,
47450
- ...typeof named?.["name"] === "string" && typeof named["size"] === "number" ? { file: {
47451
- name: named["name"],
47452
- size: named["size"]
47453
- } } : {},
47454
- ...record["rememberable"] === true ? { rememberable: true } : {},
47455
- ...typeof rememberLabel === "string" ? { rememberLabel } : {}
47456
- };
47457
- }
47458
47845
  /**
47459
47846
  * 轨迹证据载荷(分册 30)。形状不对就整段不渲染,退回 `preview` 纯文本——
47460
47847
  * 与 `pageActionDetails` 同一个先例:认不得的结构不猜。
@@ -47490,17 +47877,19 @@ function ActiveInteractionCard({ request, bridge }) {
47490
47877
  const pageAction = isAuthorize && request.capability === "pageAction" ? pageActionDetails(request.details) : void 0;
47491
47878
  const downloadedFile = isAuthorize && request.capability === "readDownloadedFile" ? downloadedFileDetails(request.details) : void 0;
47492
47879
  const traceEvidence = isAuthorize ? traceEvidenceDetails(request.details) : void 0;
47493
- const spec = interactionToUiSpec(request.type === "authorize" && pageAction !== void 0 ? {
47880
+ const localized = pageAction !== void 0 || downloadedFile !== void 0;
47881
+ const spec = interactionToUiSpec(request.type === "authorize" && localized ? {
47494
47882
  ...request,
47495
47883
  message: ""
47496
47884
  } : request, {
47497
47885
  ...interactionLabels(request, t),
47886
+ ...localized ? { message: "" } : {},
47498
47887
  title: request.type === "form" && request.title ? request.title : ""
47499
47888
  });
47500
47889
  const preview = isAuthorize && typeof request.details === "object" && request.details !== null ? request.details["preview"] : void 0;
47501
47890
  const handleAction = (event) => {
47502
47891
  if (event.intent !== "submit") {
47503
- if (request.type === "confirm" || request.type === "authorize") {
47892
+ if (declinesOnCancel(request)) {
47504
47893
  bridge.resolve({
47505
47894
  id: request.id,
47506
47895
  value: false
@@ -47604,22 +47993,30 @@ function ActiveInteractionCard({ request, bridge }) {
47604
47993
  }), pageAction.rememberLabel ?? t("interaction.pageAction.remember")]
47605
47994
  }) : null
47606
47995
  ] }) : null,
47607
- downloadedFile ? /* @__PURE__ */ jsxs(Fragment$1, { children: [downloadedFile.file ? /* @__PURE__ */ jsx("p", {
47608
- "data-testid": "interaction-downloaded-file",
47609
- className: "text-sm text-ink",
47610
- children: t("interaction.downloadedFile.file", {
47611
- name: downloadedFile.file.name,
47612
- size: formatBytes(downloadedFile.file.size)
47613
- })
47614
- }) : null, downloadedFile.rememberable === true ? /* @__PURE__ */ jsxs("label", {
47615
- className: "flex items-center gap-2 text-sm text-ink",
47616
- children: [/* @__PURE__ */ jsx("input", {
47617
- type: "checkbox",
47618
- "data-testid": "interaction-downloaded-file-remember",
47619
- checked: remember,
47620
- onChange: (event) => setRemember(event.currentTarget.checked)
47621
- }), downloadedFile.rememberLabel ?? t("interaction.downloadedFile.remember")]
47622
- }) : null] }) : null,
47996
+ downloadedFile ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
47997
+ /* @__PURE__ */ jsx("p", {
47998
+ "data-testid": "interaction-downloaded-file-action",
47999
+ className: "text-sm text-ink",
48000
+ children: t(`interaction.downloadedFile.${downloadedFile.action}`)
48001
+ }),
48002
+ downloadedFile.file ? /* @__PURE__ */ jsx("p", {
48003
+ "data-testid": "interaction-downloaded-file",
48004
+ className: "text-sm text-ink",
48005
+ children: t("interaction.downloadedFile.file", {
48006
+ name: downloadedFile.file.name,
48007
+ size: formatBytes(downloadedFile.file.size)
48008
+ })
48009
+ }) : null,
48010
+ downloadedFile.rememberable === true ? /* @__PURE__ */ jsxs("label", {
48011
+ className: "flex items-center gap-2 text-sm text-ink",
48012
+ children: [/* @__PURE__ */ jsx("input", {
48013
+ type: "checkbox",
48014
+ "data-testid": "interaction-downloaded-file-remember",
48015
+ checked: remember,
48016
+ onChange: (event) => setRemember(event.currentTarget.checked)
48017
+ }), downloadedFile.rememberLabel ?? t("interaction.downloadedFile.remember")]
48018
+ }) : null
48019
+ ] }) : null,
47623
48020
  traceEvidence ? /* @__PURE__ */ jsxs("div", {
47624
48021
  "data-testid": "interaction-trace-evidence",
47625
48022
  children: [/* @__PURE__ */ jsx("div", {
@@ -47667,7 +48064,7 @@ function ActiveInteractionCard({ request, bridge }) {
47667
48064
  }, `${step.tool}-${index}`))
47668
48065
  })]
47669
48066
  }) : null,
47670
- request.details !== void 0 && pageAction === void 0 ? /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
48067
+ request.details !== void 0 && !localized ? /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
47671
48068
  className: "mb-1 text-xs font-medium text-muted",
47672
48069
  children: t("interaction.authorize.details")
47673
48070
  }), /* @__PURE__ */ jsx("pre", {
@@ -47948,11 +48345,15 @@ function SpecInteraction({ channel, renderer }) {
47948
48345
  }, [channel, renderer]);
47949
48346
  if (!session) return null;
47950
48347
  const Surface = SURFACES[renderer];
48348
+ const { request } = session;
47951
48349
  return /* @__PURE__ */ jsx(Surface, {
47952
- surfaceId: session.request.id,
48350
+ surfaceId: request.id,
47953
48351
  spec: session.spec,
47954
48352
  actions: INTERACTION_ACTIONS,
47955
- onAction: (event) => channel.handleAction(event)
48353
+ onAction: (event) => {
48354
+ if (event.intent !== "submit" && declinesOnCancel(request)) channel.decline(request.id);
48355
+ else channel.handleAction(event);
48356
+ }
47956
48357
  });
47957
48358
  }
47958
48359
 
@@ -48088,7 +48489,7 @@ async function downloadArtifactFile(storage, chatRoot, file) {
48088
48489
  * presentation shell and accessible thread primitives.
48089
48490
  * @stable
48090
48491
  */
48091
- function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, surfaceRegistry, layout = "auto", sessionList = "auto", onOpenSettings, docked, onDock, onClose, onEngineReady }) {
48492
+ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, onDictationPermissionDenied, onCameraPermissionDenied, surfaceRegistry, layout = "auto", sessionList = "auto", onOpenSettings, docked, onDock, onClose, onEngineReady }) {
48092
48493
  const engine = useMemo(() => new ChatEngine(adapter, {
48093
48494
  ...config?.chatRoot ? { chatRoot: config.chatRoot } : {},
48094
48495
  ...config?.llm ? { llm: config.llm } : {},
@@ -48785,6 +49186,8 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48785
49186
  imageCapable: modelTakesImages
48786
49187
  },
48787
49188
  ...dictationLang !== "" ? { dictationLang } : {},
49189
+ ...onDictationPermissionDenied ? { onDictationPermissionDenied } : {},
49190
+ ...onCameraPermissionDenied ? { onCameraPermissionDenied } : {},
48788
49191
  ...composerModels && !noUsableModel ? { models: composerModels } : {},
48789
49192
  ...noUsableModel ? { noModel: { onConfigure: () => setNoModelGuideOpen(true) } } : {},
48790
49193
  ...config?.placeholder ? { placeholder: config.placeholder } : {}
@@ -48861,7 +49264,7 @@ function VercelPayloadPreview({ bridge }) {
48861
49264
  * Version of the published `@webskill/chatbot` package, injected at build time.
48862
49265
  * @stable
48863
49266
  */
48864
- const CHATBOT_VERSION = "0.12.0";
49267
+ const CHATBOT_VERSION = "0.14.0";
48865
49268
 
48866
49269
  //#endregion
48867
49270
  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 };