@michengai/dsh-btw 0.1.22 → 0.1.24

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/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## [0.1.24] - 2026-10-15
4
+
5
+ - Side questions are no longer offered in a subagent session. Typing `/btw` there explains that you should return to the main session, instead of leaving a dialog that cannot be closed (#7).
6
+ - Closing a side question that the host rejects because the session is owned by subagent routing no longer deletes an answer that is still on the way or already returned. A request that never started, or was cancelled before it produced an answer, can still be dismissed locally.
7
+
8
+ ## [0.1.23] - 2026-10-15
9
+
10
+ - Relax hardcoded timeout: Replace the strict 90s timer with a 30-minute watchdog, supporting long chain-of-thought and max-reasoning models while safeguarding concurrency slots against stalled connections (#6).
11
+ - Prominent stop button during answering: Display a dedicated stop button with alert styling during generation, allowing users to manually abort reasoning and release subagent resources immediately (#6).
12
+ - Smart auto-collapse for older side-question bubbles: Automatically collapse older bubbles when new side questions arrive, keeping the input area neat while preserving manual expansion controls and robust focus recovery.
13
+ - Seamless dual-generation host compatibility: Retain strict typings for DSH 0.2.1-alpha.3+ `startActivation` while providing a robust `subagents.start` fallback for DSH 0.2.0-rc.2 and earlier.
4
14
 
5
15
  ## [0.1.22] - 2026-10-15
6
16
 
@@ -1,6 +1,16 @@
1
1
  # 更新日志
2
2
 
3
- ## 未发布
3
+ ## [0.1.24] - 2026-10-15
4
+
5
+ - 子代理会话不再提供旁问。在那里输入 `/btw` 会提示返回主会话使用,而不是留下关不掉的弹窗 (#7)。
6
+ - 宿主因子代理路由拒绝关闭时,不再删掉还在返回或已经返回的答案。尚未启动,或在产生答案前被取消的请求,仍可在本地关掉。
7
+
8
+ ## [0.1.23] - 2026-10-15
9
+
10
+ - 放宽硬超时限制:默认超时由写死的 90s 调整为 30 分钟兜底看门狗,支持长思维链与深度推理模型充分思考;同时保留长连接死锁自动回收机制保护并发槽位 (#6)。
11
+ - 回答中提供醒目的「停止回答」按钮:在旁问处于生成阶段展示独立停止图标与警示色悬浮高亮,用户可随时手动终止推理并释放子代理 (#6)。
12
+ - 优化旁问气泡自动折叠交互:连续提出新旁问时旧气泡自动折叠、最新气泡保持展开,避免撑大输入区;支持用户手动自由切换折叠/展开状态,新气泡到达后稳定重新夺回展开焦点。
13
+ - 完善双代宿主自适应兼容:主路径保留 DSH 0.2.1-alpha.3+ 的强类型 `startActivation`,并为 0.2.0-rc.2 及更早宿主恢复严谨的 `subagents.start` 传统调用回退分支。
4
14
 
5
15
  ## [0.1.22] - 2026-10-15
6
16
 
package/lib/client.js CHANGED
@@ -467,6 +467,11 @@ var MessageCircle = createLucideIcon("MessageCircle", [
467
467
  ["path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z", key: "vv11sd" }]
468
468
  ]);
469
469
 
470
+ // node_modules/lucide-react/dist/esm/icons/square.js
471
+ var Square = createLucideIcon("Square", [
472
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
473
+ ]);
474
+
470
475
  // node_modules/lucide-react/dist/esm/icons/x.js
471
476
  var X = createLucideIcon("X", [
472
477
  ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
@@ -503,6 +508,7 @@ var zh = {
503
508
  "action.collapse": "\u6298\u53E0\u56DE\u7B54",
504
509
  "action.close": "\u5173\u95ED\u65C1\u95EE",
505
510
  "action.retryClose": "\u91CD\u8BD5\u5173\u95ED",
511
+ "action.stop": "\u505C\u6B62\u56DE\u7B54",
506
512
  "status.answering": "\u6B63\u5728\u56DE\u7B54\u2026",
507
513
  "status.closing": "\u6B63\u5728\u5173\u95ED\u2026",
508
514
  "command.hint": "\u65C1\u95EE\u5185\u5BB9",
@@ -513,6 +519,7 @@ var zh = {
513
519
  "error.attachments": "\u65C1\u95EE\u53EA\u63A5\u53D7\u6587\u5B57\uFF0C\u56FE\u7247\u548C\u6587\u4EF6\u4ECD\u4FDD\u7559\u5728\u8F93\u5165\u6846\u4E2D\u3002",
514
520
  "error.removeAttachments": "\u65C1\u95EE\u53EA\u63A5\u53D7\u6587\u5B57\uFF0C\u8BF7\u5148\u79FB\u9664\u56FE\u7247\u548C\u6587\u4EF6\u3002",
515
521
  "error.backend": "BTW \u540E\u7AEF\u5C1A\u672A\u542F\u7528\uFF0C\u8BF7\u68C0\u67E5\u63D2\u4EF6\u914D\u7F6E\u3002",
522
+ "error.subagent": "\u5B50\u4EE3\u7406\u4F1A\u8BDD\u6682\u4E0D\u652F\u6301\u65C1\u95EE\uFF0C\u8BF7\u8FD4\u56DE\u4E3B\u4F1A\u8BDD\u4F7F\u7528 /btw\u3002",
516
523
  "error.empty": "\u8BF7\u8F93\u5165\u65C1\u95EE\u5185\u5BB9\u3002",
517
524
  "error.length": `\u65C1\u95EE\u6700\u591A\u652F\u6301 ${MAX_QUESTION_LENGTH} \u4E2A\u5B57\u7B26\u3002`,
518
525
  "error.capacity": "\u6B63\u5728\u5904\u7406\u7684\u65C1\u95EE\u8F83\u591A\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
@@ -555,6 +562,7 @@ var en = {
555
562
  "action.collapse": "Collapse answer",
556
563
  "action.close": "Close side question",
557
564
  "action.retryClose": "Retry closing",
565
+ "action.stop": "Stop answering",
558
566
  "status.answering": "Answering\u2026",
559
567
  "status.closing": "Closing\u2026",
560
568
  "command.hint": "Side question",
@@ -565,6 +573,7 @@ var en = {
565
573
  "error.attachments": "Side questions accept text only. Your images and files remain in the input.",
566
574
  "error.removeAttachments": "Side questions accept text only. Remove the images and files first.",
567
575
  "error.backend": "The BTW backend is unavailable. Check the plugin configuration.",
576
+ "error.subagent": "Side questions are not supported in subagent sessions. Use /btw in the main session.",
568
577
  "error.empty": "Enter a side question.",
569
578
  "error.length": `Side questions are limited to ${MAX_QUESTION_LENGTH.toLocaleString("en-US")} characters.`,
570
579
  "error.capacity": "Too many side questions are running. Try again shortly.",
@@ -597,6 +606,8 @@ var CLOSE_COMMAND = "btw-close";
597
606
 
598
607
  // src/client/bubbles.ts
599
608
  var MAX_BUBBLES_PER_SESSION = 20;
609
+ var SideRequestNotStartedError = class extends Error {
610
+ };
600
611
  var BubbleStore = class {
601
612
  constructor(transport, t = translate()) {
602
613
  this.transport = transport;
@@ -606,6 +617,9 @@ var BubbleStore = class {
606
617
  listeners = /* @__PURE__ */ new Set();
607
618
  active = /* @__PURE__ */ new Map();
608
619
  closing = /* @__PURE__ */ new Map();
620
+ notStarted = /* @__PURE__ */ new Set();
621
+ runEnd = /* @__PURE__ */ new Map();
622
+ closeFenced = /* @__PURE__ */ new Set();
609
623
  admitting = /* @__PURE__ */ new Set();
610
624
  disposed = false;
611
625
  getSnapshot = () => this.snapshot;
@@ -642,12 +656,43 @@ var BubbleStore = class {
642
656
  const id = crypto.randomUUID();
643
657
  const controller = new AbortController();
644
658
  this.active.set(id, controller);
659
+ this.runEnd.set(id, "pending");
645
660
  this.publish([...this.snapshot, { id, sessionId, question, ...reference === void 0 ? {} : { reference }, answer: "", phase: "answering" }]);
646
661
  void Promise.resolve().then(() => reference === void 0 ? this.transport.run(sessionId, id, question, controller.signal) : this.transport.run(sessionId, id, question, controller.signal, reference)).then((result) => {
647
- if (controller.signal.aborted || this.closing.has(id)) return;
648
- this.update(id, { phase: result.kind === "success" ? "done" : "error", answer: result.kind === "success" ? result.text : "", error: result.kind === "error" ? result.text : void 0 });
662
+ if (result.kind === "success") {
663
+ this.runEnd.set(id, "success");
664
+ if (!this.snapshot.some((item) => item.id === id)) return;
665
+ const failedClose = this.closeFenced.has(id) || Boolean(this.snapshot.find((item) => item.id === id)?.closeFailed);
666
+ this.update(id, { phase: failedClose ? "error" : this.closing.has(id) ? "closing" : "done", answer: result.text, ...failedClose ? {} : { error: void 0 } });
667
+ return;
668
+ }
669
+ if (controller.signal.aborted) {
670
+ if (this.runEnd.get(id) === "pending") this.runEnd.set(id, "aborted");
671
+ this.dropIfCloseCannotFinish(id);
672
+ return;
673
+ }
674
+ this.runEnd.set(id, "failed");
675
+ if (!this.closing.has(id)) this.update(id, { phase: "error", answer: "", error: result.text });
649
676
  }).catch((error) => {
650
- if (!controller.signal.aborted && !this.closing.has(id)) this.update(id, { phase: "error", error: String(error) });
677
+ if (this.runEnd.get(id) === "success") return;
678
+ const message = error instanceof Error ? error.message : String(error);
679
+ if (error instanceof SideRequestNotStartedError && this.snapshot.some((item) => item.id === id)) {
680
+ this.runEnd.set(id, "not-started");
681
+ this.notStarted.add(id);
682
+ if (this.closeFenced.has(id) || this.closing.has(id)) {
683
+ this.remove(id);
684
+ return;
685
+ }
686
+ this.update(id, { phase: "error", error: message });
687
+ return;
688
+ }
689
+ if (controller.signal.aborted) {
690
+ if (this.runEnd.get(id) === "pending") this.runEnd.set(id, "aborted");
691
+ this.dropIfCloseCannotFinish(id);
692
+ return;
693
+ }
694
+ this.runEnd.set(id, "failed");
695
+ if (!this.closing.has(id)) this.update(id, { phase: "error", error: message });
651
696
  }).finally(() => {
652
697
  this.active.delete(id);
653
698
  });
@@ -658,12 +703,28 @@ var BubbleStore = class {
658
703
  if (existing) return existing;
659
704
  const item = this.snapshot.find((bubble) => bubble.id === id);
660
705
  if (!item) return Promise.resolve();
706
+ if (this.notStarted.has(id)) {
707
+ this.remove(id);
708
+ return Promise.resolve();
709
+ }
661
710
  this.update(id, { phase: "closing", error: void 0, closeFailed: false });
662
711
  this.active.get(id)?.abort();
663
712
  const operation = Promise.resolve().then(() => this.transport.close(item.sessionId, id)).then((result) => {
664
713
  if (result.kind === "error") throw new Error(result.text);
665
- this.publish(this.snapshot.filter((bubble) => bubble.id !== id));
714
+ this.remove(id);
666
715
  }).catch((error) => {
716
+ if (this.notStarted.has(id) || this.runEnd.get(id) === "not-started") {
717
+ this.remove(id);
718
+ return;
719
+ }
720
+ if (error instanceof SideRequestNotStartedError) {
721
+ this.closeFenced.add(id);
722
+ const end = this.runEnd.get(id);
723
+ if (end === "aborted" || end === "not-started") {
724
+ this.remove(id);
725
+ return;
726
+ }
727
+ }
667
728
  this.update(id, { phase: "error", closeFailed: true, error: error instanceof Error ? error.message : String(error) });
668
729
  }).finally(() => {
669
730
  this.closing.delete(id);
@@ -676,6 +737,17 @@ var BubbleStore = class {
676
737
  this.disposed = true;
677
738
  await Promise.all(this.snapshot.map((item) => this.close(item.id)));
678
739
  }
740
+ dropIfCloseCannotFinish(id) {
741
+ if (!this.closeFenced.has(id)) return;
742
+ const end = this.runEnd.get(id);
743
+ if (end === "not-started" || end === "aborted") this.remove(id);
744
+ }
745
+ remove(id) {
746
+ this.notStarted.delete(id);
747
+ this.runEnd.delete(id);
748
+ this.closeFenced.delete(id);
749
+ this.publish(this.snapshot.filter((item) => item.id !== id));
750
+ }
679
751
  update(id, patch2) {
680
752
  this.publish(this.snapshot.map((item) => item.id === id ? { ...item, ...patch2 } : item));
681
753
  }
@@ -13452,8 +13524,7 @@ function AnswerMarkdown({ text: text7, labels, imageLabel }) {
13452
13524
  }
13453
13525
 
13454
13526
  // src/client/BubbleView.tsx
13455
- function BubbleView({ item, close, t }) {
13456
- const [collapsed, setCollapsed] = (0, import_react5.useState)(false);
13527
+ function BubbleView({ item, close, t, collapsed, onToggleCollapse }) {
13457
13528
  const [copied, setCopied] = (0, import_react5.useState)(false);
13458
13529
  const [copyError, setCopyError] = (0, import_react5.useState)(false);
13459
13530
  const busy = item.phase === "answering" || item.phase === "closing";
@@ -13470,17 +13541,48 @@ function BubbleView({ item, close, t }) {
13470
13541
  setCopyError(true);
13471
13542
  }
13472
13543
  };
13544
+ const stopOrCloseTitle = item.closeFailed ? t("action.retryClose") : t(item.phase === "answering" ? "action.stop" : "action.close");
13473
13545
  return /* @__PURE__ */ import_react5.default.createElement("article", { className: "btw-bubble", "data-phase": item.phase, "aria-label": t("bubble.label") }, /* @__PURE__ */ import_react5.default.createElement("header", { className: "btw-header" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "btw-symbol" }, /* @__PURE__ */ import_react5.default.createElement(MessageCircle, { size: 16 })), /* @__PURE__ */ import_react5.default.createElement("span", { className: "btw-question" }, item.question), /* @__PURE__ */ import_react5.default.createElement("div", { className: "btw-actions" }, item.answer && /* @__PURE__ */ import_react5.default.createElement("button", { type: "button", title: t(copied ? "action.copied" : "action.copy"), "aria-label": t("action.copy"), onClick: () => {
13474
13546
  void copy();
13475
- } }, copied ? /* @__PURE__ */ import_react5.default.createElement(Check, { size: 16 }) : /* @__PURE__ */ import_react5.default.createElement(Copy, { size: 16 })), /* @__PURE__ */ import_react5.default.createElement("button", { type: "button", title: t(collapsed ? "action.expand" : "action.collapse"), "aria-label": t(collapsed ? "action.expand" : "action.collapse"), "aria-expanded": !collapsed, onClick: () => setCollapsed(!collapsed) }, collapsed ? /* @__PURE__ */ import_react5.default.createElement(ChevronUp, { size: 16 }) : /* @__PURE__ */ import_react5.default.createElement(ChevronDown, { size: 16 })), /* @__PURE__ */ import_react5.default.createElement("button", { type: "button", title: t(item.closeFailed ? "action.retryClose" : "action.close"), "aria-label": t("action.close"), disabled: item.phase === "closing", onClick: close }, /* @__PURE__ */ import_react5.default.createElement(X, { size: 16 })))), !collapsed && item.reference && /* @__PURE__ */ import_react5.default.createElement("details", { className: "btw-reference" }, /* @__PURE__ */ import_react5.default.createElement("summary", null, t("selection.reference")), /* @__PURE__ */ import_react5.default.createElement("blockquote", null, item.reference)), busy && /* @__PURE__ */ import_react5.default.createElement("div", { className: "btw-status", role: "status" }, /* @__PURE__ */ import_react5.default.createElement(LoaderCircle, { size: 14, className: "btw-spinner" }), t(item.phase === "closing" ? "status.closing" : "status.answering")), !collapsed && item.answer && /* @__PURE__ */ import_react5.default.createElement("div", { className: "btw-answer" }, /* @__PURE__ */ import_react5.default.createElement(AnswerMarkdown, { text: item.answer, labels, imageLabel: t("bubble.image") })), item.error && /* @__PURE__ */ import_react5.default.createElement("p", { className: "btw-error", role: "alert" }, item.closeFailed ? t("error.close", { detail: item.error }) : item.error), copyError && /* @__PURE__ */ import_react5.default.createElement("p", { className: "btw-error", role: "alert" }, t("error.copy")));
13547
+ } }, copied ? /* @__PURE__ */ import_react5.default.createElement(Check, { size: 16 }) : /* @__PURE__ */ import_react5.default.createElement(Copy, { size: 16 })), /* @__PURE__ */ import_react5.default.createElement("button", { type: "button", title: t(collapsed ? "action.expand" : "action.collapse"), "aria-label": t(collapsed ? "action.expand" : "action.collapse"), "aria-expanded": !collapsed, onClick: onToggleCollapse }, collapsed ? /* @__PURE__ */ import_react5.default.createElement(ChevronUp, { size: 16 }) : /* @__PURE__ */ import_react5.default.createElement(ChevronDown, { size: 16 })), /* @__PURE__ */ import_react5.default.createElement(
13548
+ "button",
13549
+ {
13550
+ type: "button",
13551
+ className: item.phase === "answering" ? "btw-action-stop" : void 0,
13552
+ title: stopOrCloseTitle,
13553
+ "aria-label": stopOrCloseTitle,
13554
+ disabled: item.phase === "closing",
13555
+ onClick: close
13556
+ },
13557
+ item.phase === "answering" ? /* @__PURE__ */ import_react5.default.createElement(Square, { size: 14 }) : /* @__PURE__ */ import_react5.default.createElement(X, { size: 16 })
13558
+ ))), !collapsed && item.reference && /* @__PURE__ */ import_react5.default.createElement("details", { className: "btw-reference" }, /* @__PURE__ */ import_react5.default.createElement("summary", null, t("selection.reference")), /* @__PURE__ */ import_react5.default.createElement("blockquote", null, item.reference)), busy && /* @__PURE__ */ import_react5.default.createElement("div", { className: "btw-status", role: "status" }, /* @__PURE__ */ import_react5.default.createElement(LoaderCircle, { size: 14, className: "btw-spinner" }), t(item.phase === "closing" ? "status.closing" : "status.answering")), !collapsed && item.answer && /* @__PURE__ */ import_react5.default.createElement("div", { className: "btw-answer" }, /* @__PURE__ */ import_react5.default.createElement(AnswerMarkdown, { text: item.answer, labels, imageLabel: t("bubble.image") })), item.error && /* @__PURE__ */ import_react5.default.createElement("p", { className: "btw-error", role: "alert" }, item.closeFailed ? t("error.close", { detail: item.error }) : item.error), copyError && /* @__PURE__ */ import_react5.default.createElement("p", { className: "btw-error", role: "alert" }, t("error.copy")));
13476
13559
  }
13477
13560
  function BubbleDock({ store, sessionId, t }) {
13478
13561
  const snapshot = (0, import_react5.useSyncExternalStore)(store.subscribe, store.getSnapshot, store.getSnapshot);
13479
13562
  const items = snapshot.filter((item) => item.sessionId === sessionId);
13563
+ const latestId = items.at(-1)?.id;
13564
+ const [manual, setManual] = (0, import_react5.useState)();
13565
+ const hasNewlyAddedBubble = manual !== void 0 && items.some((item) => !manual.knownIds.includes(item.id));
13566
+ const currentIds = items.map((item) => item.id);
13567
+ const isPinnedValid = manual !== void 0 && !hasNewlyAddedBubble && manual.id !== null ? items.some((item) => item.id === manual.id) : false;
13568
+ const effectiveExpandedId = manual === void 0 || hasNewlyAddedBubble ? latestId : manual.id === null ? null : isPinnedValid ? manual.id : latestId;
13480
13569
  if (!items.length) return null;
13481
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "btw-dock" }, items.map((item) => /* @__PURE__ */ import_react5.default.createElement(BubbleView, { key: item.id, item, t, close: () => {
13482
- void store.close(item.id);
13483
- } })));
13570
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "btw-dock" }, items.map((item) => /* @__PURE__ */ import_react5.default.createElement(
13571
+ BubbleView,
13572
+ {
13573
+ key: item.id,
13574
+ item,
13575
+ t,
13576
+ collapsed: item.id !== effectiveExpandedId,
13577
+ onToggleCollapse: () => {
13578
+ if (item.id === effectiveExpandedId) setManual({ id: null, knownIds: currentIds });
13579
+ else setManual({ id: item.id, knownIds: currentIds });
13580
+ },
13581
+ close: () => {
13582
+ void store.close(item.id);
13583
+ }
13584
+ }
13585
+ )));
13484
13586
  }
13485
13587
 
13486
13588
  // src/client/composer.ts
@@ -13729,6 +13831,8 @@ var CSS = `
13729
13831
  .btw-actions button{display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;flex:none;transition:background-color 120ms ease}
13730
13832
  .btw-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
13731
13833
  .btw-actions button:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,var(--dsw-alias-label-primary));outline-offset:-2px}
13834
+ .btw-actions button.btw-action-stop{color:var(--dsw-alias-state-error-primary,var(--dsw-alias-label-primary))}
13835
+ .btw-actions button.btw-action-stop:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-state-error-primary,var(--dsw-alias-label-primary))}
13732
13836
  .btw-actions button:disabled{cursor:wait;opacity:.5}
13733
13837
  .btw-status{display:flex;align-items:center;gap:8px;padding:0 14px 12px;color:var(--dsw-alias-label-secondary);font-size:13px}
13734
13838
  .btw-spinner{animation:btw-spin 1.2s linear infinite}@keyframes btw-spin{to{transform:rotate(360deg)}}
@@ -13754,7 +13858,7 @@ function sessionIdOf(session) {
13754
13858
  if (session && typeof session === "object" && "sessionId" in session) return String(session.sessionId);
13755
13859
  return String(session);
13756
13860
  }
13757
- function attachOfficialBtw(commandUi, claim, t) {
13861
+ function attachOfficialBtw(commandUi, claim, t, isSubagent) {
13758
13862
  const live = commandUi;
13759
13863
  if (!live) return () => {
13760
13864
  };
@@ -13764,7 +13868,7 @@ function attachOfficialBtw(commandUi, claim, t) {
13764
13868
  live.candidates = async (...args) => {
13765
13869
  const rows = await original.apply(live, args);
13766
13870
  if (!Array.isArray(rows)) return rows;
13767
- return rows.map((row) => {
13871
+ return rows.filter((row) => !(isSubagent(sessionIdOf(args[0])) && row && typeof row === "object" && "name" in row && row.name === "btw")).map((row) => {
13768
13872
  if (!row || typeof row !== "object" || !("name" in row) || row.name !== "btw") return row;
13769
13873
  const item = row;
13770
13874
  return {
@@ -13782,14 +13886,14 @@ function attachOfficialBtw(commandUi, claim, t) {
13782
13886
  }
13783
13887
  if (typeof live.dispatch === "function") {
13784
13888
  const original = live.dispatch.bind(live);
13785
- live.dispatch = (pick) => pick.candidate?.name === "btw" ? claim(sessionIdOf(pick.session)) : original(pick);
13889
+ live.dispatch = (pick) => pick.candidate?.name === "btw" ? isSubagent(sessionIdOf(pick.session)) ? void 0 : claim(sessionIdOf(pick.session)) : original(pick);
13786
13890
  restore.push(() => {
13787
13891
  live.dispatch = original;
13788
13892
  });
13789
13893
  }
13790
13894
  if (typeof live.matchSpace === "function") {
13791
13895
  const original = live.matchSpace.bind(live);
13792
- live.matchSpace = (session, token) => token === "/btw" ? claim(sessionIdOf(session)) : original(session, token);
13896
+ live.matchSpace = (session, token) => token === "/btw" ? isSubagent(sessionIdOf(session)) ? void 0 : claim(sessionIdOf(session)) : original(session, token);
13793
13897
  restore.push(() => {
13794
13898
  live.matchSpace = original;
13795
13899
  });
@@ -13798,6 +13902,7 @@ function attachOfficialBtw(commandUi, claim, t) {
13798
13902
  const original = live.matchEnter.bind(live);
13799
13903
  live.matchEnter = async (session, line, signal, envelope) => {
13800
13904
  if (!/^\/btw(?:\s|$)/.test(line.trim())) return original(session, line, signal, envelope);
13905
+ if (isSubagent(sessionIdOf(session))) throw new Error(t("error.subagent"));
13801
13906
  if (envelope?.attachments || envelope?.images) throw new Error(t("error.removeAttachments"));
13802
13907
  return claim(sessionIdOf(session));
13803
13908
  };
@@ -13809,6 +13914,7 @@ function attachOfficialBtw(commandUi, claim, t) {
13809
13914
  const original = live.execute.bind(live);
13810
13915
  live.execute = async (session, line, attachments) => {
13811
13916
  if (!/^\/btw(?:\s|$)/.test(line)) return original(session, line, attachments);
13917
+ if (isSubagent(sessionIdOf(session))) return { kind: "error", text: t("error.subagent") };
13812
13918
  if (Array.isArray(attachments) && attachments.length) return { kind: "error", text: t("error.attachments") };
13813
13919
  const outcome = claim(sessionIdOf(session));
13814
13920
  if (!outcome || typeof outcome !== "object" || !("claim" in outcome)) return { kind: "error", text: t("error.backend") };
@@ -13829,9 +13935,19 @@ function apply(ctx) {
13829
13935
  const t = ctx.locale.bind(NS);
13830
13936
  const slots = ctx.get("slots");
13831
13937
  const remote = ctx.get("remote");
13938
+ const sessions = ctx.get("sessions");
13939
+ const isSubagent = (id) => Boolean(sessions.subagentAddress?.(id)) || sessions.list?.getSnapshot().byId[id]?.origin === "subagent";
13832
13940
  const execute = async (session, line, signal) => {
13833
13941
  const response = await remote.commands.execute(session, line, [], signal);
13834
- if (!response.ok) throw new Error(response.error.message);
13942
+ if (!response.ok) {
13943
+ const failure = response.error;
13944
+ const code4 = failure.code;
13945
+ const details = failure.details;
13946
+ const reason = details && typeof details === "object" && "reason" in details && typeof details.reason === "string" ? details.reason : void 0;
13947
+ const ownedBySubagent = (code4 === "agent-busy" || code4 === "session/agent-busy") && (reason === "use subagent delivery for this child session" || failure.message === `session "${session}" is owned by subagent routing`);
13948
+ if (ownedBySubagent) throw new SideRequestNotStartedError(t("error.subagent"));
13949
+ throw new Error(failure.message);
13950
+ }
13835
13951
  if (!response.value) throw new Error(t("error.backend"));
13836
13952
  return { kind: response.value.result.kind, text: response.value.result.text ?? "" };
13837
13953
  };
@@ -13841,11 +13957,12 @@ function apply(ctx) {
13841
13957
  }, t);
13842
13958
  const claim = (sessionId) => ({ claim: {
13843
13959
  name: "btw",
13844
- token: "/btw",
13960
+ token: "/btw ",
13845
13961
  get hint() {
13846
13962
  return t("command.hint");
13847
13963
  },
13848
13964
  submit: async (args, _actx, attachments) => {
13965
+ if (isSubagent(sessionId)) return { kind: "error", text: t("error.subagent") };
13849
13966
  if (attachments.length) return { kind: "error", text: t("error.attachments") };
13850
13967
  try {
13851
13968
  await store.ask(sessionId, args.trim());
@@ -13855,8 +13972,8 @@ function apply(ctx) {
13855
13972
  }
13856
13973
  }
13857
13974
  } });
13858
- if (typeof ctx.inject === "function") ctx.inject(["commandUi"], (scope) => attachOfficialBtw(scope.get("commandUi"), (sessionId) => claim(sessionId), t));
13859
- else ctx.effect(() => attachOfficialBtw(ctx.get("commandUi"), (sessionId) => claim(sessionId), t));
13975
+ if (typeof ctx.inject === "function") ctx.inject(["commandUi"], (scope) => attachOfficialBtw(scope.get("commandUi"), (sessionId) => claim(sessionId), t, isSubagent));
13976
+ else ctx.effect(() => attachOfficialBtw(ctx.get("commandUi"), (sessionId) => claim(sessionId), t, isSubagent));
13860
13977
  ctx.effect(() => {
13861
13978
  const style = document.createElement("style");
13862
13979
  style.dataset.plugin = "@michengai/dsh-btw";
@@ -13868,7 +13985,7 @@ function apply(ctx) {
13868
13985
  });
13869
13986
  ctx.effect(() => () => store.dispose());
13870
13987
  function Dock(props) {
13871
- return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(SelectionAsk, { key: props.session.sessionId, store, sessionId: props.session.sessionId, t: props.t, addToConversation: (reference) => {
13988
+ return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, !props.session.subagent && !isSubagent(props.session.sessionId) && /* @__PURE__ */ import_react7.default.createElement(SelectionAsk, { key: props.session.sessionId, store, sessionId: props.session.sessionId, t: props.t, addToConversation: (reference) => {
13872
13989
  const session = ctx.get("sessions").scope(props.session.sessionId);
13873
13990
  if (!session) throw new Error(props.t("error.backend"));
13874
13991
  addQuoteToComposer(ctx.conversation.input.for(session), reference, props.t);
@@ -13892,6 +14009,7 @@ lucide-react/dist/esm/icons/chevron-up.js:
13892
14009
  lucide-react/dist/esm/icons/copy.js:
13893
14010
  lucide-react/dist/esm/icons/loader-circle.js:
13894
14011
  lucide-react/dist/esm/icons/message-circle.js:
14012
+ lucide-react/dist/esm/icons/square.js:
13895
14013
  lucide-react/dist/esm/icons/x.js:
13896
14014
  lucide-react/dist/esm/lucide-react.js:
13897
14015
  (**
package/lib/index.js CHANGED
@@ -31,6 +31,7 @@ var zh = {
31
31
  "action.collapse": "\u6298\u53E0\u56DE\u7B54",
32
32
  "action.close": "\u5173\u95ED\u65C1\u95EE",
33
33
  "action.retryClose": "\u91CD\u8BD5\u5173\u95ED",
34
+ "action.stop": "\u505C\u6B62\u56DE\u7B54",
34
35
  "status.answering": "\u6B63\u5728\u56DE\u7B54\u2026",
35
36
  "status.closing": "\u6B63\u5728\u5173\u95ED\u2026",
36
37
  "command.hint": "\u65C1\u95EE\u5185\u5BB9",
@@ -41,6 +42,7 @@ var zh = {
41
42
  "error.attachments": "\u65C1\u95EE\u53EA\u63A5\u53D7\u6587\u5B57\uFF0C\u56FE\u7247\u548C\u6587\u4EF6\u4ECD\u4FDD\u7559\u5728\u8F93\u5165\u6846\u4E2D\u3002",
42
43
  "error.removeAttachments": "\u65C1\u95EE\u53EA\u63A5\u53D7\u6587\u5B57\uFF0C\u8BF7\u5148\u79FB\u9664\u56FE\u7247\u548C\u6587\u4EF6\u3002",
43
44
  "error.backend": "BTW \u540E\u7AEF\u5C1A\u672A\u542F\u7528\uFF0C\u8BF7\u68C0\u67E5\u63D2\u4EF6\u914D\u7F6E\u3002",
45
+ "error.subagent": "\u5B50\u4EE3\u7406\u4F1A\u8BDD\u6682\u4E0D\u652F\u6301\u65C1\u95EE\uFF0C\u8BF7\u8FD4\u56DE\u4E3B\u4F1A\u8BDD\u4F7F\u7528 /btw\u3002",
44
46
  "error.empty": "\u8BF7\u8F93\u5165\u65C1\u95EE\u5185\u5BB9\u3002",
45
47
  "error.length": `\u65C1\u95EE\u6700\u591A\u652F\u6301 ${MAX_QUESTION_LENGTH} \u4E2A\u5B57\u7B26\u3002`,
46
48
  "error.capacity": "\u6B63\u5728\u5904\u7406\u7684\u65C1\u95EE\u8F83\u591A\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
@@ -83,6 +85,7 @@ var en = {
83
85
  "action.collapse": "Collapse answer",
84
86
  "action.close": "Close side question",
85
87
  "action.retryClose": "Retry closing",
88
+ "action.stop": "Stop answering",
86
89
  "status.answering": "Answering\u2026",
87
90
  "status.closing": "Closing\u2026",
88
91
  "command.hint": "Side question",
@@ -93,6 +96,7 @@ var en = {
93
96
  "error.attachments": "Side questions accept text only. Your images and files remain in the input.",
94
97
  "error.removeAttachments": "Side questions accept text only. Remove the images and files first.",
95
98
  "error.backend": "The BTW backend is unavailable. Check the plugin configuration.",
99
+ "error.subagent": "Side questions are not supported in subagent sessions. Use /btw in the main session.",
96
100
  "error.empty": "Enter a side question.",
97
101
  "error.length": `Side questions are limited to ${MAX_QUESTION_LENGTH.toLocaleString("en-US")} characters.`,
98
102
  "error.capacity": "Too many side questions are running. Try again shortly.",
@@ -157,8 +161,9 @@ ${question}`;
157
161
 
158
162
  // src/server/jobs.ts
159
163
  var failure = (error) => ({ kind: "error", text: error instanceof Error ? error.message : String(error) });
164
+ var DEFAULT_TIMEOUT_MS = 18e5;
160
165
  var SideJobs = class {
161
- constructor(start, timeoutMs = 9e4, options = {}) {
166
+ constructor(start, timeoutMs = DEFAULT_TIMEOUT_MS, options = {}) {
162
167
  this.start = start;
163
168
  this.timeoutMs = timeoutMs;
164
169
  this.options = options;
@@ -182,10 +187,10 @@ var SideJobs = class {
182
187
  const cancel = () => job.controller.abort(new Error(t("error.cancelled")));
183
188
  signal?.addEventListener("abort", cancel, { once: true });
184
189
  if (signal?.aborted) cancel();
185
- const timer = setTimeout(() => job.controller.abort(new Error(t("error.timeout"))), this.timeoutMs);
186
- timer.unref?.();
190
+ const timer = this.timeoutMs > 0 ? setTimeout(() => job.controller.abort(new Error(t("error.timeout"))), this.timeoutMs) : void 0;
191
+ timer?.unref?.();
187
192
  job.finished = this.execute(job, { scope, id, question, signal: job.controller.signal, toolFilter: { allow: [] }, context, locale }).finally(() => {
188
- clearTimeout(timer);
193
+ if (timer) clearTimeout(timer);
189
194
  signal?.removeEventListener("abort", cancel);
190
195
  this.release(job);
191
196
  });
@@ -448,50 +453,84 @@ function apply(ctx) {
448
453
  throw new Error(translate(request.locale)("error.provider"));
449
454
  }
450
455
  const label = `michengai-btw:${globalThis.crypto.randomUUID()}`;
451
- const childId = SessionId(globalThis.crypto.randomUUID());
452
456
  labels.add(label);
453
- let childAgent;
454
- try {
455
- const stopCreated = ctx.on("agent/created", ({ agent }) => {
456
- if (agent.id !== childId) return void 0;
457
- childAgent = agent;
458
- arm(agent);
459
- const scoped = agent;
460
- if (scoped.ctx?.systemPrompt) {
461
- scoped.ctx.systemPrompt.section({
462
- name: PERSONA_PREFIX_SECTION,
463
- order: scoped.ctx.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA_PREFIX"),
464
- text: PERSONA
465
- });
466
- }
467
- if (scoped.ctx?.tools) scoped.ctx.tools.restrict(request.toolFilter);
468
- return void 0;
469
- });
470
- let activation;
457
+ if (typeof ctx.subagents.startActivation === "function") {
458
+ const childId = SessionId(globalThis.crypto.randomUUID());
459
+ let childAgent;
471
460
  try {
472
- activation = await ctx.subagents.startActivation({
473
- provider: "fork",
474
- label,
475
- childId,
476
- request: {
477
- parent: request.context,
478
- prompt: [{ type: "text", text: `\u4EE5\u4E0B\u662F\u552F\u4E00\u9700\u8981\u56DE\u7B54\u7684\u65B0\u95EE\u9898\uFF1B\u5148\u524D\u5185\u5BB9\u4EC5\u4F9B\u53C2\u8003\u3002
461
+ const stopCreated = ctx.on("agent/created", ({ agent }) => {
462
+ if (agent.id !== childId) return void 0;
463
+ childAgent = agent;
464
+ arm(agent);
465
+ const scoped = agent;
466
+ if (scoped.ctx?.systemPrompt) {
467
+ scoped.ctx.systemPrompt.section({
468
+ name: PERSONA_PREFIX_SECTION,
469
+ order: scoped.ctx.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA_PREFIX"),
470
+ text: PERSONA
471
+ });
472
+ }
473
+ if (scoped.ctx?.tools) scoped.ctx.tools.restrict(request.toolFilter);
474
+ return void 0;
475
+ });
476
+ let activation;
477
+ try {
478
+ activation = await ctx.subagents.startActivation({
479
+ provider: "fork",
480
+ label,
481
+ childId,
482
+ request: {
483
+ parent: request.context,
484
+ prompt: [{ type: "text", text: `\u4EE5\u4E0B\u662F\u552F\u4E00\u9700\u8981\u56DE\u7B54\u7684\u65B0\u95EE\u9898\uFF1B\u5148\u524D\u5185\u5BB9\u4EC5\u4F9B\u53C2\u8003\u3002
479
485
 
480
486
  ${request.question}` }]
481
- },
482
- signal: request.signal,
483
- delivery: "caller"
484
- });
485
- } finally {
486
- stopCreated();
487
+ },
488
+ signal: request.signal,
489
+ delivery: "caller"
490
+ });
491
+ } finally {
492
+ stopCreated();
493
+ }
494
+ return {
495
+ result: activation.result,
496
+ dispose: async () => {
497
+ try {
498
+ await activation.dispose();
499
+ } finally {
500
+ await releaseChild(childAgent);
501
+ labels.delete(label);
502
+ }
503
+ }
504
+ };
505
+ } catch (error) {
506
+ labels.delete(label);
507
+ throw error;
487
508
  }
509
+ }
510
+ const legacySubagents = ctx.subagents;
511
+ if (typeof legacySubagents.start !== "function") {
512
+ labels.delete(label);
513
+ throw new Error(translate(request.locale)("error.provider"));
514
+ }
515
+ try {
516
+ const run = await legacySubagents.start("fork", {
517
+ parent: request.context,
518
+ label,
519
+ signal: request.signal,
520
+ toolFilter: request.toolFilter,
521
+ persona: PERSONA,
522
+ prompt: [{ type: "text", text: `\u4EE5\u4E0B\u662F\u552F\u4E00\u9700\u8981\u56DE\u7B54\u7684\u65B0\u95EE\u9898\uFF1B\u5148\u524D\u5185\u5BB9\u4EC5\u4F9B\u53C2\u8003\u3002
523
+
524
+ ${request.question}` }]
525
+ });
526
+ if (run.localAgent) arm(run.localAgent);
488
527
  return {
489
- result: activation.result,
528
+ result: run.result,
490
529
  dispose: async () => {
491
530
  try {
492
- await activation.dispose();
531
+ await run.dispose();
493
532
  } finally {
494
- await releaseChild(childAgent);
533
+ await releaseChild(run.localAgent);
495
534
  labels.delete(label);
496
535
  }
497
536
  }
@@ -500,7 +539,7 @@ ${request.question}` }]
500
539
  labels.delete(label);
501
540
  throw error;
502
541
  }
503
- }, 9e4, {
542
+ }, DEFAULT_TIMEOUT_MS, {
504
543
  onError: (error) => ctx.logger.warn(error),
505
544
  onIdle: async () => {
506
545
  stopWatch();
@@ -1,10 +1,12 @@
1
1
  import React from 'react';
2
2
  import type { Bubble, BubbleStore } from './bubbles';
3
3
  import type { BtwTranslate } from '../locales';
4
- export declare function BubbleView({ item, close, t }: {
4
+ export declare function BubbleView({ item, close, t, collapsed, onToggleCollapse }: {
5
5
  item: Bubble;
6
6
  close: () => void;
7
7
  t: BtwTranslate;
8
+ collapsed: boolean;
9
+ onToggleCollapse: () => void;
8
10
  }): React.JSX.Element;
9
11
  export declare function BubbleDock({ store, sessionId, t }: {
10
12
  store: BubbleStore;
@@ -14,6 +14,9 @@ export interface SideTransport {
14
14
  run(session: string, id: string, question: string, signal: AbortSignal, reference?: string): Promise<SideResult>;
15
15
  close(session: string, id: string): Promise<SideResult>;
16
16
  }
17
+ /** 宿主在进入 handler 前明确拒绝启动;没有后台任务需要清理。 */
18
+ export declare class SideRequestNotStartedError extends Error {
19
+ }
17
20
  /** 仅内存 UI 状态;关闭请求与答案返回按请求身份分别收敛。 */
18
21
  export declare class BubbleStore {
19
22
  private readonly transport;
@@ -22,6 +25,9 @@ export declare class BubbleStore {
22
25
  private readonly listeners;
23
26
  private readonly active;
24
27
  private readonly closing;
28
+ private readonly notStarted;
29
+ private readonly runEnd;
30
+ private readonly closeFenced;
25
31
  private readonly admitting;
26
32
  private disposed;
27
33
  constructor(transport: SideTransport, t?: BtwTranslate);
@@ -31,6 +37,8 @@ export declare class BubbleStore {
31
37
  ask(sessionId: string, question: string, reference?: string): Promise<string>;
32
38
  close(id: string): Promise<void>;
33
39
  dispose(): Promise<void>;
40
+ private dropIfCloseCannotFinish;
41
+ private remove;
34
42
  private update;
35
43
  private publish;
36
44
  }
@@ -1 +1 @@
1
- export declare const CSS = "\n.btw-reference-preview{display:block;margin-top:6px;max-height:3.2em;overflow:hidden;overflow-wrap:anywhere;line-height:1.6;color:var(--dsw-alias-label-primary)}\n.btw-selection-dialog,.btw-selection-menu{position:fixed;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px;font:inherit;font-size:13px;max-height:calc(100dvh - 32px);overflow:auto;box-shadow:0 12px 36px #0003}\n.btw-selection-dialog{width:min(520px,calc(100vw - 32px));margin:auto}.btw-selection-menu{margin:0;width:max-content;max-width:calc(100vw - 16px);padding:0;inset:auto;border-radius:10px;overflow:hidden;border-color:var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);font-family:inherit;font-size:12px;font-weight:500;line-height:16px;box-shadow:0 2px 6px #0001}\n.btw-selection-menu::backdrop{background:transparent}.btw-selection-dialog::backdrop{background:#0004}\n.btw-selection-dialog h3{margin:0 0 12px;font-size:16px}.btw-selection-dialog label{display:flex;flex-direction:column;gap:8px;margin-top:12px}\n.btw-selection-dialog textarea{box-sizing:border-box;width:100%;min-height:96px;resize:vertical;font:inherit;padding:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-base);color:inherit}\n.btw-selection-dialog button,.btw-selection-menu button{font:inherit;color:inherit;background:var(--dsw-alias-interactive-bg-hover);border:0;border-radius:6px;padding:8px 12px;cursor:pointer;min-height:36px}\n.btw-selection-menu [role=\"toolbar\"]{display:flex;align-items:center}.btw-selection-menu button+button{border-left:1px solid var(--dsw-alias-border-l1)}\n.btw-selection-menu button{display:inline-flex;align-items:center;height:28px;min-height:28px;padding:0 8px;border-radius:0;text-align:left;white-space:nowrap;background:transparent}.btw-selection-menu button:hover{background:var(--dsw-alias-interactive-bg-hover)}\n.btw-selection-dialog button:disabled{opacity:.5;cursor:default}.btw-selection-dialog :focus-visible,.btw-selection-menu :focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,var(--dsw-alias-label-primary));outline-offset:2px}\n.btw-selection-menu :focus-visible{outline-width:1px;outline-offset:-2px}\n.btw-selection-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.btw-selection-hint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;margin:10px 4px}\n.btw-reference{padding:0 14px 10px;font-size:12px}.btw-reference summary{cursor:pointer;color:var(--dsw-alias-label-secondary)}.btw-reference blockquote{margin:8px 0 0;padding:8px;border-left:2px solid var(--dsw-alias-border-l2);white-space:pre-wrap;overflow-wrap:anywhere;max-height:150px;overflow:auto}.btw-selection-dialog .btw-reference{padding:0}\n.btw-dock{display:flex;flex:none;flex-direction:column;gap:8px;width:calc(100% - var(--dsh-composer-side-clearance,0px) - var(--dsh-composer-side-clearance,0px));max-width:var(--dsh-composer-card-max-width,100%);margin:0 auto;max-height:440px;overflow:auto;padding:4px 0 10px;box-sizing:border-box;letter-spacing:0}\n.btw-bubble{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;min-width:0;flex-shrink:0;font-size:13px;line-height:1.6;font-family:var(--dsw-font-family,inherit);letter-spacing:0}\n.btw-header{display:flex;align-items:center;gap:8px;padding:8px 10px 8px 14px;min-width:0;min-height:44px}\n.btw-symbol{display:flex;align-items:center;color:var(--dsw-alias-label-secondary);height:28px;flex-shrink:0}\n.btw-question{flex:1;min-width:0;overflow-wrap:anywhere;font-size:13px;font-weight:500;line-height:22px;max-height:66px;overflow:auto}\n.btw-actions{display:flex;gap:4px;flex-shrink:0}\n.btw-actions button{display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;flex:none;transition:background-color 120ms ease}\n.btw-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}\n.btw-actions button:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,var(--dsw-alias-label-primary));outline-offset:-2px}\n.btw-actions button:disabled{cursor:wait;opacity:.5}\n.btw-status{display:flex;align-items:center;gap:8px;padding:0 14px 12px;color:var(--dsw-alias-label-secondary);font-size:13px}\n.btw-spinner{animation:btw-spin 1.2s linear infinite}@keyframes btw-spin{to{transform:rotate(360deg)}}\n.btw-answer{padding:0 14px 14px;max-height:300px;overflow:auto;overflow-wrap:anywhere;font-size:13px;line-height:1.7}\n.btw-answer>*:first-child{margin-top:0}.btw-answer>*:last-child{margin-bottom:0}\n.btw-answer p{margin:8px 0}.btw-answer h1,.btw-answer h2,.btw-answer h3{font-size:15px;line-height:1.5;margin:12px 0 6px;font-weight:600}\n.btw-answer pre{max-width:100%;overflow:auto;background:var(--dsw-alias-markdown-code-block);padding:10px;border-radius:8px;white-space:pre;font-size:12px}.btw-answer .katex-display{max-width:100%;overflow:auto}\n.btw-answer code{font-family:Consolas,monospace;font-size:12px}.btw-answer :not(pre)>code{background:var(--dsw-alias-markdown-code-block);border-radius:4px;padding:1px 3px}.btw-answer a{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-offset:2px;overflow-wrap:anywhere}\n.btw-answer table{display:block;max-width:100%;overflow:auto;border-collapse:collapse}.btw-answer th,.btw-answer td{border:1px solid var(--dsw-alias-border-l2);padding:5px 9px}\n.btw-answer ul,.btw-answer ol{padding-left:22px}.btw-answer blockquote{margin:8px 0;padding-left:10px;border-left:2px solid var(--dsw-alias-border-l4);color:var(--dsw-alias-label-secondary)}\n.btw-error{margin:0;padding:0 14px 12px;font-size:13px;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere}\n@media(max-width:480px){.btw-dock{max-height:330px}.btw-header{padding:6px 8px 6px 12px;gap:6px}.btw-answer{padding:0 12px 12px;max-height:230px}.btw-status,.btw-error{padding-left:12px}.btw-actions{gap:0}.btw-actions button{width:36px;height:36px}}\n@media(prefers-reduced-motion:reduce){.btw-spinner{animation:none}.btw-actions button{transition:none}}\n";
1
+ export declare const CSS = "\n.btw-reference-preview{display:block;margin-top:6px;max-height:3.2em;overflow:hidden;overflow-wrap:anywhere;line-height:1.6;color:var(--dsw-alias-label-primary)}\n.btw-selection-dialog,.btw-selection-menu{position:fixed;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px;font:inherit;font-size:13px;max-height:calc(100dvh - 32px);overflow:auto;box-shadow:0 12px 36px #0003}\n.btw-selection-dialog{width:min(520px,calc(100vw - 32px));margin:auto}.btw-selection-menu{margin:0;width:max-content;max-width:calc(100vw - 16px);padding:0;inset:auto;border-radius:10px;overflow:hidden;border-color:var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);font-family:inherit;font-size:12px;font-weight:500;line-height:16px;box-shadow:0 2px 6px #0001}\n.btw-selection-menu::backdrop{background:transparent}.btw-selection-dialog::backdrop{background:#0004}\n.btw-selection-dialog h3{margin:0 0 12px;font-size:16px}.btw-selection-dialog label{display:flex;flex-direction:column;gap:8px;margin-top:12px}\n.btw-selection-dialog textarea{box-sizing:border-box;width:100%;min-height:96px;resize:vertical;font:inherit;padding:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-base);color:inherit}\n.btw-selection-dialog button,.btw-selection-menu button{font:inherit;color:inherit;background:var(--dsw-alias-interactive-bg-hover);border:0;border-radius:6px;padding:8px 12px;cursor:pointer;min-height:36px}\n.btw-selection-menu [role=\"toolbar\"]{display:flex;align-items:center}.btw-selection-menu button+button{border-left:1px solid var(--dsw-alias-border-l1)}\n.btw-selection-menu button{display:inline-flex;align-items:center;height:28px;min-height:28px;padding:0 8px;border-radius:0;text-align:left;white-space:nowrap;background:transparent}.btw-selection-menu button:hover{background:var(--dsw-alias-interactive-bg-hover)}\n.btw-selection-dialog button:disabled{opacity:.5;cursor:default}.btw-selection-dialog :focus-visible,.btw-selection-menu :focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,var(--dsw-alias-label-primary));outline-offset:2px}\n.btw-selection-menu :focus-visible{outline-width:1px;outline-offset:-2px}\n.btw-selection-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.btw-selection-hint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;margin:10px 4px}\n.btw-reference{padding:0 14px 10px;font-size:12px}.btw-reference summary{cursor:pointer;color:var(--dsw-alias-label-secondary)}.btw-reference blockquote{margin:8px 0 0;padding:8px;border-left:2px solid var(--dsw-alias-border-l2);white-space:pre-wrap;overflow-wrap:anywhere;max-height:150px;overflow:auto}.btw-selection-dialog .btw-reference{padding:0}\n.btw-dock{display:flex;flex:none;flex-direction:column;gap:8px;width:calc(100% - var(--dsh-composer-side-clearance,0px) - var(--dsh-composer-side-clearance,0px));max-width:var(--dsh-composer-card-max-width,100%);margin:0 auto;max-height:440px;overflow:auto;padding:4px 0 10px;box-sizing:border-box;letter-spacing:0}\n.btw-bubble{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;min-width:0;flex-shrink:0;font-size:13px;line-height:1.6;font-family:var(--dsw-font-family,inherit);letter-spacing:0}\n.btw-header{display:flex;align-items:center;gap:8px;padding:8px 10px 8px 14px;min-width:0;min-height:44px}\n.btw-symbol{display:flex;align-items:center;color:var(--dsw-alias-label-secondary);height:28px;flex-shrink:0}\n.btw-question{flex:1;min-width:0;overflow-wrap:anywhere;font-size:13px;font-weight:500;line-height:22px;max-height:66px;overflow:auto}\n.btw-actions{display:flex;gap:4px;flex-shrink:0}\n.btw-actions button{display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;flex:none;transition:background-color 120ms ease}\n.btw-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}\n.btw-actions button:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,var(--dsw-alias-label-primary));outline-offset:-2px}\n.btw-actions button.btw-action-stop{color:var(--dsw-alias-state-error-primary,var(--dsw-alias-label-primary))}\n.btw-actions button.btw-action-stop:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-state-error-primary,var(--dsw-alias-label-primary))}\n.btw-actions button:disabled{cursor:wait;opacity:.5}\n.btw-status{display:flex;align-items:center;gap:8px;padding:0 14px 12px;color:var(--dsw-alias-label-secondary);font-size:13px}\n.btw-spinner{animation:btw-spin 1.2s linear infinite}@keyframes btw-spin{to{transform:rotate(360deg)}}\n.btw-answer{padding:0 14px 14px;max-height:300px;overflow:auto;overflow-wrap:anywhere;font-size:13px;line-height:1.7}\n.btw-answer>*:first-child{margin-top:0}.btw-answer>*:last-child{margin-bottom:0}\n.btw-answer p{margin:8px 0}.btw-answer h1,.btw-answer h2,.btw-answer h3{font-size:15px;line-height:1.5;margin:12px 0 6px;font-weight:600}\n.btw-answer pre{max-width:100%;overflow:auto;background:var(--dsw-alias-markdown-code-block);padding:10px;border-radius:8px;white-space:pre;font-size:12px}.btw-answer .katex-display{max-width:100%;overflow:auto}\n.btw-answer code{font-family:Consolas,monospace;font-size:12px}.btw-answer :not(pre)>code{background:var(--dsw-alias-markdown-code-block);border-radius:4px;padding:1px 3px}.btw-answer a{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-offset:2px;overflow-wrap:anywhere}\n.btw-answer table{display:block;max-width:100%;overflow:auto;border-collapse:collapse}.btw-answer th,.btw-answer td{border:1px solid var(--dsw-alias-border-l2);padding:5px 9px}\n.btw-answer ul,.btw-answer ol{padding-left:22px}.btw-answer blockquote{margin:8px 0;padding-left:10px;border-left:2px solid var(--dsw-alias-border-l4);color:var(--dsw-alias-label-secondary)}\n.btw-error{margin:0;padding:0 14px 12px;font-size:13px;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere}\n@media(max-width:480px){.btw-dock{max-height:330px}.btw-header{padding:6px 8px 6px 12px;gap:6px}.btw-answer{padding:0 12px 12px;max-height:230px}.btw-status,.btw-error{padding-left:12px}.btw-actions{gap:0}.btw-actions button{width:36px;height:36px}}\n@media(prefers-reduced-motion:reduce){.btw-spinner{animation:none}.btw-actions button{transition:none}}\n";
@@ -24,6 +24,7 @@ export declare const zh: {
24
24
  'action.collapse': string;
25
25
  'action.close': string;
26
26
  'action.retryClose': string;
27
+ 'action.stop': string;
27
28
  'status.answering': string;
28
29
  'status.closing': string;
29
30
  'command.hint': string;
@@ -34,6 +35,7 @@ export declare const zh: {
34
35
  'error.attachments': string;
35
36
  'error.removeAttachments': string;
36
37
  'error.backend': string;
38
+ 'error.subagent': string;
37
39
  'error.empty': string;
38
40
  'error.length': string;
39
41
  'error.capacity': string;
@@ -29,6 +29,7 @@ interface JobOptions {
29
29
  /** 卸载后所有实际资源均释放时调用;用于释放执行保护。 */
30
30
  onIdle?: () => void | Promise<void>;
31
31
  }
32
+ export declare const DEFAULT_TIMEOUT_MS = 1800000;
32
33
  /** 管理一次性任务;父会话和请求标识共同构成所有权边界。 */
33
34
  export declare class SideJobs {
34
35
  private readonly start;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-btw",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "DSH 一次性只读旁问插件:基于当前上下文回答,独立气泡,不执行工具 · One-shot read-only side-ask plugin: answers from current context in an independent bubble, no tool execution",
5
5
  "repository": {
6
6
  "type": "git",