@zhongqian97-code/ecode 0.5.22 → 0.5.24

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 +124 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5574,6 +5574,43 @@ function generateAdminHtml(version2) {
5574
5574
  .num { color: #79c0ff; }
5575
5575
  .fn { color: #d2a8ff; }
5576
5576
 
5577
+ /* \u2500\u2500 Toast notifications \u2500\u2500 */
5578
+ #toast-container {
5579
+ position: fixed;
5580
+ top: 16px;
5581
+ right: 16px;
5582
+ z-index: 200;
5583
+ display: flex;
5584
+ flex-direction: column;
5585
+ gap: 8px;
5586
+ pointer-events: none;
5587
+ }
5588
+ .toast {
5589
+ padding: 10px 16px;
5590
+ border-radius: 4px;
5591
+ font-size: 13px;
5592
+ max-width: 320px;
5593
+ background: #21262d;
5594
+ color: #c9d1d9;
5595
+ border: 1px solid #30363d;
5596
+ pointer-events: auto;
5597
+ animation: toast-in .2s ease;
5598
+ }
5599
+ .toast-error {
5600
+ background: #2d1b1b;
5601
+ color: #ff7b72;
5602
+ border-color: #f85149;
5603
+ }
5604
+ .toast-success {
5605
+ background: #1a2d1a;
5606
+ color: #3fb950;
5607
+ border-color: #2ea043;
5608
+ }
5609
+ @keyframes toast-in {
5610
+ from { opacity: 0; transform: translateX(20px); }
5611
+ to { opacity: 1; transform: translateX(0); }
5612
+ }
5613
+
5577
5614
  /* \u2500\u2500 Mobile \u2500\u2500 */
5578
5615
  @media (max-width: 600px) {
5579
5616
  #hamburger { display: block; }
@@ -5593,6 +5630,7 @@ function generateAdminHtml(version2) {
5593
5630
  <button id="hamburger" aria-label="Toggle sidebar">\u2630</button>
5594
5631
  <h1>\u26A1 ecode web admin</h1>
5595
5632
  <span class="version">v${version2}</span>
5633
+ <span id="topbar-model" class="version" style="display:none"></span>
5596
5634
  <button id="config-btn" class="btn">\u914D\u7F6E</button>
5597
5635
  <button id="upgrade-btn" class="btn">\u5347\u7EA7</button>
5598
5636
  </div>
@@ -5657,6 +5695,9 @@ function generateAdminHtml(version2) {
5657
5695
  </div>
5658
5696
  </div>
5659
5697
 
5698
+ <!-- Toast notifications -->
5699
+ <div id="toast-container"></div>
5700
+
5660
5701
  <!-- Bash approval modal -->
5661
5702
  <div id="approval-modal" role="dialog" aria-modal="true">
5662
5703
  <div id="approval-box">
@@ -5683,6 +5724,17 @@ function generateAdminHtml(version2) {
5683
5724
  streamingMsgEl: null, // DOM element currently receiving delta tokens
5684
5725
  };
5685
5726
 
5727
+ // \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
5728
+ function showToast(msg, type) {
5729
+ const container = document.getElementById('toast-container');
5730
+ if (!container) return;
5731
+ const el = document.createElement('div');
5732
+ el.className = 'toast toast-' + (type || 'info');
5733
+ el.textContent = msg;
5734
+ container.appendChild(el);
5735
+ setTimeout(() => el.remove(), 4000);
5736
+ }
5737
+
5686
5738
  // \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5687
5739
  function apiUrl(path) {
5688
5740
  const sep = path.includes('?') ? '&' : '?';
@@ -6032,7 +6084,7 @@ function generateAdminHtml(version2) {
6032
6084
  selectSession(session.id);
6033
6085
  }
6034
6086
  } catch (e) {
6035
- document.getElementById('ws-status').textContent = '\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25: ' + e.message;
6087
+ showToast('\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25: ' + e.message, 'error');
6036
6088
  }
6037
6089
  });
6038
6090
 
@@ -6087,7 +6139,7 @@ function generateAdminHtml(version2) {
6087
6139
  document.getElementById('cfg-logdir').value = data.logDir || '';
6088
6140
  document.getElementById('cfg-systemprompt').value = data.systemPrompt || '';
6089
6141
  } catch (e) {
6090
- // open modal even if fetch fails; fields will be empty
6142
+ showToast('\u52A0\u8F7D\u914D\u7F6E\u5931\u8D25: ' + e.message, 'error');
6091
6143
  }
6092
6144
  document.getElementById('config-modal').classList.add('open');
6093
6145
  });
@@ -6107,8 +6159,10 @@ function generateAdminHtml(version2) {
6107
6159
  if (apiKeyVal) body.apiKey = apiKeyVal;
6108
6160
  try {
6109
6161
  await apiFetch('/api/config', { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) });
6162
+ updateTopbarModel(body.model);
6163
+ showToast('\u914D\u7F6E\u5DF2\u4FDD\u5B58', 'success');
6110
6164
  } catch (e) {
6111
- // ignore errors silently for now
6165
+ showToast('\u4FDD\u5B58\u5931\u8D25: ' + e.message, 'error');
6112
6166
  }
6113
6167
  document.getElementById('config-modal').classList.remove('open');
6114
6168
  });
