@sanqianx/claude-ai-workbench 0.13.2 → 0.13.3

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.
Files changed (120) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +156 -156
  3. package/apps/agent-terminal/app.css +636 -613
  4. package/apps/agent-terminal/app.js +125 -65
  5. package/apps/agent-terminal/conversation-composer.js +44 -0
  6. package/apps/agent-terminal/index.html +171 -170
  7. package/apps/agent-terminal/panel-host-proof.html +152 -152
  8. package/apps/agent-terminal/panel-proof-content.html +41 -41
  9. package/apps/agent-terminal/right-workspace-dock.css +187 -159
  10. package/apps/agent-terminal/right-workspace-dock.js +623 -519
  11. package/apps/ai-profile-settings-host/index.html +25 -25
  12. package/apps/ai-profile-settings-host/page.css +1 -1
  13. package/apps/ai-profile-settings-host/page.js +113 -113
  14. package/apps/minimal-test-host/README.md +15 -15
  15. package/apps/minimal-test-host/host.css +2 -2
  16. package/apps/minimal-test-host/host.js +35 -35
  17. package/apps/minimal-test-host/index.html +58 -58
  18. package/apps/minimal-test-host/optional-mock/fault-injection.js +15 -15
  19. package/docs/sdk/FILE_LAYOUT.md +31 -31
  20. package/docs/sdk/INTEGRATION.md +126 -126
  21. package/docs/sdk/README.md +21 -21
  22. package/docs/sdk/SESSION_EVENTS.md +13 -13
  23. package/docs/sdk/openapi.yaml +8 -1
  24. package/package.json +1 -1
  25. package/packages/client/README.md +29 -29
  26. package/packages/client/index.d.ts +122 -122
  27. package/packages/client/index.js +260 -260
  28. package/packages/client/package.json +2 -2
  29. package/packages/contracts/README.md +9 -9
  30. package/packages/contracts/index.d.ts +519 -517
  31. package/packages/contracts/index.js +312 -312
  32. package/packages/contracts/package.json +1 -1
  33. package/packages/core/README.md +8 -8
  34. package/packages/core/index.d.ts +76 -76
  35. package/packages/core/index.js +222 -222
  36. package/packages/core/package.json +2 -2
  37. package/packages/server/README.md +17 -17
  38. package/packages/server/bin/agent-terminal-server.js +42 -42
  39. package/packages/server/bin/claude-workbench.js +225 -225
  40. package/packages/server/index.d.ts +171 -171
  41. package/packages/server/index.js +230 -230
  42. package/packages/server/lib/agent-discovery.js +133 -133
  43. package/packages/server/lib/agent-registry.js +143 -143
  44. package/packages/server/lib/agent-spawn-env.js +17 -17
  45. package/packages/server/lib/attachment-store.js +78 -78
  46. package/packages/server/lib/bridges/app-server-bridge.js +373 -373
  47. package/packages/server/lib/bridges/opencode-server-bridge.js +506 -506
  48. package/packages/server/lib/capture-settings-store.js +35 -35
  49. package/packages/server/lib/claude-history-recovery.js +87 -87
  50. package/packages/server/lib/cli-session-recovery.js +28 -28
  51. package/packages/server/lib/context-resolver.js +55 -55
  52. package/packages/server/lib/conversation-capture.js +51 -51
  53. package/packages/server/lib/delegation-phase-store.js +357 -357
  54. package/packages/server/lib/delegation-store.js +474 -468
  55. package/packages/server/lib/diagnostics.js +32 -32
  56. package/packages/server/lib/drivers/cli-json-runner.js +974 -974
  57. package/packages/server/lib/drivers/codex-driver.js +1442 -1442
  58. package/packages/server/lib/drivers/opencode-driver.js +1554 -1554
  59. package/packages/server/lib/drivers/zcode-driver.js +1250 -1250
  60. package/packages/server/lib/drivers/zcode-profile-runtime.js +71 -71
  61. package/packages/server/lib/event-stream.js +138 -138
  62. package/packages/server/lib/folder-picker.js +623 -623
  63. package/packages/server/lib/http-handler.js +339 -339
  64. package/packages/server/lib/json-file.js +26 -26
  65. package/packages/server/lib/knowledge-context.js +33 -33
  66. package/packages/server/lib/model-detection.js +295 -295
  67. package/packages/server/lib/parent-context.js +44 -0
  68. package/packages/server/lib/profile-store.js +283 -283
  69. package/packages/server/lib/project-store.js +102 -102
  70. package/packages/server/lib/runtime-loader.js +120 -120
  71. package/packages/server/lib/session-archive-store.js +53 -53
  72. package/packages/server/lib/session-history.js +612 -612
  73. package/packages/server/lib/system-doctor.js +125 -125
  74. package/packages/server/lib/ui-preferences-store.js +48 -48
  75. package/packages/server/lib/workbench-service.js +1696 -1675
  76. package/packages/server/package.json +2 -2
  77. package/packages/ui/README.md +30 -30
  78. package/packages/ui/ai-profile-settings.d.ts +45 -45
  79. package/packages/ui/ai-profile-settings.js +283 -283
  80. package/packages/ui/claude-terminal.d.ts +31 -31
  81. package/packages/ui/claude-terminal.js +343 -343
  82. package/packages/ui/index.d.ts +18 -18
  83. package/packages/ui/index.js +51 -51
  84. package/packages/ui/package.json +2 -2
  85. package/packages/ui/theme.css +13 -13
  86. package/src/assets/claude-prompts.json +21 -21
  87. package/src/backend/http/ai-profile-routes.js +61 -61
  88. package/src/backend/http/claude-routes.js +170 -170
  89. package/src/backend/http/node-helpers.js +45 -45
  90. package/src/backend/index.js +21 -21
  91. package/src/backend/lib/ai-adapter.js +144 -144
  92. package/src/backend/lib/ai-profile-store.js +259 -259
  93. package/src/backend/lib/ai-vendor-presets.js +83 -83
  94. package/src/backend/lib/ai-workspace.js +140 -140
  95. package/src/backend/lib/automation-config.js +253 -253
  96. package/src/backend/lib/claude-cli-runner.js +2392 -2392
  97. package/src/backend/lib/data-dir.js +169 -169
  98. package/src/backend/lib/llm-client.js +176 -176
  99. package/src/backend/lib/model-context-windows.js +82 -82
  100. package/src/backend/lib/native-task-control.js +217 -217
  101. package/src/backend/lib/native-task-history-index.js +475 -475
  102. package/src/backend/lib/native-task-history-reader.js +526 -526
  103. package/src/backend/lib/native-task-messages.js +457 -457
  104. package/src/backend/lib/native-task-registry.js +325 -325
  105. package/src/backend/lib/native-task-runtime.js +242 -242
  106. package/src/backend/lib/native-task-trace.js +440 -440
  107. package/src/backend/lib/native-task-transcript.js +393 -393
  108. package/src/backend/lib/prompt-registry.js +166 -166
  109. package/src/backend/lib/runtime-config.js +121 -121
  110. package/src/backend/lib/sdk-session-stream.js +143 -143
  111. package/src/backend/lib/sdk-turn-stream.js +16 -16
  112. package/src/backend/lib/store-scope.js +131 -131
  113. package/src/backend/lib/task-identity.js +104 -104
  114. package/src/backend/lib/task-state-projection.js +58 -58
  115. package/src/index.js +7 -7
  116. package/src/sdk-wave-a.ts +1 -1
  117. package/src/sdk-wave-b.ts +1 -1
  118. package/src/sdk-wave-c.ts +1 -1
  119. package/src/sdk-wave-d.ts +1 -1
  120. package/src/sdk-wave-e.ts +1 -1
