@webskill/chatbot 0.19.0 → 0.21.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/README.md CHANGED
@@ -114,18 +114,24 @@ implementation (OPFS storage, skills, demo/model providers, console embedding).
114
114
  `ChatEngine.listSessions()` forwards straight to the `SessionStore`, so the
115
115
  default `FsSessionStore` decides what you see:
116
116
 
117
- - Sessions are sorted by `createdAt` **ascending**, and a page is taken from the
118
- **tail** of that list. The first screen therefore holds the _newest_ sessions,
119
- and the oldest session is not on it.
117
+ - Sessions come back **newest first**: `items[0]` is the most recently created
118
+ session, and the first screen holds the newest page.
120
119
  - The page size is the store's (`FS_SESSION_PAGE_SIZE`, 50). `<Chatbot>` does
121
120
  not override it — pass your own `sessionStore` if you need another size.
122
- - `nextCursor` points further back in history. While it is set, the session list
123
- shows a **Load earlier sessions** entry; when it is absent the list is at the
124
- end and the entry disappears, so "no button" always means "nothing older".
121
+ - `nextCursor` points further back in history, and the page it returns is
122
+ appended to the **end** of the list. While it is set, the session list shows a
123
+ **Load earlier sessions** entry **below** the existing rows; when it is absent
124
+ the list is at the end and the entry disappears, so "no button" always means
125
+ "nothing older".
125
126
  - Session files that cannot be parsed are skipped rather than failing the whole
126
127
  list. Their file names come back in `skipped` and are shown above the list, so
127
128
  a session you expected to see but cannot is always accounted for.
128
129
 
130
+ If you supply your own `SessionStore`, its `list()` must return the page
131
+ newest-first. This is a behavioural contract that the type system cannot check:
132
+ a store that still returns ascending order will render the oldest session at the
133
+ top.
134
+
129
135
  ## Embedding models
130
136
 
131
137
  - **As a component** inside an existing web app — mount `<Chatbot>` in your own
