@zhongqian97-code/ecode 0.5.21 → 0.5.23
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 +139 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5250,7 +5250,7 @@ function generateAdminHtml(version2) {
|
|
|
5250
5250
|
<html lang="zh">
|
|
5251
5251
|
<head>
|
|
5252
5252
|
<meta charset="UTF-8">
|
|
5253
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5253
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content">
|
|
5254
5254
|
<title>ecode web admin</title>
|
|
5255
5255
|
<style>
|
|
5256
5256
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -5567,6 +5567,13 @@ function generateAdminHtml(version2) {
|
|
|
5567
5567
|
.modal-footer { display:flex; gap:8px; justify-content:flex-end; margin-top:16px; }
|
|
5568
5568
|
#upgrade-output { display:none; background:#0d1117; border:1px solid #30363d; border-radius:4px; padding:8px; font-family:monospace; font-size:12px; color:#c9d1d9; white-space:pre-wrap; max-height:200px; overflow-y:auto; margin-top:8px; }
|
|
5569
5569
|
|
|
5570
|
+
/* \u2500\u2500 Syntax highlight \u2500\u2500 */
|
|
5571
|
+
.kw { color: #ff7b72; }
|
|
5572
|
+
.str { color: #a5d6ff; }
|
|
5573
|
+
.cmt { color: #8b949e; font-style: italic; }
|
|
5574
|
+
.num { color: #79c0ff; }
|
|
5575
|
+
.fn { color: #d2a8ff; }
|
|
5576
|
+
|
|
5570
5577
|
/* \u2500\u2500 Mobile \u2500\u2500 */
|
|
5571
5578
|
@media (max-width: 600px) {
|
|
5572
5579
|
#hamburger { display: block; }
|
|
@@ -5586,6 +5593,7 @@ function generateAdminHtml(version2) {
|
|
|
5586
5593
|
<button id="hamburger" aria-label="Toggle sidebar">\u2630</button>
|
|
5587
5594
|
<h1>\u26A1 ecode web admin</h1>
|
|
5588
5595
|
<span class="version">v${version2}</span>
|
|
5596
|
+
<span id="topbar-model" class="version" style="display:none"></span>
|
|
5589
5597
|
<button id="config-btn" class="btn">\u914D\u7F6E</button>
|
|
5590
5598
|
<button id="upgrade-btn" class="btn">\u5347\u7EA7</button>
|
|
5591
5599
|
</div>
|
|
@@ -5694,6 +5702,21 @@ function generateAdminHtml(version2) {
|
|
|
5694
5702
|
return proto + '//' + location.host + '/api/ws/sessions/' + sessionId + '?token=' + encodeURIComponent(state.token);
|
|
5695
5703
|
}
|
|
5696
5704
|
|
|
5705
|
+
// \u2500\u2500 Code highlighting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5706
|
+
function highlightCode(code) {
|
|
5707
|
+
let s = escHtml(code);
|
|
5708
|
+
// keywords
|
|
5709
|
+
s = s.replace(/\b(function|const|let|var|return|if|else|for|while|class|import|export|from|async|await|new|this|typeof|instanceof|null|undefined|true|false)\b/g, '<span class="kw">$1</span>');
|
|
5710
|
+
// strings (single, double \u2014 after HTML escaping, quotes appear as ' or ")
|
|
5711
|
+
s = s.replace(/('[^&#]*'|"[^&]*")/g, '<span class="str">$1</span>');
|
|
5712
|
+
// comments
|
|
5713
|
+
s = s.replace(/(//[^
|
|
5714
|
+
]*)/g, '<span class="cmt">$1</span>');
|
|
5715
|
+
// numbers
|
|
5716
|
+
s = s.replace(/\b(d+.?d*)\b/g, '<span class="num">$1</span>');
|
|
5717
|
+
return s;
|
|
5718
|
+
}
|
|
5719
|
+
|
|
5697
5720
|
// Basic markdown \u2192 HTML (no libs)
|
|
5698
5721
|
function renderMarkdown(text) {
|
|
5699
5722
|
// Escape HTML first
|
|
@@ -5701,9 +5724,9 @@ function generateAdminHtml(version2) {
|
|
|
5701
5724
|
.replace(/&/g, '&')
|
|
5702
5725
|
.replace(/</g, '<')
|
|
5703
5726
|
.replace(/>/g, '>');
|
|
5704
|
-
// Code blocks
|
|
5727
|
+
// Code blocks (use highlightCode for syntax coloring)
|
|
5705
5728
|
s = s.replace(/\`\`\`([\\s\\S]*?)\`\`\`/g, (_, code) =>
|
|
5706
|
-
'<pre><code>' + code.trim() + '</code></pre>');
|
|
5729
|
+
'<pre><code>' + highlightCode(code.trim()) + '</code></pre>');
|
|
5707
5730
|
// Inline code
|
|
5708
5731
|
s = s.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
|
|
5709
5732
|
// Bold
|
|
@@ -5781,6 +5804,31 @@ function generateAdminHtml(version2) {
|
|
|
5781
5804
|
state.streamingMsgEl = null;
|
|
5782
5805
|
}
|
|
5783
5806
|
|
|
5807
|
+
async function loadMessages(sessionId) {
|
|
5808
|
+
try {
|
|
5809
|
+
const sep = '/api/sessions/' + sessionId + '/messages?token=' + encodeURIComponent(state.token);
|
|
5810
|
+
const res = await fetch(sep);
|
|
5811
|
+
if (!res.ok) return;
|
|
5812
|
+
const text = await res.text();
|
|
5813
|
+
const lines = text.split('\\n').filter(l => l.trim());
|
|
5814
|
+
const msgsEl = document.getElementById('messages');
|
|
5815
|
+
msgsEl.innerHTML = '';
|
|
5816
|
+
for (const line of lines) {
|
|
5817
|
+
try {
|
|
5818
|
+
const m = JSON.parse(line);
|
|
5819
|
+
if (m.content) {
|
|
5820
|
+
appendMessage(m.role === 'user' ? 'user' : 'assistant', renderMarkdown(m.content));
|
|
5821
|
+
}
|
|
5822
|
+
} catch { /* skip malformed lines */ }
|
|
5823
|
+
}
|
|
5824
|
+
if (!lines.length) {
|
|
5825
|
+
msgsEl.innerHTML = '<div class="empty-chat">\u6682\u65E0\u5386\u53F2\u6D88\u606F\uFF0C\u5F00\u59CB\u5BF9\u8BDD\u5427</div>';
|
|
5826
|
+
}
|
|
5827
|
+
} catch (e) {
|
|
5828
|
+
// silently ignore \u2014 session might have no log file yet
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
|
|
5784
5832
|
function appendMessage(role, htmlContent, opts) {
|
|
5785
5833
|
const msgsEl = document.getElementById('messages');
|
|
5786
5834
|
// Remove empty-chat placeholder
|
|
@@ -5848,7 +5896,12 @@ function generateAdminHtml(version2) {
|
|
|
5848
5896
|
finalizeStreamingMsg();
|
|
5849
5897
|
if (state.activeSessionId === sessionId && state.wsRetries < 5) {
|
|
5850
5898
|
state.wsRetries++;
|
|
5851
|
-
|
|
5899
|
+
const wsRetryDelay = Math.min(1000 * Math.pow(2, state.wsRetries), 30000);
|
|
5900
|
+
document.getElementById('ws-status').textContent =
|
|
5901
|
+
'\u91CD\u8FDE\u4E2D\u2026 (' + state.wsRetries + '/5\uFF0C' + (wsRetryDelay/1000).toFixed(0) + 's\u540E)';
|
|
5902
|
+
setTimeout(() => openWs(sessionId), wsRetryDelay);
|
|
5903
|
+
} else if (state.wsRetries >= 5) {
|
|
5904
|
+
document.getElementById('ws-status').textContent = '\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u9875\u9762';
|
|
5852
5905
|
}
|
|
5853
5906
|
};
|
|
5854
5907
|
}
|
|
@@ -5957,6 +6010,7 @@ function generateAdminHtml(version2) {
|
|
|
5957
6010
|
document.getElementById('chat-title').textContent =
|
|
5958
6011
|
session ? (session.title || id.slice(0, 12) + '\u2026') : id;
|
|
5959
6012
|
clearMessages();
|
|
6013
|
+
loadMessages(id);
|
|
5960
6014
|
renderSidebar();
|
|
5961
6015
|
setStatus('idle');
|
|
5962
6016
|
connectWs(id);
|
|
@@ -6054,6 +6108,7 @@ function generateAdminHtml(version2) {
|
|
|
6054
6108
|
if (apiKeyVal) body.apiKey = apiKeyVal;
|
|
6055
6109
|
try {
|
|
6056
6110
|
await apiFetch('/api/config', { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) });
|
|
6111
|
+
updateTopbarModel(body.model);
|
|
6057
6112
|
} catch (e) {
|
|
6058
6113
|
// ignore errors silently for now
|
|
6059
6114
|
}
|
|
@@ -6113,8 +6168,43 @@ function generateAdminHtml(version2) {
|
|
|
6113
6168
|
}
|
|
6114
6169
|
});
|
|
6115
6170
|
|
|
6171
|
+
// \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
|
|
6172
|
+
document.getElementById('msg-input').addEventListener('focus', () => {
|
|
6173
|
+
setTimeout(() => {
|
|
6174
|
+
document.getElementById('msg-input').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
6175
|
+
}, 300);
|
|
6176
|
+
});
|
|
6177
|
+
|
|
6178
|
+
// \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
|
|
6179
|
+
window.onerror = function(msg, src, line) {
|
|
6180
|
+
const wsStatusEl = document.getElementById('ws-status');
|
|
6181
|
+
if (wsStatusEl) wsStatusEl.textContent = '\u9519\u8BEF: ' + msg;
|
|
6182
|
+
return false;
|
|
6183
|
+
};
|
|
6184
|
+
|
|
6116
6185
|
// \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
|
|
6186
|
+
function updateTopbarModel(model) {
|
|
6187
|
+
const el = document.getElementById('topbar-model');
|
|
6188
|
+
if (!el) return;
|
|
6189
|
+
if (model) {
|
|
6190
|
+
el.textContent = model;
|
|
6191
|
+
el.style.display = '';
|
|
6192
|
+
} else {
|
|
6193
|
+
el.style.display = 'none';
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6196
|
+
|
|
6197
|
+
async function initModel() {
|
|
6198
|
+
try {
|
|
6199
|
+
const data = await apiFetch('/api/config');
|
|
6200
|
+
updateTopbarModel(data.model);
|
|
6201
|
+
} catch {
|
|
6202
|
+
// non-critical, ignore
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
|
|
6117
6206
|
loadSessions();
|
|
6207
|
+
initModel();
|
|
6118
6208
|
</script>
|
|
6119
6209
|
</body>
|
|
6120
6210
|
</html>`;
|
|
@@ -6142,33 +6232,48 @@ async function statusRoutes(app, opts) {
|
|
|
6142
6232
|
import { readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
|
|
6143
6233
|
import { join as join13 } from "path";
|
|
6144
6234
|
async function sessionsRoutes(app, opts) {
|
|
6145
|
-
app.get("/api/sessions", async (_request,
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6235
|
+
app.get("/api/sessions", async (_request, _reply) => {
|
|
6236
|
+
const fileSessions = opts.config.logDir ? listSessions(opts.config.logDir) : [];
|
|
6237
|
+
if (!opts.manager) {
|
|
6238
|
+
return fileSessions;
|
|
6239
|
+
}
|
|
6240
|
+
const runningSnapshots = opts.manager.listRunning();
|
|
6241
|
+
const fileIds = new Set(fileSessions.map((s) => s.id));
|
|
6242
|
+
const runtimeSessions = runningSnapshots.filter((s) => !fileIds.has(s.id)).map((s) => ({
|
|
6243
|
+
id: s.id,
|
|
6244
|
+
title: s.title,
|
|
6245
|
+
model: s.model,
|
|
6246
|
+
status: s.status,
|
|
6247
|
+
turnCount: s.turnCount,
|
|
6248
|
+
totalTokens: s.totalTokens,
|
|
6249
|
+
startTime: s.startedAt,
|
|
6250
|
+
lastActivity: s.lastActivity,
|
|
6251
|
+
cwd: "",
|
|
6252
|
+
logFile: ""
|
|
6253
|
+
}));
|
|
6254
|
+
return [...fileSessions, ...runtimeSessions];
|
|
6150
6255
|
});
|
|
6151
6256
|
app.get(
|
|
6152
6257
|
"/api/sessions/:id/messages",
|
|
6153
6258
|
async (request, reply) => {
|
|
6154
6259
|
const { id } = request.params;
|
|
6155
|
-
if (
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6260
|
+
if (opts.config.logDir) {
|
|
6261
|
+
const session = findSession(opts.config.logDir, id);
|
|
6262
|
+
if (session) {
|
|
6263
|
+
const logFilePath2 = join13(opts.config.logDir, session.logFile);
|
|
6264
|
+
if (existsSync4(logFilePath2)) {
|
|
6265
|
+
try {
|
|
6266
|
+
const content = readFileSync5(logFilePath2, "utf-8");
|
|
6267
|
+
return reply.header("Content-Type", "text/plain; charset=utf-8").send(content);
|
|
6268
|
+
} catch {
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6271
|
+
}
|
|
6165
6272
|
}
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
return reply.header("Content-Type", "text/plain; charset=utf-8").send(content);
|
|
6169
|
-
} catch (_err) {
|
|
6170
|
-
return reply.code(404).send({ success: false, error: `Log file not found: ${session.logFile}` });
|
|
6273
|
+
if (opts.manager && opts.manager.getSession(id)) {
|
|
6274
|
+
return reply.header("Content-Type", "text/plain; charset=utf-8").send("");
|
|
6171
6275
|
}
|
|
6276
|
+
return reply.code(404).send({ success: false, error: `Session not found: ${id}` });
|
|
6172
6277
|
}
|
|
6173
6278
|
);
|
|
6174
6279
|
app.get(
|
|
@@ -6472,7 +6577,7 @@ async function buildServer(opts) {
|
|
|
6472
6577
|
manager: opts.manager,
|
|
6473
6578
|
version: opts.version
|
|
6474
6579
|
});
|
|
6475
|
-
await app.register(sessionsRoutes, { config: opts.config });
|
|
6580
|
+
await app.register(sessionsRoutes, { config: opts.config, manager: opts.manager });
|
|
6476
6581
|
await app.register(configRoutes, { config: opts.config });
|
|
6477
6582
|
await app.register(automationRoutes, { config: opts.config });
|
|
6478
6583
|
await app.register(chatRoutes, { config: opts.config, manager: opts.manager });
|
|
@@ -6573,6 +6678,7 @@ var SessionRuntime = class {
|
|
|
6573
6678
|
config;
|
|
6574
6679
|
model;
|
|
6575
6680
|
abortController = null;
|
|
6681
|
+
_stopAfterToolRound = false;
|
|
6576
6682
|
_turnCount = 0;
|
|
6577
6683
|
_totalTokens = 0;
|
|
6578
6684
|
startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -6691,6 +6797,10 @@ var SessionRuntime = class {
|
|
|
6691
6797
|
this.bus.emit({ type: "tool.completed", callId: tc.id, toolName: tc.name, output: toolResult });
|
|
6692
6798
|
this.messages.push({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
6693
6799
|
}
|
|
6800
|
+
if (this._stopAfterToolRound) {
|
|
6801
|
+
this._stopAfterToolRound = false;
|
|
6802
|
+
break;
|
|
6803
|
+
}
|
|
6694
6804
|
} else {
|
|
6695
6805
|
if (assistantText) {
|
|
6696
6806
|
this.messages.push({
|
|
@@ -6725,7 +6835,10 @@ Proceed?`;
|
|
|
6725
6835
|
this.bus.emit({ type: "approval.requested", requestId: reqId, kind, prompt });
|
|
6726
6836
|
const approved = await promise;
|
|
6727
6837
|
this._status = "tool_calling";
|
|
6728
|
-
if (!approved)
|
|
6838
|
+
if (!approved) {
|
|
6839
|
+
this._stopAfterToolRound = true;
|
|
6840
|
+
return SKIP_MESSAGE;
|
|
6841
|
+
}
|
|
6729
6842
|
}
|
|
6730
6843
|
if (signal.aborted) return SKIP_MESSAGE;
|
|
6731
6844
|
const result = await executeBash(parsed.command);
|