@@ -6181,7 +6235,28 @@ function generateAdminHtml(version2) {
6181
6235
  };
6182
6236
 
6183
6237
  // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
6238
+ function updateTopbarModel(model) {
6239
+ const el = document.getElementById('topbar-model');
6240
+ if (!el) return;
6241
+ if (model) {
6242
+ el.textContent = model;
6243
+ el.style.display = '';
6244
+ } else {
6245
+ el.style.display = 'none';
6246
+ }
6247
+ }
6248
+
6249
+ async function initModel() {
6250
+ try {
6251
+ const data = await apiFetch('/api/config');
6252
+ updateTopbarModel(data.model);
6253
+ } catch {
6254
+ // non-critical, ignore
6255
+ }
6256
+ }
6257
+
6184
6258
  loadSessions();
6259
+ initModel();
6185
6260
  </script>
6186
6261
  </body>
6187
6262
  </html>`;
@@ -6209,33 +6284,48 @@ async function statusRoutes(app, opts) {
6209
6284
  import { readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
6210
6285
  import { join as join13 } from "path";
6211
6286
  async function sessionsRoutes(app, opts) {
6212
- app.get("/api/sessions", async (_request, reply) => {
6213
- if (!opts.config.logDir) {
6214
- return reply.send([]);
6215
- }
6216
- return listSessions(opts.config.logDir);
6287
+ app.get("/api/sessions", async (_request, _reply) => {
6288
+ const fileSessions = opts.config.logDir ? listSessions(opts.config.logDir) : [];
6289
+ if (!opts.manager) {
6290
+ return fileSessions;
6291
+ }
6292
+ const runningSnapshots = opts.manager.listRunning();
6293
+ const fileIds = new Set(fileSessions.map((s) => s.id));
6294
+ const runtimeSessions = runningSnapshots.filter((s) => !fileIds.has(s.id)).map((s) => ({
6295
+ id: s.id,
6296
+ title: s.title,
6297
+ model: s.model,
6298
+ status: s.status,
6299
+ turnCount: s.turnCount,
6300
+ totalTokens: s.totalTokens,
6301
+ startTime: s.startedAt,
6302
+ lastActivity: s.lastActivity,
6303
+ cwd: "",
6304
+ logFile: ""
6305
+ }));
6306
+ return [...fileSessions, ...runtimeSessions];
6217
6307
  });
6218
6308
  app.get(
6219
6309
  "/api/sessions/:id/messages",
6220
6310
  async (request, reply) => {
6221
6311
  const { id } = request.params;
6222
- if (!opts.config.logDir) {
6223
- return reply.code(404).send({ success: false, error: "Log directory not configured" });
6224
- }
6225
- const session = findSession(opts.config.logDir, id);
6226
- if (!session) {
6227
- return reply.code(404).send({ success: false, error: `Session not found: ${id}` });
6228
- }
6229
- const logFilePath2 = join13(opts.config.logDir, session.logFile);
6230
- if (!existsSync4(logFilePath2)) {
6231
- return reply.code(404).send({ success: false, error: `Log file not found: ${session.logFile}` });
6312
+ if (opts.config.logDir) {
6313
+ const session = findSession(opts.config.logDir, id);
6314
+ if (session) {
6315
+ const logFilePath2 = join13(opts.config.logDir, session.logFile);
6316
+ if (existsSync4(logFilePath2)) {
6317
+ try {
6318
+ const content = readFileSync5(logFilePath2, "utf-8");
6319
+ return reply.header("Content-Type", "text/plain; charset=utf-8").send(content);
6320
+ } catch {
6321
+ }
6322
+ }
6323
+ }
6232
6324
  }
6233
- try {
6234
- const content = readFileSync5(logFilePath2, "utf-8");
6235
- return reply.header("Content-Type", "text/plain; charset=utf-8").send(content);
6236
- } catch (_err) {
6237
- return reply.code(404).send({ success: false, error: `Log file not found: ${session.logFile}` });
6325
+ if (opts.manager && opts.manager.getSession(id)) {
6326
+ return reply.header("Content-Type", "text/plain; charset=utf-8").send("");
6238
6327
  }
6328
+ return reply.code(404).send({ success: false, error: `Session not found: ${id}` });
6239
6329
  }
6240
6330
  );
6241
6331
  app.get(
@@ -6490,7 +6580,7 @@ async function systemRoutes(app, opts) {
6490
6580
  try {
6491
6581
  latest = execSync("npm view @zhongqian97-code/ecode version", {
6492
6582
  encoding: "utf-8",
6493
- timeout: 2e3
6583
+ timeout: 5e3
6494
6584
  }).trim();
6495
6585
  } catch {
6496
6586
  latest = "unknown";
@@ -6539,7 +6629,7 @@ async function buildServer(opts) {
6539
6629
  manager: opts.manager,
6540
6630
  version: opts.version
6541
6631
  });
6542
- await app.register(sessionsRoutes, { config: opts.config });
6632
+ await app.register(sessionsRoutes, { config: opts.config, manager: opts.manager });
6543
6633
  await app.register(configRoutes, { config: opts.config });
6544
6634
  await app.register(automationRoutes, { config: opts.config });
6545
6635
  await app.register(chatRoutes, { config: opts.config, manager: opts.manager });
@@ -6640,6 +6730,7 @@ var SessionRuntime = class {
6640
6730
  config;
6641
6731
  model;
6642
6732
  abortController = null;
6733
+ _stopAfterToolRound = false;
6643
6734
  _turnCount = 0;
6644
6735
  _totalTokens = 0;
6645
6736
  startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -6758,6 +6849,10 @@ var SessionRuntime = class {
6758
6849
  this.bus.emit({ type: "tool.completed", callId: tc.id, toolName: tc.name, output: toolResult });
6759
6850
  this.messages.push({ role: "tool", tool_call_id: tc.id, content: toolResult });
6760
6851
  }
6852
+ if (this._stopAfterToolRound) {
6853
+ this._stopAfterToolRound = false;
6854
+ break;
6855
+ }
6761
6856
  } else {
6762
6857
  if (assistantText) {
6763
6858
  this.messages.push({
@@ -6792,7 +6887,10 @@ Proceed?`;
6792
6887
  this.bus.emit({ type: "approval.requested", requestId: reqId, kind, prompt });
6793
6888
  const approved = await promise;
6794
6889
  this._status = "tool_calling";
6795
- if (!approved) return SKIP_MESSAGE;
6890
+ if (!approved) {
6891
+ this._stopAfterToolRound = true;
6892
+ return SKIP_MESSAGE;
6893
+ }
6796
6894
  }
6797
6895
  if (signal.aborted) return SKIP_MESSAGE;
6798
6896
  const result = await executeBash(parsed.command);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.22",
3
+ "version": "0.5.24",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",