@webskill/chatbot 0.19.0 → 0.20.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 +12 -6
- package/dist/index.d.ts +21 -1
- package/dist/index.js +339 -33
- package/package.json +2 -2
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
|
|
118
|
-
|
|
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
|
|
123
|
-
|
|
124
|
-
|
|
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
|
@@ -1379,6 +1379,11 @@ declare const chatbotDictionary: {
|
|
|
1379
1379
|
'interaction.visionDelegation.rememberFallback': string;
|
|
1380
1380
|
'interaction.visionDelegation.rememberSession': string;
|
|
1381
1381
|
'interaction.visionDelegation.body': string;
|
|
1382
|
+
'interaction.delegation.title': string;
|
|
1383
|
+
'interaction.delegation.body': string;
|
|
1384
|
+
'interaction.delegation.declineNote': string;
|
|
1385
|
+
'interaction.delegation.concurrency': string;
|
|
1386
|
+
'interaction.delegation.suggestedNote': string;
|
|
1382
1387
|
'interaction.imageSource.title': string;
|
|
1383
1388
|
'interaction.imageSource.pageImageBody': string;
|
|
1384
1389
|
'interaction.imageSource.remoteImageBody': string;
|
|
@@ -1808,6 +1813,11 @@ declare const chatbotDictionary: {
|
|
|
1808
1813
|
'interaction.visionDelegation.rememberFallback': string;
|
|
1809
1814
|
'interaction.visionDelegation.rememberSession': string;
|
|
1810
1815
|
'interaction.visionDelegation.body': string;
|
|
1816
|
+
'interaction.delegation.title': string;
|
|
1817
|
+
'interaction.delegation.body': string;
|
|
1818
|
+
'interaction.delegation.declineNote': string;
|
|
1819
|
+
'interaction.delegation.concurrency': string;
|
|
1820
|
+
'interaction.delegation.suggestedNote': string;
|
|
1811
1821
|
'interaction.imageSource.title': string;
|
|
1812
1822
|
'interaction.imageSource.pageImageBody': string;
|
|
1813
1823
|
'interaction.imageSource.remoteImageBody': string;
|
|
@@ -2240,6 +2250,11 @@ declare const useT: () => TranslateFn<{
|
|
|
2240
2250
|
'interaction.visionDelegation.rememberFallback': string;
|
|
2241
2251
|
'interaction.visionDelegation.rememberSession': string;
|
|
2242
2252
|
'interaction.visionDelegation.body': string;
|
|
2253
|
+
'interaction.delegation.title': string;
|
|
2254
|
+
'interaction.delegation.body': string;
|
|
2255
|
+
'interaction.delegation.declineNote': string;
|
|
2256
|
+
'interaction.delegation.concurrency': string;
|
|
2257
|
+
'interaction.delegation.suggestedNote': string;
|
|
2243
2258
|
'interaction.imageSource.title': string;
|
|
2244
2259
|
'interaction.imageSource.pageImageBody': string;
|
|
2245
2260
|
'interaction.imageSource.remoteImageBody': string;
|
|
@@ -2669,6 +2684,11 @@ declare const useT: () => TranslateFn<{
|
|
|
2669
2684
|
'interaction.visionDelegation.rememberFallback': string;
|
|
2670
2685
|
'interaction.visionDelegation.rememberSession': string;
|
|
2671
2686
|
'interaction.visionDelegation.body': string;
|
|
2687
|
+
'interaction.delegation.title': string;
|
|
2688
|
+
'interaction.delegation.body': string;
|
|
2689
|
+
'interaction.delegation.declineNote': string;
|
|
2690
|
+
'interaction.delegation.concurrency': string;
|
|
2691
|
+
'interaction.delegation.suggestedNote': string;
|
|
2672
2692
|
'interaction.imageSource.title': string;
|
|
2673
2693
|
'interaction.imageSource.pageImageBody': string;
|
|
2674
2694
|
'interaction.imageSource.remoteImageBody': string;
|
|
@@ -3208,6 +3228,6 @@ declare function ErrorCard({ code, message, loopLimits, onOpenSettings, onDismis
|
|
|
3208
3228
|
* Version of the published `@webskill/chatbot` package, injected at build time.
|
|
3209
3229
|
* @stable
|
|
3210
3230
|
*/
|
|
3211
|
-
declare const CHATBOT_VERSION = "0.
|
|
3231
|
+
declare const CHATBOT_VERSION = "0.20.0";
|
|
3212
3232
|
//#endregion
|
|
3213
3233
|
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,7 +19,7 @@ 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";
|
|
@@ -26512,6 +26512,11 @@ const chatbotDictionary = defineDictionary({
|
|
|
26512
26512
|
"interaction.visionDelegation.rememberFallback": "Don’t ask again for this endpoint",
|
|
26513
26513
|
"interaction.visionDelegation.rememberSession": "Don’t ask again in this conversation",
|
|
26514
26514
|
"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.",
|
|
26515
|
+
"interaction.delegation.title": "Run parts of this task in parallel?",
|
|
26516
|
+
"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.",
|
|
26517
|
+
"interaction.delegation.declineNote": "If you decline, the assistant does the work itself in this conversation and will not ask again.",
|
|
26518
|
+
"interaction.delegation.concurrency": "Run at most",
|
|
26519
|
+
"interaction.delegation.suggestedNote": "The assistant suggested {count}.",
|
|
26515
26520
|
"interaction.imageSource.title": "Put a picture into the document?",
|
|
26516
26521
|
"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
26522
|
"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.",
|
|
@@ -26941,6 +26946,11 @@ const chatbotDictionary = defineDictionary({
|
|
|
26941
26946
|
"interaction.visionDelegation.rememberFallback": "以后不再为该端点询问",
|
|
26942
26947
|
"interaction.visionDelegation.rememberSession": "本次对话不再询问,默认同意",
|
|
26943
26948
|
"interaction.visionDelegation.body": "你选中的模型看不了图片。是否把「{source}」产出的 {images} 张图发往「{label}」({host})请它描述?不同意则什么都不会发出,这一轮就当没看到图。",
|
|
26949
|
+
"interaction.delegation.title": "把这件任务拆成几个并行去做?",
|
|
26950
|
+
"interaction.delegation.body": "子助手可以同时处理这件任务里互不相干的部分。它们做过什么不会记进本次会话,因此这次会话之后就存不成新技能了。",
|
|
26951
|
+
"interaction.delegation.declineNote": "不同意则助手自己在本次会话里做完,之后也不会再问。",
|
|
26952
|
+
"interaction.delegation.concurrency": "最多同时跑",
|
|
26953
|
+
"interaction.delegation.suggestedNote": "助手建议 {count} 个。",
|
|
26944
26954
|
"interaction.imageSource.title": "把图片放进文档?",
|
|
26945
26955
|
"interaction.imageSource.pageImageBody": "允许助手把当前页面上的一张图放进它正在生成的文档吗?该图会随本次会话保存,直到你删除这次会话。",
|
|
26946
26956
|
"interaction.imageSource.remoteImageBody": "允许助手从 {url} 下载一张图片放进它正在生成的文档吗?下载会把你的网络地址与访问时间告知该服务器。",
|
|
@@ -27377,6 +27387,24 @@ function redactInteractionValues(value, request) {
|
|
|
27377
27387
|
*/
|
|
27378
27388
|
const CHATBOT_PROFILE_USER_ID = "local";
|
|
27379
27389
|
/**
|
|
27390
|
+
* 并发子 agent 的宿主硬上限(0.23.0 分册 19 FR-19.16)。
|
|
27391
|
+
*
|
|
27392
|
+
* 它是模型建议值与用户选定值之上的天花板:模型建议 50、用户随手确认了,
|
|
27393
|
+
* 仍然只有这么多能同时跑。并发子 agent 会同时抢模型配额、页面操作通道与用户注意力,
|
|
27394
|
+
* 这个数字不是保守,是止损点。
|
|
27395
|
+
*/
|
|
27396
|
+
const DELEGATION_MAX_CONCURRENCY = 4;
|
|
27397
|
+
/**
|
|
27398
|
+
* 从确认卡的响应里读用户选定的并发数。
|
|
27399
|
+
*
|
|
27400
|
+
* 卡回的是 `{ concurrency: n }`(渲染层带了选择器)或 `true`(宿主用的是通用授权渲染,
|
|
27401
|
+
* 没有选择器)。后一种情况沿用建议值——用户点了同意,但没有表达过别的数字。
|
|
27402
|
+
*/
|
|
27403
|
+
function readChosenConcurrency(value, fallback, max) {
|
|
27404
|
+
const raw = typeof value === "object" && value !== null ? value["concurrency"] : void 0;
|
|
27405
|
+
return Math.min(Math.max(1, typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : fallback), Math.max(1, max));
|
|
27406
|
+
}
|
|
27407
|
+
/**
|
|
27380
27408
|
* 终止原因 → 稳定错误码(FR-21.2)。不再全部压平为 RUN_FAILED:
|
|
27381
27409
|
* “跑超时了”与“模型没配对”对用户是两件事,得给不同的下一步。
|
|
27382
27410
|
*/
|
|
@@ -27441,6 +27469,29 @@ const MAX_DOCUMENT_IMAGE_BYTES = 8e6;
|
|
|
27441
27469
|
*/
|
|
27442
27470
|
const WEB_OFFICE_TEMPLATE_PREFIX = "weboffice:";
|
|
27443
27471
|
/**
|
|
27472
|
+
* 技能自带文件作模板时的取件号前缀(0.23.0 分册 23 · FR-23.2)。
|
|
27473
|
+
*
|
|
27474
|
+
* 形态是 `skill:<技能名>/<技能目录内的相对路径>`。技能名的字符集
|
|
27475
|
+
* (`SKILL_NAME_PATTERN`)既不含 `/` 也不含 `:`,所以前缀之后的第一个 `/`
|
|
27476
|
+
* 就是唯一的分隔点,不需要第二个分隔符。
|
|
27477
|
+
*
|
|
27478
|
+
* 与 `WEB_OFFICE_TEMPLATE_PREFIX` 不同,这个常量**不进公开导出**:宿主不需要拼它,
|
|
27479
|
+
* 拼它的是模型,而模型看到的是工具描述里那句话(设计 24 §10 的零契约变更)。
|
|
27480
|
+
*/
|
|
27481
|
+
const SKILL_FILE_PREFIX = "skill:";
|
|
27482
|
+
/** 模板取件号里所有"指名道姓"的前缀:认出来就只走它自己那条通路,不回落到本地取件 */
|
|
27483
|
+
const PREFIXED_TEMPLATE_IDS = [SKILL_FILE_PREFIX, WEB_OFFICE_TEMPLATE_PREFIX];
|
|
27484
|
+
/** `skill:quotation/assets/tpl.xlsx` → `{ skill: 'quotation', path: 'assets/tpl.xlsx' }` */
|
|
27485
|
+
function parseSkillFileId(id) {
|
|
27486
|
+
const rest = id.slice(6);
|
|
27487
|
+
const cut = rest.indexOf("/");
|
|
27488
|
+
if (cut <= 0 || cut === rest.length - 1) return void 0;
|
|
27489
|
+
return {
|
|
27490
|
+
skill: rest.slice(0, cut),
|
|
27491
|
+
path: rest.slice(cut + 1)
|
|
27492
|
+
};
|
|
27493
|
+
}
|
|
27494
|
+
/**
|
|
27444
27495
|
* 图片附件的指认说明(0.21.0 分册 22 · FR-22.4b)。
|
|
27445
27496
|
*
|
|
27446
27497
|
* 文本、docx、xlsx、PDF 进上下文时都带 `--- Attachment: … ---` 标签,唯独图片是一片裸图:
|
|
@@ -27589,6 +27640,8 @@ var ChatEngine = class {
|
|
|
27589
27640
|
#ready;
|
|
27590
27641
|
/** 已装配的 runtime(cancel 等同步委托用;未装配时为 undefined) */
|
|
27591
27642
|
#runtime;
|
|
27643
|
+
/** 技能名 → 技能根目录,`skill:` 取件号用(0.23.0 分册 23)。惰性建,技能仓变更时清 */
|
|
27644
|
+
#skillIndex;
|
|
27592
27645
|
/**
|
|
27593
27646
|
* 每轮 `send()` 开头刷新的运行时配置快照(0.15.0 分册 15 · FR-15.6)。
|
|
27594
27647
|
* 只给那些必须同步回答、又不能等到重建运行时的开关用。
|
|
@@ -27951,6 +28004,94 @@ var ChatEngine = class {
|
|
|
27951
28004
|
throw new WebSkillError("VALIDATION_FAILED", `image reference "${kind}:${rest}" has no known prefix; expected artifact:, upload: or remote:`);
|
|
27952
28005
|
}
|
|
27953
28006
|
/**
|
|
28007
|
+
* 技能生成里被指认的那个附件(0.23.0 分册 20 FR-20.2)。
|
|
28008
|
+
*
|
|
28009
|
+
* **只在当前会话的消息里查**:候选集合恰好等于用户自己这次会话上传的东西,
|
|
28010
|
+
* 别的会话的 id 在这里查不到,也就谈不上被复制进技能包(AC-20.4)。
|
|
28011
|
+
* 路径一寸都不来自模型——它给的是 id,路径从附件记录里取,与 `#uploadOfRun` 同一范式。
|
|
28012
|
+
*
|
|
28013
|
+
* 落到哪条附件上仍走 `findAttachmentByRef`:模型上下文里最显眼的是文件名而不是
|
|
28014
|
+
* `att-…` 随机串,只认 id 的结果就是这条能力形同虚设。
|
|
28015
|
+
*/
|
|
28016
|
+
async #skillAttachment(id) {
|
|
28017
|
+
const sessionId = this.#currentSessionId;
|
|
28018
|
+
if (sessionId === void 0) return void 0;
|
|
28019
|
+
const messages = (await this.#sessions.get(sessionId).catch(() => void 0))?.messages ?? [];
|
|
28020
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
28021
|
+
const message = messages[i];
|
|
28022
|
+
if (message?.role !== "user") continue;
|
|
28023
|
+
const hit = findAttachmentByRef(message.attachments ?? [], id);
|
|
28024
|
+
if (hit === void 0) continue;
|
|
28025
|
+
return {
|
|
28026
|
+
bytes: await this.#adapter.storage.readBinary(resolveInsideRoot(this.attachmentsRoot, hit.path)),
|
|
28027
|
+
name: hit.name,
|
|
28028
|
+
contentType: hit.contentType
|
|
28029
|
+
};
|
|
28030
|
+
}
|
|
28031
|
+
}
|
|
28032
|
+
/**
|
|
28033
|
+
* `skill:<技能名>/<路径>` 的取件(0.23.0 分册 23 FR-23.1)。
|
|
28034
|
+
*
|
|
28035
|
+
* 这条通路**不经过** `DownloadedFilePolicy`,所以既不弹授权卡也不列下载目录——
|
|
28036
|
+
* 是结构性的,不是"记得别弹"(FR-23.8)。路径越界由 `SkillReader` 内建的
|
|
28037
|
+
* `resolveInsideRoot` 挡(FR-23.5)。
|
|
28038
|
+
*
|
|
28039
|
+
* 取不到一律**抛**而不是返回 `undefined`:模板工具对 `undefined` 会回一句
|
|
28040
|
+
* 「换个下载 id 再试」,那正是本册要根除的那句话(FR-23.9)。
|
|
28041
|
+
*/
|
|
28042
|
+
async #readSkillTemplate(id, fs) {
|
|
28043
|
+
const parsed = parseSkillFileId(id);
|
|
28044
|
+
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".`);
|
|
28045
|
+
const { skill, path } = parsed;
|
|
28046
|
+
let index = await this.#skillFileIndex(fs);
|
|
28047
|
+
if (!index.has(skill)) {
|
|
28048
|
+
this.#skillIndex = void 0;
|
|
28049
|
+
index = await this.#skillFileIndex(fs);
|
|
28050
|
+
}
|
|
28051
|
+
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>".`);
|
|
28052
|
+
const canRead = this.#options.governance?.skillStateGuard?.canRead;
|
|
28053
|
+
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.`);
|
|
28054
|
+
try {
|
|
28055
|
+
const bytes = await new SkillReader(fs, index).readSkillBinary(skill, path);
|
|
28056
|
+
return {
|
|
28057
|
+
name: path.split("/").pop() ?? path,
|
|
28058
|
+
bytes
|
|
28059
|
+
};
|
|
28060
|
+
} catch (error) {
|
|
28061
|
+
throw new WebSkillError(error instanceof WebSkillError ? error.code : "TOOL_EXECUTION_FAILED", `The skill "${skill}" has no readable file at "${path}": ${messageOf(error)}`);
|
|
28062
|
+
}
|
|
28063
|
+
}
|
|
28064
|
+
/** 技能名 → 技能根目录。惰性建:不用这种取件号的会话一次目录 IO 都不多花 */
|
|
28065
|
+
async #skillFileIndex(fs) {
|
|
28066
|
+
this.#skillIndex ??= (async () => {
|
|
28067
|
+
const result = await new SkillDiscovery(fs, this.#adapter.skillRoots).discover();
|
|
28068
|
+
return new Map(result.entries.map((entry) => [entry.name, entry.root]));
|
|
28069
|
+
})();
|
|
28070
|
+
return await this.#skillIndex;
|
|
28071
|
+
}
|
|
28072
|
+
/**
|
|
28073
|
+
* 技能根目录下有没有非 Markdown 的资源文件(0.23.0 分册 23 FR-23.3 / AC-23.8)。
|
|
28074
|
+
*
|
|
28075
|
+
* 一个都没有时工具描述里不提 `skill:` 取件号——凭空多一种读不到东西的说法,
|
|
28076
|
+
* 只会让模型多烧一轮。只列目录、不读也不解析 SKILL.md:这里要的是一个是/否。
|
|
28077
|
+
*
|
|
28078
|
+
* **漏判的方向是安全的**:资源放在 assets/references 之外的子目录时这里看不见,
|
|
28079
|
+
* 结果是描述里少一句,而 SKILL.md 自己会写明该用什么号(FR-23.4)——那是第二条、
|
|
28080
|
+
* 也是更可靠的一条告知通路。反过来(一个技能都没有却说有)才是要防的。
|
|
28081
|
+
*/
|
|
28082
|
+
async #hasSkillResources(fs) {
|
|
28083
|
+
const isResource = (stat) => stat.type === "file" && !stat.path.toLowerCase().endsWith(".md");
|
|
28084
|
+
const list = async (path) => await fs.list(path).catch(() => []);
|
|
28085
|
+
for (const root of this.#adapter.skillRoots) for (const dir of await list(root)) {
|
|
28086
|
+
if (dir.type !== "directory") continue;
|
|
28087
|
+
const entries = await list(dir.path);
|
|
28088
|
+
if (!entries.some((e) => e.type === "file" && e.path.endsWith("/SKILL.md"))) continue;
|
|
28089
|
+
if (entries.some(isResource)) return true;
|
|
28090
|
+
for (const sub of ["assets", "references"]) if ((await list(`${dir.path}/${sub}`)).some(isResource)) return true;
|
|
28091
|
+
}
|
|
28092
|
+
return false;
|
|
28093
|
+
}
|
|
28094
|
+
/**
|
|
27954
28095
|
* 这一次 run 属于哪个会话。
|
|
27955
28096
|
*
|
|
27956
28097
|
* 进行中读内存,已结束读 trace——与 `#skillNamesOfRun` 同一范式。
|
|
@@ -28180,6 +28321,7 @@ var ChatEngine = class {
|
|
|
28180
28321
|
this.#handles.clear();
|
|
28181
28322
|
this.#maxHistoryMessages = void 0;
|
|
28182
28323
|
this.#contextWindow = void 0;
|
|
28324
|
+
this.#skillIndex = void 0;
|
|
28183
28325
|
}
|
|
28184
28326
|
/**
|
|
28185
28327
|
* 技能仓变更后调用(安装/卸载/发布):作废 runtime 缓存的技能目录,下次 send 重新扫描。
|
|
@@ -28190,6 +28332,7 @@ var ChatEngine = class {
|
|
|
28190
28332
|
* @experimental
|
|
28191
28333
|
*/
|
|
28192
28334
|
invalidateSkills() {
|
|
28335
|
+
this.#skillIndex = void 0;
|
|
28193
28336
|
this.#ready?.then((runtime) => runtime.invalidate(), () => void 0);
|
|
28194
28337
|
}
|
|
28195
28338
|
/**
|
|
@@ -28933,6 +29076,96 @@ var ChatEngine = class {
|
|
|
28933
29076
|
};
|
|
28934
29077
|
}
|
|
28935
29078
|
/**
|
|
29079
|
+
* 会话级委派同意(0.23.0 分册 19 FR-19.10~19.14)。
|
|
29080
|
+
*
|
|
29081
|
+
* 记在**会话**上而不是引擎实例上:切到另一个会话是另一件任务,
|
|
29082
|
+
* 上一件任务里同意过并发委派不代表这一件也同意。
|
|
29083
|
+
*/
|
|
29084
|
+
#delegationConsent = /* @__PURE__ */ new Map();
|
|
29085
|
+
/** 用户在确认卡里选定的并发数;没问过或被拒时为 1(拒绝后根本不会走到委派) */
|
|
29086
|
+
#delegationConcurrency() {
|
|
29087
|
+
const sessionId = this.#currentSessionId;
|
|
29088
|
+
if (sessionId === void 0) return 1;
|
|
29089
|
+
return this.#delegationConsent.get(sessionId)?.concurrency ?? 1;
|
|
29090
|
+
}
|
|
29091
|
+
/**
|
|
29092
|
+
* 首次委派前问一次,之后沿用本会话的答案(FR-19.12)。
|
|
29093
|
+
* `suggested` 是模型给的建议值,`hardLimit` 是宿主经 `policy` 设的天花板(FR-19.16)。
|
|
29094
|
+
*/
|
|
29095
|
+
async #askDelegationConsent(suggested, hardLimit) {
|
|
29096
|
+
const sessionId = this.#currentSessionId ?? "";
|
|
29097
|
+
const remembered = this.#delegationConsent.get(sessionId);
|
|
29098
|
+
if (remembered !== void 0) return remembered;
|
|
29099
|
+
const max = Math.max(1, Math.floor(hardLimit));
|
|
29100
|
+
const preset = Math.min(Math.max(1, Math.floor(suggested)), max);
|
|
29101
|
+
this.#delegationAskSeq += 1;
|
|
29102
|
+
const response = await this.#uiBridge.request({
|
|
29103
|
+
type: "authorize",
|
|
29104
|
+
id: `delegation-consent-${this.#delegationAskSeq}`,
|
|
29105
|
+
capability: "confirm",
|
|
29106
|
+
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.`,
|
|
29107
|
+
details: {
|
|
29108
|
+
kind: "delegation",
|
|
29109
|
+
suggestedConcurrency: preset,
|
|
29110
|
+
maxConcurrency: max
|
|
29111
|
+
}
|
|
29112
|
+
});
|
|
29113
|
+
const decision = response.cancelled === true || response.value === false ? {
|
|
29114
|
+
approved: false,
|
|
29115
|
+
concurrency: 1
|
|
29116
|
+
} : {
|
|
29117
|
+
approved: true,
|
|
29118
|
+
concurrency: readChosenConcurrency(response.value, preset, max)
|
|
29119
|
+
};
|
|
29120
|
+
this.#delegationConsent.set(sessionId, decision);
|
|
29121
|
+
return decision;
|
|
29122
|
+
}
|
|
29123
|
+
#delegationAskSeq = 0;
|
|
29124
|
+
/**
|
|
29125
|
+
* 委派工具源 + 会话级确认闸门(0.23.0 分册 19)。
|
|
29126
|
+
*
|
|
29127
|
+
* 闸门套在**工具调用外层**而不是 `runner` 里:`runner` 已经在编排器的排队与
|
|
29128
|
+
* 预算发放之后,那时再问用户,被拒的那次已经占了一个名额与一份轮数额度。
|
|
29129
|
+
*/
|
|
29130
|
+
#delegationToolSource(deps, loopConfig) {
|
|
29131
|
+
const source = createDelegationToolSource({
|
|
29132
|
+
runner: createChatbotSubAgentRunner({
|
|
29133
|
+
deps,
|
|
29134
|
+
loopConfig,
|
|
29135
|
+
uiBridge: this.#uiBridge
|
|
29136
|
+
}),
|
|
29137
|
+
parentBudget: () => this.#parentBudget(loopConfig),
|
|
29138
|
+
policy: this.#delegationPolicy(loopConfig),
|
|
29139
|
+
concurrencyLimit: () => Math.min(this.#delegationConcurrency(), DELEGATION_MAX_CONCURRENCY),
|
|
29140
|
+
todos: this.#todoStore
|
|
29141
|
+
});
|
|
29142
|
+
return {
|
|
29143
|
+
...source,
|
|
29144
|
+
call: async (name, args) => {
|
|
29145
|
+
const suggested = args["suggestedConcurrency"];
|
|
29146
|
+
if (!(await this.#askDelegationConsent(typeof suggested === "number" && Number.isFinite(suggested) ? suggested : 1, DELEGATION_MAX_CONCURRENCY)).approved) return {
|
|
29147
|
+
ok: false,
|
|
29148
|
+
content: [],
|
|
29149
|
+
error: {
|
|
29150
|
+
code: "DELEGATION_DECLINED",
|
|
29151
|
+
message: "The user declined delegation for this conversation. Do this work yourself in this conversation and do not call delegate_task again."
|
|
29152
|
+
}
|
|
29153
|
+
};
|
|
29154
|
+
return source.call(name, args);
|
|
29155
|
+
}
|
|
29156
|
+
};
|
|
29157
|
+
}
|
|
29158
|
+
/**
|
|
29159
|
+
* 子 agent 的自身上限(FR-19.2/19.3)。**不再有内建的 8 轮**:
|
|
29160
|
+
* 缺省即不收紧,只受父级剩余预算约束;宿主在设置里调小 maxTurns 时才会收紧。
|
|
29161
|
+
*/
|
|
29162
|
+
#delegationPolicy(loopConfig) {
|
|
29163
|
+
return {
|
|
29164
|
+
...loopConfig.maxTurns !== void 0 ? { maxTurns: loopConfig.maxTurns } : {},
|
|
29165
|
+
...loopConfig.totalTimeoutMs !== void 0 ? { timeoutMs: loopConfig.totalTimeoutMs } : {}
|
|
29166
|
+
};
|
|
29167
|
+
}
|
|
29168
|
+
/**
|
|
28936
29169
|
* 浏览器内置模型自动进模型列表(FR-14.1,B4-01/FR-27.2 调整)。
|
|
28937
29170
|
* **只要浏览器支持 Prompt API**(含「模型未就绪」model-not-ready)就加入条目;
|
|
28938
29171
|
* API 完全不存在(api-missing / unsupported-browser)才不加——不可用只在列表里
|
|
@@ -29003,6 +29236,8 @@ var ChatEngine = class {
|
|
|
29003
29236
|
fs
|
|
29004
29237
|
});
|
|
29005
29238
|
const readDocumentBytes = async (id, context) => {
|
|
29239
|
+
const prefix = PREFIXED_TEMPLATE_IDS.find((p) => id.startsWith(p));
|
|
29240
|
+
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
29241
|
const attachment = this.#runAttachments.find((a) => a.id === id);
|
|
29007
29242
|
if (attachment) {
|
|
29008
29243
|
const bytes = await this.#adapter.storage.readBinary(`${this.attachmentsRoot}/${attachment.path}`);
|
|
@@ -29024,19 +29259,33 @@ var ChatEngine = class {
|
|
|
29024
29259
|
};
|
|
29025
29260
|
};
|
|
29026
29261
|
/**
|
|
29027
|
-
* 模板字节比 `read_document`
|
|
29262
|
+
* 模板字节比 `read_document` 多认两条取件号:`weboffice:<handle>`(0.22.0 分册 42 FR-42.12)
|
|
29263
|
+
* 与 `skill:<技能名>/<路径>`(0.23.0 分册 23 FR-23.1)。
|
|
29264
|
+
*
|
|
29265
|
+
* **前缀先认,认出来就只走它自己那条路,取不到即抛、不回落**(FR-23.13)。
|
|
29266
|
+
* 顺序不能倒过来:`readDocumentBytes` 的末段对认不出的号是**抛**而不是返回
|
|
29267
|
+
* `undefined`,所以「先穷尽本地再看前缀」时前缀判断根本执行不到——0.22.0 的
|
|
29268
|
+
* `weboffice:` 在接了下载端口的宿主里就是这么失效的(设计 24 §1 实测)。
|
|
29028
29269
|
*
|
|
29029
|
-
*
|
|
29030
|
-
* 前缀是显式的:不去猜某个 id 会不会恰好是个 weboffice handle,猜错的代价是
|
|
29031
|
-
* 拿了另一份文档冒充用户指的那一份。前缀不匹配时连问都不问,直接当作没有。
|
|
29270
|
+
* 不回落同样是硬的:带前缀的号是指名的那一份,回落等于拿另一份文档冒充它。
|
|
29032
29271
|
*/
|
|
29033
29272
|
const readTemplateBytes = async (id, context) => {
|
|
29034
|
-
|
|
29035
|
-
if (
|
|
29036
|
-
|
|
29037
|
-
|
|
29038
|
-
|
|
29273
|
+
if (id.startsWith("skill:")) return await this.#readSkillTemplate(id, fs);
|
|
29274
|
+
if (id.startsWith("weboffice:")) {
|
|
29275
|
+
const online = this.#adapter.webOfficeTemplateSource;
|
|
29276
|
+
if (!online) throw new WebSkillError("TOOL_UNSUPPORTED", `Cannot read "${id}": this conversation has no online document source attached.`);
|
|
29277
|
+
return await online.read(id.slice(10), context);
|
|
29278
|
+
}
|
|
29279
|
+
return await readDocumentBytes(id, context);
|
|
29039
29280
|
};
|
|
29281
|
+
/**
|
|
29282
|
+
* 模板取件号里那些**宿主接了才存在**的说法(0.22.0 FR-42.12、0.23.0 FR-23.3)。
|
|
29283
|
+
*
|
|
29284
|
+
* 无条件写死的后果是模型反复尝试一个在这个宿主里永远读不到的号。技能那一句
|
|
29285
|
+
* 还多一道闸:一个带资源的技能都没装时不提(AC-23.8)。探测只在真有模板工具时做。
|
|
29286
|
+
*/
|
|
29287
|
+
const hasTemplateEngine = Boolean(this.#adapter.xlsxTemplateEngine || this.#adapter.docxTemplateEngine);
|
|
29288
|
+
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
29289
|
const baseTools = [
|
|
29041
29290
|
...pageSkillSource ? [pageSkillSource] : [],
|
|
29042
29291
|
createSpreadsheetToolSource({
|
|
@@ -29057,6 +29306,7 @@ var ChatEngine = class {
|
|
|
29057
29306
|
ui: this.#uiBridge,
|
|
29058
29307
|
...this.#options.skillCandidates ? { sink: this.#options.skillCandidates } : {},
|
|
29059
29308
|
sessionId: () => this.#currentSessionId,
|
|
29309
|
+
resolveAttachment: (id) => this.#skillAttachment(id),
|
|
29060
29310
|
...toolStepStore ? { steps: toolStepStore } : {},
|
|
29061
29311
|
messages: { confirmSkill: (draft) => translateText(rc.appearance.locale, "skillGeneration.confirm", {
|
|
29062
29312
|
name: draft.name,
|
|
@@ -29090,7 +29340,7 @@ var ChatEngine = class {
|
|
|
29090
29340
|
}),
|
|
29091
29341
|
...this.#adapter.xlsxTemplateEngine || this.#adapter.docxTemplateEngine ? [createTemplateToolSource({
|
|
29092
29342
|
files: { read: readTemplateBytes },
|
|
29093
|
-
...
|
|
29343
|
+
...templateIdHints.length > 0 ? { idHint: templateIdHints.join(" ") } : {},
|
|
29094
29344
|
...this.#adapter.xlsxTemplateEngine ? { xlsx: this.#adapter.xlsxTemplateEngine } : {},
|
|
29095
29345
|
...this.#adapter.docxTemplateEngine ? { docx: this.#adapter.docxTemplateEngine } : {},
|
|
29096
29346
|
writeArtifact: (path, content, mimeType, context) => {
|
|
@@ -29183,15 +29433,7 @@ var ChatEngine = class {
|
|
|
29183
29433
|
};
|
|
29184
29434
|
const runtime = new WebSkillRuntime({
|
|
29185
29435
|
...deps,
|
|
29186
|
-
externalTools: [...baseTools, ...rc?.agentCapabilities.delegation === true ? [
|
|
29187
|
-
runner: createChatbotSubAgentRunner({
|
|
29188
|
-
deps,
|
|
29189
|
-
loopConfig,
|
|
29190
|
-
uiBridge: this.#uiBridge
|
|
29191
|
-
}),
|
|
29192
|
-
parentBudget: () => this.#parentBudget(loopConfig),
|
|
29193
|
-
todos: this.#todoStore
|
|
29194
|
-
})] : []]
|
|
29436
|
+
externalTools: [...baseTools, ...rc?.agentCapabilities.delegation === true ? [this.#delegationToolSource(deps, loopConfig)] : []]
|
|
29195
29437
|
});
|
|
29196
29438
|
runtime.events.on("*", (event) => {
|
|
29197
29439
|
this.#runSessions.set(event.runId, event.sessionId);
|
|
@@ -29588,7 +29830,7 @@ const useWebSkillThreadList = ({ sessions, currentId, isLoading, thread, onSwitc
|
|
|
29588
29830
|
id: mainThreadId,
|
|
29589
29831
|
createdAt: ""
|
|
29590
29832
|
};
|
|
29591
|
-
const items = useClientLookup((pending ? [...sessions
|
|
29833
|
+
const items = useClientLookup((pending ? [pending, ...sessions] : sessions).map((session) => withKey(session.id, ThreadListItemClient({
|
|
29592
29834
|
session,
|
|
29593
29835
|
onSwitchTo: () => session.id === "__webskill_new_thread__" ? onCreate() : onSwitchTo(session.id),
|
|
29594
29836
|
onRename: (title) => onRename(session.id, title),
|
|
@@ -30328,14 +30570,6 @@ function AssistantUiSessionList({ className = "", onNavigate, onCollapse, hasEar
|
|
|
30328
30570
|
className: "min-h-0 flex-1 overflow-y-auto px-2 pb-3",
|
|
30329
30571
|
role: "list",
|
|
30330
30572
|
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
30573
|
skipped.length > 0 ? /* @__PURE__ */ jsx("p", {
|
|
30340
30574
|
role: "status",
|
|
30341
30575
|
"data-testid": "session-skipped",
|
|
@@ -30379,7 +30613,15 @@ function AssistantUiSessionList({ className = "", onNavigate, onCollapse, hasEar
|
|
|
30379
30613
|
onEndRename: () => setRenamingId(void 0)
|
|
30380
30614
|
});
|
|
30381
30615
|
}
|
|
30382
|
-
})
|
|
30616
|
+
}),
|
|
30617
|
+
hasEarlier && onLoadEarlier ? /* @__PURE__ */ jsx("button", {
|
|
30618
|
+
type: "button",
|
|
30619
|
+
"data-testid": "load-earlier-sessions",
|
|
30620
|
+
disabled: loadingEarlier,
|
|
30621
|
+
onClick: onLoadEarlier,
|
|
30622
|
+
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",
|
|
30623
|
+
children: loadingEarlier ? t("session.loading") : t("session.loadEarlier")
|
|
30624
|
+
}) : null
|
|
30383
30625
|
]
|
|
30384
30626
|
})
|
|
30385
30627
|
]
|
|
@@ -30623,6 +30865,25 @@ function webOfficeDetails(details) {
|
|
|
30623
30865
|
};
|
|
30624
30866
|
}
|
|
30625
30867
|
/**
|
|
30868
|
+
* 并发委派确认卡的载荷(0.23.0 分册 19 FR-19.10/19.14)。
|
|
30869
|
+
*
|
|
30870
|
+
* 与其他授权卡不同,这张卡带一个**旋钮**:用户不只是同不同意,还要定下同时跑几个。
|
|
30871
|
+
* `max` 是宿主硬上限,`suggested` 是模型的建议值(已被引擎截到 `max` 以内)——
|
|
30872
|
+
* 面板里出现一个选不了的数字,比不出现更糟。
|
|
30873
|
+
*/
|
|
30874
|
+
function delegationDetails(details) {
|
|
30875
|
+
if (typeof details !== "object" || details === null) return void 0;
|
|
30876
|
+
const record = details;
|
|
30877
|
+
if (record["kind"] !== "delegation") return void 0;
|
|
30878
|
+
const suggested = record["suggestedConcurrency"];
|
|
30879
|
+
const max = record["maxConcurrency"];
|
|
30880
|
+
if (typeof max !== "number" || !Number.isFinite(max) || max < 1) return void 0;
|
|
30881
|
+
return {
|
|
30882
|
+
suggested: Math.min(Math.max(1, typeof suggested === "number" && Number.isFinite(suggested) ? Math.floor(suggested) : 1), Math.floor(max)),
|
|
30883
|
+
max: Math.floor(max)
|
|
30884
|
+
};
|
|
30885
|
+
}
|
|
30886
|
+
/**
|
|
30626
30887
|
* 视觉委派告知卡的载荷(0.21.0 分册 17)。
|
|
30627
30888
|
*
|
|
30628
30889
|
* 端点的 `label` 与 `host` 都要:前者是用户在设置里给它起的名字,后者是它**真实的去处**。
|
|
@@ -30726,6 +30987,7 @@ function localizedAuthorizeMessage(request, t) {
|
|
|
30726
30987
|
size: formatBytes(details.file.size)
|
|
30727
30988
|
})}`;
|
|
30728
30989
|
}
|
|
30990
|
+
if (request.capability === "confirm" && delegationDetails(request.details) !== void 0) return t("interaction.delegation.body");
|
|
30729
30991
|
if (request.capability === "readReference" || request.capability === "readAsset" || request.capability === "writeArtifact" || request.capability === "fetchData" || request.capability === "confirm") {
|
|
30730
30992
|
const details = sandboxDetails(request.details);
|
|
30731
30993
|
if (details === void 0) return void 0;
|
|
@@ -43726,8 +43988,10 @@ function ActiveInteractionCard({ request, bridge }) {
|
|
|
43726
43988
|
const webOffice = isAuthorize && request.capability === "readWebOfficeDocument" ? webOfficeDetails(request.details) : void 0;
|
|
43727
43989
|
const visionDelegation = isAuthorize && request.capability === "delegateVision" ? visionDelegationDetails(request.details) : void 0;
|
|
43728
43990
|
const imageSource = isAuthorize && request.capability === "readImageSource" ? imageSourceDetails(request.details) : void 0;
|
|
43991
|
+
const delegation = isAuthorize && request.capability === "confirm" ? delegationDetails(request.details) : void 0;
|
|
43992
|
+
const [concurrency, setConcurrency] = useState(void 0);
|
|
43729
43993
|
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;
|
|
43994
|
+
const localized = pageAction !== void 0 || downloadedFile !== void 0 || webOffice !== void 0 || visionDelegation !== void 0 || imageSource !== void 0 || delegation !== void 0;
|
|
43731
43995
|
const spec = interactionToUiSpec(request.type === "authorize" && localized ? {
|
|
43732
43996
|
...request,
|
|
43733
43997
|
message: ""
|
|
@@ -43753,6 +44017,13 @@ function ActiveInteractionCard({ request, bridge }) {
|
|
|
43753
44017
|
return;
|
|
43754
44018
|
}
|
|
43755
44019
|
const value = shapeInteractionValue(interactionToFormModel(request), event.value ?? {});
|
|
44020
|
+
if (delegation !== void 0) {
|
|
44021
|
+
bridge.resolve({
|
|
44022
|
+
id: request.id,
|
|
44023
|
+
value: { concurrency: concurrency ?? delegation.suggested }
|
|
44024
|
+
});
|
|
44025
|
+
return;
|
|
44026
|
+
}
|
|
43756
44027
|
bridge.resolve({
|
|
43757
44028
|
id: request.id,
|
|
43758
44029
|
value,
|
|
@@ -43961,6 +44232,41 @@ function ActiveInteractionCard({ request, bridge }) {
|
|
|
43961
44232
|
}), t(imageSource.rememberLabelKey ?? "interaction.imageSource.rememberFallback")]
|
|
43962
44233
|
}) : null
|
|
43963
44234
|
] }) : null,
|
|
44235
|
+
delegation ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
44236
|
+
/* @__PURE__ */ jsx("p", {
|
|
44237
|
+
"data-testid": "interaction-delegation-title",
|
|
44238
|
+
className: "text-sm font-medium text-ink",
|
|
44239
|
+
children: t("interaction.delegation.title")
|
|
44240
|
+
}),
|
|
44241
|
+
/* @__PURE__ */ jsx("p", {
|
|
44242
|
+
"data-testid": "interaction-delegation-body",
|
|
44243
|
+
className: "text-sm text-ink",
|
|
44244
|
+
children: t("interaction.delegation.body")
|
|
44245
|
+
}),
|
|
44246
|
+
/* @__PURE__ */ jsx("p", {
|
|
44247
|
+
"data-testid": "interaction-delegation-decline",
|
|
44248
|
+
className: "text-sm text-muted",
|
|
44249
|
+
children: t("interaction.delegation.declineNote")
|
|
44250
|
+
}),
|
|
44251
|
+
/* @__PURE__ */ jsxs("label", {
|
|
44252
|
+
className: "flex flex-wrap items-center gap-2 text-sm text-ink",
|
|
44253
|
+
children: [t("interaction.delegation.concurrency"), /* @__PURE__ */ jsx("select", {
|
|
44254
|
+
"data-testid": "interaction-delegation-concurrency",
|
|
44255
|
+
className: "rounded-md border border-border bg-canvas px-2 py-1 text-sm text-ink",
|
|
44256
|
+
value: String(concurrency ?? delegation.suggested),
|
|
44257
|
+
onChange: (event) => setConcurrency(Number(event.currentTarget.value)),
|
|
44258
|
+
children: Array.from({ length: delegation.max }, (_, index) => index + 1).map((n) => /* @__PURE__ */ jsx("option", {
|
|
44259
|
+
value: String(n),
|
|
44260
|
+
children: n
|
|
44261
|
+
}, n))
|
|
44262
|
+
})]
|
|
44263
|
+
}),
|
|
44264
|
+
/* @__PURE__ */ jsx("p", {
|
|
44265
|
+
"data-testid": "interaction-delegation-suggested",
|
|
44266
|
+
className: "text-xs text-muted",
|
|
44267
|
+
children: t("interaction.delegation.suggestedNote", { count: String(delegation.suggested) })
|
|
44268
|
+
})
|
|
44269
|
+
] }) : null,
|
|
43964
44270
|
traceEvidence ? /* @__PURE__ */ jsxs("div", {
|
|
43965
44271
|
"data-testid": "interaction-trace-evidence",
|
|
43966
44272
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -44971,7 +45277,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
|
|
|
44971
45277
|
if (!sessionsCursor || loadingEarlierSessions) return;
|
|
44972
45278
|
setLoadingEarlierSessions(true);
|
|
44973
45279
|
engine.listSessions({ cursor: sessionsCursor }).then((page) => {
|
|
44974
|
-
setSessions((prev) => [...page.items
|
|
45280
|
+
setSessions((prev) => [...prev, ...page.items]);
|
|
44975
45281
|
setSessionsCursor(page.nextCursor);
|
|
44976
45282
|
}).catch((e) => setError({ message: toErrorMessage(e) })).finally(() => setLoadingEarlierSessions(false));
|
|
44977
45283
|
}, [
|
|
@@ -45306,7 +45612,7 @@ function VercelPayloadPreview({ bridge }) {
|
|
|
45306
45612
|
* Version of the published `@webskill/chatbot` package, injected at build time.
|
|
45307
45613
|
* @stable
|
|
45308
45614
|
*/
|
|
45309
|
-
const CHATBOT_VERSION = "0.
|
|
45615
|
+
const CHATBOT_VERSION = "0.20.0";
|
|
45310
45616
|
|
|
45311
45617
|
//#endregion
|
|
45312
45618
|
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.
|
|
3
|
+
"version": "0.20.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.
|
|
41
|
+
"@webskill/sdk": "^0.23.0",
|
|
42
42
|
"mermaid": "^12.0.0",
|
|
43
43
|
"react": ">=19.0.0",
|
|
44
44
|
"react-dom": ">=19.0.0",
|