@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.
- package/LICENSE +201 -201
- package/README.md +156 -156
- package/apps/agent-terminal/app.css +636 -613
- package/apps/agent-terminal/app.js +125 -65
- package/apps/agent-terminal/conversation-composer.js +44 -0
- package/apps/agent-terminal/index.html +171 -170
- package/apps/agent-terminal/panel-host-proof.html +152 -152
- package/apps/agent-terminal/panel-proof-content.html +41 -41
- package/apps/agent-terminal/right-workspace-dock.css +187 -159
- package/apps/agent-terminal/right-workspace-dock.js +623 -519
- package/apps/ai-profile-settings-host/index.html +25 -25
- package/apps/ai-profile-settings-host/page.css +1 -1
- package/apps/ai-profile-settings-host/page.js +113 -113
- package/apps/minimal-test-host/README.md +15 -15
- package/apps/minimal-test-host/host.css +2 -2
- package/apps/minimal-test-host/host.js +35 -35
- package/apps/minimal-test-host/index.html +58 -58
- package/apps/minimal-test-host/optional-mock/fault-injection.js +15 -15
- package/docs/sdk/FILE_LAYOUT.md +31 -31
- package/docs/sdk/INTEGRATION.md +126 -126
- package/docs/sdk/README.md +21 -21
- package/docs/sdk/SESSION_EVENTS.md +13 -13
- package/docs/sdk/openapi.yaml +8 -1
- package/package.json +1 -1
- package/packages/client/README.md +29 -29
- package/packages/client/index.d.ts +122 -122
- package/packages/client/index.js +260 -260
- package/packages/client/package.json +2 -2
- package/packages/contracts/README.md +9 -9
- package/packages/contracts/index.d.ts +519 -517
- package/packages/contracts/index.js +312 -312
- package/packages/contracts/package.json +1 -1
- package/packages/core/README.md +8 -8
- package/packages/core/index.d.ts +76 -76
- package/packages/core/index.js +222 -222
- package/packages/core/package.json +2 -2
- package/packages/server/README.md +17 -17
- package/packages/server/bin/agent-terminal-server.js +42 -42
- package/packages/server/bin/claude-workbench.js +225 -225
- package/packages/server/index.d.ts +171 -171
- package/packages/server/index.js +230 -230
- package/packages/server/lib/agent-discovery.js +133 -133
- package/packages/server/lib/agent-registry.js +143 -143
- package/packages/server/lib/agent-spawn-env.js +17 -17
- package/packages/server/lib/attachment-store.js +78 -78
- package/packages/server/lib/bridges/app-server-bridge.js +373 -373
- package/packages/server/lib/bridges/opencode-server-bridge.js +506 -506
- package/packages/server/lib/capture-settings-store.js +35 -35
- package/packages/server/lib/claude-history-recovery.js +87 -87
- package/packages/server/lib/cli-session-recovery.js +28 -28
- package/packages/server/lib/context-resolver.js +55 -55
- package/packages/server/lib/conversation-capture.js +51 -51
- package/packages/server/lib/delegation-phase-store.js +357 -357
- package/packages/server/lib/delegation-store.js +474 -468
- package/packages/server/lib/diagnostics.js +32 -32
- package/packages/server/lib/drivers/cli-json-runner.js +974 -974
- package/packages/server/lib/drivers/codex-driver.js +1442 -1442
- package/packages/server/lib/drivers/opencode-driver.js +1554 -1554
- package/packages/server/lib/drivers/zcode-driver.js +1250 -1250
- package/packages/server/lib/drivers/zcode-profile-runtime.js +71 -71
- package/packages/server/lib/event-stream.js +138 -138
- package/packages/server/lib/folder-picker.js +623 -623
- package/packages/server/lib/http-handler.js +339 -339
- package/packages/server/lib/json-file.js +26 -26
- package/packages/server/lib/knowledge-context.js +33 -33
- package/packages/server/lib/model-detection.js +295 -295
- package/packages/server/lib/parent-context.js +44 -0
- package/packages/server/lib/profile-store.js +283 -283
- package/packages/server/lib/project-store.js +102 -102
- package/packages/server/lib/runtime-loader.js +120 -120
- package/packages/server/lib/session-archive-store.js +53 -53
- package/packages/server/lib/session-history.js +612 -612
- package/packages/server/lib/system-doctor.js +125 -125
- package/packages/server/lib/ui-preferences-store.js +48 -48
- package/packages/server/lib/workbench-service.js +1696 -1675
- package/packages/server/package.json +2 -2
- package/packages/ui/README.md +30 -30
- package/packages/ui/ai-profile-settings.d.ts +45 -45
- package/packages/ui/ai-profile-settings.js +283 -283
- package/packages/ui/claude-terminal.d.ts +31 -31
- package/packages/ui/claude-terminal.js +343 -343
- package/packages/ui/index.d.ts +18 -18
- package/packages/ui/index.js +51 -51
- package/packages/ui/package.json +2 -2
- package/packages/ui/theme.css +13 -13
- package/src/assets/claude-prompts.json +21 -21
- package/src/backend/http/ai-profile-routes.js +61 -61
- package/src/backend/http/claude-routes.js +170 -170
- package/src/backend/http/node-helpers.js +45 -45
- package/src/backend/index.js +21 -21
- package/src/backend/lib/ai-adapter.js +144 -144
- package/src/backend/lib/ai-profile-store.js +259 -259
- package/src/backend/lib/ai-vendor-presets.js +83 -83
- package/src/backend/lib/ai-workspace.js +140 -140
- package/src/backend/lib/automation-config.js +253 -253
- package/src/backend/lib/claude-cli-runner.js +2392 -2392
- package/src/backend/lib/data-dir.js +169 -169
- package/src/backend/lib/llm-client.js +176 -176
- package/src/backend/lib/model-context-windows.js +82 -82
- package/src/backend/lib/native-task-control.js +217 -217
- package/src/backend/lib/native-task-history-index.js +475 -475
- package/src/backend/lib/native-task-history-reader.js +526 -526
- package/src/backend/lib/native-task-messages.js +457 -457
- package/src/backend/lib/native-task-registry.js +325 -325
- package/src/backend/lib/native-task-runtime.js +242 -242
- package/src/backend/lib/native-task-trace.js +440 -440
- package/src/backend/lib/native-task-transcript.js +393 -393
- package/src/backend/lib/prompt-registry.js +166 -166
- package/src/backend/lib/runtime-config.js +121 -121
- package/src/backend/lib/sdk-session-stream.js +143 -143
- package/src/backend/lib/sdk-turn-stream.js +16 -16
- package/src/backend/lib/store-scope.js +131 -131
- package/src/backend/lib/task-identity.js +104 -104
- package/src/backend/lib/task-state-projection.js +58 -58
- package/src/index.js +7 -7
- package/src/sdk-wave-a.ts +1 -1
- package/src/sdk-wave-b.ts +1 -1
- package/src/sdk-wave-c.ts +1 -1
- package/src/sdk-wave-d.ts +1 -1
- 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
|
|
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
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
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
|
-
|
|
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
|
|
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 = !
|
|
1422
|
-
if (unsupported)
|
|
1423
|
-
const running =
|
|
1424
|
-
panel.
|
|
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
|
|
1428
|
-
const card = el('
|
|
1429
|
-
card.dataset.taskId = task.taskId;
|
|
1430
|
-
card.append(el('
|
|
1431
|
-
card.append(el('
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
if (task.taskRef
|
|
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 =
|
|
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 =
|
|
1526
|
-
const body =
|
|
1527
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|