@zhongqian97-code/ecode 0.5.29 → 0.5.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +445 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1448,6 +1448,59 @@ function generateTitle(firstUserMessage) {
|
|
|
1448
1448
|
const oneLine = firstUserMessage.replace(/\n+/g, " ").trim();
|
|
1449
1449
|
return oneLine.length > 50 ? oneLine.slice(0, 47) + "..." : oneLine;
|
|
1450
1450
|
}
|
|
1451
|
+
function deleteSessionFiles(logDir, id) {
|
|
1452
|
+
const session = findSession(logDir, id);
|
|
1453
|
+
if (!session) return false;
|
|
1454
|
+
const logFilePath2 = path6.join(logDir, session.logFile);
|
|
1455
|
+
const metaFilePath = metadataPathFromLogFile(logFilePath2);
|
|
1456
|
+
let deleted = false;
|
|
1457
|
+
try {
|
|
1458
|
+
fs8.unlinkSync(logFilePath2);
|
|
1459
|
+
deleted = true;
|
|
1460
|
+
} catch {
|
|
1461
|
+
}
|
|
1462
|
+
try {
|
|
1463
|
+
fs8.unlinkSync(metaFilePath);
|
|
1464
|
+
deleted = true;
|
|
1465
|
+
} catch {
|
|
1466
|
+
}
|
|
1467
|
+
return deleted;
|
|
1468
|
+
}
|
|
1469
|
+
function loadMessagesFromJsonl(logFilePath2) {
|
|
1470
|
+
try {
|
|
1471
|
+
const content = fs8.readFileSync(logFilePath2, "utf-8");
|
|
1472
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1473
|
+
const messages = [];
|
|
1474
|
+
for (const line of lines) {
|
|
1475
|
+
try {
|
|
1476
|
+
const entry = JSON.parse(line);
|
|
1477
|
+
if (!entry.role || entry.role === "system") continue;
|
|
1478
|
+
if (entry.role === "user") {
|
|
1479
|
+
messages.push({ role: "user", content: entry.content ?? "" });
|
|
1480
|
+
} else if (entry.role === "assistant") {
|
|
1481
|
+
const msg = {
|
|
1482
|
+
role: "assistant",
|
|
1483
|
+
content: entry.content ?? null
|
|
1484
|
+
};
|
|
1485
|
+
if (entry.tool_calls) {
|
|
1486
|
+
msg.tool_calls = entry.tool_calls;
|
|
1487
|
+
}
|
|
1488
|
+
messages.push(msg);
|
|
1489
|
+
} else if (entry.role === "tool") {
|
|
1490
|
+
messages.push({
|
|
1491
|
+
role: "tool",
|
|
1492
|
+
tool_call_id: entry.tool_call_id ?? "",
|
|
1493
|
+
content: entry.content ?? ""
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
} catch {
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return messages;
|
|
1500
|
+
} catch {
|
|
1501
|
+
return [];
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1451
1504
|
|
|
1452
1505
|
// src/tools/task.ts
|
|
1453
1506
|
var TASK_TOOL = {
|
|
@@ -5353,6 +5406,13 @@ function generateAdminHtml(version2) {
|
|
|
5353
5406
|
}
|
|
5354
5407
|
.session-item .s-title { color: #c9d1d9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
5355
5408
|
.session-item .s-id { color: #8b949e; font-size: 10px; }
|
|
5409
|
+
.s-delete {
|
|
5410
|
+
float: right; margin-top: -20px; background: none; border: none;
|
|
5411
|
+
color: #6e7681; cursor: pointer; font-size: 11px; padding: 0 2px; line-height: 1;
|
|
5412
|
+
display: none;
|
|
5413
|
+
}
|
|
5414
|
+
.session-item:hover .s-delete, .session-item.active .s-delete { display: inline; }
|
|
5415
|
+
.s-delete:hover { color: #f85149; }
|
|
5356
5416
|
.sidebar-empty { color: #8b949e; font-size: 12px; font-style: italic; padding: 12px; }
|
|
5357
5417
|
|
|
5358
5418
|
/* \u2500\u2500 Chat area \u2500\u2500 */
|
|
@@ -5447,6 +5507,17 @@ function generateAdminHtml(version2) {
|
|
|
5447
5507
|
white-space: pre-wrap;
|
|
5448
5508
|
word-break: break-word;
|
|
5449
5509
|
}
|
|
5510
|
+
.thinking-block {
|
|
5511
|
+
border: 1px solid #21262d;
|
|
5512
|
+
border-radius: 4px;
|
|
5513
|
+
font-size: 12px;
|
|
5514
|
+
padding: 6px 10px;
|
|
5515
|
+
margin: 4px 0;
|
|
5516
|
+
color: #6e7681;
|
|
5517
|
+
font-style: italic;
|
|
5518
|
+
white-space: pre-wrap;
|
|
5519
|
+
word-break: break-word;
|
|
5520
|
+
}
|
|
5450
5521
|
.cursor-blink::after {
|
|
5451
5522
|
content: "\u258B";
|
|
5452
5523
|
animation: blink 1s step-start infinite;
|
|
@@ -5629,6 +5700,21 @@ function generateAdminHtml(version2) {
|
|
|
5629
5700
|
to { opacity: 1; transform: translateX(0); }
|
|
5630
5701
|
}
|
|
5631
5702
|
|
|
5703
|
+
/* \u2500\u2500 Skill dropdown \u2500\u2500 */
|
|
5704
|
+
#skill-dropdown .skill-item {
|
|
5705
|
+
padding: 8px 12px;
|
|
5706
|
+
cursor: pointer;
|
|
5707
|
+
border-bottom: 1px solid #30363d;
|
|
5708
|
+
display: flex;
|
|
5709
|
+
gap: 8px;
|
|
5710
|
+
align-items: baseline;
|
|
5711
|
+
}
|
|
5712
|
+
#skill-dropdown .skill-item:last-child { border-bottom: none; }
|
|
5713
|
+
#skill-dropdown .skill-item:hover,
|
|
5714
|
+
#skill-dropdown .skill-item.selected { background: #2d333b; }
|
|
5715
|
+
#skill-dropdown .skill-name { color: #79c0ff; font-weight: 600; font-size: 0.9em; }
|
|
5716
|
+
#skill-dropdown .skill-desc { color: #8b949e; font-size: 0.8em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 300px; }
|
|
5717
|
+
|
|
5632
5718
|
/* \u2500\u2500 Mobile \u2500\u2500 */
|
|
5633
5719
|
@media (max-width: 600px) {
|
|
5634
5720
|
#hamburger { display: block; }
|
|
@@ -5640,6 +5726,22 @@ function generateAdminHtml(version2) {
|
|
|
5640
5726
|
transition: transform .2s ease;
|
|
5641
5727
|
}
|
|
5642
5728
|
#sidebar.open { transform: translateX(0); }
|
|
5729
|
+
|
|
5730
|
+
/* Fix input bar on mobile */
|
|
5731
|
+
#input-bar {
|
|
5732
|
+
position: fixed;
|
|
5733
|
+
bottom: 0;
|
|
5734
|
+
left: 0;
|
|
5735
|
+
right: 0;
|
|
5736
|
+
bottom: env(safe-area-inset-bottom, 0px);
|
|
5737
|
+
z-index: 40;
|
|
5738
|
+
padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
|
|
5739
|
+
}
|
|
5740
|
+
|
|
5741
|
+
/* Add bottom padding to chat so messages don't hide behind fixed input */
|
|
5742
|
+
#messages {
|
|
5743
|
+
padding-bottom: 80px;
|
|
5744
|
+
}
|
|
5643
5745
|
}
|
|
5644
5746
|
</style>
|
|
5645
5747
|
</head>
|
|
@@ -5651,6 +5753,7 @@ function generateAdminHtml(version2) {
|
|
|
5651
5753
|
<span id="topbar-model" class="version" style="display:none"></span>
|
|
5652
5754
|
<button id="config-btn" class="btn">\u914D\u7F6E</button>
|
|
5653
5755
|
<button id="upgrade-btn" class="btn">\u5347\u7EA7</button>
|
|
5756
|
+
<button id="toggle-tools-btn" class="btn">\u5DE5\u5177 \u25BE</button>
|
|
5654
5757
|
</div>
|
|
5655
5758
|
|
|
5656
5759
|
<div id="app">
|
|
@@ -5677,6 +5780,7 @@ function generateAdminHtml(version2) {
|
|
|
5677
5780
|
<div class="empty-chat">\u2190 \u4ECE\u5DE6\u4FA7\u9009\u62E9\u4F1A\u8BDD\uFF0C\u6216\u70B9\u51FB"\u65B0\u5EFA\u4F1A\u8BDD"\u5F00\u59CB</div>
|
|
5678
5781
|
</div>
|
|
5679
5782
|
<div id="ws-status" class="ws-status"></div>
|
|
5783
|
+
<div id="skill-dropdown" style="display:none;position:fixed;bottom:60px;left:14px;right:14px;max-height:200px;overflow-y:auto;background:#1c2128;border:1px solid #30363d;border-radius:6px;z-index:60;"></div>
|
|
5680
5784
|
<div id="input-bar">
|
|
5681
5785
|
<textarea id="msg-input" rows="1" placeholder="\u8F93\u5165\u6D88\u606F\uFF0C\u6309 Enter \u53D1\u9001\uFF08Shift+Enter \u6362\u884C\uFF09\u2026" disabled></textarea>
|
|
5682
5786
|
<button id="send-btn" disabled>\u53D1\u9001</button>
|
|
@@ -5740,6 +5844,9 @@ function generateAdminHtml(version2) {
|
|
|
5740
5844
|
status: 'idle', // idle | thinking | tool_calling | awaiting_confirm
|
|
5741
5845
|
wsRetries: 0,
|
|
5742
5846
|
streamingMsgEl: null, // DOM element currently receiving delta tokens
|
|
5847
|
+
showTools: true,
|
|
5848
|
+
skills: [], // cached skills from /api/skills
|
|
5849
|
+
skillsLoaded: false,
|
|
5743
5850
|
};
|
|
5744
5851
|
|
|
5745
5852
|
// \u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -5848,15 +5955,45 @@ function generateAdminHtml(version2) {
|
|
|
5848
5955
|
const title = (s.title || '(\u65E0\u6807\u9898)').replace(/</g, '<');
|
|
5849
5956
|
const id = s.id.slice(0, 8);
|
|
5850
5957
|
return '<div class="session-item' + active + '" data-id="' + s.id + '">' +
|
|
5958
|
+
'<button class="s-delete" data-sid="' + s.id + '" title="\u5220\u9664\u4F1A\u8BDD">\u2715</button>' +
|
|
5851
5959
|
'<div class="s-title">' + title + '</div>' +
|
|
5852
5960
|
'<div class="s-id">' + id + '\u2026</div>' +
|
|
5853
5961
|
'</div>';
|
|
5854
5962
|
}).join('');
|
|
5855
5963
|
listEl.querySelectorAll('.session-item').forEach(el => {
|
|
5856
|
-
el.addEventListener('click', () =>
|
|
5964
|
+
el.addEventListener('click', (e) => {
|
|
5965
|
+
if (e.target.classList.contains('s-delete')) return;
|
|
5966
|
+
selectSession(el.dataset.id);
|
|
5967
|
+
});
|
|
5968
|
+
});
|
|
5969
|
+
listEl.querySelectorAll('.s-delete').forEach(btn => {
|
|
5970
|
+
btn.addEventListener('click', (e) => {
|
|
5971
|
+
e.stopPropagation();
|
|
5972
|
+
deleteSession(btn.dataset.sid);
|
|
5973
|
+
});
|
|
5857
5974
|
});
|
|
5858
5975
|
}
|
|
5859
5976
|
|
|
5977
|
+
async function deleteSession(id) {
|
|
5978
|
+
if (!confirm('\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002')) return;
|
|
5979
|
+
try {
|
|
5980
|
+
await apiFetch('/api/sessions/' + id, { method: 'DELETE' });
|
|
5981
|
+
state.sessions = state.sessions.filter(s => s.id !== id);
|
|
5982
|
+
if (state.activeSessionId === id) {
|
|
5983
|
+
state.activeSessionId = null;
|
|
5984
|
+
document.getElementById('messages').innerHTML =
|
|
5985
|
+
'<div class="empty-chat">\u2190 \u4ECE\u5DE6\u4FA7\u9009\u62E9\u4F1A\u8BDD\uFF0C\u6216\u70B9\u51FB"\u65B0\u5EFA\u4F1A\u8BDD"\u5F00\u59CB</div>';
|
|
5986
|
+
document.getElementById('chat-title').textContent = '\u9009\u62E9\u6216\u65B0\u5EFA\u4F1A\u8BDD';
|
|
5987
|
+
if (state.ws) { state.ws.onclose = null; state.ws.close(); state.ws = null; }
|
|
5988
|
+
setStatus('idle');
|
|
5989
|
+
}
|
|
5990
|
+
renderSidebar();
|
|
5991
|
+
showToast('\u4F1A\u8BDD\u5DF2\u5220\u9664', 'success');
|
|
5992
|
+
} catch (e) {
|
|
5993
|
+
showToast('\u5220\u9664\u5931\u8D25: ' + e.message, 'error');
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5860
5997
|
async function loadSessions() {
|
|
5861
5998
|
try {
|
|
5862
5999
|
const data = await apiFetch('/api/sessions');
|
|
@@ -5963,7 +6100,12 @@ function generateAdminHtml(version2) {
|
|
|
5963
6100
|
document.getElementById('ws-status').textContent = 'WebSocket \u9519\u8BEF';
|
|
5964
6101
|
};
|
|
5965
6102
|
|
|
5966
|
-
ws.onclose = () => {
|
|
6103
|
+
ws.onclose = (evt) => {
|
|
6104
|
+
// 4404 = \u4F1A\u8BDD\u4E0D\u5728\u5185\u5B58\u4E2D\uFF08\u5DF2\u7531 resume API \u5904\u7406\uFF09\uFF0C\u4E0D\u9700\u8981\u91CD\u8BD5
|
|
6105
|
+
if (evt.code === 4404) {
|
|
6106
|
+
document.getElementById('ws-status').textContent = '';
|
|
6107
|
+
return;
|
|
6108
|
+
}
|
|
5967
6109
|
document.getElementById('ws-status').textContent = 'WebSocket \u5DF2\u65AD\u5F00';
|
|
5968
6110
|
finalizeStreamingMsg();
|
|
5969
6111
|
if (state.activeSessionId === sessionId && state.wsRetries < 5) {
|
|
@@ -6004,6 +6146,7 @@ function generateAdminHtml(version2) {
|
|
|
6004
6146
|
boxEl.innerHTML =
|
|
6005
6147
|
'<div class="tool-box-header">\u250C\u2500 ' + escHtml(name) + ' \u2500</div>' +
|
|
6006
6148
|
'<div class="tool-box-body">\u2026</div>';
|
|
6149
|
+
if (!state.showTools) boxEl.style.display = 'none';
|
|
6007
6150
|
msgsEl.appendChild(boxEl);
|
|
6008
6151
|
msgsEl.scrollTop = msgsEl.scrollHeight;
|
|
6009
6152
|
setStatus('tool_calling');
|
|
@@ -6028,6 +6171,16 @@ function generateAdminHtml(version2) {
|
|
|
6028
6171
|
} else if (type === 'session.idle') {
|
|
6029
6172
|
finalizeStreamingMsg();
|
|
6030
6173
|
setStatus('idle');
|
|
6174
|
+
} else if (type === 'message.reasoning') {
|
|
6175
|
+
const msgsEl = document.getElementById('messages');
|
|
6176
|
+
const placeholder = msgsEl.querySelector('.empty-chat');
|
|
6177
|
+
if (placeholder) placeholder.remove();
|
|
6178
|
+
const thinkEl = document.createElement('div');
|
|
6179
|
+
thinkEl.className = 'thinking-block';
|
|
6180
|
+
thinkEl.textContent = msg.reasoning || msg.text || '';
|
|
6181
|
+
if (!state.showTools) thinkEl.style.display = 'none';
|
|
6182
|
+
msgsEl.appendChild(thinkEl);
|
|
6183
|
+
msgsEl.scrollTop = msgsEl.scrollHeight;
|
|
6031
6184
|
} else if (type === 'session.error') {
|
|
6032
6185
|
finalizeStreamingMsg();
|
|
6033
6186
|
setStatus('idle');
|
|
@@ -6042,6 +6195,70 @@ function generateAdminHtml(version2) {
|
|
|
6042
6195
|
.replace(/>/g, '>');
|
|
6043
6196
|
}
|
|
6044
6197
|
|
|
6198
|
+
// \u2500\u2500 Skill dropdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6199
|
+
var selectedSkillIndex = -1;
|
|
6200
|
+
|
|
6201
|
+
async function loadSkills() {
|
|
6202
|
+
if (state.skillsLoaded) return;
|
|
6203
|
+
try {
|
|
6204
|
+
const data = await apiFetch('/api/skills');
|
|
6205
|
+
state.skills = Array.isArray(data) ? data : [];
|
|
6206
|
+
state.skillsLoaded = true;
|
|
6207
|
+
} catch (e) {
|
|
6208
|
+
state.skills = [];
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
|
|
6212
|
+
function showSkillDropdown(query) {
|
|
6213
|
+
const dropdown = document.getElementById('skill-dropdown');
|
|
6214
|
+
if (!dropdown) return;
|
|
6215
|
+
const q = query.toLowerCase();
|
|
6216
|
+
const filtered = state.skills.filter(function(s) {
|
|
6217
|
+
return s.name.toLowerCase().startsWith(q);
|
|
6218
|
+
});
|
|
6219
|
+
if (!filtered.length) {
|
|
6220
|
+
dropdown.style.display = 'none';
|
|
6221
|
+
selectedSkillIndex = -1;
|
|
6222
|
+
return;
|
|
6223
|
+
}
|
|
6224
|
+
selectedSkillIndex = -1;
|
|
6225
|
+
dropdown.innerHTML = filtered.map(function(s, i) {
|
|
6226
|
+
return '<div class="skill-item" data-name="' + escHtml(s.name) + '">' +
|
|
6227
|
+
'<span class="skill-name">/' + escHtml(s.name) + '</span>' +
|
|
6228
|
+
'<span class="skill-desc">' + escHtml(s.description || '') + '</span>' +
|
|
6229
|
+
'</div>';
|
|
6230
|
+
}).join('');
|
|
6231
|
+
dropdown.querySelectorAll('.skill-item').forEach(function(el) {
|
|
6232
|
+
el.addEventListener('click', function() {
|
|
6233
|
+
var name = el.getAttribute('data-name');
|
|
6234
|
+
var inputEl = document.getElementById('msg-input');
|
|
6235
|
+
inputEl.value = '/' + name + ' ';
|
|
6236
|
+
hideSkillDropdown();
|
|
6237
|
+
inputEl.focus();
|
|
6238
|
+
});
|
|
6239
|
+
});
|
|
6240
|
+
dropdown.style.display = 'block';
|
|
6241
|
+
}
|
|
6242
|
+
|
|
6243
|
+
function hideSkillDropdown() {
|
|
6244
|
+
const dropdown = document.getElementById('skill-dropdown');
|
|
6245
|
+
if (dropdown) dropdown.style.display = 'none';
|
|
6246
|
+
selectedSkillIndex = -1;
|
|
6247
|
+
}
|
|
6248
|
+
|
|
6249
|
+
function updateSkillSelection() {
|
|
6250
|
+
const dropdown = document.getElementById('skill-dropdown');
|
|
6251
|
+
if (!dropdown) return;
|
|
6252
|
+
const items = dropdown.querySelectorAll('.skill-item');
|
|
6253
|
+
items.forEach(function(el, i) {
|
|
6254
|
+
if (i === selectedSkillIndex) {
|
|
6255
|
+
el.classList.add('selected');
|
|
6256
|
+
} else {
|
|
6257
|
+
el.classList.remove('selected');
|
|
6258
|
+
}
|
|
6259
|
+
});
|
|
6260
|
+
}
|
|
6261
|
+
|
|
6045
6262
|
// \u2500\u2500 Approval modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6046
6263
|
function showApprovalModal(msg) {
|
|
6047
6264
|
const box = document.getElementById('approval-box');
|
|
@@ -6075,7 +6292,7 @@ function generateAdminHtml(version2) {
|
|
|
6075
6292
|
document.getElementById('deny-btn').addEventListener('click', () => sendApproval(false));
|
|
6076
6293
|
|
|
6077
6294
|
// \u2500\u2500 Session selection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6078
|
-
function selectSession(id) {
|
|
6295
|
+
async function selectSession(id) {
|
|
6079
6296
|
state.activeSessionId = id;
|
|
6080
6297
|
const session = state.sessions.find(s => s.id === id);
|
|
6081
6298
|
document.getElementById('chat-title').textContent =
|
|
@@ -6084,6 +6301,12 @@ function generateAdminHtml(version2) {
|
|
|
6084
6301
|
loadMessages(id);
|
|
6085
6302
|
renderSidebar();
|
|
6086
6303
|
setStatus('idle');
|
|
6304
|
+
// \u5148\u5C1D\u8BD5\u6062\u590D\uFF08\u5C06\u5386\u53F2\u4F1A\u8BDD\u52A0\u8F7D\u5230\u5185\u5B58\uFF09\uFF0C\u518D\u8FDE\u63A5 WS
|
|
6305
|
+
try {
|
|
6306
|
+
await apiFetch('/api/chat/sessions/' + id + '/resume', { method: 'POST' });
|
|
6307
|
+
} catch {
|
|
6308
|
+
// \u6062\u590D\u5931\u8D25\u4E5F\u7EE7\u7EED\uFF08\u53EF\u80FD\u5DF2\u5728\u5185\u5B58\u4E2D\uFF0C\u6216\u53EA\u8BFB\u67E5\u770B\uFF09
|
|
6309
|
+
}
|
|
6087
6310
|
connectWs(id);
|
|
6088
6311
|
// Close sidebar on mobile
|
|
6089
6312
|
document.getElementById('sidebar').classList.remove('open');
|
|
@@ -6135,17 +6358,57 @@ function generateAdminHtml(version2) {
|
|
|
6135
6358
|
|
|
6136
6359
|
document.getElementById('send-btn').addEventListener('click', sendMessage);
|
|
6137
6360
|
|
|
6138
|
-
document.getElementById('msg-input').addEventListener('keydown', (e)
|
|
6361
|
+
document.getElementById('msg-input').addEventListener('keydown', function(e) {
|
|
6362
|
+
const dropdown = document.getElementById('skill-dropdown');
|
|
6363
|
+
const dropdownVisible = dropdown && dropdown.style.display !== 'none';
|
|
6364
|
+
if (dropdownVisible) {
|
|
6365
|
+
const items = dropdown.querySelectorAll('.skill-item');
|
|
6366
|
+
if (e.key === 'ArrowDown') {
|
|
6367
|
+
e.preventDefault();
|
|
6368
|
+
selectedSkillIndex = Math.min(selectedSkillIndex + 1, items.length - 1);
|
|
6369
|
+
updateSkillSelection();
|
|
6370
|
+
return;
|
|
6371
|
+
}
|
|
6372
|
+
if (e.key === 'ArrowUp') {
|
|
6373
|
+
e.preventDefault();
|
|
6374
|
+
selectedSkillIndex = Math.max(selectedSkillIndex - 1, -1);
|
|
6375
|
+
updateSkillSelection();
|
|
6376
|
+
return;
|
|
6377
|
+
}
|
|
6378
|
+
if (e.key === 'Enter' && selectedSkillIndex >= 0) {
|
|
6379
|
+
e.preventDefault();
|
|
6380
|
+
const selected = items[selectedSkillIndex];
|
|
6381
|
+
if (selected) {
|
|
6382
|
+
var name = selected.getAttribute('data-name');
|
|
6383
|
+
var inputEl = document.getElementById('msg-input');
|
|
6384
|
+
inputEl.value = '/' + name + ' ';
|
|
6385
|
+
hideSkillDropdown();
|
|
6386
|
+
inputEl.focus();
|
|
6387
|
+
}
|
|
6388
|
+
return;
|
|
6389
|
+
}
|
|
6390
|
+
if (e.key === 'Escape') {
|
|
6391
|
+
e.preventDefault();
|
|
6392
|
+
hideSkillDropdown();
|
|
6393
|
+
return;
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6139
6396
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
6140
6397
|
e.preventDefault();
|
|
6141
6398
|
sendMessage();
|
|
6142
6399
|
}
|
|
6143
6400
|
});
|
|
6144
6401
|
|
|
6145
|
-
// Auto-resize textarea
|
|
6402
|
+
// Auto-resize textarea + slash command dropdown
|
|
6146
6403
|
document.getElementById('msg-input').addEventListener('input', function() {
|
|
6147
6404
|
this.style.height = 'auto';
|
|
6148
6405
|
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
|
|
6406
|
+
const val = this.value;
|
|
6407
|
+
if (val.startsWith('/')) {
|
|
6408
|
+
loadSkills().then(function() { showSkillDropdown(val.slice(1)); });
|
|
6409
|
+
} else {
|
|
6410
|
+
hideSkillDropdown();
|
|
6411
|
+
}
|
|
6149
6412
|
});
|
|
6150
6413
|
|
|
6151
6414
|
// \u2500\u2500 Hamburger (mobile) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -6248,6 +6511,22 @@ function generateAdminHtml(version2) {
|
|
|
6248
6511
|
}
|
|
6249
6512
|
});
|
|
6250
6513
|
|
|
6514
|
+
// \u2500\u2500 \u5DE5\u5177/\u601D\u8003\u53EF\u89C1\u6027\u5207\u6362 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6515
|
+
document.getElementById('toggle-tools-btn').addEventListener('click', () => {
|
|
6516
|
+
state.showTools = !state.showTools;
|
|
6517
|
+
document.getElementById('toggle-tools-btn').textContent = state.showTools ? '\u5DE5\u5177 \u25BE' : '\u5DE5\u5177 \u25B8';
|
|
6518
|
+
document.querySelectorAll('.tool-box, .thinking-block').forEach(el => {
|
|
6519
|
+
el.style.display = state.showTools ? '' : 'none';
|
|
6520
|
+
});
|
|
6521
|
+
});
|
|
6522
|
+
|
|
6523
|
+
// \u2500\u2500 Skill dropdown: hide on outside click \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6524
|
+
document.addEventListener('click', function(e) {
|
|
6525
|
+
if (!e.target.closest('#skill-dropdown') && !e.target.closest('#msg-input')) {
|
|
6526
|
+
hideSkillDropdown();
|
|
6527
|
+
}
|
|
6528
|
+
});
|
|
6529
|
+
|
|
6251
6530
|
// \u2500\u2500 Mobile keyboard adaptation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6252
6531
|
document.getElementById('msg-input').addEventListener('focus', () => {
|
|
6253
6532
|
setTimeout(() => {
|
|
@@ -6255,6 +6534,15 @@ function generateAdminHtml(version2) {
|
|
|
6255
6534
|
}, 300);
|
|
6256
6535
|
});
|
|
6257
6536
|
|
|
6537
|
+
if (window.visualViewport) {
|
|
6538
|
+
window.visualViewport.addEventListener('resize', function() {
|
|
6539
|
+
const bar = document.getElementById('input-bar');
|
|
6540
|
+
if (!bar) return;
|
|
6541
|
+
const offsetBottom = window.innerHeight - window.visualViewport.height - window.visualViewport.offsetTop;
|
|
6542
|
+
bar.style.bottom = Math.max(0, offsetBottom) + 'px';
|
|
6543
|
+
});
|
|
6544
|
+
}
|
|
6545
|
+
|
|
6258
6546
|
// \u2500\u2500 Global error boundary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6259
6547
|
window.onerror = function(msg, src, line) {
|
|
6260
6548
|
const wsStatusEl = document.getElementById('ws-status');
|
|
@@ -6372,6 +6660,23 @@ async function sessionsRoutes(app, opts) {
|
|
|
6372
6660
|
return reply.send({ success: true, command });
|
|
6373
6661
|
}
|
|
6374
6662
|
);
|
|
6663
|
+
app.delete(
|
|
6664
|
+
"/api/sessions/:id",
|
|
6665
|
+
async (request, reply) => {
|
|
6666
|
+
const { id } = request.params;
|
|
6667
|
+
if (!opts.config.logDir) {
|
|
6668
|
+
return reply.code(404).send({ success: false, error: "Log directory not configured" });
|
|
6669
|
+
}
|
|
6670
|
+
const deleted = deleteSessionFiles(opts.config.logDir, id);
|
|
6671
|
+
if (!deleted) {
|
|
6672
|
+
return reply.code(404).send({ success: false, error: `Session not found: ${id}` });
|
|
6673
|
+
}
|
|
6674
|
+
if (opts.manager) {
|
|
6675
|
+
opts.manager.deleteSession(id);
|
|
6676
|
+
}
|
|
6677
|
+
return reply.send({ success: true });
|
|
6678
|
+
}
|
|
6679
|
+
);
|
|
6375
6680
|
app.get(
|
|
6376
6681
|
"/api/sessions/:id/fork-command",
|
|
6377
6682
|
async (request, reply) => {
|
|
@@ -6503,6 +6808,7 @@ async function automationRoutes(app, opts) {
|
|
|
6503
6808
|
}
|
|
6504
6809
|
|
|
6505
6810
|
// src/web/routes/chat.ts
|
|
6811
|
+
import { join as join14 } from "path";
|
|
6506
6812
|
var BUSY_STATUSES = /* @__PURE__ */ new Set(["thinking", "tool_calling", "awaiting_confirm"]);
|
|
6507
6813
|
async function chatRoutes(app, opts) {
|
|
6508
6814
|
const { manager } = opts;
|
|
@@ -6512,6 +6818,9 @@ async function chatRoutes(app, opts) {
|
|
|
6512
6818
|
if (typeof body.systemPrompt === "string") {
|
|
6513
6819
|
sessionOpts.systemPrompt = body.systemPrompt;
|
|
6514
6820
|
}
|
|
6821
|
+
if (opts.autoApprove) {
|
|
6822
|
+
sessionOpts.autoApproveNormal = true;
|
|
6823
|
+
}
|
|
6515
6824
|
if (!opts.config.apiKey) {
|
|
6516
6825
|
return reply.code(400).send({
|
|
6517
6826
|
success: false,
|
|
@@ -6556,6 +6865,37 @@ async function chatRoutes(app, opts) {
|
|
|
6556
6865
|
return reply.send({ success: true });
|
|
6557
6866
|
}
|
|
6558
6867
|
);
|
|
6868
|
+
app.post(
|
|
6869
|
+
"/api/chat/sessions/:id/resume",
|
|
6870
|
+
async (request, reply) => {
|
|
6871
|
+
const { id } = request.params;
|
|
6872
|
+
if (manager.getSession(id)) {
|
|
6873
|
+
return reply.send({ success: true, resumed: false });
|
|
6874
|
+
}
|
|
6875
|
+
if (!opts.config.apiKey) {
|
|
6876
|
+
return reply.code(400).send({
|
|
6877
|
+
success: false,
|
|
6878
|
+
error: "\u672A\u914D\u7F6E API Key\uFF0C\u8BF7\u5148\u70B9\u51FB\u53F3\u4E0A\u89D2\u300C\u914D\u7F6E\u300D\u6309\u94AE\u586B\u5199"
|
|
6879
|
+
});
|
|
6880
|
+
}
|
|
6881
|
+
if (!opts.config.logDir) {
|
|
6882
|
+
return reply.code(404).send({ success: false, error: "Log directory not configured" });
|
|
6883
|
+
}
|
|
6884
|
+
const session = findSession(opts.config.logDir, id);
|
|
6885
|
+
if (!session) {
|
|
6886
|
+
return reply.code(404).send({ success: false, error: `Session not found: ${id}` });
|
|
6887
|
+
}
|
|
6888
|
+
const logFilePath2 = join14(opts.config.logDir, session.logFile);
|
|
6889
|
+
const initialMessages = loadMessagesFromJsonl(logFilePath2);
|
|
6890
|
+
try {
|
|
6891
|
+
await manager.createSession({ sessionId: id, initialMessages });
|
|
6892
|
+
return reply.send({ success: true, resumed: true });
|
|
6893
|
+
} catch (err) {
|
|
6894
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6895
|
+
return reply.code(500).send({ success: false, error: msg });
|
|
6896
|
+
}
|
|
6897
|
+
}
|
|
6898
|
+
);
|
|
6559
6899
|
app.post(
|
|
6560
6900
|
"/api/chat/sessions/:id/approve",
|
|
6561
6901
|
async (request, reply) => {
|
|
@@ -6645,6 +6985,80 @@ async function systemRoutes(app, opts) {
|
|
|
6645
6985
|
});
|
|
6646
6986
|
}
|
|
6647
6987
|
|
|
6988
|
+
// src/web/routes/skills.ts
|
|
6989
|
+
import { readdir as readdir5, readFile as readFile10 } from "fs/promises";
|
|
6990
|
+
import { join as join15 } from "path";
|
|
6991
|
+
import os2 from "os";
|
|
6992
|
+
var BUILTIN_COMMANDS = [
|
|
6993
|
+
{ name: "clear", description: "Clear conversation history", type: "builtin" },
|
|
6994
|
+
{ name: "interrupt", description: "Interrupt current operation", type: "builtin" },
|
|
6995
|
+
{ name: "help", description: "Show help information", type: "builtin" }
|
|
6996
|
+
];
|
|
6997
|
+
function parseFrontmatter2(content) {
|
|
6998
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
6999
|
+
if (!match) return null;
|
|
7000
|
+
const frontmatter = match[1];
|
|
7001
|
+
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
7002
|
+
if (!nameMatch) return null;
|
|
7003
|
+
const descMatch = frontmatter.match(/^description:\s*(?:>-?|[|>+])?\s*(.+)$/m);
|
|
7004
|
+
if (!descMatch) return null;
|
|
7005
|
+
const name = nameMatch[1].trim();
|
|
7006
|
+
const description = descMatch[1].trim();
|
|
7007
|
+
if (!name || !description) return null;
|
|
7008
|
+
return { name, description };
|
|
7009
|
+
}
|
|
7010
|
+
async function loadAgents() {
|
|
7011
|
+
const agentsDir = join15(os2.homedir(), ".claude", "agents");
|
|
7012
|
+
let files;
|
|
7013
|
+
try {
|
|
7014
|
+
const entries = await readdir5(agentsDir);
|
|
7015
|
+
files = entries.filter((f) => f.endsWith(".md"));
|
|
7016
|
+
} catch {
|
|
7017
|
+
return [];
|
|
7018
|
+
}
|
|
7019
|
+
const items = [];
|
|
7020
|
+
for (const file of files) {
|
|
7021
|
+
try {
|
|
7022
|
+
const content = await readFile10(join15(agentsDir, file), "utf-8");
|
|
7023
|
+
const parsed = parseFrontmatter2(content);
|
|
7024
|
+
if (parsed) {
|
|
7025
|
+
items.push({ ...parsed, type: "agent" });
|
|
7026
|
+
}
|
|
7027
|
+
} catch {
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
return items;
|
|
7031
|
+
}
|
|
7032
|
+
async function loadSkills() {
|
|
7033
|
+
const skillsDir = join15(process.cwd(), "skills");
|
|
7034
|
+
let subdirs;
|
|
7035
|
+
try {
|
|
7036
|
+
const entries = await readdir5(skillsDir, { withFileTypes: true });
|
|
7037
|
+
subdirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
7038
|
+
} catch {
|
|
7039
|
+
return [];
|
|
7040
|
+
}
|
|
7041
|
+
const items = [];
|
|
7042
|
+
for (const subdir of subdirs) {
|
|
7043
|
+
try {
|
|
7044
|
+
const skillFile = join15(skillsDir, subdir, "SKILL.md");
|
|
7045
|
+
const content = await readFile10(skillFile, "utf-8");
|
|
7046
|
+
const parsed = parseFrontmatter2(content);
|
|
7047
|
+
if (parsed) {
|
|
7048
|
+
items.push({ ...parsed, type: "skill" });
|
|
7049
|
+
}
|
|
7050
|
+
} catch {
|
|
7051
|
+
}
|
|
7052
|
+
}
|
|
7053
|
+
return items;
|
|
7054
|
+
}
|
|
7055
|
+
async function skillsRoutes(app) {
|
|
7056
|
+
app.get("/api/skills", async (_request, _reply) => {
|
|
7057
|
+
const [agents, skills] = await Promise.all([loadAgents(), loadSkills()]);
|
|
7058
|
+
return [...BUILTIN_COMMANDS, ...agents, ...skills];
|
|
7059
|
+
});
|
|
7060
|
+
}
|
|
7061
|
+
|
|
6648
7062
|
// src/web/server.ts
|
|
6649
7063
|
async function buildServer(opts) {
|
|
6650
7064
|
const app = Fastify({ logger: false });
|
|
@@ -6671,8 +7085,9 @@ async function buildServer(opts) {
|
|
|
6671
7085
|
await app.register(sessionsRoutes, { config: opts.config, manager: opts.manager });
|
|
6672
7086
|
await app.register(configRoutes, { config: opts.config, save: saveConfig });
|
|
6673
7087
|
await app.register(automationRoutes, { config: opts.config });
|
|
6674
|
-
await app.register(chatRoutes, { config: opts.config, manager: opts.manager });
|
|
7088
|
+
await app.register(chatRoutes, { config: opts.config, manager: opts.manager, autoApprove: opts.autoApprove });
|
|
6675
7089
|
await app.register(systemRoutes, { version: opts.version });
|
|
7090
|
+
await app.register(skillsRoutes);
|
|
6676
7091
|
await app.register(sessionHubRoutes, { manager: opts.manager });
|
|
6677
7092
|
return app;
|
|
6678
7093
|
}
|
|
@@ -6768,6 +7183,7 @@ var SessionRuntime = class {
|
|
|
6768
7183
|
messages;
|
|
6769
7184
|
config;
|
|
6770
7185
|
model;
|
|
7186
|
+
autoApproveNormal;
|
|
6771
7187
|
abortController = null;
|
|
6772
7188
|
_stopAfterToolRound = false;
|
|
6773
7189
|
_turnCount = 0;
|
|
@@ -6780,6 +7196,7 @@ var SessionRuntime = class {
|
|
|
6780
7196
|
this.config = config2;
|
|
6781
7197
|
this.llm = opts.llm ?? createProvider(resolveActiveProfile(config2));
|
|
6782
7198
|
this.model = config2.model;
|
|
7199
|
+
this.autoApproveNormal = opts.autoApproveNormal ?? false;
|
|
6783
7200
|
const systemContent = opts.systemPrompt ?? (config2.systemPrompt ?? DEFAULT_SYSTEM_PROMPT);
|
|
6784
7201
|
this.messages = systemContent ? [{ role: "system", content: systemContent }] : [];
|
|
6785
7202
|
if (opts.initialMessages) {
|
|
@@ -6918,17 +7335,20 @@ var SessionRuntime = class {
|
|
|
6918
7335
|
const parsed = JSON.parse(args);
|
|
6919
7336
|
const cls = classifyCommand(parsed.command, this.config.dangerousPatterns);
|
|
6920
7337
|
if (cls === "normal" || cls === "danger") {
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
7338
|
+
if (cls === "normal" && this.autoApproveNormal) {
|
|
7339
|
+
} else {
|
|
7340
|
+
this._status = "awaiting_confirm";
|
|
7341
|
+
const kind = cls === "danger" ? "danger" : "normal";
|
|
7342
|
+
const prompt = cls === "danger" ? `\u26A0\uFE0F DANGEROUS COMMAND: ${parsed.command}` : `Execute command: ${parsed.command}
|
|
6924
7343
|
Proceed?`;
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
7344
|
+
const { id: reqId, promise } = this.approvals.request(kind, prompt);
|
|
7345
|
+
this.bus.emit({ type: "approval.requested", requestId: reqId, kind, prompt });
|
|
7346
|
+
const approved = await promise;
|
|
7347
|
+
this._status = "tool_calling";
|
|
7348
|
+
if (!approved) {
|
|
7349
|
+
this._stopAfterToolRound = true;
|
|
7350
|
+
return SKIP_MESSAGE;
|
|
7351
|
+
}
|
|
6932
7352
|
}
|
|
6933
7353
|
}
|
|
6934
7354
|
if (signal.aborted) return SKIP_MESSAGE;
|
|
@@ -7050,6 +7470,10 @@ var SessionManager = class {
|
|
|
7050
7470
|
listRunning() {
|
|
7051
7471
|
return [...this.sessions.values()].map((s) => s.snapshot());
|
|
7052
7472
|
}
|
|
7473
|
+
/** 从内存中移除指定会话(不影响磁盘文件,由上层路由负责文件清理) */
|
|
7474
|
+
deleteSession(sessionId) {
|
|
7475
|
+
this.sessions.delete(sessionId);
|
|
7476
|
+
}
|
|
7053
7477
|
};
|
|
7054
7478
|
|
|
7055
7479
|
// src/index.ts
|
|
@@ -7202,6 +7626,7 @@ if (rawArgs[0] === "sessions") {
|
|
|
7202
7626
|
if (rawArgs[0] === "web") {
|
|
7203
7627
|
let webPort = 4310;
|
|
7204
7628
|
let webHost = "127.0.0.1";
|
|
7629
|
+
let webAutoApprove = false;
|
|
7205
7630
|
for (let i = 1; i < rawArgs.length; i++) {
|
|
7206
7631
|
const arg = rawArgs[i];
|
|
7207
7632
|
const next = rawArgs[i + 1];
|
|
@@ -7212,6 +7637,8 @@ if (rawArgs[0] === "web") {
|
|
|
7212
7637
|
} else if (arg === "--host" && next && !next.startsWith("-")) {
|
|
7213
7638
|
webHost = next;
|
|
7214
7639
|
i++;
|
|
7640
|
+
} else if (arg === "--auto") {
|
|
7641
|
+
webAutoApprove = true;
|
|
7215
7642
|
}
|
|
7216
7643
|
}
|
|
7217
7644
|
const token = generateAccessToken();
|
|
@@ -7220,7 +7647,8 @@ if (rawArgs[0] === "web") {
|
|
|
7220
7647
|
config: finalConfig,
|
|
7221
7648
|
manager,
|
|
7222
7649
|
token,
|
|
7223
|
-
version: VERSION
|
|
7650
|
+
version: VERSION,
|
|
7651
|
+
autoApprove: webAutoApprove
|
|
7224
7652
|
});
|
|
7225
7653
|
await server.listen({ port: webPort, host: webHost });
|
|
7226
7654
|
const browserHost = webHost === "0.0.0.0" ? "localhost" : webHost;
|