cc-viewer 1.7.1 → 1.7.2
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/assets/{App-gUSmU6Ny.js → App-B224r-8M.js} +1 -1
- package/dist/assets/{MdxEditorPanel-DAR39A3S.js → MdxEditorPanel-BUubiE6Y.js} +1 -1
- package/dist/assets/{Mobile-qfRsRth0.js → Mobile-CKmfRRvo.js} +1 -1
- package/dist/assets/{index-B51Ci0GL.js → index-CjsQEQc2.js} +2 -2
- package/dist/assets/{seqResourceLoaders-DZJxMMaR.js → seqResourceLoaders-C-T_EMiO.js} +2 -2
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/lib/log-management.js +1 -0
- package/server/lib/stats-worker.js +21 -2
- package/server/lib/v2/adapter.js +52 -12
- package/server/lib/v2/journal.js +9 -5
- package/server/lib/v2/jsonl-read.js +127 -0
- package/server/lib/v2/live-feed.js +114 -19
- package/server/lib/v2/meta-rows.js +22 -21
- package/server/lib/v2/replay.js +5 -5
- package/server/lib/v2/session-select.js +69 -2
- package/server/routes/im.js +1 -1
- package/server/workspace-registry.js +5 -0
|
@@ -75,9 +75,13 @@ const SCAN_CHUNK = 64 * 1024;
|
|
|
75
75
|
* @param {string} dir - absolute session dir
|
|
76
76
|
* @param {number} budget - max bytes to read before giving up
|
|
77
77
|
* @param {(line:string) => boolean} onLine
|
|
78
|
+
* @param {boolean} [ioErrorResult=false] - returned when the journal EXISTS
|
|
79
|
+
* but reading it throws (transient lock / fd exhaustion). The cold-load
|
|
80
|
+
* scanners keep the false default ("not activated"); the discard predicate
|
|
81
|
+
* passes true so an I/O hiccup can never hide a REAL session.
|
|
78
82
|
* @returns {boolean}
|
|
79
83
|
*/
|
|
80
|
-
function scanJournal(dir, budget, onLine) {
|
|
84
|
+
function scanJournal(dir, budget, onLine, ioErrorResult = false) {
|
|
81
85
|
const journal = join(dir, 'journal.jsonl');
|
|
82
86
|
if (!existsSync(journal)) return false;
|
|
83
87
|
let fd;
|
|
@@ -105,7 +109,7 @@ function scanJournal(dir, budget, onLine) {
|
|
|
105
109
|
const last = (carry + decoder.end()).trim();
|
|
106
110
|
return !!(last && onLine(last));
|
|
107
111
|
} catch {
|
|
108
|
-
return
|
|
112
|
+
return ioErrorResult;
|
|
109
113
|
} finally {
|
|
110
114
|
if (fd !== undefined) { try { closeSync(fd); } catch {} }
|
|
111
115
|
}
|
|
@@ -167,6 +171,69 @@ export function sessionHasCompletedMainTurn(dir) {
|
|
|
167
171
|
});
|
|
168
172
|
}
|
|
169
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Does this session dir have at least one req of kind 'main' OR 'teammate'?
|
|
176
|
+
* The positive half of the discardable-session predicate. Budgeted with the
|
|
177
|
+
* WIDE budget (not MAIN_REQ_SCAN_BUDGET): journal req lines run ~1.1-1.3KB
|
|
178
|
+
* (headers + params), and time-driven heartbeat/countTokens lines can pile up
|
|
179
|
+
* before a real session's first main — a 256KB budget could give up early and
|
|
180
|
+
* misjudge a REAL session as discardable. Sessions that have a main still
|
|
181
|
+
* early-exit at its line; only genuinely main-less journals pay the budget.
|
|
182
|
+
* @param {string} dir - absolute session dir
|
|
183
|
+
* @returns {boolean}
|
|
184
|
+
*/
|
|
185
|
+
export function sessionHasMainOrTeammateReq(dir) {
|
|
186
|
+
// ioErrorResult=true: a transient read error must KEEP the session (treat
|
|
187
|
+
// as main-bearing) — hiding a real session is the worse failure direction;
|
|
188
|
+
// a missing journal still returns false (discard is correct there).
|
|
189
|
+
return scanJournal(dir, COMPLETED_TURN_SCAN_BUDGET, (line) => {
|
|
190
|
+
if (!line.includes('"ph":"req"')) return false;
|
|
191
|
+
if (!line.includes('"kind":"main"') && !line.includes('"kind":"teammate"')) return false;
|
|
192
|
+
try {
|
|
193
|
+
const o = JSON.parse(line);
|
|
194
|
+
return !!(o && o.ph === 'req' && (o.kind === 'main' || o.kind === 'teammate'));
|
|
195
|
+
} catch { return false; } // torn line — keep scanning
|
|
196
|
+
}, true);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Discardable-session predicate (2026-07-16): a session dir that must be
|
|
201
|
+
* DISCARDED by every read surface — never listed, counted, followed, or used
|
|
202
|
+
* as a candidate in any logic. These are orphan dirs minted by Claude Code's
|
|
203
|
+
* quota probes (max_tokens:1, one `'quota'` user message, a throwaway
|
|
204
|
+
* session_id per probe — fired at launches and agent-team spawns), plus any
|
|
205
|
+
* torn/empty dir with no renderable identity.
|
|
206
|
+
*
|
|
207
|
+
* discard ⟺ meta.leader ABSENT (not a teammate session)
|
|
208
|
+
* AND no journal req line of kind 'main' or 'teammate'
|
|
209
|
+
*
|
|
210
|
+
* The 'teammate' kind clause is the safety net for a torn meta.json. Every
|
|
211
|
+
* real session carries a main req at/near the journal head (verified across
|
|
212
|
+
* the full real dataset: 18/18 main-less dirs were single quota probes), so a
|
|
213
|
+
* kept session early-exits its scan cheaply. Read-side only and self-healing:
|
|
214
|
+
* the moment a dir gains its first main req, the predicate flips and every
|
|
215
|
+
* surface picks it up on its next scan/poll.
|
|
216
|
+
*
|
|
217
|
+
* KEEP IN SYNC: adapter.js listV2Sessions pre-computes the same verdict
|
|
218
|
+
* inline (hasMainOrTeammate inside its existing full journal fold — unbounded,
|
|
219
|
+
* vs this predicate's 8MB budget; intentional asymmetry) and then CONFIRMS a
|
|
220
|
+
* discard through this predicate so the error→keep direction is shared.
|
|
221
|
+
* Change the rule here and there together.
|
|
222
|
+
*
|
|
223
|
+
* @param {string} dir - absolute session dir
|
|
224
|
+
* @param {object|null} [meta] - pre-parsed meta.json (avoids a re-read);
|
|
225
|
+
* omitted → read here; unreadable → treated as leaderless (journal decides)
|
|
226
|
+
* @returns {boolean} true = discard everywhere
|
|
227
|
+
*/
|
|
228
|
+
export function isDiscardableSession(dir, meta) {
|
|
229
|
+
let m = meta;
|
|
230
|
+
if (m === undefined) {
|
|
231
|
+
try { m = JSON.parse(readFileSync(join(dir, 'meta.json'), 'utf-8')); } catch { m = null; }
|
|
232
|
+
}
|
|
233
|
+
if (m && m.leader) return false;
|
|
234
|
+
return !sessionHasMainOrTeammateReq(dir);
|
|
235
|
+
}
|
|
236
|
+
|
|
170
237
|
/**
|
|
171
238
|
* The newest readable, non-teammate session that HAS a main turn, as
|
|
172
239
|
* `{ dir, sessionId }`, or null. "Readable" mirrors listV2Sessions' gates (spec
|
package/server/routes/im.js
CHANGED
|
@@ -259,7 +259,7 @@ function imLogs(req, res, parsedUrl, isLocal, deps) {
|
|
|
259
259
|
try {
|
|
260
260
|
// wire-v2: prefer the newest v2 session (by meta.startTs, journal mtime as
|
|
261
261
|
// tie-break); fall back to legacy v1 files until they are migrated.
|
|
262
|
-
const sessions = listV2Sessions(join(LOG_DIR, project)).filter((s) => !s.leader);
|
|
262
|
+
const sessions = listV2Sessions(join(LOG_DIR, project)).filter((s) => !s.leader && !s.discard);
|
|
263
263
|
if (sessions.length > 0) {
|
|
264
264
|
sessions.sort((a, b) => (a.startTs < b.startTs ? 1 : a.startTs > b.startTs ? -1 : 0));
|
|
265
265
|
latest = `v2:${project}/${sessions[0].sid}`; // 直接喂给 /api/local-log?file=
|
|
@@ -4,6 +4,7 @@ import { readdir, stat } from 'node:fs/promises';
|
|
|
4
4
|
import { renameSyncWithRetry } from './lib/file-api.js';
|
|
5
5
|
import { withFileLockAsync } from './lib/async-file-lock.js';
|
|
6
6
|
import { dirSizeSync } from './lib/v2/layout.js';
|
|
7
|
+
import { isDiscardableSession } from './lib/v2/session-select.js';
|
|
7
8
|
import { join, basename, resolve } from 'node:path';
|
|
8
9
|
import { randomBytes } from 'node:crypto';
|
|
9
10
|
import { LOG_DIR } from '../findcc.js';
|
|
@@ -117,6 +118,10 @@ export async function getWorkspaces() {
|
|
|
117
118
|
const sessionDir = join(logDir, 'sessions', e.name);
|
|
118
119
|
try {
|
|
119
120
|
await stat(join(sessionDir, 'journal.jsonl'));
|
|
121
|
+
// Quota-probe orphans must not count: logCount>0 drives the
|
|
122
|
+
// auto -c heuristic, and a probe-only workspace would auto-continue
|
|
123
|
+
// into a conversation that does not exist (2026-07-16).
|
|
124
|
+
if (isDiscardableSession(sessionDir)) continue;
|
|
120
125
|
// Folder size, not journal size — conv/blob/response files carry
|
|
121
126
|
// most of a session's bytes (journal alone undercounts ~12x).
|
|
122
127
|
totalSize += dirSizeSync(sessionDir);
|