@@ -115,9 +115,29 @@ const dock = window.createRightWorkspaceDock && client
115
115
  client, apiBase, document,
116
116
  storage: typeof localStorage !== 'undefined' ? localStorage : null,
117
117
  renderMarkdown, toast,
118
+ renderToolStep: (event, input) => buildToolStep(event, input, { taskLinks: false }),
119
+ renderToolResult: (step, event) => finishToolStep(step, event, true),
120
+ renderMessage: (role, text) => buildConversationMessage(role, text),
121
+ renderProcess: () => buildConversationProcess(),
122
+ renderPageIcon: name => icon(name || 'file', 16),
123
+ createComposer: options => window.createConversationComposer({ ...options, document, icon }),
124
+ getSelection: () => {
125
+ const selected = state.sessionSummary || {};
126
+ return { agentId: selected.agentId || state.currentAgent, aiProfileId: selected.aiProfileId === undefined ? state.currentProfileId : selected.aiProfileId,
127
+ ...(state.currentModel ? { model: state.currentModel } : {}) };
128
+ },
129
+ taskTitle: toolUseId => {
130
+ const step = $('stream-col')?.querySelector(`.step[data-tool-id="${cssEscape(toolUseId || '')}"]`);
131
+ return step?._toolInput?.description || '';
132
+ },
133
+ renderChanges: events => {
134
+ const ctx = { changes: new Map() };
135
+ for (const event of events) collectTurnChanges(ctx, event, event.input || {});
136
+ return ctx.changes.size ? renderTurnDiffs(ctx.changes) : null;
137
+ },
118
138
  registry: panelRegistry,
