@webskill/chatbot 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/chatbot.css CHANGED
@@ -291,8 +291,8 @@
291
291
  .right-full {
292
292
  right: 100%;
293
293
  }
294
- .-bottom-7 {
295
- bottom: calc(var(--spacing) * -7);
294
+ .-bottom-9 {
295
+ bottom: calc(var(--spacing) * -9);
296
296
  }
297
297
  .bottom-4 {
298
298
  bottom: calc(var(--spacing) * 4);
@@ -402,8 +402,8 @@
402
402
  .mb-2 {
403
403
  margin-bottom: calc(var(--spacing) * 2);
404
404
  }
405
- .mb-7 {
406
- margin-bottom: calc(var(--spacing) * 7);
405
+ .mb-9 {
406
+ margin-bottom: calc(var(--spacing) * 9);
407
407
  }
408
408
  .block {
409
409
  display: block;
@@ -530,6 +530,9 @@
530
530
  .min-h-0 {
531
531
  min-height: 0px;
532
532
  }
533
+ .min-h-8 {
534
+ min-height: calc(var(--spacing) * 8);
535
+ }
533
536
  .min-h-16 {
534
537
  min-height: calc(var(--spacing) * 16);
535
538
  }
@@ -572,6 +575,9 @@
572
575
  .w-72 {
573
576
  width: calc(var(--spacing) * 72);
574
577
  }
578
+ .w-\[80\%\] {
579
+ width: 80%;
580
+ }
575
581
  .w-\[min\(36rem\,calc\(100vw-2rem\)\)\] {
576
582
  width: min(36rem, calc(100vw - 2rem));
577
583
  }
@@ -715,6 +721,9 @@
715
721
  .grid-cols-1 {
716
722
  grid-template-columns: repeat(1, minmax(0, 1fr));
717
723
  }
724
+ .grid-cols-\[auto_auto_auto\] {
725
+ grid-template-columns: auto auto auto;
726
+ }
718
727
  .flex-col {
719
728
  flex-direction: column;
720
729
  }
@@ -730,6 +739,9 @@
730
739
  .items-center {
731
740
  align-items: center;
732
741
  }
742
+ .items-end {
743
+ align-items: flex-end;
744
+ }
733
745
  .items-start {
734
746
  align-items: flex-start;
735
747
  }
@@ -1229,6 +1241,9 @@
1229
1241
  .whitespace-nowrap {
1230
1242
  white-space: nowrap;
1231
1243
  }
1244
+ .whitespace-pre {
1245
+ white-space: pre;
1246
+ }
1232
1247
  .whitespace-pre-wrap {
1233
1248
  white-space: pre-wrap;
1234
1249
  }
@@ -1286,6 +1301,9 @@
1286
1301
  .uppercase {
1287
1302
  text-transform: uppercase;
1288
1303
  }
1304
+ .italic {
1305
+ font-style: italic;
1306
+ }
1289
1307
  .tabular-nums {
1290
1308
  --tw-numeric-spacing: tabular-nums;
1291
1309
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
@@ -2039,6 +2057,36 @@
2039
2057
  --brand-foreground: oklch(0.985 0 0);
2040
2058
  --text-min: 0.75rem;
2041
2059
  --text-code: 0.8125rem;
2060
+ --code-base: hsl(230, 8%, 24%);
2061
+ --code-comment: hsl(230, 4%, 64%);
2062
+ --code-prolog: hsl(230, 4%, 64%);
2063
+ --code-cdata: hsl(230, 4%, 64%);
2064
+ --code-doctype: hsl(230, 8%, 24%);
2065
+ --code-punctuation: hsl(230, 8%, 24%);
2066
+ --code-entity: hsl(230, 8%, 24%);
2067
+ --code-attr-name: hsl(35, 99%, 36%);
2068
+ --code-class-name: hsl(35, 99%, 36%);
2069
+ --code-boolean: hsl(35, 99%, 36%);
2070
+ --code-constant: hsl(35, 99%, 36%);
2071
+ --code-number: hsl(35, 99%, 36%);
2072
+ --code-atrule: hsl(35, 99%, 36%);
2073
+ --code-keyword: hsl(301, 63%, 40%);
2074
+ --code-property: hsl(5, 74%, 59%);
2075
+ --code-tag: hsl(5, 74%, 59%);
2076
+ --code-symbol: hsl(5, 74%, 59%);
2077
+ --code-deleted: hsl(5, 74%, 59%);
2078
+ --code-important: hsl(5, 74%, 59%);
2079
+ --code-selector: hsl(119, 34%, 47%);
2080
+ --code-string: hsl(119, 34%, 47%);
2081
+ --code-char: hsl(119, 34%, 47%);
2082
+ --code-builtin: hsl(119, 34%, 47%);
2083
+ --code-inserted: hsl(119, 34%, 47%);
2084
+ --code-regex: hsl(119, 34%, 47%);
2085
+ --code-attr-value: hsl(119, 34%, 47%);
2086
+ --code-variable: hsl(221, 87%, 60%);
2087
+ --code-operator: hsl(221, 87%, 60%);
2088
+ --code-function: hsl(221, 87%, 60%);
2089
+ --code-url: hsl(198, 99%, 37%);
2042
2090
  }
2043
2091
  :where(.dark, [data-theme='dark']) {
2044
2092
  --background: oklch(0.145 0 0);
@@ -2092,6 +2140,36 @@
2092
2140
  --sidebar-ring: oklch(0.556 0 0);
2093
2141
  --brand: oklch(0.68 0.14 275);
2094
2142
  --brand-foreground: oklch(0.205 0 0);
2143
+ --code-base: hsl(220, 14%, 71%);
2144
+ --code-comment: hsl(220, 10%, 40%);
2145
+ --code-prolog: hsl(220, 10%, 40%);
2146
+ --code-cdata: hsl(220, 10%, 40%);
2147
+ --code-doctype: hsl(220, 14%, 71%);
2148
+ --code-punctuation: hsl(220, 14%, 71%);
2149
+ --code-entity: hsl(220, 14%, 71%);
2150
+ --code-attr-name: hsl(29, 54%, 61%);
2151
+ --code-class-name: hsl(29, 54%, 61%);
2152
+ --code-boolean: hsl(29, 54%, 61%);
2153
+ --code-constant: hsl(29, 54%, 61%);
2154
+ --code-number: hsl(29, 54%, 61%);
2155
+ --code-atrule: hsl(29, 54%, 61%);
2156
+ --code-keyword: hsl(286, 60%, 67%);
2157
+ --code-property: hsl(355, 65%, 65%);
2158
+ --code-tag: hsl(355, 65%, 65%);
2159
+ --code-symbol: hsl(355, 65%, 65%);
2160
+ --code-deleted: hsl(355, 65%, 65%);
2161
+ --code-important: hsl(355, 65%, 65%);
2162
+ --code-selector: hsl(95, 38%, 62%);
2163
+ --code-string: hsl(95, 38%, 62%);
2164
+ --code-char: hsl(95, 38%, 62%);
2165
+ --code-builtin: hsl(95, 38%, 62%);
2166
+ --code-inserted: hsl(95, 38%, 62%);
2167
+ --code-regex: hsl(95, 38%, 62%);
2168
+ --code-attr-value: hsl(95, 38%, 62%);
2169
+ --code-variable: hsl(207, 82%, 66%);
2170
+ --code-operator: hsl(207, 82%, 66%);
2171
+ --code-function: hsl(207, 82%, 66%);
2172
+ --code-url: hsl(187, 47%, 55%);
2095
2173
  }
2096
2174
  @layer base {
2097
2175
  *, ::before, ::after {
package/dist/index.d.ts CHANGED
@@ -1059,6 +1059,7 @@ declare const chatbotDictionary: {
1059
1059
  'result.artifacts': string;
1060
1060
  'result.download': string;
1061
1061
  'result.file': string;
1062
+ 'result.block.unknown': string;
1062
1063
  'status.aria': string;
1063
1064
  'status.route': string;
1064
1065
  'status.activate': string;
@@ -1379,6 +1380,7 @@ declare const chatbotDictionary: {
1379
1380
  'result.artifacts': string;
1380
1381
  'result.download': string;
1381
1382
  'result.file': string;
1383
+ 'result.block.unknown': string;
1382
1384
  'status.aria': string;
1383
1385
  'status.route': string;
1384
1386
  'status.activate': string;
@@ -1702,6 +1704,7 @@ declare const useT: () => TranslateFn<{
1702
1704
  'result.artifacts': string;
1703
1705
  'result.download': string;
1704
1706
  'result.file': string;
1707
+ 'result.block.unknown': string;
1705
1708
  'status.aria': string;
1706
1709
  'status.route': string;
1707
1710
  'status.activate': string;
@@ -2022,6 +2025,7 @@ declare const useT: () => TranslateFn<{
2022
2025
  'result.artifacts': string;
2023
2026
  'result.download': string;
2024
2027
  'result.file': string;
2028
+ 'result.block.unknown': string;
2025
2029
  'status.aria': string;
2026
2030
  'status.route': string;
2027
2031
  'status.activate': string;
@@ -2256,8 +2260,9 @@ interface ChatbotProps {
2256
2260
  /**
2257
2261
  * 打开配置面(FR-20.4)。配置面本身已迁到 console,chatbot 只负责发出这个请求;
2258
2262
  * 宿主不接就不渲染任何「打开设置」出口——给一个点了没反应的按钮更糟。
2263
+ * `section` 可选(FR-16.5):齿轮不带分区(→ Overview),带诉求的入口(错误卡等)才传分区。
2259
2264
  */
2260
- onOpenSettings?(section: SettingsSectionId): void;
2265
+ onOpenSettings?(section?: SettingsSectionId): void;
2261
2266
  /**
2262
2267
  * 装配完成后把引擎交给宿主,用于接线跨界面动作(例如 console 的快照恢复入口
2263
2268
  * 需要调 `engine.resume(runId)`)。每次重新装配都会再调一次。
@@ -2447,4 +2452,12 @@ interface ErrorCardProps {
2447
2452
  /** 结构化错误卡:错误码徽章 + 描述 + 建议(engine error 事件 / 失败 run) @stable */
2448
2453
  declare function ErrorCard({ code, message, loopLimits, onOpenSettings, onDismiss }: ErrorCardProps): import("react").JSX.Element;
2449
2454
  //#endregion
2450
- export { A2uiSurfaceHost, type A2uiSurfaceHostProps, A2uiSurfaceSnapshotHost, type A2uiSurfaceSnapshotHostProps, type AppearanceChange, type ChatAttachmentInput, type ChatAttachmentMeta, ChatEngine, type ChatEngineOptions, type ChatEvent, type ChatInteractionRecord, type ChatLayout, type ChatMessage, type ChatRunPhase, type ChatSessionListResult, type ChatSessionMeta, type ChatTheme, type ChatToolCall, Chatbot, type ChatbotConfig, type ChatbotGovernancePorts, type ChatbotHostAdapter, type ChatbotProps, CompositeUiBridge, type CompositeUiBridgeDeps, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, type DownloadableFile, ErrorCard, type ErrorCardProps, InteractionCard, type InteractionCardProps, InterruptedBanner, type InterruptedBannerProps, type Locale, OpenUiSurfaceHost, type OpenUiSurfaceHostProps, OpenUiSurfaceSnapshotHost, type OpenUiSurfaceSnapshotHostProps, type RendererCapability, type RendererKind, type ResolvedChatLayout, ResultBlockList, type ResultBlockListProps, ResultBlocksPro, type ResultBlocksProProps, type RunSnapshot, type RuntimeAppearanceConfig, type RuntimeConfig, type RuntimeConfigStore, type RuntimeRendererId, type SandboxExecutorDeps, type SettingsSectionId, SkillBadges, SpecInteraction, type SpecInteractionProps, VercelPayloadPreview, type VercelPayloadPreviewProps, VercelSurfaceHost, type VercelSurfaceHostProps, VercelSurfaceSnapshotHost, type VercelSurfaceSnapshotHostProps, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };
2455
+ //#region src/version.d.ts
2456
+ /** Generated by scripts/syncVersionConstant.mjs from packages/chatbot/package.json. Do not edit by hand. */
2457
+ /**
2458
+ * Version of the published `@webskill/chatbot` package, injected at build time.
2459
+ * @stable
2460
+ */
2461
+ declare const CHATBOT_VERSION = "0.6.0";
2462
+ //#endregion
2463
+ export { A2uiSurfaceHost, type A2uiSurfaceHostProps, A2uiSurfaceSnapshotHost, type A2uiSurfaceSnapshotHostProps, type AppearanceChange, CHATBOT_VERSION, type ChatAttachmentInput, type ChatAttachmentMeta, ChatEngine, type ChatEngineOptions, type ChatEvent, type ChatInteractionRecord, type ChatLayout, type ChatMessage, type ChatRunPhase, type ChatSessionListResult, type ChatSessionMeta, type ChatTheme, type ChatToolCall, Chatbot, type ChatbotConfig, type ChatbotGovernancePorts, type ChatbotHostAdapter, type ChatbotProps, CompositeUiBridge, type CompositeUiBridgeDeps, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, type DownloadableFile, ErrorCard, type ErrorCardProps, InteractionCard, type InteractionCardProps, InterruptedBanner, type InterruptedBannerProps, type Locale, OpenUiSurfaceHost, type OpenUiSurfaceHostProps, OpenUiSurfaceSnapshotHost, type OpenUiSurfaceSnapshotHostProps, type RendererCapability, type RendererKind, type ResolvedChatLayout, ResultBlockList, type ResultBlockListProps, ResultBlocksPro, type ResultBlocksProProps, type RunSnapshot, type RuntimeAppearanceConfig, type RuntimeConfig, type RuntimeConfigStore, type RuntimeRendererId, type SandboxExecutorDeps, type SettingsSectionId, SkillBadges, SpecInteraction, type SpecInteractionProps, VercelPayloadPreview, type VercelPayloadPreviewProps, VercelSurfaceHost, type VercelSurfaceHostProps, VercelSurfaceSnapshotHost, type VercelSurfaceSnapshotHostProps, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { t as yaml } from "./yaml-C888tCbJ.js";
6
6
  import { t as json } from "./json-BOcU6nei.js";
7
7
  import { t as typescript } from "./typescript-DAau_Qyl.js";
8
8
  import { t as jsx$1 } from "./jsx-D2Jv0wvt.js";
9
- import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FullDisclosureRouter, SerializingMemoryStore, SkillDiscovery, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, diffUserProfile, exportUserProfile, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, summarizeToolCalls, textParts } from "@webskill/sdk";
9
+ import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FullDisclosureRouter, SerializingMemoryStore, SkillDiscovery, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, atomicWriteText, diffUserProfile, exportUserProfile, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, summarizeToolCalls, textParts } from "@webskill/sdk";
10
10
  import { BrowserWorkerScriptExecutor, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, deleteMemoryEncryptionKey, openMemoryEncryptionKey, probeChromeBuiltinAvailability, startDictation } from "@webskill/sdk/browser";
11
11
  import { JsonRenderSpecSurface, NativeSpecSurface, OpenUiSpecSurface, ReactBridgeState, SpecInteractionChannel, SurfaceFormTextsProvider, UiSurfaceList, UiSurfaceSnapshotList, probeJsonRenderAvailability } from "@webskill/sdk/ui-react";
12
12
  import { A2UI_SPEC_FORM_PATH, collectFormScopes, createUiCatalogToolSource, fromA2uiSpecAction, fromA2uiSurfaceAction, interactionToFormModel, interactionToUiSpec, loadOpenUiPeers, loadWebSkillLitCatalog, renderMiniChart, shapeInteractionValue, toA2uiSpecMessages, toA2uiSurfaceAction, toVercelToolInvocation, uiCatalog } from "@webskill/sdk/ui";
@@ -21,6 +21,7 @@ import { DataRenderers, Tools } from "@assistant-ui/core/react";
21
21
  import { ModelContext, Suggestions } from "@assistant-ui/core/store";
22
22
  import { Derived, attachTransformScopes, useClientLookup, useClientResource } from "@assistant-ui/store";
23
23
  import { resource, useMemoCache, withKey } from "@assistant-ui/tap";
24
+ import { POLICY_DENIAL_CODES } from "@webskill/sdk/governance";
24
25
 
25
26
  //#region ../ui-kit/src/i18n/createI18n.ts
26
27
  /** 字典工厂:各包定义自己的双语字典(恒等返回,保留字面量类型) */
@@ -1160,7 +1161,7 @@ const variantClasses$1 = {
1160
1161
  danger: "bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm disabled:opacity-60"
1161
1162
  };
1162
1163
  const sizeClasses$2 = {
1163
- sm: "h-7 px-2.5 text-xs gap-1.5 rounded-md",
1164
+ sm: "h-8 px-2.5 text-xs gap-1.5 rounded-md",
1164
1165
  md: "h-9 px-3.5 text-sm gap-2 rounded-lg"
1165
1166
  };
1166
1167
  function Button({ variant = "primary", size = "md", loading = false, icon, disabled, className = "", children, type = "button", ...rest }) {
@@ -1184,7 +1185,7 @@ const variantClasses = {
1184
1185
  danger: "text-destructive hover:bg-destructive-soft"
1185
1186
  };
1186
1187
  const sizeClasses$1 = {
1187
- sm: "size-7",
1188
+ sm: "size-8",
1188
1189
  md: "size-9"
1189
1190
  };
1190
1191
  /**
@@ -37803,11 +37804,40 @@ var one_light_default = {
37803
37804
 
37804
37805
  //#endregion
37805
37806
  //#region ../ui-kit/src/markdown/prismTheme.ts
37806
- /** 全站唯一的代码高亮主题对。引用相等是验收判据(AC-19.8),不导出到包外。 */
37807
+ /** react-syntax-highlighter 顶层 token 键(comment/punctuation/attr-name…);其余是容器选择器键 */
37808
+ const TOKEN_KEY = /^[a-z][a-z-]*$/;
37809
+ /**
37810
+ * 把主题对象的每个 color 替换为 CSS 变量引用:
37811
+ * - token 键(punctuation/string/attr-name…)→ `var(--code-<token>)`
37812
+ * - 容器选择器键(code[]/pre[]/selection…)→ `var(--code-base)`
37813
+ *
37814
+ * token 内联的是 var 引用而非固定色值;主题切换时 CSS 变量在元素上动态重求值,
37815
+ * **已渲染的旧代码块无需重挂载即可跟随**(0.9.0 分册 13,UX-01 根因修复)。
37816
+ * 色值本体由 theme.css 的 `--code-*`(浅/深两套)持有——单一来源。
37817
+ */
37818
+ function toVarTheme(theme) {
37819
+ const out = {};
37820
+ for (const [key, rule] of Object.entries(theme)) {
37821
+ const next = { ...rule };
37822
+ if (typeof next.color === "string") next.color = `var(${TOKEN_KEY.test(key) ? `--code-${key}` : "--code-base"})`;
37823
+ out[key] = next;
37824
+ }
37825
+ return out;
37826
+ }
37827
+ /**
37828
+ * 全站唯一的代码高亮主题对。引用相等是验收判据(AC-19.8),不导出到包外。
37829
+ * token 色值一律 `var(--code-*)`(CSS 变量,见 theme.css),不烘焙固定色值。
37830
+ */
37807
37831
  const PRISM_THEME = {
37808
- dark: one_dark_default,
37809
- light: one_light_default
37832
+ dark: toVarTheme(one_dark_default),
37833
+ light: toVarTheme(one_light_default)
37810
37834
  };
37835
+ /**
37836
+ * token → CSS 变量名(chat 侧用);与 theme.css 的 `--code-*` 一一对应。
37837
+ * 单一来源:Monaco 主题与 Prism 都从这里取变量名,不许手写(AC-G10)。
37838
+ * @stable
37839
+ */
37840
+ const CODE_TOKEN_VARS = Object.fromEntries(Object.keys(PRISM_THEME.light).filter((key) => TOKEN_KEY.test(key)).map((key) => [key, `--code-${key}`]));
37811
37841
 
37812
37842
  //#endregion
37813
37843
  //#region ../ui-kit/src/markdown/Markdown.tsx
@@ -37816,7 +37846,7 @@ function CodeBlock({ node: _node, className, children, ...rest }) {
37816
37846
  const language = resolveLanguage(/language-(\w+)/.exec(className ?? "")?.[1]);
37817
37847
  if (language) return /* @__PURE__ */ jsxs("div", {
37818
37848
  ref: themeRef,
37819
- className: "my-3 overflow-hidden rounded-card border border-border",
37849
+ className: "my-3 overflow-hidden rounded-card border border-border bg-card",
37820
37850
  children: [/* @__PURE__ */ jsx("div", {
37821
37851
  className: "border-b border-border bg-subtle px-3 py-1.5 font-mono text-xs text-muted",
37822
37852
  children: language
@@ -43548,16 +43578,25 @@ const STATUS_TONE_VAR = {
43548
43578
  * 外观配置的读写口(0.6.0 FR-20.1)。chatbot 与 console 共用它,因此两端看到的是
43549
43579
  * 同一份数据:谁写都经 `store.save`,再由 `store.subscribe` 广播回另一端。
43550
43580
  *
43581
+ * 0.9.0 分册 28 起是显式保存语义:`update` 只改内存 draft(视觉即时),
43582
+ * `save` 才写盘;`updateImmediate` 是快捷切换(非设置表单)的即时持久化出口。
43583
+ *
43551
43584
  * 无 `store` 时退回 0.5.x 的 props 受控行为,宿主不必立刻迁移。
43552
43585
  * @experimental
43553
43586
  */
43554
43587
  function useRuntimeAppearance(store, fallback) {
43555
43588
  const [appearance, setAppearance] = useState(fallback);
43589
+ const savedRef = useRef(fallback);
43590
+ const [dirty, setDirty] = useState(false);
43556
43591
  const fallbackRef = useRef(fallback);
43557
43592
  fallbackRef.current = fallback;
43593
+ const latestRef = useRef(appearance);
43594
+ latestRef.current = appearance;
43558
43595
  useEffect(() => {
43559
43596
  if (store) return;
43560
43597
  setAppearance(fallbackRef.current);
43598
+ savedRef.current = fallbackRef.current;
43599
+ setDirty(false);
43561
43600
  }, [
43562
43601
  store,
43563
43602
  fallback.theme,
@@ -43571,7 +43610,10 @@ function useRuntimeAppearance(store, fallback) {
43571
43610
  let cancelled = false;
43572
43611
  const reload = () => {
43573
43612
  merged.load().then((config) => {
43574
- if (!cancelled) setAppearance(config.appearance);
43613
+ if (cancelled) return;
43614
+ setAppearance(config.appearance);
43615
+ savedRef.current = config.appearance;
43616
+ setDirty(false);
43575
43617
  }).catch(() => void 0);
43576
43618
  };
43577
43619
  reload();
@@ -43581,6 +43623,23 @@ function useRuntimeAppearance(store, fallback) {
43581
43623
  unsubscribe?.();
43582
43624
  };
43583
43625
  }, [store]);
43626
+ /** 写盘;成功后才把该版本记为「已保存」,失败不破坏本地视觉(保存条保留,可重试) */
43627
+ const persist = useCallback(async (next) => {
43628
+ if (!store) return;
43629
+ const merged = withRuntimeConfigDefaults(store);
43630
+ try {
43631
+ const config = await merged.load();
43632
+ await merged.save({
43633
+ ...config,
43634
+ appearance: {
43635
+ ...config.appearance,
43636
+ ...next
43637
+ }
43638
+ });
43639
+ savedRef.current = next;
43640
+ setDirty(false);
43641
+ } catch {}
43642
+ }, [store]);
43584
43643
  return {
43585
43644
  appearance,
43586
43645
  update: useCallback((patch) => {
@@ -43588,16 +43647,24 @@ function useRuntimeAppearance(store, fallback) {
43588
43647
  ...current,
43589
43648
  ...patch
43590
43649
  }));
43591
- if (!store) return;
43592
- const merged = withRuntimeConfigDefaults(store);
43593
- merged.load().then((config) => merged.save({
43594
- ...config,
43595
- appearance: {
43596
- ...config.appearance,
43597
- ...patch
43598
- }
43599
- })).catch(() => void 0);
43600
- }, [store])
43650
+ setDirty(true);
43651
+ }, []),
43652
+ updateImmediate: useCallback((patch) => {
43653
+ const next = {
43654
+ ...latestRef.current,
43655
+ ...patch
43656
+ };
43657
+ setAppearance(next);
43658
+ persist(next);
43659
+ }, [persist]),
43660
+ save: useCallback(async () => {
43661
+ await persist(latestRef.current);
43662
+ }, [persist]),
43663
+ discard: useCallback(() => {
43664
+ setAppearance(savedRef.current);
43665
+ setDirty(false);
43666
+ }, []),
43667
+ dirty
43601
43668
  };
43602
43669
  }
43603
43670
 
@@ -43862,6 +43929,23 @@ function phaseNote(event) {
43862
43929
  }
43863
43930
  }
43864
43931
 
43932
+ //#endregion
43933
+ //#region src/core/title.ts
43934
+ /**
43935
+ * 会话标题按词边界截断(0.9.0 FR-18.1 / UX-11 / A-15):
43936
+ * 固定字符数处回退到最近空格,避免切出半个英文单词;
43937
+ * 找不到空格(如无空格长串)时按 Unicode 码点硬切。
43938
+ * 截断后标题里的词都是完整的——会话搜索按标题原文匹配,
43939
+ * 词边界截断保证开头关键词(如 `ORION-A`)不被切半、仍可命中。
43940
+ */
43941
+ function truncateTitle(text, max = 30) {
43942
+ const trimmed = text.trim();
43943
+ if (trimmed.length <= max) return trimmed;
43944
+ const cut = trimmed.slice(0, max);
43945
+ const lastSpace = Math.max(cut.lastIndexOf(" "), cut.lastIndexOf(" "));
43946
+ return (lastSpace > 0 ? cut.slice(0, lastSpace) : cut).trimEnd();
43947
+ }
43948
+
43865
43949
  //#endregion
43866
43950
  //#region src/core/subAgent.ts
43867
43951
  /**
@@ -44152,6 +44236,12 @@ var ChatEngine = class {
44152
44236
  * 终态消息只承载水位之后的内容,否则同一段正文与表单会出现两遍。
44153
44237
  */
44154
44238
  #flushed = /* @__PURE__ */ new Map();
44239
+ /**
44240
+ * runId → 该 run 的结果渲染请求(FR-12.1)。
44241
+ * `bridge.latestResult` 是全局单槽,多 run 并发时被后一个 run 覆盖;这里按 runId 缓存,
44242
+ * #finishRun 按 run 归属取 blocks,终态后清理。
44243
+ */
44244
+ #runBlocks = /* @__PURE__ */ new Map();
44155
44245
  /** 父 run 的预算消耗(委派时按「父级剩余」收紧子 agent 预算,FR-11.4) */
44156
44246
  #runStartedMs = 0;
44157
44247
  #runTurn = 0;
@@ -44168,7 +44258,10 @@ var ChatEngine = class {
44168
44258
  fs: adapter.storage
44169
44259
  });
44170
44260
  this.#renderer = options.renderer ?? "native";
44171
- this.bridge = new ReactBridgeState({ onSurfaceDraftChange: (runId, surfaceId, value) => this.#persistSurfaceDraft(runId, surfaceId, value) });
44261
+ this.bridge = new ReactBridgeState({
44262
+ onSurfaceDraftChange: (runId, surfaceId, value) => this.#persistSurfaceDraft(runId, surfaceId, value),
44263
+ onRenderResult: (runId, request) => this.#runBlocks.set(runId, request)
44264
+ });
44172
44265
  const accumulate = this.bridge.onTextDelta.bind(this.bridge);
44173
44266
  this.bridge.onTextDelta = (runId, delta) => {
44174
44267
  this.#emit({
@@ -44421,7 +44514,7 @@ var ChatEngine = class {
44421
44514
  if (text.trim() === "" && attachments.length === 0) return;
44422
44515
  const admitted = await this.#admitAttachments(attachments);
44423
44516
  const runtime = await this.#ensureReady();
44424
- const sessionId = this.#currentSessionId ?? (await this.createSession({ title: text.slice(0, 30) })).id;
44517
+ const sessionId = this.#currentSessionId ?? (await this.createSession({ title: truncateTitle(text) })).id;
44425
44518
  const session = await this.#requireSession(sessionId);
44426
44519
  const prompt = await this.#withAttachments(text, admitted.accepted);
44427
44520
  const userMessage = {
@@ -44434,7 +44527,7 @@ var ChatEngine = class {
44434
44527
  };
44435
44528
  await this.#sessions.appendMessages(sessionId, [userMessage]);
44436
44529
  if (!session.title && !session.titleLocked) {
44437
- await this.#sessions.setTitle(sessionId, text.slice(0, 30));
44530
+ await this.#sessions.setTitle(sessionId, truncateTitle(text));
44438
44531
  this.#emit({
44439
44532
  type: "session-updated",
44440
44533
  session: toSessionMeta(await this.#requireSession(sessionId))
@@ -44774,13 +44867,17 @@ var ChatEngine = class {
44774
44867
  };
44775
44868
  }
44776
44869
  /**
44777
- * 浏览器内置模型就绪时自动进模型列表(FR-14.1)。
44778
- * 探测失败静默:这是「少一个可选项」,不是配置错误,弹错只会吓到用不到它的人。
44870
+ * 浏览器内置模型自动进模型列表(FR-14.1,B4-01/FR-27.2 调整)。
44871
+ * **只要浏览器支持 Prompt API**(含「模型未就绪」model-not-ready)就加入条目;
44872
+ * API 完全不存在(api-missing / unsupported-browser)才不加——不可用只在列表里
44873
+ * 呈现状态(Unavailable + 原因),不再以「探测成功」为加入门控(否则未就绪时
44874
+ * 条目不出现,用户无从知道为什么没有它)。
44779
44875
  */
44780
44876
  async #autoAddChromeBuiltin(rc) {
44781
44877
  const store = this.#runtimeConfigStore();
44782
44878
  if (rc === void 0 || store === void 0 || !shouldAutoAddChromeBuiltin(rc)) return rc;
44783
- if ((await probeChromeBuiltinAvailability().catch(() => void 0))?.available !== true) return rc;
44879
+ const availability = await probeChromeBuiltinAvailability().catch(() => void 0);
44880
+ if (!(availability?.available === true || availability?.available === false && availability.reason === "model-not-ready")) return rc;
44784
44881
  const next = {
44785
44882
  ...rc,
44786
44883
  llm: {
@@ -44945,7 +45042,7 @@ var ChatEngine = class {
44945
45042
  const toolCalls = summarizeToolCalls(run);
44946
45043
  const flushed = this.#flushed.get(run.id);
44947
45044
  const text = (this.bridge.streamingRunId === run.id ? this.bridge.streamingText : "").slice(flushed?.text ?? 0);
44948
- const latest = this.bridge.latestResult;
45045
+ const latest = this.#runBlocks.get(run.id);
44949
45046
  const blocks = latest && latest.runId === run.id ? latest.blocks.filter((_, index) => index !== latest.outputBlockIndex) : void 0;
44950
45047
  const surfaces = this.bridge.surfaceSnapshots.filter((snapshot) => snapshot.runId === run.id && !flushed?.surfaces.has(snapshot.id));
44951
45048
  const todos = this.#runTodos.get(run.id);
@@ -44969,6 +45066,20 @@ var ChatEngine = class {
44969
45066
  ...this.#activeModelId !== void 0 ? { model: this.#activeModelId } : {}
44970
45067
  };
44971
45068
  await this.#sessions.appendMessages(sessionId, [assistantMessage]);
45069
+ if (blocks && blocks.length > 0 || surfaces.length > 0) {
45070
+ const snapshotPath = `${this.#chatRoot}/snapshots/${run.id}.blocks.json`;
45071
+ await atomicWriteText(this.#adapter.storage, snapshotPath, JSON.stringify({
45072
+ runId: run.id,
45073
+ sessionId,
45074
+ status,
45075
+ blocks: blocks ?? [],
45076
+ surfaces: surfaces.map((s) => ({
45077
+ id: s.id,
45078
+ runId: s.runId
45079
+ })),
45080
+ snapshotAt: (/* @__PURE__ */ new Date()).toISOString()
45081
+ }, null, 2)).catch(() => void 0);
45082
+ }
44972
45083
  await this.#traceStore.put(run);
44973
45084
  this.#emit({
44974
45085
  type: "run-completed",
@@ -44988,6 +45099,7 @@ var ChatEngine = class {
44988
45099
  this.#runCandidates.delete(run.id);
44989
45100
  this.#runPhases.delete(run.id);
44990
45101
  this.#flushed.delete(run.id);
45102
+ this.#runBlocks.delete(run.id);
44991
45103
  }
44992
45104
  /**
44993
45105
  * 交互提交后把「到此为止」的助手内容切成一条消息(FR-17.8)。
@@ -45014,6 +45126,8 @@ var ChatEngine = class {
45014
45126
  request,
45015
45127
  ...response.value === void 0 ? {} : { submittedValues: redactInteractionValues(response.value, request) }
45016
45128
  };
45129
+ const latestBlocks = this.#runBlocks.get(runId);
45130
+ const blocks = latestBlocks && latestBlocks.runId === runId ? latestBlocks.blocks.filter((_, index) => index !== latestBlocks.outputBlockIndex) : void 0;
45017
45131
  const message = {
45018
45132
  id: newId("msg"),
45019
45133
  role: "assistant",
@@ -45021,6 +45135,7 @@ var ChatEngine = class {
45021
45135
  ts: (/* @__PURE__ */ new Date()).toISOString(),
45022
45136
  runId,
45023
45137
  interactions: [record],
45138
+ ...blocks && blocks.length > 0 ? { blocks } : {},
45024
45139
  ...surfaces.length > 0 ? { surfaces } : {},
45025
45140
  ...phases.length > 0 ? { phases } : {},
45026
45141
  ...this.#activeModelId !== void 0 ? { model: this.#activeModelId } : {}
@@ -45230,6 +45345,7 @@ const chatbotDictionary = defineDictionary({
45230
45345
  "result.artifacts": "Artifacts",
45231
45346
  "result.download": "Download",
45232
45347
  "result.file": "File",
45348
+ "result.block.unknown": "Unsupported result block: {type}.",
45233
45349
  "status.aria": "Run status",
45234
45350
  "status.route": "Routing",
45235
45351
  "status.activate": "Activating skill",
@@ -45550,6 +45666,7 @@ const chatbotDictionary = defineDictionary({
45550
45666
  "result.artifacts": "产物",
45551
45667
  "result.download": "下载",
45552
45668
  "result.file": "文件",
45669
+ "result.block.unknown": "不支持的结果块:{type}。",
45553
45670
  "status.aria": "运行状态",
45554
45671
  "status.route": "路由中",
45555
45672
  "status.activate": "激活技能",
@@ -46695,6 +46812,16 @@ function AssistantUiEmptyState({ title, prompts, disabled, onPrompt }) {
46695
46812
  });
46696
46813
  }
46697
46814
 
46815
+ //#endregion
46816
+ //#region src/react/format.ts
46817
+ /** 字节数 → 展示文本;压缩前后要放在同一行才看得出效果。
46818
+ * 单一来源(AC-G10):Composer 附件与消息附件芯片共用。 */
46819
+ function formatBytes(bytes) {
46820
+ if (bytes < 1024) return `${bytes} B`;
46821
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
46822
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
46823
+ }
46824
+
46698
46825
  //#endregion
46699
46826
  //#region src/react/InteractionWaitBar.tsx
46700
46827
  /** composer 的 `aria-describedby` 指向这里:禁用原因必须能被读出来(FR-21.3) */
@@ -46844,12 +46971,6 @@ function CAPABILITY_ICON_CLASS(supported, interactive) {
46844
46971
  const base = `inline-flex size-8 shrink-0 items-center justify-center rounded-full ${supported ? "text-ink" : "text-muted"}`;
46845
46972
  return interactive ? `${base} transition-colors hover:bg-accent hover:text-ink disabled:cursor-not-allowed disabled:hover:bg-transparent` : base;
46846
46973
  }
46847
- /** 字节数 → 展示文本;压缩前后要放在同一行才看得出效果 */
46848
- function formatBytes(bytes) {
46849
- if (bytes < 1024) return `${bytes} B`;
46850
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
46851
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
46852
- }
46853
46974
  function AttachmentMeta({ info }) {
46854
46975
  const t = useT();
46855
46976
  if (!info) return null;
@@ -46882,9 +47003,9 @@ function ModelMenuButton({ models }) {
46882
47003
  children: /* @__PURE__ */ jsxs("button", {
46883
47004
  type: "button",
46884
47005
  "data-testid": "composer-model",
46885
- className: "inline-flex h-8 max-w-48 shrink-0 cursor-pointer items-center gap-1 rounded-full px-2.5 text-sm text-muted transition-colors hover:bg-accent hover:text-ink",
47006
+ className: "inline-flex max-w-full shrink-0 cursor-pointer items-center gap-1 rounded-full px-2.5 py-1 text-sm text-muted transition-colors hover:bg-accent hover:text-ink",
46886
47007
  children: [/* @__PURE__ */ jsx("span", {
46887
- className: "truncate",
47008
+ className: "min-w-0 break-words",
46888
47009
  children: current?.label ?? t("composer.model")
46889
47010
  }), /* @__PURE__ */ jsx(ChevronDown, {
46890
47011
  className: "size-3.5 shrink-0",
@@ -47307,7 +47428,7 @@ function TraceRunId({ runId }) {
47307
47428
  const t = useT();
47308
47429
  if (typeof runId !== "string" || runId === "") return null;
47309
47430
  return /* @__PURE__ */ jsxs("div", {
47310
- className: "flex items-center gap-1.5 font-mono text-xs text-muted",
47431
+ className: "grid grid-cols-[auto_auto_auto] items-center gap-1.5 font-mono text-xs text-muted",
47311
47432
  "data-testid": "trace-run-id",
47312
47433
  children: [
47313
47434
  /* @__PURE__ */ jsx("span", {
@@ -47315,11 +47436,11 @@ function TraceRunId({ runId }) {
47315
47436
  children: t("trace.runId")
47316
47437
  }),
47317
47438
  /* @__PURE__ */ jsx("span", {
47318
- className: "truncate",
47439
+ className: "min-w-0 truncate",
47319
47440
  children: runId
47320
47441
  }),
47321
47442
  /* @__PURE__ */ jsx(CopyButton, {
47322
- className: "ms-auto",
47443
+ className: "shrink-0",
47323
47444
  value: runId,
47324
47445
  label: t("trace.runId.copy"),
47325
47446
  copiedLabel: t("trace.runId.copied")
@@ -47365,24 +47486,26 @@ function LifecycleStep({ data }) {
47365
47486
  const tone = status !== void 0 ? runStatusTone(status) : endedAt === void 0 ? "info" : "neutral";
47366
47487
  return /* @__PURE__ */ jsxs("li", {
47367
47488
  "aria-label": duration ? `${label}, ${duration}` : label,
47368
- className: "flex items-center gap-2 font-mono text-xs text-muted",
47489
+ className: "grid grid-cols-[auto_auto_auto] items-center gap-2 font-mono text-xs text-muted",
47369
47490
  children: [
47370
- /* @__PURE__ */ jsx("span", {
47371
- className: "size-1.5 shrink-0 rounded-full",
47372
- style: { backgroundColor: `var(${STATUS_TONE_VAR[tone]})` },
47373
- "data-webskill-step-tone": tone,
47374
- "aria-hidden": true
47375
- }),
47376
- /* @__PURE__ */ jsx("span", {
47377
- className: "min-w-20 text-ink",
47378
- children: label
47491
+ /* @__PURE__ */ jsxs("span", {
47492
+ className: "flex min-w-0 items-center gap-2",
47493
+ children: [/* @__PURE__ */ jsx("span", {
47494
+ className: "size-1.5 shrink-0 rounded-full",
47495
+ style: { backgroundColor: `var(${STATUS_TONE_VAR[tone]})` },
47496
+ "data-webskill-step-tone": tone,
47497
+ "aria-hidden": true
47498
+ }), /* @__PURE__ */ jsx("span", {
47499
+ className: "min-w-20 text-ink",
47500
+ children: label
47501
+ })]
47379
47502
  }),
47380
47503
  note ? /* @__PURE__ */ jsx("span", {
47381
- className: "truncate",
47504
+ className: "min-w-0 truncate",
47382
47505
  children: note
47383
- }) : null,
47506
+ }) : /* @__PURE__ */ jsx("span", { "aria-hidden": true }),
47384
47507
  /* @__PURE__ */ jsx("span", {
47385
- className: "ms-auto shrink-0",
47508
+ className: "shrink-0",
47386
47509
  children: duration ?? ""
47387
47510
  })
47388
47511
  ]
@@ -47452,13 +47575,13 @@ function ToolGroup({ count, running, children }) {
47452
47575
  }), children]
47453
47576
  });
47454
47577
  }
47455
- /** 被 `allowed-tools` 拦下时 runtime 回喂的错误码;与执行失败必须分开显示(需求 1 FR-1.6) */
47456
- const DENIED_CODE = "TOOL_NOT_ALLOWED";
47457
47578
  /** 错误码 → 标题文案;未列出的码走通用「失败原因」(0.6.0 FR-18.2) */
47458
47579
  const ERROR_TITLE_KEYS = {
47459
- [DENIED_CODE]: "tool.denied.reason",
47580
+ ["TOOL_NOT_ALLOWED"]: "tool.denied.reason",
47460
47581
  TOOL_NOT_FOUND: "tool.notFound.reason",
47461
- TOOL_RESOLUTION_EXHAUSTED: "tool.exhausted.reason"
47582
+ TOOL_RESOLUTION_EXHAUSTED: "tool.exhausted.reason",
47583
+ NETWORK_BLOCKED: "tool.denied.reason",
47584
+ FS_PERMISSION_DENIED: "tool.denied.reason"
47462
47585
  };
47463
47586
  function toolErrorOf(part) {
47464
47587
  const result = part.result;
@@ -47479,7 +47602,7 @@ function ToolRow({ part }) {
47479
47602
  useEffect(() => {
47480
47603
  if (part.isError === true) setOpen(true);
47481
47604
  }, [part.isError]);
47482
- const denied = part.isError === true && errorCode === DENIED_CODE;
47605
+ const denied = part.isError === true && typeof errorCode === "string" && POLICY_DENIAL_CODES.has(errorCode);
47483
47606
  const status = running ? "running" : denied ? "denied" : part.isError === true ? "failed" : "completed";
47484
47607
  const duration = part.timing && completedAt !== void 0 ? formatDuration(completedAt - part.timing.startedAt) : void 0;
47485
47608
  return /* @__PURE__ */ jsxs("div", {
@@ -47524,17 +47647,17 @@ function ToolRow({ part }) {
47524
47647
  }), open && (part.argsText !== "" || errorMessage !== void 0) ? /* @__PURE__ */ jsxs("div", {
47525
47648
  className: "border-t border-border px-2 py-1.5",
47526
47649
  children: [errorMessage !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", {
47527
- className: "text-xs text-muted",
47650
+ className: "text-xs text-muted italic",
47528
47651
  children: t(errorCode !== void 0 ? ERROR_TITLE_KEYS[errorCode] ?? "tool.failed.reason" : "tool.failed.reason")
47529
47652
  }), /* @__PURE__ */ jsx("p", {
47530
47653
  "data-testid": "tool-error-reason",
47531
47654
  className: "text-xs break-words text-ink",
47532
47655
  children: errorMessage
47533
47656
  })] }) : null, part.argsText !== "" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", {
47534
- className: "text-xs text-muted",
47657
+ className: "text-xs text-muted italic",
47535
47658
  children: t("tool.args")
47536
47659
  }), /* @__PURE__ */ jsx("pre", {
47537
- className: "overflow-x-auto font-mono text-xs text-ink",
47660
+ className: "overflow-x-auto whitespace-pre font-mono text-xs text-ink",
47538
47661
  children: part.argsText
47539
47662
  })] }) : null]
47540
47663
  }) : null]
@@ -47758,6 +47881,40 @@ function TodoPanel({ items }) {
47758
47881
  });
47759
47882
  }
47760
47883
 
47884
+ //#endregion
47885
+ //#region src/react/UserAttachmentChips.tsx
47886
+ /**
47887
+ * 用户消息气泡上方的附件芯片(0.9.0 分册 12,FR-12.2)。
47888
+ * 数据源是持久化的 `ChatMessage.attachments`(旧会话文件无该字段时缺省不渲染)。
47889
+ * 纯 props 组件:不依赖 assistant-ui 上下文,便于单测(AC-12.3)。
47890
+ */
47891
+ function UserAttachmentChips({ attachments }) {
47892
+ if (!attachments || attachments.length === 0) return null;
47893
+ return /* @__PURE__ */ jsx("div", {
47894
+ "data-testid": "user-attachments",
47895
+ className: "mb-1 flex flex-col items-end gap-1",
47896
+ children: attachments.map((att) => /* @__PURE__ */ jsxs("span", {
47897
+ "data-testid": "user-attachment-chip",
47898
+ "aria-label": `${att.name}, ${formatBytes(att.size)}`,
47899
+ className: "inline-flex max-w-full items-center gap-1.5 rounded-md border border-border bg-surface px-2 py-1 text-xs text-ink",
47900
+ children: [
47901
+ /* @__PURE__ */ jsx(File, {
47902
+ className: "size-3.5 shrink-0 text-muted",
47903
+ "aria-hidden": true
47904
+ }),
47905
+ /* @__PURE__ */ jsx("span", {
47906
+ className: "truncate font-medium",
47907
+ children: att.name
47908
+ }),
47909
+ /* @__PURE__ */ jsx("span", {
47910
+ className: "shrink-0 text-muted",
47911
+ children: formatBytes(att.size)
47912
+ })
47913
+ ]
47914
+ }, att.id))
47915
+ });
47916
+ }
47917
+
47761
47918
  //#endregion
47762
47919
  //#region src/react/OpenUiSurfaceHost.tsx
47763
47920
  /** Renders persisted UI spec snapshots through the OpenUI extension without a live bridge. @experimental */
@@ -57859,6 +58016,7 @@ function TableBlock({ columns, rows }) {
57859
58016
  });
57860
58017
  }
57861
58018
  function Block({ block, runId, onDownload }) {
58019
+ const t = useT();
57862
58020
  switch (block.type) {
57863
58021
  case "markdown": return /* @__PURE__ */ jsx("div", {
57864
58022
  className: "rounded-card border border-border bg-surface px-4 py-3 shadow-card",
@@ -57885,6 +58043,10 @@ function Block({ block, runId, onDownload }) {
57885
58043
  },
57886
58044
  ...onDownload ? { onDownload } : {}
57887
58045
  });
58046
+ default: return /* @__PURE__ */ jsx("div", {
58047
+ className: "rounded-card border border-border bg-surface px-4 py-3 text-sm text-muted",
58048
+ children: t("result.block.unknown", { type: String(block.type ?? "unknown") })
58049
+ });
57888
58050
  }
57889
58051
  }
57890
58052
  /**
@@ -58087,7 +58249,7 @@ function AssistantUiMessageActions({ message, onRetry, onDelete, onViewTrace, mo
58087
58249
  return /* @__PURE__ */ jsxs("div", {
58088
58250
  "data-testid": "assistant-ui-message-actions",
58089
58251
  "aria-label": t("a11y.messageActions"),
58090
- className: "pointer-events-none absolute inset-x-0 -bottom-7 flex h-7 items-center",
58252
+ className: "pointer-events-none absolute inset-x-0 -bottom-9 flex h-7 items-center",
58091
58253
  children: [/* @__PURE__ */ jsxs(ActionBarPrimitive.Root, {
58092
58254
  hideWhenRunning: true,
58093
58255
  autohide: "always",
@@ -58159,12 +58321,12 @@ function AssistantUiMessage(props) {
58159
58321
  const todos = useAuiState((s) => s.message.metadata?.custom?.["todos"]);
58160
58322
  const isEditing = useAuiState((s) => s.composer.isEditing);
58161
58323
  if (role === "user") return /* @__PURE__ */ jsxs("div", {
58162
- className: "webskill-aui-message webskill-aui-message--user relative mb-7",
58324
+ className: "webskill-aui-message webskill-aui-message--user relative mb-9",
58163
58325
  "data-testid": "assistant-ui-user-message",
58164
58326
  children: [
58165
58327
  isEditing ? /* @__PURE__ */ jsxs(ComposerPrimitive.Root, {
58166
58328
  "data-testid": "message-edit-composer",
58167
- className: "flex flex-col gap-2 rounded-card border border-border bg-surface p-2",
58329
+ className: "flex w-[80%] flex-col gap-2 rounded-card border border-border bg-surface p-2",
58168
58330
  children: [/* @__PURE__ */ jsx(ComposerPrimitive.Input, {
58169
58331
  autoFocus: true,
58170
58332
  className: "min-h-16 w-full resize-none bg-transparent text-sm text-ink outline-none"
@@ -58180,10 +58342,10 @@ function AssistantUiMessage(props) {
58180
58342
  children: t("message.editSend")
58181
58343
  })]
58182
58344
  })]
58183
- }) : /* @__PURE__ */ jsx("div", {
58345
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(UserAttachmentChips, { attachments: message?.attachments }), /* @__PURE__ */ jsx("div", {
58184
58346
  className: "webskill-aui-user-bubble",
58185
58347
  children: message?.text ?? ""
58186
- }),
58348
+ })] }),
58187
58349
  message?.notice ? /* @__PURE__ */ jsx("p", {
58188
58350
  "data-testid": "message-notice",
58189
58351
  className: "mt-1 text-right text-xs text-muted",
@@ -58193,7 +58355,7 @@ function AssistantUiMessage(props) {
58193
58355
  ]
58194
58356
  });
58195
58357
  return /* @__PURE__ */ jsxs("div", {
58196
- className: "webskill-aui-message webskill-aui-message--assistant relative mb-7 flex min-w-0 flex-col gap-3",
58358
+ className: "webskill-aui-message webskill-aui-message--assistant relative mb-9 flex min-w-0 flex-col gap-3",
58197
58359
  "data-testid": "assistant-ui-assistant-message",
58198
58360
  children: [
58199
58361
  Array.isArray(todos) ? /* @__PURE__ */ jsx(TodoPanel, { items: todos }) : null,
@@ -59167,7 +59329,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
59167
59329
  * `theme` / `locale` / `renderer` / `dictationLang` 这几个 props 只作为它缺席时的兜底。
59168
59330
  */
59169
59331
  const configStore = config?.runtimeConfig;
59170
- const { appearance, update: updateAppearance } = useRuntimeAppearance(configStore, useMemo(() => ({
59332
+ const { appearance, updateImmediate: updateAppearanceImmediate } = useRuntimeAppearance(configStore, useMemo(() => ({
59171
59333
  theme: themeProp ?? "light",
59172
59334
  locale: localeProp ?? "en",
59173
59335
  renderer: rendererProp ?? "native",
@@ -59427,12 +59589,32 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
59427
59589
  onError: (e) => setError({ message: toErrorMessage(e) })
59428
59590
  });
59429
59591
  const flushPendingDeletes = pendingDeletes.flush;
59592
+ /**
59593
+ * 会话创建串行化(0.9.0 复核:AC-21.16 全量偶发红的根因)。
59594
+ * `handleCreate`(点 New chat)与 `handleSend`(无当前会话时兜底建会话)并发时,
59595
+ * 各调一次 `engine.createSession()` 会建出**两个**会话——消息进后建的那个,
59596
+ * 先建的变成永久空会话(列表第一行永远显示兜底文案)。
59597
+ *
59598
+ * 这里用 ref 共享同一个 in-flight promise:谁先发起谁创建,其它调用方拿到同一个会话。
59599
+ * 注意 ref 的生命周期:**不能**在 promise settle 时立刻清空——「handleCreate 建完、
59600
+ * 但 React 还没因 setCurrentSessionId 重渲染」的窗口里,handleSend 兜底仍要复用这个会话,
59601
+ * 立刻清空会在这个窗口里再建一个(第一版修复在全量 e2e 里实测仍红)。
59602
+ * 清空交给下面的 useEffect:一旦 UI 真正选中了某个会话,最近一次创建就已被消费,ref 让位。
59603
+ */
59604
+ const creatingRef = useRef(void 0);
59605
+ const ensureSession = useCallback(async () => {
59606
+ if (!creatingRef.current) creatingRef.current = engine.createSession();
59607
+ return creatingRef.current;
59608
+ }, [engine]);
59609
+ useEffect(() => {
59610
+ if (creatingRef.current !== void 0) creatingRef.current = void 0;
59611
+ }, [currentSessionId]);
59430
59612
  const handleSend = useCallback((text, attachments = []) => {
59431
59613
  (async () => {
59432
59614
  await flushPendingDeletes();
59433
59615
  let sessionId = currentSessionId;
59434
59616
  if (!sessionId) {
59435
- const meta = await engine.createSession();
59617
+ const meta = await ensureSession();
59436
59618
  sessionId = meta.id;
59437
59619
  setCurrentSessionId(meta.id);
59438
59620
  await refreshSessions();
@@ -59445,7 +59627,8 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
59445
59627
  engine,
59446
59628
  currentSessionId,
59447
59629
  refreshSessions,
59448
- flushPendingDeletes
59630
+ flushPendingDeletes,
59631
+ ensureSession
59449
59632
  ]);
59450
59633
  const handleStop = useCallback(() => {
59451
59634
  if (currentRunId) engine.cancel(currentRunId);
@@ -59524,7 +59707,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
59524
59707
  loadingEarlierSessions
59525
59708
  ]);
59526
59709
  const handleCreate = useCallback(() => {
59527
- flushPendingDeletes().then(() => engine.createSession()).then(async (meta) => {
59710
+ flushPendingDeletes().then(() => ensureSession()).then(async (meta) => {
59528
59711
  setCurrentSessionId(meta.id);
59529
59712
  setMessages([]);
59530
59713
  setMessagesCursor(void 0);
@@ -59534,7 +59717,8 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
59534
59717
  }, [
59535
59718
  engine,
59536
59719
  refreshSessions,
59537
- flushPendingDeletes
59720
+ flushPendingDeletes,
59721
+ ensureSession
59538
59722
  ]);
59539
59723
  const handleRenameSession = useCallback((id, title) => {
59540
59724
  flushPendingDeletes().then(() => engine.renameSession(id, title)).then(refreshSessions).catch((e) => setError({ message: toErrorMessage(e) }));
@@ -59572,9 +59756,9 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
59572
59756
  flushPendingDeletes
59573
59757
  ]);
59574
59758
  const handleAppearanceChange = useCallback((next) => {
59575
- updateAppearance(next);
59759
+ updateAppearanceImmediate(next);
59576
59760
  onAppearanceChange?.(next);
59577
- }, [onAppearanceChange, updateAppearance]);
59761
+ }, [onAppearanceChange, updateAppearanceImmediate]);
59578
59762
  /** 画像读写全部经引擎(需求 19):设置面板不直接碰存储,加密与作用域只有一处实现 */
59579
59763
  const chatRoot = (config?.chatRoot ?? "/chat").replace(/\/+$/, "");
59580
59764
  /**
@@ -59647,7 +59831,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
59647
59831
  onOpenSessions: () => setDrawerOpen(true),
59648
59832
  onCreateSession: handleCreate,
59649
59833
  onOpenConsole: () => adapter.navigation.openConsole(),
59650
- ...onOpenSettings ? { onOpenSettings: () => onOpenSettings("settings.runtime") } : {},
59834
+ ...onOpenSettings ? { onOpenSettings: () => onOpenSettings() } : {},
59651
59835
  theme,
59652
59836
  onToggleTheme: () => handleAppearanceChange({ theme: theme === "dark" ? "light" : "dark" })
59653
59837
  }),
@@ -59829,4 +60013,13 @@ function VercelPayloadPreview({ bridge }) {
59829
60013
  }
59830
60014
 
59831
60015
  //#endregion
59832
- export { A2uiSurfaceHost, A2uiSurfaceSnapshotHost, ChatEngine, Chatbot, CompositeUiBridge, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, ErrorCard, InteractionCard, InterruptedBanner, OpenUiSurfaceHost, OpenUiSurfaceSnapshotHost, ResultBlockList, ResultBlocksPro, SkillBadges, SpecInteraction, VercelPayloadPreview, VercelSurfaceHost, VercelSurfaceSnapshotHost, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };
60016
+ //#region src/version.ts
60017
+ /** Generated by scripts/syncVersionConstant.mjs from packages/chatbot/package.json. Do not edit by hand. */
60018
+ /**
60019
+ * Version of the published `@webskill/chatbot` package, injected at build time.
60020
+ * @stable
60021
+ */
60022
+ const CHATBOT_VERSION = "0.6.0";
60023
+
60024
+ //#endregion
60025
+ export { A2uiSurfaceHost, A2uiSurfaceSnapshotHost, CHATBOT_VERSION, ChatEngine, Chatbot, CompositeUiBridge, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, ErrorCard, InteractionCard, InterruptedBanner, OpenUiSurfaceHost, OpenUiSurfaceSnapshotHost, ResultBlockList, ResultBlocksPro, SkillBadges, SpecInteraction, VercelPayloadPreview, VercelSurfaceHost, VercelSurfaceSnapshotHost, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webskill/chatbot",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -26,7 +26,7 @@
26
26
  "@a2ui/web_core": "^0.10.5",
27
27
  "@lit/context": "^1.1.0",
28
28
  "@openuidev/react-lang": "0.2.8",
29
- "@webskill/sdk": "^0.8.0",
29
+ "@webskill/sdk": "^0.9.0",
30
30
  "react": ">=19.0.0",
31
31
  "react-dom": ">=19.0.0",
32
32
  "zod": "^3.25.0 || ^4.0.0"