@zhongqian97-code/ecode 0.5.29 → 0.5.30
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 +190 -3
- 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;
|
|
@@ -5651,6 +5722,7 @@ function generateAdminHtml(version2) {
|
|
|
5651
5722
|
<span id="topbar-model" class="version" style="display:none"></span>
|
|
5652
5723
|
<button id="config-btn" class="btn">\u914D\u7F6E</button>
|
|
5653
5724
|
<button id="upgrade-btn" class="btn">\u5347\u7EA7</button>
|
|
5725
|
+
<button id="toggle-tools-btn" class="btn">\u5DE5\u5177 \u25BE</button>
|
|
5654
5726
|
</div>
|
|
5655
5727
|
|
|
5656
5728
|
<div id="app">
|
|
@@ -5740,6 +5812,7 @@ function generateAdminHtml(version2) {
|
|
|
5740
5812
|
status: 'idle', // idle | thinking | tool_calling | awaiting_confirm
|
|
5741
5813
|
wsRetries: 0,
|
|
5742
5814
|
streamingMsgEl: null, // DOM element currently receiving delta tokens
|
|
5815
|
+
showTools: true,
|
|
5743
5816
|
};
|
|
5744
5817
|
|
|
5745
5818
|
// \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,13 +5921,43 @@ function generateAdminHtml(version2) {
|
|
|
5848
5921
|
const title = (s.title || '(\u65E0\u6807\u9898)').replace(/</g, '<');
|
|
5849
5922
|
const id = s.id.slice(0, 8);
|
|
5850
5923
|
return '<div class="session-item' + active + '" data-id="' + s.id + '">' +
|
|
5924
|
+
'<button class="s-delete" data-sid="' + s.id + '" title="\u5220\u9664\u4F1A\u8BDD">\u2715</button>' +
|
|
5851
5925
|
'<div class="s-title">' + title + '</div>' +
|
|
5852
5926
|
'<div class="s-id">' + id + '\u2026</div>' +
|
|
5853
5927
|
'</div>';
|
|
5854
5928
|
}).join('');
|
|
5855
5929
|
listEl.querySelectorAll('.session-item').forEach(el => {
|
|
5856
|
-
el.addEventListener('click', () =>
|
|
5930
|
+
el.addEventListener('click', (e) => {
|
|
5931
|
+
if (e.target.classList.contains('s-delete')) return;
|
|
5932
|
+
selectSession(el.dataset.id);
|
|
5933
|
+
});
|
|
5857
5934
|
});
|
|
5935
|
+
listEl.querySelectorAll('.s-delete').forEach(btn => {
|
|
5936
|
+
btn.addEventListener('click', (e) => {
|
|
5937
|
+
e.stopPropagation();
|
|
5938
|
+
deleteSession(btn.dataset.sid);
|
|
5939
|
+
});
|
|
5940
|
+
});
|
|
5941
|
+
}
|
|
5942
|
+
|
|
5943
|
+
async function deleteSession(id) {
|
|
5944
|
+
if (!confirm('\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002')) return;
|
|
5945
|
+
try {
|
|
5946
|
+
await apiFetch('/api/sessions/' + id, { method: 'DELETE' });
|
|
5947
|
+
state.sessions = state.sessions.filter(s => s.id !== id);
|
|
5948
|
+
if (state.activeSessionId === id) {
|
|
5949
|
+
state.activeSessionId = null;
|
|
5950
|
+
document.getElementById('messages').innerHTML =
|
|
5951
|
+
'<div class="empty-chat">\u2190 \u4ECE\u5DE6\u4FA7\u9009\u62E9\u4F1A\u8BDD\uFF0C\u6216\u70B9\u51FB"\u65B0\u5EFA\u4F1A\u8BDD"\u5F00\u59CB</div>';
|
|
5952
|
+
document.getElementById('chat-title').textContent = '\u9009\u62E9\u6216\u65B0\u5EFA\u4F1A\u8BDD';
|
|
5953
|
+
if (state.ws) { state.ws.onclose = null; state.ws.close(); state.ws = null; }
|
|
5954
|
+
setStatus('idle');
|
|
5955
|
+
}
|
|
5956
|
+
renderSidebar();
|
|
5957
|
+
showToast('\u4F1A\u8BDD\u5DF2\u5220\u9664', 'success');
|
|
5958
|
+
} catch (e) {
|
|
5959
|
+
showToast('\u5220\u9664\u5931\u8D25: ' + e.message, 'error');
|
|
5960
|
+
}
|
|
5858
5961
|
}
|
|
5859
5962
|
|
|
5860
5963
|
async function loadSessions() {
|
|
@@ -5963,7 +6066,12 @@ function generateAdminHtml(version2) {
|
|
|
5963
6066
|
document.getElementById('ws-status').textContent = 'WebSocket \u9519\u8BEF';
|
|
5964
6067
|
};
|
|
5965
6068
|
|
|
5966
|
-
ws.onclose = () => {
|
|
6069
|
+
ws.onclose = (evt) => {
|
|
6070
|
+
// 4404 = \u4F1A\u8BDD\u4E0D\u5728\u5185\u5B58\u4E2D\uFF08\u5DF2\u7531 resume API \u5904\u7406\uFF09\uFF0C\u4E0D\u9700\u8981\u91CD\u8BD5
|
|
6071
|
+
if (evt.code === 4404) {
|
|
6072
|
+
document.getElementById('ws-status').textContent = '';
|
|
6073
|
+
return;
|
|
6074
|
+
}
|
|
5967
6075
|
document.getElementById('ws-status').textContent = 'WebSocket \u5DF2\u65AD\u5F00';
|
|
5968
6076
|
finalizeStreamingMsg();
|
|
5969
6077
|
if (state.activeSessionId === sessionId && state.wsRetries < 5) {
|
|
@@ -6004,6 +6112,7 @@ function generateAdminHtml(version2) {
|
|
|
6004
6112
|
boxEl.innerHTML =
|
|
6005
6113
|
'<div class="tool-box-header">\u250C\u2500 ' + escHtml(name) + ' \u2500</div>' +
|
|
6006
6114
|
'<div class="tool-box-body">\u2026</div>';
|
|
6115
|
+
if (!state.showTools) boxEl.style.display = 'none';
|
|
6007
6116
|
msgsEl.appendChild(boxEl);
|
|
6008
6117
|
msgsEl.scrollTop = msgsEl.scrollHeight;
|
|
6009
6118
|
setStatus('tool_calling');
|
|
@@ -6028,6 +6137,16 @@ function generateAdminHtml(version2) {
|
|
|
6028
6137
|
} else if (type === 'session.idle') {
|
|
6029
6138
|
finalizeStreamingMsg();
|
|
6030
6139
|
setStatus('idle');
|
|
6140
|
+
} else if (type === 'message.reasoning') {
|
|
6141
|
+
const msgsEl = document.getElementById('messages');
|
|
6142
|
+
const placeholder = msgsEl.querySelector('.empty-chat');
|
|
6143
|
+
if (placeholder) placeholder.remove();
|
|
6144
|
+
const thinkEl = document.createElement('div');
|
|
6145
|
+
thinkEl.className = 'thinking-block';
|
|
6146
|
+
thinkEl.textContent = msg.reasoning || msg.text || '';
|
|
6147
|
+
if (!state.showTools) thinkEl.style.display = 'none';
|
|
6148
|
+
msgsEl.appendChild(thinkEl);
|
|
6149
|
+
msgsEl.scrollTop = msgsEl.scrollHeight;
|
|
6031
6150
|
} else if (type === 'session.error') {
|
|
6032
6151
|
finalizeStreamingMsg();
|
|
6033
6152
|
setStatus('idle');
|
|
@@ -6075,7 +6194,7 @@ function generateAdminHtml(version2) {
|
|
|
6075
6194
|
document.getElementById('deny-btn').addEventListener('click', () => sendApproval(false));
|
|
6076
6195
|
|
|
6077
6196
|
// \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) {
|
|
6197
|
+
async function selectSession(id) {
|
|
6079
6198
|
state.activeSessionId = id;
|
|
6080
6199
|
const session = state.sessions.find(s => s.id === id);
|
|
6081
6200
|
document.getElementById('chat-title').textContent =
|
|
@@ -6084,6 +6203,12 @@ function generateAdminHtml(version2) {
|
|
|
6084
6203
|
loadMessages(id);
|
|
6085
6204
|
renderSidebar();
|
|
6086
6205
|
setStatus('idle');
|
|
6206
|
+
// \u5148\u5C1D\u8BD5\u6062\u590D\uFF08\u5C06\u5386\u53F2\u4F1A\u8BDD\u52A0\u8F7D\u5230\u5185\u5B58\uFF09\uFF0C\u518D\u8FDE\u63A5 WS
|
|
6207
|
+
try {
|
|
6208
|
+
await apiFetch('/api/chat/sessions/' + id + '/resume', { method: 'POST' });
|
|
6209
|
+
} catch {
|
|
6210
|
+
// \u6062\u590D\u5931\u8D25\u4E5F\u7EE7\u7EED\uFF08\u53EF\u80FD\u5DF2\u5728\u5185\u5B58\u4E2D\uFF0C\u6216\u53EA\u8BFB\u67E5\u770B\uFF09
|
|
6211
|
+
}
|
|
6087
6212
|
connectWs(id);
|
|
6088
6213
|
// Close sidebar on mobile
|
|
6089
6214
|
document.getElementById('sidebar').classList.remove('open');
|
|
@@ -6248,6 +6373,15 @@ function generateAdminHtml(version2) {
|
|
|
6248
6373
|
}
|
|
6249
6374
|
});
|
|
6250
6375
|
|
|
6376
|
+
// \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
|
|
6377
|
+
document.getElementById('toggle-tools-btn').addEventListener('click', () => {
|
|
6378
|
+
state.showTools = !state.showTools;
|
|
6379
|
+
document.getElementById('toggle-tools-btn').textContent = state.showTools ? '\u5DE5\u5177 \u25BE' : '\u5DE5\u5177 \u25B8';
|
|
6380
|
+
document.querySelectorAll('.tool-box, .thinking-block').forEach(el => {
|
|
6381
|
+
el.style.display = state.showTools ? '' : 'none';
|
|
6382
|
+
});
|
|
6383
|
+
});
|
|
6384
|
+
|
|
6251
6385
|
// \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
6386
|
document.getElementById('msg-input').addEventListener('focus', () => {
|
|
6253
6387
|
setTimeout(() => {
|
|
@@ -6372,6 +6506,23 @@ async function sessionsRoutes(app, opts) {
|
|
|
6372
6506
|
return reply.send({ success: true, command });
|
|
6373
6507
|
}
|
|
6374
6508
|
);
|
|
6509
|
+
app.delete(
|
|
6510
|
+
"/api/sessions/:id",
|
|
6511
|
+
async (request, reply) => {
|
|
6512
|
+
const { id } = request.params;
|
|
6513
|
+
if (!opts.config.logDir) {
|
|
6514
|
+
return reply.code(404).send({ success: false, error: "Log directory not configured" });
|
|
6515
|
+
}
|
|
6516
|
+
const deleted = deleteSessionFiles(opts.config.logDir, id);
|
|
6517
|
+
if (!deleted) {
|
|
6518
|
+
return reply.code(404).send({ success: false, error: `Session not found: ${id}` });
|
|
6519
|
+
}
|
|
6520
|
+
if (opts.manager) {
|
|
6521
|
+
opts.manager.deleteSession(id);
|
|
6522
|
+
}
|
|
6523
|
+
return reply.send({ success: true });
|
|
6524
|
+
}
|
|
6525
|
+
);
|
|
6375
6526
|
app.get(
|
|
6376
6527
|
"/api/sessions/:id/fork-command",
|
|
6377
6528
|
async (request, reply) => {
|
|
@@ -6503,6 +6654,7 @@ async function automationRoutes(app, opts) {
|
|
|
6503
6654
|
}
|
|
6504
6655
|
|
|
6505
6656
|
// src/web/routes/chat.ts
|
|
6657
|
+
import { join as join14 } from "path";
|
|
6506
6658
|
var BUSY_STATUSES = /* @__PURE__ */ new Set(["thinking", "tool_calling", "awaiting_confirm"]);
|
|
6507
6659
|
async function chatRoutes(app, opts) {
|
|
6508
6660
|
const { manager } = opts;
|
|
@@ -6556,6 +6708,37 @@ async function chatRoutes(app, opts) {
|
|
|
6556
6708
|
return reply.send({ success: true });
|
|
6557
6709
|
}
|
|
6558
6710
|
);
|
|
6711
|
+
app.post(
|
|
6712
|
+
"/api/chat/sessions/:id/resume",
|
|
6713
|
+
async (request, reply) => {
|
|
6714
|
+
const { id } = request.params;
|
|
6715
|
+
if (manager.getSession(id)) {
|
|
6716
|
+
return reply.send({ success: true, resumed: false });
|
|
6717
|
+
}
|
|
6718
|
+
if (!opts.config.apiKey) {
|
|
6719
|
+
return reply.code(400).send({
|
|
6720
|
+
success: false,
|
|
6721
|
+
error: "\u672A\u914D\u7F6E API Key\uFF0C\u8BF7\u5148\u70B9\u51FB\u53F3\u4E0A\u89D2\u300C\u914D\u7F6E\u300D\u6309\u94AE\u586B\u5199"
|
|
6722
|
+
});
|
|
6723
|
+
}
|
|
6724
|
+
if (!opts.config.logDir) {
|
|
6725
|
+
return reply.code(404).send({ success: false, error: "Log directory not configured" });
|
|
6726
|
+
}
|
|
6727
|
+
const session = findSession(opts.config.logDir, id);
|
|
6728
|
+
if (!session) {
|
|
6729
|
+
return reply.code(404).send({ success: false, error: `Session not found: ${id}` });
|
|
6730
|
+
}
|
|
6731
|
+
const logFilePath2 = join14(opts.config.logDir, session.logFile);
|
|
6732
|
+
const initialMessages = loadMessagesFromJsonl(logFilePath2);
|
|
6733
|
+
try {
|
|
6734
|
+
await manager.createSession({ sessionId: id, initialMessages });
|
|
6735
|
+
return reply.send({ success: true, resumed: true });
|
|
6736
|
+
} catch (err) {
|
|
6737
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6738
|
+
return reply.code(500).send({ success: false, error: msg });
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
);
|
|
6559
6742
|
app.post(
|
|
6560
6743
|
"/api/chat/sessions/:id/approve",
|
|
6561
6744
|
async (request, reply) => {
|
|
@@ -7050,6 +7233,10 @@ var SessionManager = class {
|
|
|
7050
7233
|
listRunning() {
|
|
7051
7234
|
return [...this.sessions.values()].map((s) => s.snapshot());
|
|
7052
7235
|
}
|
|
7236
|
+
/** 从内存中移除指定会话(不影响磁盘文件,由上层路由负责文件清理) */
|
|
7237
|
+
deleteSession(sessionId) {
|
|
7238
|
+
this.sessions.delete(sessionId);
|
|
7239
|
+
}
|
|
7053
7240
|
};
|
|
7054
7241
|
|
|
7055
7242
|
// src/index.ts
|