claude-simple 0.2.32 → 0.2.34

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.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.32.min.css">
7
+ <link rel="stylesheet" href="/style.0.2.34.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.32.min.js"></script>
14
+ <script type="module" src="/app.0.2.34.min.js"></script>
15
15
  </body>
16
16
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-simple",
3
- "version": "0.2.32",
3
+ "version": "0.2.34",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude-simple": "server.js",
package/public/chat.js CHANGED
@@ -305,8 +305,9 @@ export function applyComposerAccess() {
305
305
  banner.classList.remove('hidden');
306
306
  banner.textContent = composerBlockReason();
307
307
  } else {
308
- banner.classList.add('hidden');
309
- banner.textContent = '';
308
+ const notice = ctx.nativeRunNotice?.sessionId === ctx.sessionId ? ctx.nativeRunNotice.message : '';
309
+ banner.classList.toggle('hidden', !notice);
310
+ banner.textContent = notice;
310
311
  }
311
312
  }
312
313
  }
@@ -393,6 +394,15 @@ export function connectWS(opts = {}) {
393
394
  return;
394
395
  }
395
396
  if (msg.type === 'run-status') {
397
+ if (msg._hubReplay) return;
398
+ if (msg.sessionId && msg.sessionId !== ctx.sessionId) return;
399
+ if (typeof msg.running !== 'boolean') {
400
+ ctx.nativeRunNotice = { sessionId: ctx.sessionId, message: msg.message || '终端会话未确认结束' };
401
+ applyComposerAccess();
402
+ return;
403
+ }
404
+ ctx.nativeRunNotice = null;
405
+ applyComposerAccess();
396
406
  // Only adopt "running" from server; never re-lock after we already finished a turn
397
407
  // unless the server still has a busy run (reconnect mid-stream).
398
408
  if (msg.running) {
package/public/index.html CHANGED
@@ -85,7 +85,7 @@
85
85
  <script src="/vendor/mermaid.min.js"></script>
86
86
  <link rel="stylesheet" href="/vendor/katex.min.css">
87
87
  <script src="/vendor/katex.min.js"></script>
88
- <link rel="stylesheet" href="style.css?v=ui-26">
88
+ <link rel="stylesheet" href="style.css?v=ui-28">
89
89
  </head>
90
90
  <body class="h-dvh overflow-hidden flex flex-col bg-base-100 text-base-content">
91
91
  <div id="root" class="flex flex-col flex-1 overflow-hidden"></div>
@@ -101,6 +101,6 @@
101
101
  }
102
102
  }, 10000);
103
103
  </script>
104
- <script type="module" src="app.js?v=ui-26"></script>
104
+ <script type="module" src="app.js?v=ui-28"></script>
105
105
  </body>
106
106
  </html>
package/public/sw.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /* Agent UI service worker — cache shell assets only.
2
2
  * Never intercepts WebSocket or mutates API semantics beyond offline fallback.
3
3
  * Bump CACHE when shipping UI that must invalidate old shells. */