package/dist/index.d.ts CHANGED
@@ -1344,6 +1344,8 @@ declare const chatbotDictionary: {
1344
1344
  'interaction.pageAction.attach': string;
1345
1345
  'interaction.pageAction.scroll.down': string;
1346
1346
  'interaction.pageAction.scroll.up': string;
1347
+ 'interaction.pageAction.scroll.left': string;
1348
+ 'interaction.pageAction.scroll.right': string;
1347
1349
  'interaction.pageAction.hover': string;
1348
1350
  'interaction.pageAction.drag': string;
1349
1351
  'interaction.pageAction.back': string;
@@ -1379,6 +1381,11 @@ declare const chatbotDictionary: {
1379
1381
  'interaction.visionDelegation.rememberFallback': string;
1380
1382
  'interaction.visionDelegation.rememberSession': string;
1381
1383
  'interaction.visionDelegation.body': string;
1384
+ 'interaction.delegation.title': string;
1385
+ 'interaction.delegation.body': string;
1386
+ 'interaction.delegation.declineNote': string;
1387
+ 'interaction.delegation.concurrency': string;
1388
+ 'interaction.delegation.suggestedNote': string;
1382
1389
  'interaction.imageSource.title': string;
1383
1390
  'interaction.imageSource.pageImageBody': string;
1384
1391
  'interaction.imageSource.remoteImageBody': string;
@@ -1773,6 +1780,8 @@ declare const chatbotDictionary: {
1773
1780
  'interaction.pageAction.attach': string;
1774
1781
  'interaction.pageAction.scroll.down': string;
1775
1782
  'interaction.pageAction.scroll.up': string;
1783
+ 'interaction.pageAction.scroll.left': string;
1784
+ 'interaction.pageAction.scroll.right': string;
1776
1785
  'interaction.pageAction.hover': string;
1777
1786
  'interaction.pageAction.drag': string;
1778
1787
  'interaction.pageAction.back': string;
@@ -1808,6 +1817,11 @@ declare const chatbotDictionary: {
1808
1817
  'interaction.visionDelegation.rememberFallback': string;
1809
1818
  'interaction.visionDelegation.rememberSession': string;
1810
1819
  'interaction.visionDelegation.body': string;
1820
+ 'interaction.delegation.title': string;
1821
+ 'interaction.delegation.body': string;
1822
+ 'interaction.delegation.declineNote': string;
1823
+ 'interaction.delegation.concurrency': string;
1824
+ 'interaction.delegation.suggestedNote': string;
1811
1825
  'interaction.imageSource.title': string;
1812
1826
  'interaction.imageSource.pageImageBody': string;
1813
1827
  'interaction.imageSource.remoteImageBody': string;
@@ -2205,6 +2219,8 @@ declare const useT: () => TranslateFn<{
2205
2219
  'interaction.pageAction.attach': string;
2206
2220
  'interaction.pageAction.scroll.down': string;
2207
2221
  'interaction.pageAction.scroll.up': string;
2222
+ 'interaction.pageAction.scroll.left': string;
2223
+ 'interaction.pageAction.scroll.right': string;
2208
2224
  'interaction.pageAction.hover': string;
2209
2225
  'interaction.pageAction.drag': string;
2210
2226
  'interaction.pageAction.back': string;
@@ -2240,6 +2256,11 @@ declare const useT: () => TranslateFn<{
2240
2256
  'interaction.visionDelegation.rememberFallback': string;
2241
2257
  'interaction.visionDelegation.rememberSession': string;
2242
2258
  'interaction.visionDelegation.body': string;
2259
+ 'interaction.delegation.title': string;
2260
+ 'interaction.delegation.body': string;
2261
+ 'interaction.delegation.declineNote': string;
2262
+ 'interaction.delegation.concurrency': string;
2263
+ 'interaction.delegation.suggestedNote': string;
2243
2264
  'interaction.imageSource.title': string;
2244
2265
  'interaction.imageSource.pageImageBody': string;
2245
2266
  'interaction.imageSource.remoteImageBody': string;
@@ -2634,6 +2655,8 @@ declare const useT: () => TranslateFn<{
2634
2655
  'interaction.pageAction.attach': string;
2635
2656
  'interaction.pageAction.scroll.down': string;
2636
2657
  'interaction.pageAction.scroll.up': string;
2658
+ 'interaction.pageAction.scroll.left': string;
2659
+ 'interaction.pageAction.scroll.right': string;
2637
2660
  'interaction.pageAction.hover': string;
2638
2661
  'interaction.pageAction.drag': string;
2639
2662
  'interaction.pageAction.back': string;
@@ -2669,6 +2692,11 @@ declare const useT: () => TranslateFn<{
2669
2692
  'interaction.visionDelegation.rememberFallback': string;
2670
2693
  'interaction.visionDelegation.rememberSession': string;
2671
2694
  'interaction.visionDelegation.body': string;
2695
+ 'interaction.delegation.title': string;
2696
+ 'interaction.delegation.body': string;
2697
+ 'interaction.delegation.declineNote': string;
2698
+ 'interaction.delegation.concurrency': string;
2699
+ 'interaction.delegation.suggestedNote': string;
2672
2700
  'interaction.imageSource.title': string;
2673
2701
  'interaction.imageSource.pageImageBody': string;
2674
2702
  'interaction.imageSource.remoteImageBody': string;
@@ -3208,6 +3236,6 @@ declare function ErrorCard({ code, message, loopLimits, onOpenSettings, onDismis
3208
3236
  * Version of the published `@webskill/chatbot` package, injected at build time.
3209
3237
  * @stable
3210
3238
  */
3211
- declare const CHATBOT_VERSION = "0.19.0";
3239
+ declare const CHATBOT_VERSION = "0.21.0";
3212
3240
  //#endregion
3213
3241
  export { A2uiSurfaceHost, type A2uiSurfaceHostProps, A2uiSurfaceSnapshotHost, type A2uiSurfaceSnapshotHostProps, type AppearanceChange, CHATBOT_VERSION, type ChatAttachmentInput, type ChatAttachmentMeta, type ChatContentPart, ChatEngine, type ChatEngineOptions, type ChatEvent, type ChatInteractionRecord, type ChatLayout, type ChatMessage, type ChatRunPhase, type ChatSessionListResult, type ChatSessionMeta, type ChatTheme, type ChatThinking, type ChatTokenUsage, type ChatToolCall, Chatbot, type ChatbotConfig, type ChatbotGovernancePorts, type ChatbotHostAdapter, type ChatbotProps, ChatbotVisionDelegate, type ChatbotVisionDelegateOptions, CompositeUiBridge, type CompositeUiBridgeDeps, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, type DownloadableFile, type DwgHost, ErrorCard, type ErrorCardProps, InteractionCard, type InteractionCardProps, InterruptedBanner, type InterruptedBannerProps, type Locale, type LocalizedText, OpenUiSurfaceHost, type OpenUiSurfaceHostProps, OpenUiSurfaceSnapshotHost, type OpenUiSurfaceSnapshotHostProps, type QuickPrompt, type QuickPromptIconName, type RendererCapability, type RendererKind, type ResolvedChatLayout, ResultBlockList, type ResultBlockListProps, ResultBlocksPro, type ResultBlocksProProps, type RunSnapshot, type RuntimeAppearanceConfig, type RuntimeConfig, type RuntimeConfigStore, type RuntimeQuickPrompt, type RuntimeRendererId, type SandboxExecutorDeps, type SettingsSectionId, SkillBadges, SpecInteraction, type SpecInteractionProps, VercelPayloadPreview, type VercelPayloadPreviewProps, VercelSurfaceHost, type VercelSurfaceHostProps, VercelSurfaceSnapshotHost, type VercelSurfaceSnapshotHostProps, type VisionDelegationConsent, type VisionDelegationEndpoint, WEB_OFFICE_TEMPLATE_PREFIX, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, isLlmEntryUsable, pickUsableLlmEntry, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };
package/dist/index.js CHANGED
@@ -19,16 +19,16 @@ import { fromMarkdown } from "mdast-util-from-markdown";
19
19
  import { gfmFromMarkdown, gfmToMarkdown } from "mdast-util-gfm";
20
20
  import { gfm } from "micromark-extension-gfm";
21
21
  import { ActionBarPrimitive, AttachmentPrimitive, AuiProvider, ComposerPrimitive, ExternalThread, INTERNAL, MessagePrimitive, ThreadListItemPrimitive, ThreadListPrimitive, ThreadPrimitive, useAui, useAuiState, useMessagePartText } from "@assistant-ui/react";
22
- import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FullDisclosureRouter, SPREADSHEET_LIMITS, SUPPORTED_DOCUMENT_MIME, SerializingMemoryStore, SkillDiscovery, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, assertSafePathSegment, atomicWriteText, diffUserProfile, exportUserProfile, extractedDocumentFormat, formatAttachmentText, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, resolveInsideRoot, sanitizeUntrustedLine, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
22
+ import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FullDisclosureRouter, SPREADSHEET_LIMITS, SUPPORTED_DOCUMENT_MIME, SerializingMemoryStore, SkillDiscovery, SkillReader, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, assertSafePathSegment, atomicWriteText, diffUserProfile, exportUserProfile, extractedDocumentFormat, formatAttachmentText, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, resolveInsideRoot, sanitizeUntrustedLine, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
23
23
  import { BrowserWorkerScriptExecutor, WebOfficePolicy, capturePhoto, checkCameraAvailability, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, createWebOfficeToolSource, deleteMemoryEncryptionKey, openCamera, openMemoryEncryptionKey, probeChromeBuiltinAvailability, startDictation, toDocumentImage } from "@webskill/sdk/browser";
24
24
  import { JsonRenderSpecSurface, NativeSpecSurface, OpenUiSpecSurface, ReactBridgeState, SpecInteractionChannel, SurfaceFormTextsProvider, UiSurfaceSnapshotList, probeJsonRenderAvailability } from "@webskill/sdk/ui-react";
25
25
  import { A2UI_SPEC_FORM_PATH, collectFormScopes, createUiCatalogToolSource, fromA2uiSpecAction, fromA2uiSurfaceAction, interactionToFormModel, interactionToUiSpec, loadOpenUiPeers, loadWebSkillLitCatalog, renderMiniChart, shapeInteractionValue, toA2uiSpecMessages, toA2uiSurfaceAction, toVercelToolInvocation, uiCatalog } from "@webskill/sdk/ui";
26
- import { DownloadedFilePolicy, PAGE_ACTION_KINDS, TodoStore, createDelegationToolSource, createDocumentToolSource, createDownloadedFileToolSource, createDwgToolSource, createPageActionToolSource, createPageImageToolSource, createPagePerceptionToolSource, createRemoteImageToolSource, createSkillGenerationToolSource, createSpreadsheetToolSource, createTabWorksetToolSource, createTemplateToolSource, createTodoToolSource, withDelegationOrigin } from "@webskill/sdk/agent";
26
+ import { DownloadedFilePolicy, PAGE_ACTION_KINDS, SCROLL_DIRECTIONS, TodoStore, createDelegationToolSource, createDocumentToolSource, createDownloadedFileToolSource, createDwgToolSource, createPageActionToolSource, createPageImageToolSource, createPagePerceptionToolSource, createRemoteImageToolSource, createSkillGenerationToolSource, createSpreadsheetToolSource, createTabWorksetToolSource, createTemplateToolSource, createTodoToolSource, withDelegationOrigin } from "@webskill/sdk/agent";
27
27
  import { DataRenderers, Tools } from "@assistant-ui/core/react";
28
28
  import { ModelContext, Suggestions } from "@assistant-ui/core/store";
29
29
  import { Derived, attachTransformScopes, useClientLookup, useClientResource } from "@assistant-ui/store";
30
30
  import { resource, useMemoCache, withKey } from "@assistant-ui/tap";
31
- import { POLICY_DENIAL_CODES } from "@webskill/sdk/governance";
31
+ import { POLICY_DENIAL_CODES, USER_CAUSED_CODES } from "@webskill/sdk/governance";
32
32
 
33
33
  //#region ../ui-kit/src/i18n/createI18n.ts
34
34
  /** 字典工厂:各包定义自己的双语字典(恒等返回,保留字面量类型) */
@@ -26477,6 +26477,8 @@ const chatbotDictionary = defineDictionary({
26477
26477
  "interaction.pageAction.attach": "Allow the assistant to attach a file to “{target}”?",
26478
26478
  "interaction.pageAction.scroll.down": "Allow the assistant to scroll down the area around “{target}”?",
26479
26479
  "interaction.pageAction.scroll.up": "Allow the assistant to scroll up the area around “{target}”?",
26480
+ "interaction.pageAction.scroll.left": "Allow the assistant to scroll left the area around “{target}”?",
26481
+ "interaction.pageAction.scroll.right": "Allow the assistant to scroll right the area around “{target}”?",
26480
26482
  "interaction.pageAction.hover": "Allow the assistant to hover over “{target}”?",
26481
26483
  "interaction.pageAction.drag": "Allow the assistant to drag “{target}” onto another element?",
26482
26484
  "interaction.pageAction.back": "Allow the assistant to go back to the previous page on “{target}”?",
@@ -26512,6 +26514,11 @@ const chatbotDictionary = defineDictionary({
26512
26514
  "interaction.visionDelegation.rememberFallback": "Don’t ask again for this endpoint",
26513
26515
  "interaction.visionDelegation.rememberSession": "Don’t ask again in this conversation",
26514
26516
  "interaction.visionDelegation.body": "The model you selected cannot read images. Send the {images} image(s) produced by “{source}” to “{label}” at {host} to be described? If you decline, nothing is sent and the turn continues without them.",
26517
+ "interaction.delegation.title": "Run parts of this task in parallel?",
26518
+ "interaction.delegation.body": "Sub-agents can work on independent parts of this task at the same time. What they do is not recorded in this conversation, so this conversation can no longer be saved as a new skill.",
26519
+ "interaction.delegation.declineNote": "If you decline, the assistant does the work itself in this conversation and will not ask again.",
26520
+ "interaction.delegation.concurrency": "Run at most",
26521
+ "interaction.delegation.suggestedNote": "The assistant suggested {count}.",
26515
26522
  "interaction.imageSource.title": "Put a picture into the document?",
26516
26523
  "interaction.imageSource.pageImageBody": "Allow the assistant to copy a picture from the page into the document it is building? The picture is saved with this conversation until you delete it.",
26517
26524
  "interaction.imageSource.remoteImageBody": "Allow the assistant to download an image from {url} and put it into the document it is building? Fetching it tells that server your network address and when you asked for it.",
@@ -26906,6 +26913,8 @@ const chatbotDictionary = defineDictionary({
26906
26913
  "interaction.pageAction.attach": "允许助手向「{target}」附加文件吗?",
26907
26914
  "interaction.pageAction.scroll.down": "允许助手在「{target}」所在区域向下滚动吗?",
26908
26915
  "interaction.pageAction.scroll.up": "允许助手在「{target}」所在区域向上滚动吗?",
26916
+ "interaction.pageAction.scroll.left": "允许助手在「{target}」所在区域向左滚动吗?",
26917
+ "interaction.pageAction.scroll.right": "允许助手在「{target}」所在区域向右滚动吗?",
26909
26918
  "interaction.pageAction.hover": "允许助手将鼠标悬停在「{target}」上吗?",
26910
26919
  "interaction.pageAction.drag": "允许助手将「{target}」拖到另一个位置吗?",
26911
26920
  "interaction.pageAction.back": "允许助手在「{target}」上返回上一页吗?",
@@ -26941,6 +26950,11 @@ const chatbotDictionary = defineDictionary({
26941
26950
  "interaction.visionDelegation.rememberFallback": "以后不再为该端点询问",
26942
26951
  "interaction.visionDelegation.rememberSession": "本次对话不再询问,默认同意",
26943
26952
  "interaction.visionDelegation.body": "你选中的模型看不了图片。是否把「{source}」产出的 {images} 张图发往「{label}」({host})请它描述?不同意则什么都不会发出,这一轮就当没看到图。",
26953
+ "interaction.delegation.title": "把这件任务拆成几个并行去做?",
26954
+ "interaction.delegation.body": "子助手可以同时处理这件任务里互不相干的部分。它们做过什么不会记进本次会话,因此这次会话之后就存不成新技能了。",
26955
+ "interaction.delegation.declineNote": "不同意则助手自己在本次会话里做完,之后也不会再问。",
26956
+ "interaction.delegation.concurrency": "最多同时跑",
26957
+ "interaction.delegation.suggestedNote": "助手建议 {count} 个。",
26944
26958
  "interaction.imageSource.title": "把图片放进文档?",
26945
26959
  "interaction.imageSource.pageImageBody": "允许助手把当前页面上的一张图放进它正在生成的文档吗?该图会随本次会话保存,直到你删除这次会话。",
26946
26960
  "interaction.imageSource.remoteImageBody": "允许助手从 {url} 下载一张图片放进它正在生成的文档吗?下载会把你的网络地址与访问时间告知该服务器。",
@@ -27377,6 +27391,24 @@ function redactInteractionValues(value, request) {
27377
27391
  */
27378
27392
  const CHATBOT_PROFILE_USER_ID = "local";
27379
27393
  /**
27394
+ * 并发子 agent 的宿主硬上限(0.23.0 分册 19 FR-19.16)。
27395
+ *
27396
+ * 它是模型建议值与用户选定值之上的天花板:模型建议 50、用户随手确认了,
27397
+ * 仍然只有这么多能同时跑。并发子 agent 会同时抢模型配额、页面操作通道与用户注意力,
27398
+ * 这个数字不是保守,是止损点。
27399
+ */
27400
+ const DELEGATION_MAX_CONCURRENCY = 4;
27401
+ /**
27402
+ * 从确认卡的响应里读用户选定的并发数。
27403
+ *
27404
+ * 卡回的是 `{ concurrency: n }`(渲染层带了选择器)或 `true`(宿主用的是通用授权渲染,
27405
+ * 没有选择器)。后一种情况沿用建议值——用户点了同意,但没有表达过别的数字。
27406
+ */
27407
+ function readChosenConcurrency(value, fallback, max) {
27408
+ const raw = typeof value === "object" && value !== null ? value["concurrency"] : void 0;
27409
+ return Math.min(Math.max(1, typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : fallback), Math.max(1, max));
27410
+ }
27411
+ /**
27380
27412
  * 终止原因 → 稳定错误码(FR-21.2)。不再全部压平为 RUN_FAILED:
27381
27413
  * “跑超时了”与“模型没配对”对用户是两件事,得给不同的下一步。
27382
27414
  */
@@ -27441,6 +27473,29 @@ const MAX_DOCUMENT_IMAGE_BYTES = 8e6;
27441
27473
  */
27442
27474
  const WEB_OFFICE_TEMPLATE_PREFIX = "weboffice:";
27443
27475
  /**
27476
+ * 技能自带文件作模板时的取件号前缀(0.23.0 分册 23 · FR-23.2)。
27477
+ *
27478
+ * 形态是 `skill:<技能名>/<技能目录内的相对路径>`。技能名的字符集
27479
+ * (`SKILL_NAME_PATTERN`)既不含 `/` 也不含 `:`,所以前缀之后的第一个 `/`
27480
+ * 就是唯一的分隔点,不需要第二个分隔符。
27481
+ *
27482
+ * 与 `WEB_OFFICE_TEMPLATE_PREFIX` 不同,这个常量**不进公开导出**:宿主不需要拼它,
27483
+ * 拼它的是模型,而模型看到的是工具描述里那句话(设计 24 §10 的零契约变更)。
27484
+ */
27485
+ const SKILL_FILE_PREFIX = "skill:";
27486
+ /** 模板取件号里所有"指名道姓"的前缀:认出来就只走它自己那条通路,不回落到本地取件 */
27487
+ const PREFIXED_TEMPLATE_IDS = [SKILL_FILE_PREFIX, WEB_OFFICE_TEMPLATE_PREFIX];
27488
+ /** `skill:quotation/assets/tpl.xlsx` → `{ skill: 'quotation', path: 'assets/tpl.xlsx' }` */
27489
+ function parseSkillFileId(id) {
27490
+ const rest = id.slice(6);
27491
+ const cut = rest.indexOf("/");
27492
+ if (cut <= 0 || cut === rest.length - 1) return void 0;
27493
+ return {
27494
+ skill: rest.slice(0, cut),
27495
+ path: rest.slice(cut + 1)
27496
+ };
27497
+ }
27498
+ /**
27444
27499
  * 图片附件的指认说明(0.21.0 分册 22 · FR-22.4b)。
27445
27500
  *
27446
27501
  * 文本、docx、xlsx、PDF 进上下文时都带 `--- Attachment: … ---` 标签,唯独图片是一片裸图:
@@ -27589,6 +27644,8 @@ var ChatEngine = class {
27589
27644
  #ready;
27590
27645
  /** 已装配的 runtime(cancel 等同步委托用;未装配时为 undefined) */
27591
27646
  #runtime;
27647
+ /** 技能名 → 技能根目录,`skill:` 取件号用(0.23.0 分册 23)。惰性建,技能仓变更时清 */
27648
+ #skillIndex;
27592
27649
  /**
27593
27650
  * 每轮 `send()` 开头刷新的运行时配置快照(0.15.0 分册 15 · FR-15.6)。
27594
27651
  * 只给那些必须同步回答、又不能等到重建运行时的开关用。
@@ -27951,6 +28008,94 @@ var ChatEngine = class {
27951
28008
  throw new WebSkillError("VALIDATION_FAILED", `image reference "${kind}:${rest}" has no known prefix; expected artifact:, upload: or remote:`);
27952
28009
  }
27953
28010
  /**
28011
+ * 技能生成里被指认的那个附件(0.23.0 分册 20 FR-20.2)。
28012
+ *
28013
+ * **只在当前会话的消息里查**:候选集合恰好等于用户自己这次会话上传的东西,
28014
+ * 别的会话的 id 在这里查不到,也就谈不上被复制进技能包(AC-20.4)。
28015
+ * 路径一寸都不来自模型——它给的是 id,路径从附件记录里取,与 `#uploadOfRun` 同一范式。
28016
+ *
28017
+ * 落到哪条附件上仍走 `findAttachmentByRef`:模型上下文里最显眼的是文件名而不是
28018
+ * `att-…` 随机串,只认 id 的结果就是这条能力形同虚设。
28019
+ */
28020
+ async #skillAttachment(id) {
28021
+ const sessionId = this.#currentSessionId;
28022
+ if (sessionId === void 0) return void 0;
28023
+ const messages = (await this.#sessions.get(sessionId).catch(() => void 0))?.messages ?? [];
28024
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
28025
+ const message = messages[i];
28026
+ if (message?.role !== "user") continue;
28027
+ const hit = findAttachmentByRef(message.attachments ?? [], id);
28028
+ if (hit === void 0) continue;
28029
+ return {
28030
+ bytes: await this.#adapter.storage.readBinary(resolveInsideRoot(this.attachmentsRoot, hit.path)),
28031
+ name: hit.name,
28032
+ contentType: hit.contentType
28033
+ };
28034
+ }
28035
+ }
28036
+ /**
28037
+ * `skill:<技能名>/<路径>` 的取件(0.23.0 分册 23 FR-23.1)。
28038
+ *
28039
+ * 这条通路**不经过** `DownloadedFilePolicy`,所以既不弹授权卡也不列下载目录——
28040
+ * 是结构性的,不是"记得别弹"(FR-23.8)。路径越界由 `SkillReader` 内建的
28041
+ * `resolveInsideRoot` 挡(FR-23.5)。
28042
+ *
28043
+ * 取不到一律**抛**而不是返回 `undefined`:模板工具对 `undefined` 会回一句
28044
+ * 「换个下载 id 再试」,那正是本册要根除的那句话(FR-23.9)。
28045
+ */
28046
+ async #readSkillTemplate(id, fs) {
28047
+ const parsed = parseSkillFileId(id);
28048
+ if (!parsed) throw new WebSkillError("TOOL_UNSUPPORTED", `Malformed skill file id "${id}". Use "${SKILL_FILE_PREFIX}<skill name>/<path inside the skill>", for example "${SKILL_FILE_PREFIX}quotation/assets/template.xlsx".`);
28049
+ const { skill, path } = parsed;
28050
+ let index = await this.#skillFileIndex(fs);
28051
+ if (!index.has(skill)) {
28052
+ this.#skillIndex = void 0;
28053
+ index = await this.#skillFileIndex(fs);
28054
+ }
28055
+ if (!index.has(skill)) throw new WebSkillError("SKILL_NOT_FOUND", `No installed skill is named "${skill}". Skill file ids look like "${SKILL_FILE_PREFIX}<skill name>/<path>".`);
28056
+ const canRead = this.#options.governance?.skillStateGuard?.canRead;
28057
+ if (canRead && await canRead(skill) === false) throw new WebSkillError("SKILL_DISABLED", `The skill "${skill}" is currently disabled or quarantined, so its files cannot be read. Tell the user instead of trying another path.`);
28058
+ try {
28059
+ const bytes = await new SkillReader(fs, index).readSkillBinary(skill, path);
28060
+ return {
28061
+ name: path.split("/").pop() ?? path,
28062
+ bytes
28063
+ };
28064
+ } catch (error) {
28065
+ throw new WebSkillError(error instanceof WebSkillError ? error.code : "TOOL_EXECUTION_FAILED", `The skill "${skill}" has no readable file at "${path}": ${messageOf(error)}`);
28066
+ }
28067
+ }
28068
+ /** 技能名 → 技能根目录。惰性建:不用这种取件号的会话一次目录 IO 都不多花 */
28069
+ async #skillFileIndex(fs) {
28070
+ this.#skillIndex ??= (async () => {
28071
+ const result = await new SkillDiscovery(fs, this.#adapter.skillRoots).discover();
28072
+ return new Map(result.entries.map((entry) => [entry.name, entry.root]));
28073
+ })();
28074
+ return await this.#skillIndex;
28075
+ }
28076
+ /**
28077
+ * 技能根目录下有没有非 Markdown 的资源文件(0.23.0 分册 23 FR-23.3 / AC-23.8)。
28078
+ *
28079
+ * 一个都没有时工具描述里不提 `skill:` 取件号——凭空多一种读不到东西的说法,
28080
+ * 只会让模型多烧一轮。只列目录、不读也不解析 SKILL.md:这里要的是一个是/否。
28081
+ *
28082
+ * **漏判的方向是安全的**:资源放在 assets/references 之外的子目录时这里看不见,
28083
+ * 结果是描述里少一句,而 SKILL.md 自己会写明该用什么号(FR-23.4)——那是第二条、
28084
+ * 也是更可靠的一条告知通路。反过来(一个技能都没有却说有)才是要防的。
28085
+ */
28086
+ async #hasSkillResources(fs) {
28087
+ const isResource = (stat) => stat.type === "file" && !stat.path.toLowerCase().endsWith(".md");
28088
+ const list = async (path) => await fs.list(path).catch(() => []);
28089
+ for (const root of this.#adapter.skillRoots) for (const dir of await list(root)) {
28090
+ if (dir.type !== "directory") continue;
28091
+ const entries = await list(dir.path);
28092
+ if (!entries.some((e) => e.type === "file" && e.path.endsWith("/SKILL.md"))) continue;
28093
+ if (entries.some(isResource)) return true;
28094
+ for (const sub of ["assets", "references"]) if ((await list(`${dir.path}/${sub}`)).some(isResource)) return true;
28095
+ }
28096
+ return false;
28097
+ }
28098
+ /**
27954
28099
  * 这一次 run 属于哪个会话。
27955
28100
  *
27956
28101
  * 进行中读内存,已结束读 trace——与 `#skillNamesOfRun` 同一范式。
@@ -28180,6 +28325,7 @@ var ChatEngine = class {
28180
28325
  this.#handles.clear();
28181
28326
  this.#maxHistoryMessages = void 0;
28182
28327
  this.#contextWindow = void 0;
28328
+ this.#skillIndex = void 0;
28183
28329
  }
28184
28330
  /**
28185
28331
  * 技能仓变更后调用(安装/卸载/发布):作废 runtime 缓存的技能目录,下次 send 重新扫描。
@@ -28190,6 +28336,7 @@ var ChatEngine = class {
28190
28336
  * @experimental
28191
28337
  */
28192
28338
  invalidateSkills() {
28339
+ this.#skillIndex = void 0;
28193
28340
  this.#ready?.then((runtime) => runtime.invalidate(), () => void 0);
28194
28341
  }
28195
28342
  /**
@@ -28933,6 +29080,96 @@ var ChatEngine = class {
28933
29080
  };
28934
29081
  }
28935
29082
  /**
29083
+ * 会话级委派同意(0.23.0 分册 19 FR-19.10~19.14)。
29084
+ *
29085
+ * 记在**会话**上而不是引擎实例上:切到另一个会话是另一件任务,
29086
+ * 上一件任务里同意过并发委派不代表这一件也同意。
29087
+ */
29088
+ #delegationConsent = /* @__PURE__ */ new Map();
29089
+ /** 用户在确认卡里选定的并发数;没问过或被拒时为 1(拒绝后根本不会走到委派) */
29090
+ #delegationConcurrency() {
29091
+ const sessionId = this.#currentSessionId;
29092
+ if (sessionId === void 0) return 1;
29093
+ return this.#delegationConsent.get(sessionId)?.concurrency ?? 1;
29094
+ }
29095
+ /**
29096
+ * 首次委派前问一次,之后沿用本会话的答案(FR-19.12)。
29097
+ * `suggested` 是模型给的建议值,`hardLimit` 是宿主经 `policy` 设的天花板(FR-19.16)。
29098
+ */
29099
+ async #askDelegationConsent(suggested, hardLimit) {
29100
+ const sessionId = this.#currentSessionId ?? "";
29101
+ const remembered = this.#delegationConsent.get(sessionId);
29102
+ if (remembered !== void 0) return remembered;
29103
+ const max = Math.max(1, Math.floor(hardLimit));
29104
+ const preset = Math.min(Math.max(1, Math.floor(suggested)), max);
29105
+ this.#delegationAskSeq += 1;
29106
+ const response = await this.#uiBridge.request({
29107
+ type: "authorize",
29108
+ id: `delegation-consent-${this.#delegationAskSeq}`,
29109
+ capability: "confirm",
29110
+ message: `Run parts of this task as parallel sub-agents? What they do is not recorded in this conversation, so this conversation can no longer be saved as a new skill. At most ${preset} sub-agent(s) would run at the same time.`,
29111
+ details: {
29112
+ kind: "delegation",
29113
+ suggestedConcurrency: preset,
29114
+ maxConcurrency: max
29115
+ }
29116
+ });
29117
+ const decision = response.cancelled === true || response.value === false ? {
29118
+ approved: false,
29119
+ concurrency: 1
29120
+ } : {
29121
+ approved: true,
29122
+ concurrency: readChosenConcurrency(response.value, preset, max)
29123
+ };
29124
+ this.#delegationConsent.set(sessionId, decision);
29125
+ return decision;
29126
+ }
29127
+ #delegationAskSeq = 0;
29128
+ /**
29129
+ * 委派工具源 + 会话级确认闸门(0.23.0 分册 19)。
29130
+ *
29131
+ * 闸门套在**工具调用外层**而不是 `runner` 里:`runner` 已经在编排器的排队与
29132
+ * 预算发放之后,那时再问用户,被拒的那次已经占了一个名额与一份轮数额度。
29133
+ */
29134
+ #delegationToolSource(deps, loopConfig) {
29135
+ const source = createDelegationToolSource({
29136
+ runner: createChatbotSubAgentRunner({
29137
+ deps,
29138
+ loopConfig,
29139
+ uiBridge: this.#uiBridge
29140
+ }),
29141
+ parentBudget: () => this.#parentBudget(loopConfig),
29142
+ policy: this.#delegationPolicy(loopConfig),
29143
+ concurrencyLimit: () => Math.min(this.#delegationConcurrency(), DELEGATION_MAX_CONCURRENCY),
29144
+ todos: this.#todoStore
29145
+ });
29146
+ return {
29147
+ ...source,
29148
+ call: async (name, args) => {
29149
+ const suggested = args["suggestedConcurrency"];
29150
+ if (!(await this.#askDelegationConsent(typeof suggested === "number" && Number.isFinite(suggested) ? suggested : 1, DELEGATION_MAX_CONCURRENCY)).approved) return {
29151
+ ok: false,
29152
+ content: [],
29153
+ error: {
29154
+ code: "DELEGATION_DECLINED",
29155
+ message: "The user declined delegation for this conversation. Do this work yourself in this conversation and do not call delegate_task again."
29156
+ }
29157
+ };
29158
+ return source.call(name, args);
29159
+ }
29160
+ };
29161
+ }
29162
+ /**
29163
+ * 子 agent 的自身上限(FR-19.2/19.3)。**不再有内建的 8 轮**:
29164
+ * 缺省即不收紧,只受父级剩余预算约束;宿主在设置里调小 maxTurns 时才会收紧。
29165
+ */
29166
+ #delegationPolicy(loopConfig) {
29167
+ return {
29168
+ ...loopConfig.maxTurns !== void 0 ? { maxTurns: loopConfig.maxTurns } : {},
29169
+ ...loopConfig.totalTimeoutMs !== void 0 ? { timeoutMs: loopConfig.totalTimeoutMs } : {}
29170
+ };
29171
+ }
29172
+ /**
28936
29173
  * 浏览器内置模型自动进模型列表(FR-14.1,B4-01/FR-27.2 调整)。
28937
29174
  * **只要浏览器支持 Prompt API**(含「模型未就绪」model-not-ready)就加入条目;
28938
29175
  * API 完全不存在(api-missing / unsupported-browser)才不加——不可用只在列表里
@@ -29003,6 +29240,8 @@ var ChatEngine = class {
29003
29240
  fs
29004
29241
  });
29005
29242
  const readDocumentBytes = async (id, context) => {
29243
+ const prefix = PREFIXED_TEMPLATE_IDS.find((p) => id.startsWith(p));
29244
+ if (prefix) throw new WebSkillError("TOOL_UNSUPPORTED", `The id "${id}" names a ${prefix === "skill:" ? "file bundled with a skill" : "document open in an online viewer"}. It can only be used as a template (inspect_template / fill_template), not read here.`);
29006
29245
  const attachment = this.#runAttachments.find((a) => a.id === id);
29007
29246
  if (attachment) {
29008
29247
  const bytes = await this.#adapter.storage.readBinary(`${this.attachmentsRoot}/${attachment.path}`);
@@ -29024,19 +29263,33 @@ var ChatEngine = class {
29024
29263
  };
29025
29264
  };
29026
29265
  /**
29027
- * 模板字节比 `read_document` 多认一条取件号:`weboffice:<handle>`(0.22.0 分册 42 FR-42.12)。
29266
+ * 模板字节比 `read_document` 多认两条取件号:`weboffice:<handle>`(0.22.0 分册 42 FR-42.12)
29267
+ * 与 `skill:<技能名>/<路径>`(0.23.0 分册 23 FR-23.1)。
29268
+ *
29269
+ * **前缀先认,认出来就只走它自己那条路,取不到即抛、不回落**(FR-23.13)。
29270
+ * 顺序不能倒过来:`readDocumentBytes` 的末段对认不出的号是**抛**而不是返回
29271
+ * `undefined`,所以「先穷尽本地再看前缀」时前缀判断根本执行不到——0.22.0 的
29272
+ * `weboffice:` 在接了下载端口的宿主里就是这么失效的(设计 24 §1 实测)。
29028
29273
  *
29029
- * 多的只是**取件**这一段——拿到字节之后走的与本地模板是同一条路(FR-42.12 后半句)。
29030
- * 前缀是显式的:不去猜某个 id 会不会恰好是个 weboffice handle,猜错的代价是
29031
- * 拿了另一份文档冒充用户指的那一份。前缀不匹配时连问都不问,直接当作没有。
29274
+ * 不回落同样是硬的:带前缀的号是指名的那一份,回落等于拿另一份文档冒充它。
29032
29275
  */
29033
29276
  const readTemplateBytes = async (id, context) => {
29034
- const local = await readDocumentBytes(id, context);
29035
- if (local) return local;
29036
- const online = this.#adapter.webOfficeTemplateSource;
29037
- if (!online || !id.startsWith("weboffice:")) return void 0;
29038
- return await online.read(id.slice(10), context);
29277
+ if (id.startsWith("skill:")) return await this.#readSkillTemplate(id, fs);
29278
+ if (id.startsWith("weboffice:")) {
29279
+ const online = this.#adapter.webOfficeTemplateSource;
29280
+ if (!online) throw new WebSkillError("TOOL_UNSUPPORTED", `Cannot read "${id}": this conversation has no online document source attached.`);
29281
+ return await online.read(id.slice(10), context);
29282
+ }
29283
+ return await readDocumentBytes(id, context);
29039
29284
  };
29285
+ /**
29286
+ * 模板取件号里那些**宿主接了才存在**的说法(0.22.0 FR-42.12、0.23.0 FR-23.3)。
29287
+ *
29288
+ * 无条件写死的后果是模型反复尝试一个在这个宿主里永远读不到的号。技能那一句
29289
+ * 还多一道闸:一个带资源的技能都没装时不提(AC-23.8)。探测只在真有模板工具时做。
29290
+ */
29291
+ const hasTemplateEngine = Boolean(this.#adapter.xlsxTemplateEngine || this.#adapter.docxTemplateEngine);
29292
+ const templateIdHints = [...this.#adapter.webOfficeTemplateSource ? [`For a document open in an online WPS/WebOffice viewer, use "${WEB_OFFICE_TEMPLATE_PREFIX}<handle>" with the handle from list_weboffice_documents.`] : [], ...hasTemplateEngine && await this.#hasSkillResources(fs) ? [`For a file that ships inside an installed skill, use "${SKILL_FILE_PREFIX}<skill name>/<path inside the skill>", for example "${SKILL_FILE_PREFIX}quotation/assets/template.xlsx".`] : []];
29040
29293
  const baseTools = [
29041
29294
  ...pageSkillSource ? [pageSkillSource] : [],
29042
29295
  createSpreadsheetToolSource({
@@ -29057,6 +29310,7 @@ var ChatEngine = class {
29057
29310
  ui: this.#uiBridge,
29058
29311
  ...this.#options.skillCandidates ? { sink: this.#options.skillCandidates } : {},
29059
29312
  sessionId: () => this.#currentSessionId,
29313
+ resolveAttachment: (id) => this.#skillAttachment(id),
29060
29314
  ...toolStepStore ? { steps: toolStepStore } : {},
29061
29315
  messages: { confirmSkill: (draft) => translateText(rc.appearance.locale, "skillGeneration.confirm", {
29062
29316
  name: draft.name,
@@ -29090,7 +29344,7 @@ var ChatEngine = class {
29090
29344
  }),
29091
29345
  ...this.#adapter.xlsxTemplateEngine || this.#adapter.docxTemplateEngine ? [createTemplateToolSource({
29092
29346
  files: { read: readTemplateBytes },
29093
- ...this.#adapter.webOfficeTemplateSource ? { idHint: `For a document open in an online WPS/WebOffice viewer, use "${WEB_OFFICE_TEMPLATE_PREFIX}<handle>" with the handle from list_weboffice_documents.` } : {},
29347
+ ...templateIdHints.length > 0 ? { idHint: templateIdHints.join(" ") } : {},
29094
29348
  ...this.#adapter.xlsxTemplateEngine ? { xlsx: this.#adapter.xlsxTemplateEngine } : {},
29095
29349
  ...this.#adapter.docxTemplateEngine ? { docx: this.#adapter.docxTemplateEngine } : {},
29096
29350
  writeArtifact: (path, content, mimeType, context) => {
@@ -29183,15 +29437,7 @@ var ChatEngine = class {
29183
29437
  };
29184
29438
  const runtime = new WebSkillRuntime({
29185
29439
  ...deps,
29186
- externalTools: [...baseTools, ...rc?.agentCapabilities.delegation === true ? [createDelegationToolSource({
29187
- runner: createChatbotSubAgentRunner({
29188
- deps,
29189
- loopConfig,
29190
- uiBridge: this.#uiBridge
29191
- }),
29192
- parentBudget: () => this.#parentBudget(loopConfig),
29193
- todos: this.#todoStore
29194
- })] : []]
29440
+ externalTools: [...baseTools, ...rc?.agentCapabilities.delegation === true ? [this.#delegationToolSource(deps, loopConfig)] : []]
29195
29441
  });
29196
29442
  runtime.events.on("*", (event) => {
29197
29443
  this.#runSessions.set(event.runId, event.sessionId);
@@ -29588,7 +29834,7 @@ const useWebSkillThreadList = ({ sessions, currentId, isLoading, thread, onSwitc
29588
29834
  id: mainThreadId,
29589
29835
  createdAt: ""
29590
29836
  };
29591
- const items = useClientLookup((pending ? [...sessions, pending] : sessions).map((session) => withKey(session.id, ThreadListItemClient({
29837
+ const items = useClientLookup((pending ? [pending, ...sessions] : sessions).map((session) => withKey(session.id, ThreadListItemClient({
29592
29838
  session,
29593
29839
  onSwitchTo: () => session.id === "__webskill_new_thread__" ? onCreate() : onSwitchTo(session.id),
29594
29840
  onRename: (title) => onRename(session.id, title),
@@ -30328,14 +30574,6 @@ function AssistantUiSessionList({ className = "", onNavigate, onCollapse, hasEar
30328
30574
  className: "min-h-0 flex-1 overflow-y-auto px-2 pb-3",
30329
30575
  role: "list",
30330
30576
  children: [
30331
- hasEarlier && onLoadEarlier ? /* @__PURE__ */ jsx("button", {
30332
- type: "button",
30333
- "data-testid": "load-earlier-sessions",
30334
- disabled: loadingEarlier,
30335
- onClick: onLoadEarlier,
30336
- className: "mb-1 w-full rounded-md px-2.5 py-2 text-sm text-muted transition-colors hover:bg-subtle hover:text-ink disabled:opacity-60",
30337
- children: loadingEarlier ? t("session.loading") : t("session.loadEarlier")
30338
- }) : null,
30339
30577
  skipped.length > 0 ? /* @__PURE__ */ jsx("p", {
30340
30578
  role: "status",
30341
30579
  "data-testid": "session-skipped",
@@ -30379,7 +30617,15 @@ function AssistantUiSessionList({ className = "", onNavigate, onCollapse, hasEar
30379
30617
  onEndRename: () => setRenamingId(void 0)
30380
30618
  });
30381
30619
  }
30382
- })
30620
+ }),
30621
+ hasEarlier && onLoadEarlier ? /* @__PURE__ */ jsx("button", {
30622
+ type: "button",
30623
+ "data-testid": "load-earlier-sessions",
30624
+ disabled: loadingEarlier,
30625
+ onClick: onLoadEarlier,
30626
+ className: "mt-1 w-full rounded-md px-2.5 py-2 text-sm text-muted transition-colors hover:bg-subtle hover:text-ink disabled:opacity-60",
30627
+ children: loadingEarlier ? t("session.loading") : t("session.loadEarlier")
30628
+ }) : null
30383
30629
  ]
30384
30630
  })
30385
30631
  ]
@@ -30538,7 +30784,7 @@ function pageActionDetails(details) {
30538
30784
  role,
30539
30785
  ...typeof name === "string" ? { name } : {},
30540
30786
  ...typeof value === "string" ? { value } : {},
30541
- ...direction === "down" || direction === "up" ? { direction } : {},
30787
+ ...typeof direction === "string" && SCROLL_DIRECTIONS.includes(direction) ? { direction } : {},
30542
30788
  ...typeof frame === "string" ? { frame } : {},
30543
30789
  ...record["elevated"] === true ? { elevated: true } : {},
30544
30790
  ...record["rememberable"] === true ? { rememberable: true } : {},
@@ -30623,6 +30869,25 @@ function webOfficeDetails(details) {
30623
30869
  };
30624
30870
  }
30625
30871
  /**
30872
+ * 并发委派确认卡的载荷(0.23.0 分册 19 FR-19.10/19.14)。
30873
+ *
30874
+ * 与其他授权卡不同,这张卡带一个**旋钮**:用户不只是同不同意,还要定下同时跑几个。
30875
+ * `max` 是宿主硬上限,`suggested` 是模型的建议值(已被引擎截到 `max` 以内)——
30876
+ * 面板里出现一个选不了的数字,比不出现更糟。
30877
+ */
30878
+ function delegationDetails(details) {
30879
+ if (typeof details !== "object" || details === null) return void 0;
30880
+ const record = details;
30881
+ if (record["kind"] !== "delegation") return void 0;
30882
+ const suggested = record["suggestedConcurrency"];
30883
+ const max = record["maxConcurrency"];
30884
+ if (typeof max !== "number" || !Number.isFinite(max) || max < 1) return void 0;
30885
+ return {
30886
+ suggested: Math.min(Math.max(1, typeof suggested === "number" && Number.isFinite(suggested) ? Math.floor(suggested) : 1), Math.floor(max)),
30887
+ max: Math.floor(max)
30888
+ };
30889
+ }
30890
+ /**
30626
30891
  * 视觉委派告知卡的载荷(0.21.0 分册 17)。
30627
30892
  *
30628
30893
  * 端点的 `label` 与 `host` 都要:前者是用户在设置里给它起的名字,后者是它**真实的去处**。
@@ -30726,6 +30991,7 @@ function localizedAuthorizeMessage(request, t) {
30726
30991
  size: formatBytes(details.file.size)
30727
30992
  })}`;
30728
30993
  }
30994
+ if (request.capability === "confirm" && delegationDetails(request.details) !== void 0) return t("interaction.delegation.body");
30729
30995
  if (request.capability === "readReference" || request.capability === "readAsset" || request.capability === "writeArtifact" || request.capability === "fetchData" || request.capability === "confirm") {
30730
30996
  const details = sandboxDetails(request.details);
30731
30997
  if (details === void 0) return void 0;
@@ -42308,13 +42574,17 @@ function ToolGroup({ count, running, children }) {
42308
42574
  }) : null, children]
42309
42575
  });
42310
42576
  }
42311
- /** 错误码 → 标题文案;未列出的码走通用「失败原因」(0.6.0 FR-18.2) */
42577
+ /** 被策略拦下的错误码与执行失败必须分开显示(需求 1 FR-1.6)。
42578
+ * 0.9.0 分册 14(UX-13):判定消费统一码集,不再只认 TOOL_NOT_ALLOWED。
42579
+ * 0.24.0 分册 12:界面口径是**并集**——策略拦下与用户拒绝,对用户而言是同一件事:
42580
+ * 都不是技能坏了。(治理面仍然分族,见 governance 侧两个集合的注释。) */
42581
+ const isDenied = (code) => POLICY_DENIAL_CODES.has(code) || USER_CAUSED_CODES.has(code);
42582
+ /** 错误码 → 标题文案;**拒绝类不在表内**(由 isDenied 统一判),
42583
+ * 其余未列出的码走通用「失败原因」(0.6.0 FR-18.2)。
42584
+ * 拒绝类曾经在这里另拄一份名单,结果是往集合里加码只改得动徽章、标题留在原地。 */
42312
42585
  const ERROR_TITLE_KEYS = {
42313
- ["TOOL_NOT_ALLOWED"]: "tool.denied.reason",
42314
42586
  TOOL_NOT_FOUND: "tool.notFound.reason",
42315
- TOOL_RESOLUTION_EXHAUSTED: "tool.exhausted.reason",
42316
- NETWORK_BLOCKED: "tool.denied.reason",
42317
- FS_PERMISSION_DENIED: "tool.denied.reason"
42587
+ TOOL_RESOLUTION_EXHAUSTED: "tool.exhausted.reason"
42318
42588
  };
42319
42589
  function toolErrorOf(part) {
42320
42590
  const result = part.result;
@@ -42325,7 +42595,8 @@ function toolErrorOf(part) {
42325
42595
  ...typeof record["errorMessage"] === "string" ? { errorMessage: record["errorMessage"] } : {}
42326
42596
  };
42327
42597
  }
42328
- /** 工具行:状态 / 耗时 / 参数摘要。工具返回值不在 chatbot 数据面内,因此不渲染结果区。 */
42598
+ /** 工具行:状态 / 耗时 / 参数摘要。工具返回值不在 chatbot 数据面内,因此不渲染结果区。
42599
+ * 导出仅供同包用例直接渲染(与本文件其余组件同例);包入口不再导出,不进 API 快照。 */
42329
42600
  function ToolRow({ part }) {
42330
42601
  const t = useT();
42331
42602
  const [open, setOpen] = useState(false);
@@ -42335,7 +42606,7 @@ function ToolRow({ part }) {
42335
42606
  useEffect(() => {
42336
42607
  if (part.isError === true) setOpen(true);
42337
42608
  }, [part.isError]);
42338
- const denied = part.isError === true && typeof errorCode === "string" && POLICY_DENIAL_CODES.has(errorCode);
42609
+ const denied = part.isError === true && typeof errorCode === "string" && isDenied(errorCode);
42339
42610
  const status = running ? "running" : denied ? "denied" : part.isError === true ? "failed" : "completed";
42340
42611
  const duration = part.timing && completedAt !== void 0 ? formatDuration(completedAt - part.timing.startedAt) : void 0;
42341
42612
  return /* @__PURE__ */ jsxs("div", {
@@ -42390,7 +42661,7 @@ function ToolRow({ part }) {
42390
42661
  children: [errorMessage !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
42391
42662
  /* @__PURE__ */ jsx("p", {
42392
42663
  className: "text-xs text-muted italic",
42393
- children: t(errorCode !== void 0 ? ERROR_TITLE_KEYS[errorCode] ?? "tool.failed.reason" : "tool.failed.reason")
42664
+ children: t(errorCode === void 0 ? "tool.failed.reason" : denied ? "tool.denied.reason" : ERROR_TITLE_KEYS[errorCode] ?? "tool.failed.reason")
42394
42665
  }),
42395
42666
  /* @__PURE__ */ jsx("p", {
42396
42667
  "data-testid": "tool-error-reason",
@@ -43726,8 +43997,10 @@ function ActiveInteractionCard({ request, bridge }) {
43726
43997
  const webOffice = isAuthorize && request.capability === "readWebOfficeDocument" ? webOfficeDetails(request.details) : void 0;
43727
43998
  const visionDelegation = isAuthorize && request.capability === "delegateVision" ? visionDelegationDetails(request.details) : void 0;
43728
43999
  const imageSource = isAuthorize && request.capability === "readImageSource" ? imageSourceDetails(request.details) : void 0;
44000
+ const delegation = isAuthorize && request.capability === "confirm" ? delegationDetails(request.details) : void 0;
44001
+ const [concurrency, setConcurrency] = useState(void 0);
43729
44002
  const traceEvidence = isAuthorize ? traceEvidenceDetails(request.details) : void 0;
43730
- const localized = pageAction !== void 0 || downloadedFile !== void 0 || webOffice !== void 0 || visionDelegation !== void 0 || imageSource !== void 0;
44003
+ const localized = pageAction !== void 0 || downloadedFile !== void 0 || webOffice !== void 0 || visionDelegation !== void 0 || imageSource !== void 0 || delegation !== void 0;
43731
44004
  const spec = interactionToUiSpec(request.type === "authorize" && localized ? {
43732
44005
  ...request,
43733
44006
  message: ""
@@ -43753,6 +44026,13 @@ function ActiveInteractionCard({ request, bridge }) {
43753
44026
  return;
43754
44027
  }
43755
44028
  const value = shapeInteractionValue(interactionToFormModel(request), event.value ?? {});
44029
+ if (delegation !== void 0) {
44030
+ bridge.resolve({
44031
+ id: request.id,
44032
+ value: { concurrency: concurrency ?? delegation.suggested }
44033
+ });
44034
+ return;
44035
+ }
43756
44036
  bridge.resolve({
43757
44037
  id: request.id,
43758
44038
  value,
@@ -43961,6 +44241,41 @@ function ActiveInteractionCard({ request, bridge }) {
43961
44241
  }), t(imageSource.rememberLabelKey ?? "interaction.imageSource.rememberFallback")]
43962
44242
  }) : null
43963
44243
  ] }) : null,
44244
+ delegation ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
44245
+ /* @__PURE__ */ jsx("p", {
44246
+ "data-testid": "interaction-delegation-title",
44247
+ className: "text-sm font-medium text-ink",
44248
+ children: t("interaction.delegation.title")
44249
+ }),
44250
+ /* @__PURE__ */ jsx("p", {
44251
+ "data-testid": "interaction-delegation-body",
44252
+ className: "text-sm text-ink",
44253
+ children: t("interaction.delegation.body")
44254
+ }),
44255
+ /* @__PURE__ */ jsx("p", {
44256
+ "data-testid": "interaction-delegation-decline",
44257
+ className: "text-sm text-muted",
44258
+ children: t("interaction.delegation.declineNote")
44259
+ }),
44260
+ /* @__PURE__ */ jsxs("label", {
44261
+ className: "flex flex-wrap items-center gap-2 text-sm text-ink",
44262
+ children: [t("interaction.delegation.concurrency"), /* @__PURE__ */ jsx("select", {
44263
+ "data-testid": "interaction-delegation-concurrency",
44264
+ className: "rounded-md border border-border bg-canvas px-2 py-1 text-sm text-ink",
44265
+ value: String(concurrency ?? delegation.suggested),
44266
+ onChange: (event) => setConcurrency(Number(event.currentTarget.value)),
44267
+ children: Array.from({ length: delegation.max }, (_, index) => index + 1).map((n) => /* @__PURE__ */ jsx("option", {
44268
+ value: String(n),
44269
+ children: n
44270
+ }, n))
44271
+ })]
44272
+ }),
44273
+ /* @__PURE__ */ jsx("p", {
44274
+ "data-testid": "interaction-delegation-suggested",
44275
+ className: "text-xs text-muted",
44276
+ children: t("interaction.delegation.suggestedNote", { count: String(delegation.suggested) })
44277
+ })
44278
+ ] }) : null,
43964
44279
  traceEvidence ? /* @__PURE__ */ jsxs("div", {
43965
44280
  "data-testid": "interaction-trace-evidence",
43966
44281
  children: [/* @__PURE__ */ jsx("div", {
@@ -44621,7 +44936,8 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
44621
44936
  notifyStorageError: (fileName) => storageToastRef.current?.(fileName),
44622
44937
  drawingsSupported: () => adapter.dwg !== void 0,
44623
44938
  ...adapter.docxExtractor ? { docxExtractor: adapter.docxExtractor } : {},
44624
- ...adapter.xlsxExtractor ? { xlsxExtractor: adapter.xlsxExtractor } : {}
44939
+ ...adapter.xlsxExtractor ? { xlsxExtractor: adapter.xlsxExtractor } : {},
44940
+ ...adapter.pptxExtractor ? { pptxExtractor: adapter.pptxExtractor } : {}
44625
44941
  }), [adapter, engine]);
44626
44942
  useEffect(() => {
44627
44943
  let cancelled = false;
@@ -44971,7 +45287,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
44971
45287
  if (!sessionsCursor || loadingEarlierSessions) return;
44972
45288
  setLoadingEarlierSessions(true);
44973
45289
  engine.listSessions({ cursor: sessionsCursor }).then((page) => {
44974
- setSessions((prev) => [...page.items, ...prev]);
45290
+ setSessions((prev) => [...prev, ...page.items]);
44975
45291
  setSessionsCursor(page.nextCursor);
44976
45292
  }).catch((e) => setError({ message: toErrorMessage(e) })).finally(() => setLoadingEarlierSessions(false));
44977
45293
  }, [
@@ -45306,7 +45622,7 @@ function VercelPayloadPreview({ bridge }) {
45306
45622
  * Version of the published `@webskill/chatbot` package, injected at build time.
45307
45623
  * @stable
45308
45624
  */
45309
- const CHATBOT_VERSION = "0.19.0";
45625
+ const CHATBOT_VERSION = "0.21.0";
45310
45626
 
45311
45627
  //#endregion
45312
45628
  export { A2uiSurfaceHost, A2uiSurfaceSnapshotHost, CHATBOT_VERSION, ChatEngine, Chatbot, ChatbotVisionDelegate, CompositeUiBridge, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, ErrorCard, InteractionCard, InterruptedBanner, OpenUiSurfaceHost, OpenUiSurfaceSnapshotHost, ResultBlockList, ResultBlocksPro, SkillBadges, SpecInteraction, VercelPayloadPreview, VercelSurfaceHost, VercelSurfaceSnapshotHost, WEB_OFFICE_TEMPLATE_PREFIX, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, isLlmEntryUsable, pickUsableLlmEntry, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webskill/chatbot",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -38,7 +38,7 @@
38
38
  "@a2ui/web_core": "^0.10.5",
39
39
  "@lit/context": "^1.1.0",
40
40
  "@openuidev/react-lang": "0.2.8",
41
- "@webskill/sdk": "^0.22.0",
41
+ "@webskill/sdk": "^0.24.0",
42
42
  "mermaid": "^12.0.0",
43
43
  "react": ">=19.0.0",
44
44
  "react-dom": ">=19.0.0",