@sanqianx/claude-ai-workbench 0.13.2 → 0.13.4
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 +652 -613
- package/apps/agent-terminal/app.js +143 -67
- package/apps/agent-terminal/conversation-composer.js +44 -0
- package/apps/agent-terminal/index.html +172 -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 +189 -159
- package/apps/agent-terminal/right-workspace-dock.js +652 -521
- package/apps/agent-terminal/user-question-card.js +68 -0
- 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 +15 -13
- package/docs/sdk/openapi.yaml +16 -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 +1461 -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/user-questions.js +67 -0
- 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 +2408 -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,11 @@ 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
|
+
if (snapshot.permission?.kind === 'question') renderEvent({ type: 'claude/permission-request', ...snapshot.permission });
|
|
995
|
+
for (const card of state.questionCards?.values() || []) {
|
|
996
|
+
if (!card.classList.contains('question-settled') && card.dataset.permId !== snapshot.permission?.requestId) card.settle({ cancelled: true });
|
|
997
|
+
}
|
|
998
|
+
renderBackgroundTasks();
|
|
974
999
|
rememberTitleFromEvents(historyView.events);
|
|
975
1000
|
// The SSE replay window can overlap the snapshot (a slow turn raced the
|
|
976
1001
|
// open, or the server answered a stale cursor with a full replay). Late or
|
|
@@ -994,6 +1019,7 @@ async function openSession(sessionId, options = {}) {
|
|
|
994
1019
|
replayThrough = Number(event.highWater);
|
|
995
1020
|
if (!Number.isFinite(replayThrough) || replayThrough < 0) replayThrough = 0;
|
|
996
1021
|
resetStream();
|
|
1022
|
+
renderBackgroundTasks();
|
|
997
1023
|
state.replaying = true;
|
|
998
1024
|
return;
|
|
999
1025
|
}
|
|
@@ -1052,6 +1078,7 @@ function rebuildHistory(view, events, prepend = true) {
|
|
|
1052
1078
|
state.replaying = true;
|
|
1053
1079
|
try { for (const event of events) renderEvent(event); }
|
|
1054
1080
|
finally { state.replaying = false; state.follow = following; state.scrollIntent = intent; }
|
|
1081
|
+
renderBackgroundTasks();
|
|
1055
1082
|
const rebuiltFolds = [...($('stream-col').querySelectorAll?.('details') || [])];
|
|
1056
1083
|
const offset = rebuiltFolds.length - folds.length;
|
|
1057
1084
|
if (offset >= 0) for (let i = 0; i < folds.length; i++) rebuiltFolds[offset + i].open = folds[i];
|
|
@@ -1148,6 +1175,7 @@ function rememberTitleFromEvents(events) {
|
|
|
1148
1175
|
}
|
|
1149
1176
|
|
|
1150
1177
|
function resetStream() {
|
|
1178
|
+
state.questionCards?.clear();
|
|
1151
1179
|
endTurnVisuals();
|
|
1152
1180
|
const col = $('stream-col');
|
|
1153
1181
|
col.textContent = '';
|
|
@@ -1234,17 +1262,12 @@ function startTurn() {
|
|
|
1234
1262
|
|
|
1235
1263
|
function ensureProc(t) {
|
|
1236
1264
|
if (!(t.proc && t.proc.isConnected)) {
|
|
1237
|
-
const
|
|
1265
|
+
const process = buildConversationProcess();
|
|
1266
|
+
const details = process.node;
|
|
1238
1267
|
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);
|
|
1268
|
+
t.meta = process.meta;
|
|
1269
|
+
t.meta.textContent = '工作中 0 秒';
|
|
1270
|
+
t.steps = process.steps;
|
|
1248
1271
|
$('stream-col').append(details);
|
|
1249
1272
|
t.proc = details;
|
|
1250
1273
|
if (!state.replaying) {
|
|
@@ -1411,32 +1434,32 @@ function restoreBackgroundTasks(tasks) {
|
|
|
1411
1434
|
renderBackgroundTasks();
|
|
1412
1435
|
}
|
|
1413
1436
|
function renderBackgroundTasks() {
|
|
1414
|
-
|
|
1437
|
+
let panel = $('background-tasks');
|
|
1438
|
+
if (!panel && $('stream-col')) { panel = el('section', 'background-tasks'); panel.id = 'background-tasks'; panel.setAttribute('aria-label', '子代理任务'); $('stream-col').append(panel); }
|
|
1415
1439
|
if (!panel || !panel.dataset) return;
|
|
1416
|
-
const
|
|
1440
|
+
const indexed = state.activeSessionId && dock && typeof dock.taskViews === 'function' ? dock.taskViews(state.activeSessionId) : [];
|
|
1441
|
+
const tasks = [...backgroundTasks.values()].filter(task => !indexed.some(view => view.taskId && view.taskId === task.taskId));
|
|
1442
|
+
tasks.push(...indexed);
|
|
1443
|
+
const signature = JSON.stringify([state.caps?.backgroundTasks, tasks]);
|
|
1417
1444
|
if (panel.dataset?.signature === signature) return;
|
|
1418
1445
|
if (panel.dataset) panel.dataset.signature = signature;
|
|
1419
1446
|
panel.textContent = '';
|
|
1420
|
-
const unsupported = state.caps?.backgroundTasks === false;
|
|
1421
|
-
panel.hidden = !
|
|
1422
|
-
if (unsupported)
|
|
1423
|
-
const running =
|
|
1424
|
-
panel.
|
|
1447
|
+
const unsupported = state.caps?.backgroundTasks === false && !indexed.length;
|
|
1448
|
+
panel.hidden = !tasks.length;
|
|
1449
|
+
if (unsupported) return;
|
|
1450
|
+
const running = tasks.filter(t => ['running', 'pending'].includes(t.status)).length;
|
|
1451
|
+
panel.setAttribute('aria-label', '子代理任务');
|
|
1425
1452
|
// 单个后台任务的精确停止 API 未交付:本切片没有任何停止入口,也不以
|
|
1426
1453
|
// 主会话 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
|
-
}
|
|
1454
|
+
for (const task of tasks) {
|
|
1455
|
+
const card = el('button', 'task-card task-row');
|
|
1456
|
+
card.type = 'button'; card.dataset.taskId = task.taskId || task.delegationId || '';
|
|
1457
|
+
card.append(icon('agent', 14), el('span', 'task-row-title', task.description || '子代理任务'));
|
|
1458
|
+
card.append(el('span', 'task-row-status', task.statusLabel || ({ pending: '待执行', running: '运行中', completed: '已完成', failed: '失败', stopped: '已停止' })[task.status] || '状态待确认'));
|
|
1459
|
+
card.append(icon('arrowLeft', 13));
|
|
1460
|
+
card.setAttribute('aria-label', '打开子代理:' + (task.description || '子代理任务'));
|
|
1461
|
+
card.disabled = !(task.taskRef || task.delegationId);
|
|
1462
|
+
card.addEventListener('click', () => { if (!dock) return; if (task.delegationId) dock.openDelegation(task.delegationId); else dock.openTask(task.taskRef, { reuse: true }); });
|
|
1440
1463
|
panel.append(card);
|
|
1441
1464
|
}
|
|
1442
1465
|
}
|
|
@@ -1487,15 +1510,8 @@ function renderEvent(event) {
|
|
|
1487
1510
|
state.completedReply = null;
|
|
1488
1511
|
rememberTitleFromEvents([event]);
|
|
1489
1512
|
if (state.activeSessionId && event.text) state.sessionTitles.set(state.activeSessionId, event.text.replace(/\s+/g, ' ').slice(0, 24));
|
|
1490
|
-
const wrap =
|
|
1513
|
+
const wrap = buildConversationMessage('user', event.text || '');
|
|
1491
1514
|
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
1515
|
if (Array.isArray(event.attachments) && event.attachments.length) {
|
|
1500
1516
|
const tray = el('div', 'attach-tray');
|
|
1501
1517
|
for (const item of event.attachments) {
|
|
@@ -1522,9 +1538,9 @@ function renderEvent(event) {
|
|
|
1522
1538
|
stampThinking(t);
|
|
1523
1539
|
const ctx = t || startTurn();
|
|
1524
1540
|
if (!ctx.liveText || !ctx.liveText.isConnected) {
|
|
1525
|
-
const msg =
|
|
1526
|
-
const body =
|
|
1527
|
-
|
|
1541
|
+
const msg = buildConversationMessage('assistant', '');
|
|
1542
|
+
const body = msg.querySelector('.md-body');
|
|
1543
|
+
body.classList.add('streaming');
|
|
1528
1544
|
col.append(msg);
|
|
1529
1545
|
ctx.liveText = msg;
|
|
1530
1546
|
ctx.liveBody = body;
|
|
@@ -1570,26 +1586,21 @@ function renderEvent(event) {
|
|
|
1570
1586
|
case 'claude/tool-result': {
|
|
1571
1587
|
const target = col.querySelector(`.step[data-tool-id="${cssEscape(event.id || '')}"]`);
|
|
1572
1588
|
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');
|
|
1589
|
+
finishToolStep(step, event, state.replaying);
|
|
1589
1590
|
if (t) collectTurnChanges(t, event, step._toolInput || {});
|
|
1590
1591
|
break;
|
|
1591
1592
|
}
|
|
1592
1593
|
case 'claude/permission-request': {
|
|
1594
|
+
if (col.querySelector(`[data-perm-id="${cssEscape(event.requestId || '')}"]`)) break;
|
|
1595
|
+
if (event.kind === 'question' && Array.isArray(event.questions)) {
|
|
1596
|
+
const sessionId = state.activeSessionId;
|
|
1597
|
+
const card = window.createUserQuestionCard({ document, event,
|
|
1598
|
+
storageKey: `kb.workbench.questions:${apiBase}:${sessionId}:${event.requestId}`,
|
|
1599
|
+
onSubmit: answers => client.resolvePermission({ sessionId, requestId: event.requestId, allow: true, answers }),
|
|
1600
|
+
});
|
|
1601
|
+
state.questionCards ||= new Map(); state.questionCards.set(event.requestId, card);
|
|
1602
|
+
col.append(card); setBusyState('pending-permission'); break;
|
|
1603
|
+
}
|
|
1593
1604
|
const card = el('div', 'perm');
|
|
1594
1605
|
card.dataset.permId = event.requestId || '';
|
|
1595
1606
|
const pIcon = el('span', 'p-icon');
|
|
@@ -1612,7 +1623,8 @@ function renderEvent(event) {
|
|
|
1612
1623
|
}
|
|
1613
1624
|
case 'claude/permission-resolved': {
|
|
1614
1625
|
const card = col.querySelector(`.perm[data-perm-id="${cssEscape(event.requestId || '')}"]`);
|
|
1615
|
-
if (card)
|
|
1626
|
+
if (card?.settle) card.settle(event);
|
|
1627
|
+
else if (card) settlePermissionCard(card, event.allow === true);
|
|
1616
1628
|
setBusyState('running');
|
|
1617
1629
|
break;
|
|
1618
1630
|
}
|
|
@@ -1639,11 +1651,7 @@ function renderEvent(event) {
|
|
|
1639
1651
|
col.append(err);
|
|
1640
1652
|
} else {
|
|
1641
1653
|
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));
|
|
1654
|
+
col.append(buildConversationMessage('assistant', String(event.result)));
|
|
1647
1655
|
}
|
|
1648
1656
|
const lastMsg = [...col.children].reverse().find(node => node.classList && node.classList.contains('ai'));
|
|
1649
1657
|
if (lastMsg) lastMsg.append(msgFooter(String(event.result || lastMsg.textContent), lastPrompt, { model: turnModel, at: event.at }));
|
|
@@ -1749,7 +1757,37 @@ function renderEvent(event) {
|
|
|
1749
1757
|
if (stream && state.follow) stream.scrollTop = stream.scrollHeight;
|
|
1750
1758
|
}
|
|
1751
1759
|
|
|
1752
|
-
|
|
1760
|
+
// Shared conversation primitives keep task views in the same product language.
|
|
1761
|
+
function buildConversationMessage(role, text) {
|
|
1762
|
+
const user = role === 'user';
|
|
1763
|
+
const node = el('div', user ? 'msg user' : 'msg ai');
|
|
1764
|
+
const body = el('div', user ? 'bubble' : 'md-body');
|
|
1765
|
+
if (user) body.textContent = text;
|
|
1766
|
+
else setMarkdown(body, text);
|
|
1767
|
+
node.append(body);
|
|
1768
|
+
if (user) {
|
|
1769
|
+
const copy = el('button', 'bubble-copy');
|
|
1770
|
+
copy.title = '复制';
|
|
1771
|
+
copy.append(icon('copy', 13));
|
|
1772
|
+
copy.addEventListener('click', () => copyText(text, copy));
|
|
1773
|
+
node.append(copy);
|
|
1774
|
+
}
|
|
1775
|
+
return node;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
function buildConversationProcess() {
|
|
1779
|
+
const node = el('details', 'proc');
|
|
1780
|
+
const summary = el('summary');
|
|
1781
|
+
const caret = el('span', 'caret');
|
|
1782
|
+
caret.append(icon('caret', 12));
|
|
1783
|
+
const meta = el('span', 'p-meta');
|
|
1784
|
+
const steps = el('div', 'proc-steps');
|
|
1785
|
+
summary.append(caret, meta);
|
|
1786
|
+
node.append(summary, steps);
|
|
1787
|
+
return { node, meta, steps };
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
function buildToolStep(event, input, options = {}) {
|
|
1753
1791
|
const step = el('details', 'step');
|
|
1754
1792
|
step.dataset.toolId = event.id || '';
|
|
1755
1793
|
step._toolInput = input;
|
|
@@ -1760,6 +1798,18 @@ function buildToolStep(event, input) {
|
|
|
1760
1798
|
const caret = el('span', 's-caret');
|
|
1761
1799
|
caret.append(icon('caret', 12));
|
|
1762
1800
|
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', '运行中'));
|
|
1801
|
+
if (options.taskLinks !== false && verb === '子任务' && event.id && dock && typeof dock.openTaskForToolUseId === 'function') {
|
|
1802
|
+
const open = el('button', 'task-open', '打开子代理');
|
|
1803
|
+
open.type = 'button';
|
|
1804
|
+
open.addEventListener('click', async ev => {
|
|
1805
|
+
ev.preventDefault();
|
|
1806
|
+
ev.stopPropagation();
|
|
1807
|
+
open.disabled = true;
|
|
1808
|
+
try { await dock.openTaskForToolUseId(event.id); }
|
|
1809
|
+
finally { open.disabled = false; }
|
|
1810
|
+
});
|
|
1811
|
+
summary.append(open);
|
|
1812
|
+
}
|
|
1763
1813
|
step.append(summary);
|
|
1764
1814
|
// Edit/write steps change the workspace; keep them visible when the fold opens.
|
|
1765
1815
|
if (verb === '编辑' || verb === '写入') step.open = true;
|
|
@@ -1770,6 +1820,27 @@ function buildToolStep(event, input) {
|
|
|
1770
1820
|
return step;
|
|
1771
1821
|
}
|
|
1772
1822
|
|
|
1823
|
+
// Both the main conversation and task panels use the same tool/result and
|
|
1824
|
+
// file-change components, so read-only task views retain the product UI.
|
|
1825
|
+
function finishToolStep(step, event, replaying = true) {
|
|
1826
|
+
const body = step.querySelector('.step-body');
|
|
1827
|
+
if (body) {
|
|
1828
|
+
const output = event.output && typeof event.output === 'object' ? JSON.stringify(event.output, null, 2) : String(event.output ?? '');
|
|
1829
|
+
body.append(ioCard('text', output, '输出'));
|
|
1830
|
+
}
|
|
1831
|
+
renderFileChanges(step, event, step._toolInput || {});
|
|
1832
|
+
const meta = step.querySelector('.s-meta');
|
|
1833
|
+
if (meta) {
|
|
1834
|
+
const done = event.isError ? '失败' : '完成';
|
|
1835
|
+
const seconds = replaying ? null : Math.round((Date.now() - (step._startedAt || Date.now())) / 1000);
|
|
1836
|
+
meta.textContent = seconds == null ? done : `${done} · ${fmtElapsed(seconds * 1000)}`;
|
|
1837
|
+
meta.classList.remove('running');
|
|
1838
|
+
meta.classList.toggle('failed', Boolean(event.isError));
|
|
1839
|
+
}
|
|
1840
|
+
step.classList.add('done');
|
|
1841
|
+
if (event.isError) step.classList.add('failed');
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1773
1844
|
const TOOL_ICONS = {
|
|
1774
1845
|
'命令': 'terminal', '读取': 'file', '写入': 'pencil', '编辑': 'pencil',
|
|
1775
1846
|
'搜索': 'search', '网络': 'globe', '子任务': 'agent',
|
|
@@ -1800,6 +1871,7 @@ function toolVerb(name) {
|
|
|
1800
1871
|
|
|
1801
1872
|
function toolDetail(name, input) {
|
|
1802
1873
|
const tool = String(name || '').toLowerCase();
|
|
1874
|
+
if (tool === 'agent' || tool === 'task') return String(input.description || input.prompt || name).replace(/\s+/g, ' ').slice(0, 90);
|
|
1803
1875
|
const file = input.file_path || input.path || input.filePath || '';
|
|
1804
1876
|
if (tool === 'bash' && input.command) return String(input.command).replace(/\s+/g, ' ').slice(0, 90);
|
|
1805
1877
|
if (file) return String(file).slice(0, 90);
|
|
@@ -2161,7 +2233,7 @@ function renderSessionStatus(activity) {
|
|
|
2161
2233
|
status.hidden = false;
|
|
2162
2234
|
text.textContent = ({
|
|
2163
2235
|
running: '工作中', thinking: '思考中', 'tool-running': '执行工具', spawning: '启动中',
|
|
2164
|
-
'pending-permission': '
|
|
2236
|
+
'pending-permission': '等待你确认', aborted: '已停止', failed: '失败',
|
|
2165
2237
|
}[sessionState] || sessionState) + (background > 0 ? ' · 后台运行中' : '');
|
|
2166
2238
|
return;
|
|
2167
2239
|
}
|
|
@@ -2216,6 +2288,8 @@ async function refreshSessionRow() { await renderProjectList(); }
|
|
|
2216
2288
|
|
|
2217
2289
|
function wireComposer() {
|
|
2218
2290
|
const input = $('chat-input');
|
|
2291
|
+
if (window.bindConversationComposer) window.bindConversationComposer({ input, send: $('btn-send'), onSend: sendCurrent, onStop: stopCurrent, isBusy: () => state.busy, onInput: saveCurrentDraft });
|
|
2292
|
+
else {
|
|
2219
2293
|
input.addEventListener('input', () => {
|
|
2220
2294
|
input.style.height = 'auto';
|
|
2221
2295
|
input.style.height = `${Math.min(150, input.scrollHeight)}px`;
|
|
@@ -2227,6 +2301,7 @@ function wireComposer() {
|
|
|
2227
2301
|
$('btn-send').addEventListener('click', () => {
|
|
2228
2302
|
if (state.busy) { stopCurrent(); } else { sendCurrent(); }
|
|
2229
2303
|
});
|
|
2304
|
+
}
|
|
2230
2305
|
$('btn-attach').addEventListener('click', () => $('file-input').click());
|
|
2231
2306
|
$('file-input').addEventListener('change', async event => {
|
|
2232
2307
|
for (const file of event.target.files || []) await addAttachment(file);
|
|
@@ -3073,6 +3148,7 @@ function panelStatePayload(reason) {
|
|
|
3073
3148
|
open: true,
|
|
3074
3149
|
active: tab.id === snap.activeTabId,
|
|
3075
3150
|
error: tab.hostError || null,
|
|
3151
|
+
ready: tab.hostReady === true,
|
|
3076
3152
|
content: tab.content || null,
|
|
3077
3153
|
scope: snap.scope ? { projectId: snap.scope.contextId, sessionId: snap.scope.parentSessionId } : null,
|
|
3078
3154
|
})) : [];
|
|
@@ -3168,7 +3244,7 @@ window.addEventListener('message', async ev => {
|
|
|
3168
3244
|
// hosts may only ever register same-origin iframe panels.
|
|
3169
3245
|
const descriptor = Object.assign({}, data.panel, { kind: 'iframe', origin: ev.origin });
|
|
3170
3246
|
const result = panelRegistry.register(descriptor, hostPanelOwner);
|
|
3171
|
-
if (result.ok) postPanelState('register');
|
|
3247
|
+
if (result.ok) { dock.refreshProviders?.(); postPanelState('register'); }
|
|
3172
3248
|
reply({ type: 'kb:panel/registered', ok: result.ok, code: result.code || null, error: result.message || null, providerId: result.ok ? result.providerId : null });
|
|
3173
3249
|
return;
|
|
3174
3250
|
}
|
|
@@ -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);
|