4
- const CACHE = 'agent-ui-shell-v35';
4
+ const CACHE = 'agent-ui-shell-v37';
5
5
  const PRECACHE = [
6
6
  '/',
7
7
  '/index.html',
8
8
  '/manifest.webmanifest',
9
- '/style.css?v=ui-26',
9
+ '/style.css?v=ui-28',
10
10
  '/style.css',
11
11
  '/ui.min.css',
12
12
  '/vendor/mini-react.min.js',
@@ -16,7 +16,7 @@ const PRECACHE = [
16
16
  '/vendor/mermaid.min.js',
17
17
  '/vendor/katex.min.js',
18
18
  '/vendor/katex.min.css',
19
- '/app.js?v=ui-26',
19
+ '/app.js?v=ui-28',
20
20
  '/icons/icon-192.png',
21
21
  '/icons/icon-512.png',
22
22
  '/icons/icon-maskable-512.png',
@@ -424,6 +424,18 @@ export async function parseCodexMessages(filePath) {
424
424
  return entry.messages;
425
425
  }
426
426
 
427
+ export async function readCodexRuntime(filePath, now = Date.now()) {
428
+ await parseCodexMessages(filePath);
429
+ const entry = codexTranscripts.get(filePath);
430
+ const runtime = entry?.runtime;
431
+ const pending = runtime?.state === 'pending';
432
+ const state = pending ? (now - entry.mtimeMs < 120_000 ? 'running' : 'unconfirmed') : runtime?.state || 'unknown';
433
+ return { ...runtime, state, running: state === 'running' ? true : ['completed','aborted'].includes(state) ? false : null,
434
+ updatedAt: entry?.mtimeMs || null, source: 'native-jsonl',
435
+ message: state === 'unconfirmed' || state === 'unknown' ? '终端会话未确认结束,请以原始记录核对' : state === 'running' ? '终端会话正在运行' : '终端会话已结束',
436
+ };
437
+ }
438
+
427
439
  async function updateCodexTranscript(filePath, entry, stat) {
428
440
  const messages = entry.messages;
429
441
  // Codex writes the same assistant turn twice:
@@ -467,10 +479,19 @@ async function updateCodexTranscript(filePath, entry, stat) {
467
479
  // their multi-megabyte payloads when building the visible transcript.
468
480
  const head = line.slice(0, 400);
469
481
  if (!/"type"\s*:\s*"(?:response_item|event_msg)"/.test(head) ||
470
- !/"type"\s*:\s*"(?:message|agent_message|function_call|command_execution|custom_tool_call)"/.test(head)) continue;
482
+ !/"type"\s*:\s*"(?:message|agent_message|function_call|command_execution|custom_tool_call|task_started|task_complete|turn_aborted)"/.test(head)) continue;
471
483
  let d;
472
484
  try { d = JSON.parse(line); } catch { continue; }
473
485
 
486
+ if (d.type === 'event_msg' && d.payload) {
487
+ const p = d.payload;
488
+ if (p.type === 'task_started') entry.runtime = { state: 'pending', turnId: p.turn_id || null, startedAt: d.timestamp };
489
+ if ((p.type === 'task_complete' || p.type === 'turn_aborted') &&
490
+ (!entry.runtime?.turnId || !p.turn_id || entry.runtime.turnId === p.turn_id)) {
491
+ entry.runtime = { ...entry.runtime, state: p.type === 'task_complete' ? 'completed' : 'aborted', endedAt: d.timestamp };
492
+ }
493
+ }
494
+
474
495
  if (d.type === 'response_item' && d.payload) {
475
496
  const p = d.payload;
476
497
  if (p.type === 'message' && p.role === 'user') {
@@ -1739,7 +1760,10 @@ export async function loadSessionMessages(sessionId, agent, { claudeConfigDirs,
1739
1760
  if (a === 'codex') {
1740
1761
  const file = await findCodexSessionFile(sessionId, codexHome);
1741
1762
  if (!file) return null;
1742
- return { messages: await parseCodexMessages(file), context: null, agent: 'codex' };
1763
+ const messages = await parseCodexMessages(file);
1764
+ const runtime = await readCodexRuntime(file);
1765
+ const entry = codexTranscripts.get(file);
1766
+ return { messages, context: null, agent: 'codex', runtime, revision: `${entry?.ino}:${entry?.size}:${entry?.mtimeMs}` };
1743
1767
  }
1744
1768
  if (a === 'grok') {
1745
1769
  const dir = await findGrokSessionDir(sessionId, grokHome);
package/src/app.js CHANGED
@@ -1452,6 +1452,7 @@ export function createApp() {
1452
1452
  oldestIndex: page.oldestIndex,
1453
1453
  tail: page.tail,
1454
1454
  compact,
1455
+ runtime: bundle.runtime || null,
1455
1456
  };
1456
1457
  res.setHeader('X-Session-Agent', payload.agent);
1457
1458
  // A session can be appended while a turn is streaming. Returning a
@@ -1603,7 +1604,9 @@ export function createApp() {
1603
1604
  tail: req.query.tail === '1', maxChars: req.query.maxChars });
1604
1605
  if (!result) return res.status(404).json({ error: 'Session JSONL not found' });
1605
1606
  res.setHeader('Cache-Control', 'no-store');
1606
- res.json({ ...result, sessionId, running: isSessionBusy(sessionId) });
1607
+ const bundle = isSessionBusy(sessionId) ? null : await readSessionBundle(sessionId, agent);
1608
+ const runtime = bundle?.runtime || { running: isSessionBusy(sessionId), source: 'ui' };
1609
+ res.json({ ...result, sessionId, ...runtime });
1607
1610
  } catch (e) { res.status(500).json({ error: e.message }); }
1608
1611
  });
1609
1612
 
@@ -2048,11 +2051,16 @@ export function createApp() {
2048
2051
  if (cmd.cmd === 'session.subscribe') {
2049
2052
  client.sessionId = cmd.sessionId || client.sessionId || null;
2050
2053
  client.agent = cmd.agent || client.agent || null;
2051
- wireSend({
2052
- type: 'run-status',
2053
- sessionId: client.sessionId,
2054
- running: isSessionBusy(client.sessionId),
2055
- });
2054
+ const sid = client.sessionId;
2055
+ const agent = client.agent;
2056
+ if (isSessionBusy(sid)) {
2057
+ wireSend({ type: 'run-status', sessionId: sid, agent, running: true, source: 'ui' });
2058
+ } else {
2059
+ const bundle = await readSessionBundle(sid, agent).catch(() => null);
2060
+ if (client.sessionId !== sid || client.agent !== agent) return;
2061
+ wireSend({ type: 'run-status', sessionId: sid, agent,
2062
+ ...(bundle?.runtime || { running: false, source: 'ui' }) });
2063
+ }
2056
2064
  // UI-managed active runs already stream their events directly. For
2057
2065
  // everything else, tail the native persisted transcript so an agent
2058
2066
  // launched in an ordinary terminal appears live in this UI.
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  function transcriptFingerprint(bundle) {
13
+ if (bundle?.revision) return `${bundle.revision}:${JSON.stringify(bundle.runtime || null)}`;
13
14
  const messages = Array.isArray(bundle?.messages) ? bundle.messages : [];
14
15
  // Message parsing already normalizes the native formats. JSON preserves a
15
16
  // change to an in-progress/rewritten final message as well as append-only
@@ -49,6 +50,7 @@ export function createExternalSessionMirror({ readSession, emit, intervalMs = 75
49
50
  const changed = fingerprint !== entry.fingerprint;
50
51
  entry.fingerprint = fingerprint;
51
52
  if (changed || announce) {
53
+ if (bundle.runtime) emit(entry.sessionId, { type: 'run-status', sessionId: entry.sessionId, agent: bundle.agent || entry.agent, ...bundle.runtime });
52
54
  emit(entry.sessionId, {
53
55
  type: 'external-session-updated',
54
56
  sessionId: entry.sessionId,
@@ -47,7 +47,13 @@ export function createHTTPTransfer({ gatewayURL, token, machineId, budget = new
47
47
  });
48
48
  try {
49
49
  const res = await fetch(url, { ...options, signal: controller.signal });
50
- await send({ type: 'http-res-start', status: res.status, headers: sanitizeHeaders(Object.fromEntries(res.headers.entries())) });
50
+ const headers = sanitizeHeaders(Object.fromEntries(res.headers.entries()));
51
+ // Identity bodies retain their exact length (ordinary proxy replies recompute it).
52
+ if (!res.headers.get('content-encoding') || res.headers.get('content-encoding') === 'identity') {
53
+ const length = res.headers.get('content-length');
54
+ if (length !== null) headers['content-length'] = length;
55
+ }
56
+ await send({ type: 'http-res-start', status: res.status, headers });
51
57
  for await (const value of res.body || []) {
52
58
  // Each frame is small; sends wait for socket backpressure and share a rate cap.
53
59
  for (let offset = 0; offset < value.byteLength; offset += 24 * 1024) {