claude-simple 0.2.26 → 0.2.27
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.
|
@@ -655,6 +655,7 @@ ${String(e||"")}`}function Ji(e,t=u.sessionId){let n=Wc(e,t);!e||ca.has(n)||(ca.
|
|
|
655
655
|
<div class="card-body gap-3 p-8">
|
|
656
656
|
<h1 class="text-xl font-bold">\u{1F916} Agent UI</h1>
|
|
657
657
|
<p id="auth-subtitle" class="text-sm text-base-content/60 mb-1">Claude \xB7 Codex \xB7 Grok \xB7 Gemini \xB7 Cursor</p>
|
|
658
|
+
<a href="/download/agentui-android.apk" class="btn btn-ghost btn-sm border border-base-300" download>\u4E0B\u8F7D Android App</a>
|
|
658
659
|
<form id="auth-form" class="flex flex-col gap-3" autocomplete="on" action="#">
|
|
659
660
|
<label class="form-control">
|
|
660
661
|
<div class="label py-1"><span class="label-text text-xs uppercase tracking-wide">Username</span></div>
|
|
@@ -711,6 +712,7 @@ ${String(e||"")}`}function Ji(e,t=u.sessionId){let n=Wc(e,t);!e||ca.has(n)||(ca.
|
|
|
711
712
|
<span id="conn-dot" class="w-2 h-2 rounded-full bg-base-content/30 flex-shrink-0" title="\u8FDE\u63A5\u72B6\u6001"></span>
|
|
712
713
|
<div class="flex gap-2 topbar-actions">
|
|
713
714
|
<button id="btn-pwa-install" class="btn btn-ghost btn-xs border border-base-300 hidden" title="\u5B89\u88C5\u5230\u4E3B\u5C4F\u5E55">\u5B89\u88C5</button>
|
|
715
|
+
<a href="/download/agentui-android.apk" class="btn btn-ghost btn-xs border border-base-300" title="\u4E0B\u8F7D Android APK" download>Android</a>
|
|
714
716
|
<button id="btn-meta-agent" class="btn btn-ghost btn-xs border border-primary/40 text-primary topbar-icon-btn" title="Meta Agent \u2014 \u62A5\u544A / \u95EE\u7B54 / VLM" aria-label="\u6253\u5F00 Meta Agent">
|
|
715
717
|
<svg class="topbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="m12 2 1.6 6.4L20 10l-6.4 1.6L12 18l-1.6-6.4L4 10l6.4-1.6L12 2Z"/><path d="m19 16 .7 2.3L22 19l-2.3.7L19 22l-.7-2.3L16 19l2.3-.7L19 16Z"/></svg><span class="topbar-icon-label">Agent</span>
|
|
716
718
|
</button>
|
|
@@ -1079,7 +1081,8 @@ ${String(e||"")}`}function Ji(e,t=u.sessionId){let n=Wc(e,t);!e||ca.has(n)||(ca.
|
|
|
1079
1081
|
<li>\u70B9\u53F3\u4E0A\u89D2\u83DC\u5355 <strong>\u22EE</strong></li>
|
|
1080
1082
|
<li>\u9009\u62E9 <strong>\u5B89\u88C5\u5E94\u7528</strong> \u6216 <strong>\u6DFB\u52A0\u5230\u4E3B\u5C4F\u5E55</strong></li>
|
|
1081
1083
|
</ol>
|
|
1082
|
-
<p class="text-xs text-base-content/50 mt-3">\u82E5\u83DC\u5355\u91CC\u6CA1\u6709\u8BE5\u9879\uFF1A\u8BF7\u7528 <strong>Chrome</strong> \u6253\u5F00\uFF0C\u5E76\u5148\u63A5\u53D7\u672C\u7AD9\u8BC1\u4E66\u63D0\u793A\u3002\u81EA\u7B7E HTTPS \u4E0B\u90E8\u5206\u673A\u578B\u4E0D\u5F39\u51FA\u4E00\u952E\u5B89\u88C5\u3002</p
|
|
1084
|
+
<p class="text-xs text-base-content/50 mt-3">\u82E5\u83DC\u5355\u91CC\u6CA1\u6709\u8BE5\u9879\uFF1A\u8BF7\u7528 <strong>Chrome</strong> \u6253\u5F00\uFF0C\u5E76\u5148\u63A5\u53D7\u672C\u7AD9\u8BC1\u4E66\u63D0\u793A\u3002\u81EA\u7B7E HTTPS \u4E0B\u90E8\u5206\u673A\u578B\u4E0D\u5F39\u51FA\u4E00\u952E\u5B89\u88C5\u3002</p>
|
|
1085
|
+
<a class="btn btn-primary btn-sm mt-4" href="/download/agentui-android.apk" download>\u4E0B\u8F7D Android APK</a>`:n=`<p class="text-sm text-base-content/80">\u8BF7\u5728\u6D4F\u89C8\u5668\u83DC\u5355\u4E2D\u9009\u62E9 <strong>\u5B89\u88C5\u5E94\u7528 / \u5B89\u88C5\u7F51\u9875\u5E94\u7528 / \u6DFB\u52A0\u5230\u4E3B\u5C4F\u5E55</strong>\u3002</p>
|
|
1083
1086
|
<p class="text-xs text-base-content/50 mt-3">Chrome / Edge \u5730\u5740\u680F\u53F3\u4FA7\u6709\u65F6\u4E5F\u4F1A\u51FA\u73B0\u5B89\u88C5\u56FE\u6807\u3002</p>`;let s=document.getElementById("modal-pwa-install");s||(s=document.createElement("dialog"),s.id="modal-pwa-install",s.className="modal",document.body.appendChild(s)),s.innerHTML=`
|
|
1084
1087
|
<div class="modal-box max-w-sm">
|
|
1085
1088
|
<h3 class="font-bold text-base mb-3">\u5B89\u88C5\u5230\u4E3B\u5C4F\u5E55</h3>
|
package/dist/index.html
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
|
6
6
|
<title>Claude Code</title>
|
|
7
|
-
<link rel="stylesheet" href="/style.0.2.
|
|
7
|
+
<link rel="stylesheet" href="/style.0.2.27.min.css">
|
|
8
8
|
<link rel="stylesheet" href="/highlight.min.css">
|
|
9
9
|
<script src="/marked.min.js"></script>
|
|
10
10
|
<script src="/highlight.min.js"></script>
|
|
11
11
|
</head>
|
|
12
12
|
<body class="h-dvh overflow-hidden flex flex-col bg-base-100 text-base-content">
|
|
13
13
|
<div id="root" class="flex flex-col flex-1 overflow-hidden"></div>
|
|
14
|
-
<script type="module" src="/app.0.2.
|
|
14
|
+
<script type="module" src="/app.0.2.27.min.js"></script>
|
|
15
15
|
</body>
|
|
16
16
|
</html>
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ export const AuthScreen = () => `
|
|
|
7
7
|
<div class="card-body gap-3 p-8">
|
|
8
8
|
<h1 class="text-xl font-bold">🤖 Agent UI</h1>
|
|
9
9
|
<p id="auth-subtitle" class="text-sm text-base-content/60 mb-1">Claude · Codex · Grok · Gemini · Cursor</p>
|
|
10
|
+
<a href="/download/agentui-android.apk" class="btn btn-ghost btn-sm border border-base-300" download>下载 Android App</a>
|
|
10
11
|
<form id="auth-form" class="flex flex-col gap-3" autocomplete="on" action="#">
|
|
11
12
|
<label class="form-control">
|
|
12
13
|
<div class="label py-1"><span class="label-text text-xs uppercase tracking-wide">Username</span></div>
|
|
@@ -80,6 +81,7 @@ export const AppShell = (opts = {}) => {
|
|
|
80
81
|
<span id="conn-dot" class="w-2 h-2 rounded-full bg-base-content/30 flex-shrink-0" title="连接状态"></span>
|
|
81
82
|
<div class="flex gap-2 topbar-actions">
|
|
82
83
|
<button id="btn-pwa-install" class="btn btn-ghost btn-xs border border-base-300 hidden" title="安装到主屏幕">安装</button>
|
|
84
|
+
<a href="/download/agentui-android.apk" class="btn btn-ghost btn-xs border border-base-300" title="下载 Android APK" download>Android</a>
|
|
83
85
|
<button id="btn-meta-agent" class="btn btn-ghost btn-xs border border-primary/40 text-primary topbar-icon-btn" title="Meta Agent — 报告 / 问答 / VLM" aria-label="打开 Meta Agent">
|
|
84
86
|
<svg class="topbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="m12 2 1.6 6.4L20 10l-6.4 1.6L12 18l-1.6-6.4L4 10l6.4-1.6L12 2Z"/><path d="m19 16 .7 2.3L22 19l-2.3.7L19 22l-.7-2.3L16 19l2.3-.7L19 16Z"/></svg><span class="topbar-icon-label">Agent</span>
|
|
85
87
|
</button>
|
package/public/shell.js
CHANGED
|
@@ -78,7 +78,8 @@ function showPwaInstallHelp() {
|
|
|
78
78
|
<li>点右上角菜单 <strong>⋮</strong></li>
|
|
79
79
|
<li>选择 <strong>安装应用</strong> 或 <strong>添加到主屏幕</strong></li>
|
|
80
80
|
</ol>
|
|
81
|
-
<p class="text-xs text-base-content/50 mt-3">若菜单里没有该项:请用 <strong>Chrome</strong> 打开,并先接受本站证书提示。自签 HTTPS 下部分机型不弹出一键安装。</p
|
|
81
|
+
<p class="text-xs text-base-content/50 mt-3">若菜单里没有该项:请用 <strong>Chrome</strong> 打开,并先接受本站证书提示。自签 HTTPS 下部分机型不弹出一键安装。</p>
|
|
82
|
+
<a class="btn btn-primary btn-sm mt-4" href="/download/agentui-android.apk" download>下载 Android APK</a>`;
|
|
82
83
|
} else {
|
|
83
84
|
body = `<p class="text-sm text-base-content/80">请在浏览器菜单中选择 <strong>安装应用 / 安装网页应用 / 添加到主屏幕</strong>。</p>
|
|
84
85
|
<p class="text-xs text-base-content/50 mt-3">Chrome / Edge 地址栏右侧有时也会出现安装图标。</p>`;
|
package/src/agents/sessions.js
CHANGED
|
@@ -217,6 +217,38 @@ export async function parseClaudeMessages(filePath) {
|
|
|
217
217
|
return messages;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
async function forkClaudeNativeSession(sourceSessionId, targetSessionId, claudeConfigDirs, assistantText, occurrence) {
|
|
221
|
+
const sourceFile = await findClaudeSessionFile(sourceSessionId, claudeConfigDirs);
|
|
222
|
+
if (!sourceFile) throw new Error('Claude source transcript was not found');
|
|
223
|
+
|
|
224
|
+
const source = await fs.readFile(sourceFile, 'utf8');
|
|
225
|
+
const output = [];
|
|
226
|
+
let foundForkPoint = false;
|
|
227
|
+
let seenMatches = 0;
|
|
228
|
+
for (const line of source.split(/\r?\n/)) {
|
|
229
|
+
if (!line.trim()) continue;
|
|
230
|
+
let record;
|
|
231
|
+
try { record = JSON.parse(line); } catch { continue; }
|
|
232
|
+
output.push(JSON.stringify(replaceSessionId(record, sourceSessionId, targetSessionId)));
|
|
233
|
+
if (record.type !== 'assistant' || !Array.isArray(record.message?.content)) continue;
|
|
234
|
+
for (const part of record.message.content) {
|
|
235
|
+
if (part?.type === 'text' && part.text?.trim() === assistantText && ++seenMatches === occurrence) {
|
|
236
|
+
foundForkPoint = true;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (foundForkPoint) break;
|
|
241
|
+
}
|
|
242
|
+
if (!foundForkPoint) throw new Error('Selected Claude assistant message was not found in the native transcript');
|
|
243
|
+
|
|
244
|
+
const targetFile = path.join(path.dirname(sourceFile), `${targetSessionId}.jsonl`);
|
|
245
|
+
const tempFile = `${targetFile}.tmp-${crypto.randomUUID()}`;
|
|
246
|
+
await fs.writeFile(tempFile, `${output.join('\n')}\n`, 'utf8');
|
|
247
|
+
await fs.rename(tempFile, targetFile);
|
|
248
|
+
claudeFileCache.set(targetSessionId, targetFile);
|
|
249
|
+
return targetFile;
|
|
250
|
+
}
|
|
251
|
+
|
|
220
252
|
// ─── Codex ────────────────────────────────────────────────────────────────────
|
|
221
253
|
|
|
222
254
|
async function loadCodexIndex(codexHome) {
|
|
@@ -407,6 +439,63 @@ export async function parseCodexMessages(filePath) {
|
|
|
407
439
|
return messages;
|
|
408
440
|
}
|
|
409
441
|
|
|
442
|
+
function replaceSessionId(value, sourceSessionId, targetSessionId) {
|
|
443
|
+
if (value === sourceSessionId) return targetSessionId;
|
|
444
|
+
if (Array.isArray(value)) return value.map(item => replaceSessionId(item, sourceSessionId, targetSessionId));
|
|
445
|
+
if (value && typeof value === 'object') {
|
|
446
|
+
return Object.fromEntries(Object.entries(value)
|
|
447
|
+
.map(([key, item]) => [key, replaceSessionId(item, sourceSessionId, targetSessionId)]));
|
|
448
|
+
}
|
|
449
|
+
return value;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Codex can only resume a session from its native event log. Re-serializing a
|
|
454
|
+
* chat transcript loses turn context, tool results, and injected instructions,
|
|
455
|
+
* so a fork must clone the native rollout and cut it at the selected answer.
|
|
456
|
+
*/
|
|
457
|
+
async function forkCodexNativeSession(sourceSessionId, targetSessionId, codexHome, assistantText, occurrence) {
|
|
458
|
+
const sourceFile = await findCodexSessionFile(sourceSessionId, codexHome);
|
|
459
|
+
if (!sourceFile) throw new Error('Codex source rollout was not found');
|
|
460
|
+
|
|
461
|
+
const source = await fs.readFile(sourceFile, 'utf8');
|
|
462
|
+
const output = [];
|
|
463
|
+
let foundForkPoint = false;
|
|
464
|
+
let seenMatches = 0;
|
|
465
|
+
for (const line of source.split(/\r?\n/)) {
|
|
466
|
+
if (!line.trim()) continue;
|
|
467
|
+
let record;
|
|
468
|
+
try { record = JSON.parse(line); } catch { continue; }
|
|
469
|
+
|
|
470
|
+
output.push(JSON.stringify(replaceSessionId(record, sourceSessionId, targetSessionId)));
|
|
471
|
+
const payload = record.type === 'response_item' ? record.payload : null;
|
|
472
|
+
if (payload?.type !== 'message' || payload.role !== 'assistant') continue;
|
|
473
|
+
const text = (payload.content || [])
|
|
474
|
+
.filter(part => part?.type === 'output_text' || part?.type === 'text')
|
|
475
|
+
.map(part => part.text || '')
|
|
476
|
+
.join('\n')
|
|
477
|
+
.trim();
|
|
478
|
+
if (text === assistantText && ++seenMatches === occurrence) {
|
|
479
|
+
foundForkPoint = true;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (!foundForkPoint) {
|
|
484
|
+
throw new Error('Selected Codex assistant message was not found in the native rollout');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const now = new Date();
|
|
488
|
+
const day = now.toISOString().slice(0, 10).split('-');
|
|
489
|
+
const targetDir = path.join(codexHome, 'sessions', day[0], day[1], day[2]);
|
|
490
|
+
const fileStamp = now.toISOString().replace(/[:.]/g, '-').replace('Z', '');
|
|
491
|
+
const targetFile = path.join(targetDir, `rollout-${fileStamp}-${targetSessionId}.jsonl`);
|
|
492
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
493
|
+
const tempFile = `${targetFile}.tmp-${crypto.randomUUID()}`;
|
|
494
|
+
await fs.writeFile(tempFile, `${output.join('\n')}\n`, 'utf8');
|
|
495
|
+
await fs.rename(tempFile, targetFile);
|
|
496
|
+
return targetFile;
|
|
497
|
+
}
|
|
498
|
+
|
|
410
499
|
// ─── Grok ─────────────────────────────────────────────────────────────────────
|
|
411
500
|
|
|
412
501
|
export async function scanGrokSessions(grokHome, filterCwd = null) {
|
|
@@ -1671,6 +1760,42 @@ export async function convertSession(sourceSessionId, sourceAgent, targetAgent,
|
|
|
1671
1760
|
if (ta === 'gemini' || ta === 'cursor') {
|
|
1672
1761
|
throw new Error(`${ta === 'cursor' ? 'Cursor' : 'Gemini'} sessions use native conversation storage and cannot be cloned safely. Start a new session instead.`);
|
|
1673
1762
|
}
|
|
1763
|
+
|
|
1764
|
+
if (ta === 'claude' && sourceAgent.toLowerCase() === 'claude' && Number.isInteger(upTo)) {
|
|
1765
|
+
const selected = messages[messages.length - 1];
|
|
1766
|
+
if (selected?.role !== 'assistant' || !selected.content?.trim()) {
|
|
1767
|
+
throw new Error('A Claude fork must end at a persisted assistant message');
|
|
1768
|
+
}
|
|
1769
|
+
const occurrence = messages.filter(message => message.role === 'assistant'
|
|
1770
|
+
&& message.content?.trim() === selected.content.trim()).length;
|
|
1771
|
+
await forkClaudeNativeSession(sourceSessionId, targetSessionId, claudeConfigDirs, selected.content.trim(), occurrence);
|
|
1772
|
+
try {
|
|
1773
|
+
const configDir = claudeConfigDirs?.[0] || path.join(os.homedir(), '.claude');
|
|
1774
|
+
await fs.appendFile(path.join(configDir, 'history.jsonl'), JSON.stringify({
|
|
1775
|
+
display, pastedContents: {}, timestamp: Date.now(), project: cwd, sessionId: targetSessionId,
|
|
1776
|
+
}) + '\n', 'utf8');
|
|
1777
|
+
} catch {}
|
|
1778
|
+
return { targetSessionId };
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// A same-agent Codex fork must remain a native rollout. The old synthetic
|
|
1782
|
+
// serializer produced a visible transcript, but `codex exec resume` did not
|
|
1783
|
+
// have the original turn context to continue it as an actual branch.
|
|
1784
|
+
if (ta === 'codex' && sourceAgent.toLowerCase() === 'codex' && Number.isInteger(upTo)) {
|
|
1785
|
+
const home = codexHome || path.join(os.homedir(), '.codex');
|
|
1786
|
+
const selected = messages[messages.length - 1];
|
|
1787
|
+
if (selected?.role !== 'assistant' || !selected.content?.trim()) {
|
|
1788
|
+
throw new Error('A Codex fork must end at a persisted assistant message');
|
|
1789
|
+
}
|
|
1790
|
+
const occurrence = messages.filter(message => message.role === 'assistant'
|
|
1791
|
+
&& message.content?.trim() === selected.content.trim()).length;
|
|
1792
|
+
await forkCodexNativeSession(sourceSessionId, targetSessionId, home, selected.content.trim(), occurrence);
|
|
1793
|
+
try {
|
|
1794
|
+
const indexFile = path.join(home, 'session_index.jsonl');
|
|
1795
|
+
await fs.appendFile(indexFile, JSON.stringify({ id: targetSessionId, thread_name: display }) + '\n', 'utf8');
|
|
1796
|
+
} catch {}
|
|
1797
|
+
return { targetSessionId };
|
|
1798
|
+
}
|
|
1674
1799
|
|
|
1675
1800
|
if (ta === 'claude') {
|
|
1676
1801
|
const configDir = claudeConfigDirs?.[0] || path.join(os.homedir(), '.claude');
|
|
File without changes
|