119
139
  onPanelsChanged: reason => postPanelState(reason),
120
- onBackgroundActivityChanged: activity => renderSessionStatus(activity),
140
+ onBackgroundActivityChanged: activity => { renderSessionStatus(activity); renderBackgroundTasks(); },
121
141
  })
122
142
  : null;
123
143
 
@@ -971,6 +991,7 @@ async function openSession(sessionId, options = {}) {
971
991
  try {
972
992
  for (const event of historyView.events) { renderEvent(event); if (!page) last = Math.max(last, event.sequence || 0); }
973
993
  } finally { state.replaying = false; }
994
+ renderBackgroundTasks();
974
995
  rememberTitleFromEvents(historyView.events);
975
996
  // The SSE replay window can overlap the snapshot (a slow turn raced the
976
997
  // open, or the server answered a stale cursor with a full replay). Late or
@@ -994,6 +1015,7 @@ async function openSession(sessionId, options = {}) {
994
1015
  replayThrough = Number(event.highWater);
995
1016
  if (!Number.isFinite(replayThrough) || replayThrough < 0) replayThrough = 0;
996
1017
  resetStream();
1018
+ renderBackgroundTasks();
997
1019
  state.replaying = true;
998
1020
  return;
999
1021
  }
@@ -1052,6 +1074,7 @@ function rebuildHistory(view, events, prepend = true) {
1052
1074
  state.replaying = true;
1053
1075
  try { for (const event of events) renderEvent(event); }
1054
1076
  finally { state.replaying = false; state.follow = following; state.scrollIntent = intent; }
1077
+ renderBackgroundTasks();
1055
1078
  const rebuiltFolds = [...($('stream-col').querySelectorAll?.('details') || [])];
1056
1079
  const offset = rebuiltFolds.length - folds.length;
1057
1080
  if (offset >= 0) for (let i = 0; i < folds.length; i++) rebuiltFolds[offset + i].open = folds[i];
@@ -1234,17 +1257,12 @@ function startTurn() {
1234
1257
 
1235
1258
  function ensureProc(t) {
1236
1259
  if (!(t.proc && t.proc.isConnected)) {
1237
- const details = el('details', 'proc');
1260
+ const process = buildConversationProcess();
1261
+ const details = process.node;
1238
1262
  details.open = true; // live process output stays visible while running; folds when the turn ends
1239
- const summary = el('summary', null, '');
1240
- const procCaret = el('span', 'caret');
1241
- procCaret.append(icon('caret', 12));
1242
- summary.append(procCaret);
1243
- t.meta = el('span', 'p-meta', '工作中 0 秒');
1244
- summary.append(t.meta);
1245
- details.append(summary);
1246
- t.steps = el('div', 'proc-steps');
1247
- details.append(t.steps);
1263
+ t.meta = process.meta;
1264
+ t.meta.textContent = '工作中 0 秒';
1265
+ t.steps = process.steps;
1248
1266
  $('stream-col').append(details);
1249
1267
  t.proc = details;
1250
1268
  if (!state.replaying) {
@@ -1411,32 +1429,32 @@ function restoreBackgroundTasks(tasks) {
1411
1429
  renderBackgroundTasks();
1412
1430
  }
1413
1431
  function renderBackgroundTasks() {
1414
- const panel = $('background-tasks');
1432
+ let panel = $('background-tasks');
1433
+ if (!panel && $('stream-col')) { panel = el('section', 'background-tasks'); panel.id = 'background-tasks'; panel.setAttribute('aria-label', '子代理任务'); $('stream-col').append(panel); }
1415
1434
  if (!panel || !panel.dataset) return;
1416
- const signature = JSON.stringify([state.caps?.backgroundTasks, [...backgroundTasks.values()]]);
1435
+ const indexed = state.activeSessionId && dock && typeof dock.taskViews === 'function' ? dock.taskViews(state.activeSessionId) : [];
1436
+ const tasks = [...backgroundTasks.values()].filter(task => !indexed.some(view => view.taskId && view.taskId === task.taskId));
1437
+ tasks.push(...indexed);
1438
+ const signature = JSON.stringify([state.caps?.backgroundTasks, tasks]);
1417
1439
  if (panel.dataset?.signature === signature) return;
1418
1440
  if (panel.dataset) panel.dataset.signature = signature;
1419
1441
  panel.textContent = '';
1420
- const unsupported = state.caps?.backgroundTasks === false;
1421
- panel.hidden = !unsupported && !backgroundTasks.size;
1422
- if (unsupported) { panel.append(el('p', 'task-detail', '当前 Agent 未提供后台任务状态;文字声明不能证明任务已启动。')); return; }
1423
- const running = [...backgroundTasks.values()].filter(t => ['running', 'pending'].includes(t.status)).length;
1424
- panel.append(el('strong', null, `后台任务 · ${running} 个运行中`));
1442
+ const unsupported = state.caps?.backgroundTasks === false && !indexed.length;
1443
+ panel.hidden = !tasks.length;
1444
+ if (unsupported) return;
1445
+ const running = tasks.filter(t => ['running', 'pending'].includes(t.status)).length;
1446
+ panel.setAttribute('aria-label', '子代理任务');
1425
1447
  // 单个后台任务的精确停止 API 未交付:本切片没有任何停止入口,也不以
1426
1448
  // 主会话 abort 冒充(stopCurrent 仅属于前台发送按钮)。
1427
- for (const task of backgroundTasks.values()) {
1428
- const card = el('article', 'task-card');
1429
- card.dataset.taskId = task.taskId;
1430
- card.append(el('strong', null, task.description || task.taskId));
1431
- card.append(el('div', null, ({ pending: '待执行', running: '运行中', completed: '已完成', failed: '失败', stopped: '已停止' })[task.status] || task.status));
1432
- const detail = [task.lastToolName && `最近工具:${task.lastToolName}`, task.summary,
1433
- task.usage && `工具调用 ${task.usage.tool_uses ?? '未知'} · 用时 ${task.usage.duration_ms ?? '未知'}ms`].filter(Boolean).join('\n');
1434
- if (detail) card.append(el('div', 'task-detail', detail));
1435
- if (task.taskRef && dock) {
1436
- const open = el('button', 'linklike', '在右侧任务面板查看');
1437
- open.addEventListener('click', () => { dock.openTask(task.taskRef); });
1438
- card.append(open);
1439
- }
1449
+ for (const task of tasks) {
1450
+ const card = el('button', 'task-card task-row');
1451
+ card.type = 'button'; card.dataset.taskId = task.taskId || task.delegationId || '';
1452
+ card.append(icon('agent', 14), el('span', 'task-row-title', task.description || '子代理任务'));
1453
+ card.append(el('span', 'task-row-status', task.statusLabel || ({ pending: '待执行', running: '运行中', completed: '已完成', failed: '失败', stopped: '已停止' })[task.status] || '状态待确认'));
1454
+ card.append(icon('arrowLeft', 13));
1455
+ card.setAttribute('aria-label', '打开子代理:' + (task.description || '子代理任务'));
1456
+ card.disabled = !(task.taskRef || task.delegationId);
1457
+ card.addEventListener('click', () => { if (!dock) return; if (task.delegationId) dock.openDelegation(task.delegationId); else dock.openTask(task.taskRef, { reuse: true }); });
1440
1458
  panel.append(card);
1441
1459
  }
1442
1460
  }
@@ -1487,15 +1505,8 @@ function renderEvent(event) {
1487
1505
  state.completedReply = null;
1488
1506
  rememberTitleFromEvents([event]);
1489
1507
  if (state.activeSessionId && event.text) state.sessionTitles.set(state.activeSessionId, event.text.replace(/\s+/g, ' ').slice(0, 24));
1490
- const wrap = el('div', 'msg user');
1508
+ const wrap = buildConversationMessage('user', event.text || '');
1491
1509
  wrap.dataset.historyKey = historyEventKey(event);
1492
- const bubble = el('div', 'bubble', event.text || '');
1493
- wrap.append(bubble);
1494
- const copy = el('button', 'bubble-copy');
1495
- copy.title = '复制';
1496
- copy.append(icon('copy', 13));
1497
- copy.addEventListener('click', () => copyText(event.text || '', copy));
1498
- wrap.append(copy);
1499
1510
  if (Array.isArray(event.attachments) && event.attachments.length) {
1500
1511
  const tray = el('div', 'attach-tray');
1501
1512
  for (const item of event.attachments) {
@@ -1522,9 +1533,9 @@ function renderEvent(event) {
1522
1533
  stampThinking(t);
1523
1534
  const ctx = t || startTurn();
1524
1535
  if (!ctx.liveText || !ctx.liveText.isConnected) {
1525
- const msg = el('div', 'msg ai');
1526
- const body = el('div', 'md-body streaming');
1527
- msg.append(body);
1536
+ const msg = buildConversationMessage('assistant', '');
1537
+ const body = msg.querySelector('.md-body');
1538
+ body.classList.add('streaming');
1528
1539
  col.append(msg);
1529
1540
  ctx.liveText = msg;
1530
1541
  ctx.liveBody = body;
@@ -1570,22 +1581,7 @@ function renderEvent(event) {
1570
1581
  case 'claude/tool-result': {
1571
1582
  const target = col.querySelector(`.step[data-tool-id="${cssEscape(event.id || '')}"]`);
1572
1583
  const step = target || col.appendChild(buildToolStep({ id: event.id, name: event.toolName || '' }, {}));
1573
- const body = step.querySelector('.step-body');
1574
- if (body) {
1575
- const output = event.output && typeof event.output === 'object' ? JSON.stringify(event.output, null, 2) : String(event.output || '');
1576
- body.append(ioCard('text', output.slice(0, 4000), '输出'));
1577
- }
1578
- renderFileChanges(step, event, step._toolInput || {});
1579
- const meta = step.querySelector('.s-meta');
1580
- if (meta) {
1581
- const done = event.isError ? '失败' : '完成';
1582
- const seconds = state.replaying ? null : Math.round((Date.now() - (step._startedAt || Date.now())) / 1000);
1583
- meta.textContent = seconds == null ? done : `${done} · ${fmtElapsed(seconds * 1000)}`;
1584
- meta.classList.remove('running');
1585
- meta.classList.toggle('failed', Boolean(event.isError));
1586
- }
1587
- step.classList.add('done');
1588
- if (event.isError) step.classList.add('failed');
1584
+ finishToolStep(step, event, state.replaying);
1589
1585
  if (t) collectTurnChanges(t, event, step._toolInput || {});
1590
1586
  break;
1591
1587
  }
@@ -1639,11 +1635,7 @@ function renderEvent(event) {
1639
1635
  col.append(err);
1640
1636
  } else {
1641
1637
  if (!hadLiveText && event.result) {
1642
- const msg = el('div', 'msg ai');
1643
- const body = el('div', 'md-body');
1644
- msg.append(body);
1645
- col.append(msg);
1646
- setMarkdown(body, String(event.result));
1638
+ col.append(buildConversationMessage('assistant', String(event.result)));
1647
1639
  }
1648
1640
  const lastMsg = [...col.children].reverse().find(node => node.classList && node.classList.contains('ai'));
1649
1641
  if (lastMsg) lastMsg.append(msgFooter(String(event.result || lastMsg.textContent), lastPrompt, { model: turnModel, at: event.at }));
@@ -1749,7 +1741,37 @@ function renderEvent(event) {
1749
1741
  if (stream && state.follow) stream.scrollTop = stream.scrollHeight;
1750
1742
  }
1751
1743
 
1752
- function buildToolStep(event, input) {
1744
+ // Shared conversation primitives keep task views in the same product language.
1745
+ function buildConversationMessage(role, text) {
1746
+ const user = role === 'user';
1747
+ const node = el('div', user ? 'msg user' : 'msg ai');
1748
+ const body = el('div', user ? 'bubble' : 'md-body');
1749
+ if (user) body.textContent = text;
1750
+ else setMarkdown(body, text);
1751
+ node.append(body);
1752
+ if (user) {
1753
+ const copy = el('button', 'bubble-copy');
1754
+ copy.title = '复制';
1755
+ copy.append(icon('copy', 13));
1756
+ copy.addEventListener('click', () => copyText(text, copy));
1757
+ node.append(copy);
1758
+ }
1759
+ return node;
1760
+ }
1761
+
1762
+ function buildConversationProcess() {
1763
+ const node = el('details', 'proc');
1764
+ const summary = el('summary');
1765
+ const caret = el('span', 'caret');
1766
+ caret.append(icon('caret', 12));
1767
+ const meta = el('span', 'p-meta');
1768
+ const steps = el('div', 'proc-steps');
1769
+ summary.append(caret, meta);
1770
+ node.append(summary, steps);
1771
+ return { node, meta, steps };
1772
+ }
1773
+
1774
+ function buildToolStep(event, input, options = {}) {
1753
1775
  const step = el('details', 'step');
1754
1776
  step.dataset.toolId = event.id || '';
1755
1777
  step._toolInput = input;
@@ -1760,6 +1782,18 @@ function buildToolStep(event, input) {
1760
1782
  const caret = el('span', 's-caret');
1761
1783
  caret.append(icon('caret', 12));
1762
1784
  summary.append(caret, icon(TOOL_ICONS[verb] || 'dot', 13), el('span', 's-name', verb), el('span', 's-desc', toolDetail(event.name, input)), buildToolArgs(input), el('span', 's-meta running', '运行中'));
1785
+ if (options.taskLinks !== false && verb === '子任务' && event.id && dock && typeof dock.openTaskForToolUseId === 'function') {
1786
+ const open = el('button', 'task-open', '打开子代理');
1787
+ open.type = 'button';
1788
+ open.addEventListener('click', async ev => {
1789
+ ev.preventDefault();
1790
+ ev.stopPropagation();
1791
+ open.disabled = true;
1792
+ try { await dock.openTaskForToolUseId(event.id); }
1793
+ finally { open.disabled = false; }
1794
+ });
1795
+ summary.append(open);
1796
+ }
1763
1797
  step.append(summary);
1764
1798
  // Edit/write steps change the workspace; keep them visible when the fold opens.
1765
1799
  if (verb === '编辑' || verb === '写入') step.open = true;
@@ -1770,6 +1804,27 @@ function buildToolStep(event, input) {
1770
1804
  return step;
1771
1805
  }
1772
1806
 
1807
+ // Both the main conversation and task panels use the same tool/result and
1808
+ // file-change components, so read-only task views retain the product UI.
1809
+ function finishToolStep(step, event, replaying = true) {
1810
+ const body = step.querySelector('.step-body');
1811
+ if (body) {
1812
+ const output = event.output && typeof event.output === 'object' ? JSON.stringify(event.output, null, 2) : String(event.output ?? '');
1813
+ body.append(ioCard('text', output, '输出'));
1814
+ }
1815
+ renderFileChanges(step, event, step._toolInput || {});
1816
+ const meta = step.querySelector('.s-meta');
1817
+ if (meta) {
1818
+ const done = event.isError ? '失败' : '完成';
1819
+ const seconds = replaying ? null : Math.round((Date.now() - (step._startedAt || Date.now())) / 1000);
1820
+ meta.textContent = seconds == null ? done : `${done} · ${fmtElapsed(seconds * 1000)}`;
1821
+ meta.classList.remove('running');
1822
+ meta.classList.toggle('failed', Boolean(event.isError));
1823
+ }
1824
+ step.classList.add('done');
1825
+ if (event.isError) step.classList.add('failed');
1826
+ }
1827
+
1773
1828
  const TOOL_ICONS = {
1774
1829
  '命令': 'terminal', '读取': 'file', '写入': 'pencil', '编辑': 'pencil',
1775
1830
  '搜索': 'search', '网络': 'globe', '子任务': 'agent',
@@ -1800,6 +1855,7 @@ function toolVerb(name) {
1800
1855
 
1801
1856
  function toolDetail(name, input) {
1802
1857
  const tool = String(name || '').toLowerCase();
1858
+ if (tool === 'agent' || tool === 'task') return String(input.description || input.prompt || name).replace(/\s+/g, ' ').slice(0, 90);
1803
1859
  const file = input.file_path || input.path || input.filePath || '';
1804
1860
  if (tool === 'bash' && input.command) return String(input.command).replace(/\s+/g, ' ').slice(0, 90);
1805
1861
  if (file) return String(file).slice(0, 90);
@@ -2216,6 +2272,8 @@ async function refreshSessionRow() { await renderProjectList(); }
2216
2272
 
2217
2273
  function wireComposer() {
2218
2274
  const input = $('chat-input');
2275
+ if (window.bindConversationComposer) window.bindConversationComposer({ input, send: $('btn-send'), onSend: sendCurrent, onStop: stopCurrent, isBusy: () => state.busy, onInput: saveCurrentDraft });
2276
+ else {
2219
2277
  input.addEventListener('input', () => {
2220
2278
  input.style.height = 'auto';
2221
2279
  input.style.height = `${Math.min(150, input.scrollHeight)}px`;
@@ -2227,6 +2285,7 @@ function wireComposer() {
2227
2285
  $('btn-send').addEventListener('click', () => {
2228
2286
  if (state.busy) { stopCurrent(); } else { sendCurrent(); }
2229
2287
  });
2288
+ }
2230
2289
  $('btn-attach').addEventListener('click', () => $('file-input').click());
2231
2290
  $('file-input').addEventListener('change', async event => {
2232
2291
  for (const file of event.target.files || []) await addAttachment(file);
@@ -3073,6 +3132,7 @@ function panelStatePayload(reason) {
3073
3132
  open: true,
3074
3133
  active: tab.id === snap.activeTabId,
3075
3134
  error: tab.hostError || null,
3135
+ ready: tab.hostReady === true,
3076
3136
  content: tab.content || null,
3077
3137
  scope: snap.scope ? { projectId: snap.scope.contextId, sessionId: snap.scope.parentSessionId } : null,
3078
3138
  })) : [];
@@ -3168,7 +3228,7 @@ window.addEventListener('message', async ev => {
3168
3228
  // hosts may only ever register same-origin iframe panels.
3169
3229
  const descriptor = Object.assign({}, data.panel, { kind: 'iframe', origin: ev.origin });
3170
3230
  const result = panelRegistry.register(descriptor, hostPanelOwner);
3171
- if (result.ok) postPanelState('register');
3231
+ if (result.ok) { dock.refreshProviders?.(); postPanelState('register'); }
3172
3232
  reply({ type: 'kb:panel/registered', ok: result.ok, code: result.code || null, error: result.message || null, providerId: result.ok ? result.providerId : null });
3173
3233
  return;
3174
3234
  }
@@ -0,0 +1,44 @@
1
+ (function (global) {
2
+ 'use strict';
3
+ function bindConversationComposer(options) {
4
+ const { input, send, onSend, onStop, onInput } = options;
5
+ input.addEventListener('input', () => {
6
+ input.style.height = 'auto';
7
+ input.style.height = Math.min(150, input.scrollHeight || 48) + 'px';
8
+ if (onInput) onInput(input.value);
9
+ });
10
+ input.addEventListener('keydown', event => {
11
+ if (event.key === 'Enter' && !event.shiftKey && !event.isComposing) {
12
+ event.preventDefault();
13
+ if (!options.isBusy || !options.isBusy()) onSend();
14
+ }
15
+ });
16
+ send.addEventListener('click', () => {
17
+ if (options.isBusy && options.isBusy()) { if (onStop) onStop(); }
18
+ else onSend();
19
+ });
20
+ }
21
+ function createConversationComposer(options) {
22
+ const doc = options.document;
23
+ const node = doc.createElement('div'); node.className = 'composer-wrap rd-composer-wrap';
24
+ const box = doc.createElement('div'); box.className = 'composer';
25
+ const input = doc.createElement('textarea'); input.rows = 1;
26
+ input.placeholder = options.placeholder || '给子代理发送消息';
27
+ input.setAttribute('aria-label', input.placeholder); input.value = options.value || '';
28
+ const row = doc.createElement('div'); row.className = 'cmdrow';
29
+ const label = doc.createElement('span'); label.className = 'rd-composer-label'; label.textContent = '跟随主对话 · 默认防护';
30
+ const send = doc.createElement('button'); send.type = 'button'; send.className = 'send';
31
+ if (options.icon) send.append(options.icon('send', 16)); else send.textContent = '↑';
32
+ send.setAttribute('aria-label', '发送子代理消息');
33
+ row.append(label, send); box.append(input, row); node.append(box);
34
+ bindConversationComposer({ ...options, input, send });
35
+ return { node, input, send, setLabel(text) { label.textContent = text; }, setBusy(busy) {
36
+ send.setAttribute('aria-label', busy ? '停止子代理' : '发送子代理消息');
37
+ send.textContent = '';
38
+ if (options.icon) send.append(options.icon(busy ? 'stop' : 'send', 16)); else send.textContent = busy ? '■' : '↑';
39
+ input.disabled = Boolean(options.disabled && options.disabled());
40
+ } };
41
+ }
42
+ global.bindConversationComposer = bindConversationComposer;
43
+ global.createConversationComposer = createConversationComposer;
44
+ })(typeof window === 'undefined' ? globalThis : window);