@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.
Files changed (2) hide show
  1. package/dist/index.js +190 -3
  2. 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, '&lt;');
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', () => selectSession(el.dataset.id));
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.29",
3
+ "version": "0.5.30",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",