cc-viewer 1.6.348 → 1.7.1
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/README.md +13 -1
- package/cli.js +116 -37
- package/dist/assets/App-gUSmU6Ny.js +2 -0
- package/dist/assets/{MdxEditorPanel-ualoRFOL.js → MdxEditorPanel-DAR39A3S.js} +1 -1
- package/dist/assets/Mobile-qfRsRth0.js +1 -0
- package/dist/assets/{_baseUniq-CG81mgN_.js → _baseUniq-D9_E5ZDp.js} +1 -1
- package/dist/assets/{arc-C161J4f2.js → arc-D4iFrlhb.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-BmbDMdn9.js → architectureDiagram-Q4EWVU46-f1neo7hg.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-DXpMvdxd.js → blockDiagram-DXYQGD6D-DWi9e9tG.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-8Mypg0IU.js → c4Diagram-AHTNJAMY-DAdj49A2.js} +1 -1
- package/dist/assets/{channel-B2khdmvc.js → channel-B3VthT0m.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-I61r1g6z.js → chunk-4BX2VUAB-BrPcAHV3.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-VFPlRE5E.js → chunk-4TB4RGXK-Bvopc1V9.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DvCamg_6.js → chunk-55IACEB6-Juw-7Z3i.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-Dj4Q8RwR.js → chunk-EDXVE4YY-DVYP9tpB.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-_bgQuuJh.js → chunk-FMBD7UC4-VQ7VOXl3.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-3to38LyN.js → chunk-OYMX7WX6-F8Rj4ZUZ.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Zl26wKKz.js → chunk-QZHKN3VN-Dn7K3yi9.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-RLYPrlh0.js → chunk-YZCP3GAM-UjO-KTsn.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-DmSozxoO.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-DmSozxoO.js +1 -0
- package/dist/assets/clone-BQtvy9Ae.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-CsYOjJ0V.js → cose-bilkent-S5V4N54A-BDhNj8rq.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-C3YXXaFU.js → dagre-KV5264BT-BzdcDuyt.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-DUCOg1ld.js → diagram-5BDNPKRD-DP_SX65N.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-BtlLl_zX.js → diagram-G4DWMVQ6-B-pBFw8Q.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-1Qp9xfOk.js → diagram-MMDJMWI5-6XSfN1eN.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-Cj7VMp3L.js → diagram-TYMM5635-DJvGxvpE.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-CVZVn541.js → erDiagram-SMLLAGMA-CpqdpGYj.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-CiU9Fkkv.js → flowDiagram-DWJPFMVM-C9m7wjwI.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-aRaG97ID.js → ganttDiagram-T4ZO3ILL-CU0XOfY3.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-Cr2LJlvX.js → gitGraphDiagram-UUTBAWPF-ipyGULnP.js} +1 -1
- package/dist/assets/{graph-B59lDyMn.js → graph-BR2KmES9.js} +1 -1
- package/dist/assets/{index-C1ptnCpq.js → index--wgRW_OB.js} +1 -1
- package/dist/assets/index-B51Ci0GL.js +2 -0
- package/dist/assets/{index-CRAWA8PC.js → index-C-be29ey.js} +1 -1
- package/dist/assets/{index-CSM1Kw8l.js → index-C16fiVhv.js} +1 -1
- package/dist/assets/{index-Ddk-kg22.js → index-D5YruOKD.js} +1 -1
- package/dist/assets/{index-BnhNIjbj.js → index-D5tSeMU7.js} +1 -1
- package/dist/assets/{index-vFEQq-NB.js → index-DuZQ6FqR.js} +1 -1
- package/dist/assets/{index-CO2hZ1s4.js → index-OWzSX2T8.js} +1 -1
- package/dist/assets/{infoDiagram-42DDH7IO-CnO22bw8.js → infoDiagram-42DDH7IO-BQWQjfFX.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-ByNzpScH.js → ishikawaDiagram-UXIWVN3A-Df-KH_Hz.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-yaVaCWPq.js → journeyDiagram-VCZTEJTY-CMbB_4G9.js} +1 -1
- package/dist/assets/{jszip.min-BdVFP3OW.js → jszip.min-B6k2t9Ch.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-RIPR_RQ3.js → kanban-definition-6JOO6SKY-CImdlKN3.js} +1 -1
- package/dist/assets/{layout-0rzel0v_.js → layout-DQl4aw09.js} +1 -1
- package/dist/assets/{linear-sktN0tB6.js → linear-CLD3aPlK.js} +1 -1
- package/dist/assets/mermaid.core-CN35dvKJ.js +7 -0
- package/dist/assets/{min-rspGOkz_.js → min-_D86henJ.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-BsVzZzTz.js → mindmap-definition-QFDTVHPH-2JVcu1nk.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-CsoWsw4f.js → pieDiagram-DEJITSTG-Dik-m5u7.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-CTMKuCAI.js → quadrantDiagram-34T5L4WZ-Bm5l40Jq.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-C67256te.js → requirementDiagram-MS252O5E-Ck9w5rTj.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-X8Y7KnW0.js → sankeyDiagram-XADWPNL6-BbGVCFEf.js} +1 -1
- package/dist/assets/{seqResourceLoaders-B5Zy_CC_.css → seqResourceLoaders-DFrHmgnK.css} +1 -1
- package/dist/assets/seqResourceLoaders-DZJxMMaR.js +2 -0
- package/dist/assets/{sequenceDiagram-FGHM5R23-Cils0TAp.js → sequenceDiagram-FGHM5R23-vjvEdksk.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-DFnohvpG.js → stateDiagram-FHFEXIEX-DTqMoiB2.js} +1 -1
- package/dist/assets/{stateDiagram-v2-QKLJ7IA2-BTX_4o81.js → stateDiagram-v2-QKLJ7IA2-CNbAwnhu.js} +1 -1
- package/dist/assets/{timeline-definition-GMOUNBTQ-DrIYFrzZ.js → timeline-definition-GMOUNBTQ-Cw8O-52d.js} +1 -1
- package/dist/assets/{vendor-antd-D5HmSDSg.js → vendor-antd-DI7JL-mE.js} +1 -1
- package/dist/assets/{vendor-codemirror-C5yuj7ze.js → vendor-codemirror-B9c49dtM.js} +1 -1
- package/dist/assets/{vendor-mdxeditor-BcEooTvb.js → vendor-mdxeditor-B6cpBtIE.js} +2 -2
- package/dist/assets/{vendor-qrcode-d9LCn0-9.js → vendor-qrcode-Dn90-s-u.js} +1 -1
- package/dist/assets/{vendor-virtuoso-K9m4MN6a.js → vendor-virtuoso-CpdSKD_6.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-lRqBYTr9.js → vennDiagram-DHZGUBPP-DCuqymb7.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-e8YfxpOe.js → wardley-RL74JXVD-BJ20wJUU.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-Bxrg774h.js → wardleyDiagram-NUSXRM2D-Vz6US3qt.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-DWhZqhq8.js → xychartDiagram-5P7HB3ND-CzkLKrmi.js} +1 -1
- package/dist/index.html +4 -4
- package/findcc.js +4 -3
- package/package.json +3 -1
- package/server/i18n.js +21 -141
- package/server/interceptor.js +211 -403
- package/server/lib/async-write-queue.js +12 -1
- package/server/lib/delta-reconstructor.js +3 -3
- package/server/lib/error-report.js +45 -0
- package/server/lib/im-log-watcher.js +68 -8
- package/server/lib/interceptor-core.js +19 -174
- package/server/lib/log-management.js +214 -247
- package/server/lib/log-stream.js +59 -108
- package/server/lib/log-watcher.js +62 -274
- package/server/lib/log-zip.js +148 -0
- package/server/lib/session-boundary.js +266 -0
- package/server/lib/session-pin-store.js +10 -19
- package/server/lib/stats-worker.js +138 -267
- package/server/lib/user-prompt-extract.js +250 -0
- package/server/lib/v2/adapter.js +1124 -0
- package/server/lib/v2/blob-store.js +44 -0
- package/server/lib/v2/conversation-store.js +217 -0
- package/server/lib/v2/convert-manager.js +79 -0
- package/server/lib/v2/convert-worker.js +42 -0
- package/server/lib/v2/convert.js +409 -0
- package/server/lib/v2/identity.js +204 -0
- package/server/lib/v2/journal.js +62 -0
- package/server/lib/v2/layout.js +167 -0
- package/server/lib/v2/live-feed.js +558 -0
- package/server/lib/v2/meta-rows.js +360 -0
- package/server/lib/v2/migrate-prompt.js +54 -0
- package/server/lib/v2/replay.js +191 -0
- package/server/lib/v2/session-select.js +222 -0
- package/server/lib/v2/singleflight.js +104 -0
- package/server/lib/v2/v2-writer.js +528 -0
- package/server/lib/v2/verify.js +233 -0
- package/server/lib/wire-compress.js +167 -0
- package/server/routes/events.js +120 -150
- package/server/routes/files-fs.js +4 -2
- package/server/routes/im.js +11 -2
- package/server/routes/logs.js +209 -164
- package/server/routes/preferences.js +0 -3
- package/server/routes/project-meta.js +2 -3
- package/server/routes/session-pin.js +7 -8
- package/server/routes/v2.js +60 -0
- package/server/routes/workspaces.js +50 -14
- package/server/server.js +70 -66
- package/server/workspace-registry.js +24 -4
- package/src/utils/apiUrl.js +26 -0
- package/src/utils/askFallback.js +21 -0
- package/src/utils/askOptionDesc.js +55 -0
- package/src/utils/askPortalMatcher.js +54 -0
- package/src/utils/autoApproveOptions.js +27 -0
- package/src/utils/avatarAnimationPostPass.js +49 -0
- package/src/utils/builtinPresets.js +18 -0
- package/src/utils/clearCheckpoint.js +7 -0
- package/src/utils/commandValidator.js +45 -0
- package/src/utils/contentFilter.js +620 -0
- package/src/utils/contextRaw.js +18 -0
- package/src/utils/contextSidebarNavigation.js +16 -0
- package/src/utils/contextTurns.js +117 -0
- package/src/utils/displayScaleHelper.js +47 -0
- package/src/utils/effectiveModel.js +13 -0
- package/src/utils/entry-slim.js +563 -0
- package/src/utils/entryCache.js +144 -0
- package/src/utils/errorReport.js +6 -0
- package/src/utils/fileExpandedPathsStorage.js +70 -0
- package/src/utils/fileIcons.jsx +36 -0
- package/src/utils/fileOpen.js +37 -0
- package/src/utils/formatters.js +48 -0
- package/src/utils/gitApi.js +50 -0
- package/src/utils/gitTreeBuilder.js +19 -0
- package/src/utils/helpers.js +846 -0
- package/src/utils/identityHeal.js +99 -0
- package/src/utils/imConnState.js +59 -0
- package/src/utils/imOrigin.js +24 -0
- package/src/utils/imTr.js +7 -0
- package/src/utils/imageCompress.js +83 -0
- package/src/utils/imageDownscale.js +28 -0
- package/src/utils/imageResize.js +112 -0
- package/src/utils/ingestPipeline.js +47 -0
- package/src/utils/lazyWithReload.js +53 -0
- package/src/utils/markdown.js +30 -0
- package/src/utils/markdownIncremental.js +39 -0
- package/src/utils/markdownProfiler.js +113 -0
- package/src/utils/mdExtensionDetect.js +64 -0
- package/src/utils/memoryLinkParser.js +44 -0
- package/src/utils/modalMask.js +15 -0
- package/src/utils/monotime.js +6 -0
- package/src/utils/pinnedMenu.js +55 -0
- package/src/utils/presetShortcuts.js +14 -0
- package/src/utils/projectAlias.js +135 -0
- package/src/utils/promptClassifier.js +112 -0
- package/src/utils/promptDetect.js +196 -0
- package/src/utils/promptNav.js +60 -0
- package/src/utils/ptyChunkBuilder.js +251 -0
- package/src/utils/quickMenuHoverIntent.js +35 -0
- package/src/utils/rateLimitParser.js +119 -0
- package/src/utils/readResultPool.js +106 -0
- package/src/utils/refreshCachedItemProp.js +30 -0
- package/src/utils/requestType.js +208 -0
- package/src/utils/resizeCalc.js +21 -0
- package/src/utils/resolveLocalized.js +17 -0
- package/src/utils/searchApi.js +47 -0
- package/src/utils/searchReplace.js +51 -0
- package/src/utils/seqResourceLoaders.js +79 -0
- package/src/utils/sessionManager.js +471 -0
- package/src/utils/sessionMerge.js +180 -0
- package/src/utils/skillModalController.js +111 -0
- package/src/utils/skillsParser.js +116 -0
- package/src/utils/slashCommandLabels.js +80 -0
- package/src/utils/splitDragCalc.js +73 -0
- package/src/utils/stickyBottomController.js +643 -0
- package/src/utils/svgSanitize.js +48 -0
- package/src/utils/systemTags.js +47 -0
- package/src/utils/tClaude.js +20 -0
- package/src/utils/teamModalBuilder.js +351 -0
- package/src/utils/teamSessionParser.js +190 -0
- package/src/utils/teammateAvatars.js +194 -0
- package/src/utils/teammateDetector.js +121 -0
- package/src/utils/termDiag.js +114 -0
- package/src/utils/terminalClipboard.js +117 -0
- package/src/utils/terminalWriteQueue.js +398 -0
- package/src/utils/thinkingMerge.js +49 -0
- package/src/utils/toolCatalog.js +29 -0
- package/src/utils/toolResultBuilder.js +393 -0
- package/src/utils/toolResultClassifier.js +16 -0
- package/src/utils/toolResultCore.js +167 -0
- package/src/utils/toolsDiff.js +44 -0
- package/src/utils/toolsXmlFormatter.js +3 -0
- package/src/utils/ultraplanController.js +109 -0
- package/src/utils/ultraplanExperts.js +86 -0
- package/src/utils/ultraplanTemplates.js +154 -0
- package/src/utils/userImageRefs.js +49 -0
- package/src/utils/v3Assembler.js +112 -0
- package/src/utils/v3Rows.js +48 -0
- package/src/utils/voicePackPlayer.js +233 -0
- package/src/utils/webSearchGrouping.js +111 -0
- package/src/utils/workflowFormat.js +52 -0
- package/src/utils/workflowRuns.js +118 -0
- package/src/utils/workflowStore.js +94 -0
- package/dist/assets/App-VTo4NVdo.js +0 -2
- package/dist/assets/Mobile-CcmYvMm_.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-BXnyUgsj.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-BXnyUgsj.js +0 -1
- package/dist/assets/clone-md-Mc1-T.js +0 -1
- package/dist/assets/index-DCo650PG.js +0 -2
- package/dist/assets/mermaid.core-C2Parj1q.js +0 -7
- package/dist/assets/seqResourceLoaders-BVozq68i.js +0 -2
- package/server/lib/instance-registry.js +0 -57
- package/server/lib/jsonl-archive.js +0 -180
- package/server/lib/teammate-detect.js +0 -32
- package/server/lib/zip-safety.js +0 -68
package/server/routes/events.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// SSE event stream + log-registration / resume / turn-end routes (moved verbatim from server.js).
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { _projectName, getLiveLogSource, isContinuedLaunch } from '../interceptor.js';
|
|
5
5
|
import { LOG_DIR } from '../../findcc.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { streamRawEntriesAsync } from '../lib/log-stream.js';
|
|
7
|
+
import { migrationStatus } from '../lib/v2/migrate-prompt.js';
|
|
8
|
+
import { reportSwallowed } from '../lib/error-report.js';
|
|
9
|
+
import { sseHead, sseWrite, needsDrain, wireEnd, awaitWireDrain } from '../lib/wire-compress.js';
|
|
10
|
+
import { readV2ColdBundle } from '../lib/v2/meta-rows.js';
|
|
11
|
+
import { readV2SingleEntry } from '../lib/v2/adapter.js';
|
|
9
12
|
import { enrichRawIfNeeded } from '../lib/enrich-plan-input.js';
|
|
10
13
|
import { validateLogPath } from '../lib/log-management.js';
|
|
11
14
|
import { isMainAgentEntry, extractCachedContent } from '../lib/kv-cache-analyzer.js';
|
|
@@ -49,77 +52,6 @@ function turnEndNotify(req, res, parsedUrl, isLocal, deps) {
|
|
|
49
52
|
});
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
// 注册新的日志文件进行 watch(供新进程复用旧服务时调用)
|
|
53
|
-
function registerLog(req, res, parsedUrl, isLocal, deps) {
|
|
54
|
-
let body = '';
|
|
55
|
-
req.on('data', chunk => { body += chunk; if (body.length > deps.MAX_POST_BODY) req.destroy(); });
|
|
56
|
-
req.on('end', () => {
|
|
57
|
-
try {
|
|
58
|
-
const { logFile } = JSON.parse(body);
|
|
59
|
-
if (logFile && typeof logFile === 'string' && logFile.startsWith(LOG_DIR) && existsSync(logFile)) {
|
|
60
|
-
watchLogFile(deps.logWatcherOpts(logFile));
|
|
61
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
62
|
-
res.end(JSON.stringify({ ok: true }));
|
|
63
|
-
} else {
|
|
64
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
65
|
-
res.end(JSON.stringify({ error: 'Invalid log file path' }));
|
|
66
|
-
}
|
|
67
|
-
} catch {
|
|
68
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
69
|
-
res.end(JSON.stringify({ error: 'Invalid request body' }));
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// 用户选择继续/新开日志
|
|
75
|
-
function resumeChoice(req, res, parsedUrl, isLocal, deps) {
|
|
76
|
-
let body = '';
|
|
77
|
-
req.on('data', chunk => { body += chunk; if (body.length > deps.MAX_POST_BODY) req.destroy(); });
|
|
78
|
-
req.on('end', async () => {
|
|
79
|
-
try {
|
|
80
|
-
const { choice } = JSON.parse(body);
|
|
81
|
-
if (choice !== 'continue' && choice !== 'new') {
|
|
82
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
83
|
-
res.end(JSON.stringify({ error: 'Invalid choice' }));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const result = resolveResumeChoice(choice);
|
|
87
|
-
if (!result) {
|
|
88
|
-
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
89
|
-
res.end(JSON.stringify({ error: 'Already resolved' }));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
// 重新 watch 最终的日志文件
|
|
93
|
-
watchLogFile(deps.logWatcherOpts(result.logFile));
|
|
94
|
-
// 广播 resume_resolved + full_reload
|
|
95
|
-
const resolvedData = JSON.stringify({ logFile: result.logFile });
|
|
96
|
-
deps.clients.forEach(client => {
|
|
97
|
-
try {
|
|
98
|
-
client.write(`event: resume_resolved\ndata: ${resolvedData}\n\n`);
|
|
99
|
-
} catch { }
|
|
100
|
-
});
|
|
101
|
-
// 流式分段广播 full_reload,避免全量加载 OOM
|
|
102
|
-
const reloadTotal = await countLogEntries(LOG_FILE);
|
|
103
|
-
deps.clients.forEach(client => {
|
|
104
|
-
try { client.write(`event: load_start\ndata: ${JSON.stringify({ total: reloadTotal, incremental: false })}\n\n`); } catch { }
|
|
105
|
-
});
|
|
106
|
-
await streamRawEntriesAsync(LOG_FILE, (raw) => {
|
|
107
|
-
deps.clients.forEach(client => {
|
|
108
|
-
try { client.write('event: load_chunk\ndata: ['); client.write(raw.replace(/\n/g, '')); client.write(']\n\n'); } catch { }
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
deps.clients.forEach(client => {
|
|
112
|
-
try { client.write(`event: load_end\ndata: {}\n\n`); } catch { }
|
|
113
|
-
});
|
|
114
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
115
|
-
res.end(JSON.stringify({ ok: true, logFile: result.logFile }));
|
|
116
|
-
} catch {
|
|
117
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
118
|
-
res.end(JSON.stringify({ error: 'Invalid request body' }));
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
55
|
// SSE endpoint
|
|
124
56
|
// 构造「有新版」徽标的 SSE 帧(pending = {version, source})。pending 为空返回 null。
|
|
125
57
|
// 抽成纯函数便于单测帧格式;events() 在新连接上调用它补推,使版本徽标跨刷新持续显示。
|
|
@@ -130,7 +62,10 @@ export function sseUpdateBadgeFrame(pending) {
|
|
|
130
62
|
}
|
|
131
63
|
|
|
132
64
|
async function events(req, res, parsedUrl, isLocal, deps) {
|
|
133
|
-
|
|
65
|
+
// Negotiated Content-Encoding (br|identity). From here on, every byte of
|
|
66
|
+
// this response MUST go through sseWrite — a bare res.write would corrupt
|
|
67
|
+
// the compressed stream (see server/lib/wire-compress.js).
|
|
68
|
+
sseHead(req, res, 200, {
|
|
134
69
|
'Content-Type': 'text/event-stream',
|
|
135
70
|
'Cache-Control': 'no-cache',
|
|
136
71
|
'Connection': 'keep-alive',
|
|
@@ -144,7 +79,7 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
144
79
|
|
|
145
80
|
// SSE 心跳保活:每 30s 发送 ping 事件,防止连接被 OS/代理/浏览器静默断开
|
|
146
81
|
const pingTimer = setInterval(() => {
|
|
147
|
-
try { res
|
|
82
|
+
try { sseWrite(res, 'event: ping\ndata: {}\n\n'); } catch {}
|
|
148
83
|
}, 30000);
|
|
149
84
|
|
|
150
85
|
// server_config: 给前端推一次性的关键运行时常量,让前端 cooldown / debounce 等
|
|
@@ -152,20 +87,26 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
152
87
|
// 见 server.js 顶部 turnEnd debounce 的 SUNSET-MARKER 注释。
|
|
153
88
|
// write 失败要 warn:之前静默吞会让 env override 漂移到前端硬常量 10s 而无人发觉。
|
|
154
89
|
try {
|
|
155
|
-
res
|
|
90
|
+
sseWrite(res, `event: server_config\ndata: ${JSON.stringify({ turnEndDebounceMs: deps.turnEndDebounceMs, wireV3: !!deps.wireV3, build: deps.serverBuild || '' })}\n\n`);
|
|
156
91
|
} catch (err) {
|
|
157
92
|
console.warn(`[server_config] SSE write failed (turnEndDebounceMs=${deps.turnEndDebounceMs}):`, err && err.message);
|
|
158
93
|
}
|
|
159
94
|
|
|
160
|
-
// 如果有待决的 resume 选择,发送 resume_prompt 事件
|
|
161
|
-
if (_resumeState) {
|
|
162
|
-
res.write(`event: resume_prompt\ndata: ${JSON.stringify({ recentFileName: _resumeState.recentFileName })}\n\n`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
95
|
// 补推「有新版」徽标:复用启动检查缓存的结果,让刷新/新标签页连上即恢复徽标。
|
|
166
96
|
// 置于 deps.clients.push(res) 之前 → 新客户端仅得一份,不与一次性广播竞态。
|
|
167
97
|
const updFrame = sseUpdateBadgeFrame(deps.pendingMajorUpdate);
|
|
168
|
-
if (updFrame) res
|
|
98
|
+
if (updFrame) sseWrite(res, updFrame);
|
|
99
|
+
|
|
100
|
+
// 1.7.0 迁移引导(P2):当前项目仍有未转换的 v1 日志 → 连接即推 migrate_prompt。
|
|
101
|
+
// 是否弹窗由客户端决定(「不再提醒」偏好在客户端;continued=true 时无视 dismissed
|
|
102
|
+
// 再提醒一次——`-c` 续接的对话前半段在旧格式里,不迁移就看不到)。工作区切换后的
|
|
103
|
+
// 提示由 workspaces launch 路由对存量连接广播(本帧只覆盖新连接)。
|
|
104
|
+
try {
|
|
105
|
+
const mig = migrationStatus(LOG_DIR, _projectName || '');
|
|
106
|
+
if (mig.pending) {
|
|
107
|
+
sseWrite(res, `event: migrate_prompt\ndata: ${JSON.stringify({ ...mig, continued: isContinuedLaunch() })}\n\n`);
|
|
108
|
+
}
|
|
109
|
+
} catch (e) { reportSwallowed('sse.migrate_prompt', e); }
|
|
169
110
|
|
|
170
111
|
// 增量加载参数:移动端带 since/cc/project 请求增量数据
|
|
171
112
|
const sinceParam = parsedUrl.searchParams.get('since');
|
|
@@ -202,10 +143,21 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
202
143
|
// ring=3 的容错意义:团队会话末尾常有连续 teammate 伪 mainAgent 条目,单记忆位会被
|
|
203
144
|
// 挤掉真实 mainAgent;子串预过滤的理论误伤(键/值恰为 "teammate" 的真实条目)也由
|
|
204
145
|
// 环内更早候选兜底。
|
|
146
|
+
// KEEP IN SYNC: server/lib/v2/adapter.js MAINAGENT_RING_DEPTH — on the v2 path
|
|
147
|
+
// the adapter's mainAgentRing (that depth) wholesale REPLACES this ring, so a
|
|
148
|
+
// mismatch would silently give v1 and v2 sessions different candidate depths.
|
|
205
149
|
const MAINAGENT_SCAN_RING = 3;
|
|
206
150
|
const mainAgentRawRing = [];
|
|
207
151
|
|
|
208
|
-
|
|
152
|
+
// Wire v3 (V3.S5): flagged + v2 source ⇒ the legacy full-entry cold stream
|
|
153
|
+
// is REPLACED by rows + native lines (the byte win); the client assembler
|
|
154
|
+
// rebuilds entries locally. v1 legacy files keep the entry pipeline.
|
|
155
|
+
const _v3Src = deps.wireV3 ? getLiveLogSource() : null;
|
|
156
|
+
const v3Cold = !!(_v3Src && existsSync(join(_v3Src, 'journal.jsonl')));
|
|
157
|
+
|
|
158
|
+
// S6b: the cold-load source is the current v2 session dir when the v2
|
|
159
|
+
// writer is active (adapter stream), else the v1 file.
|
|
160
|
+
const coldLoadResult = v3Cold ? null : await streamRawEntriesAsync(getLiveLogSource(), async (raw) => {
|
|
209
161
|
// 直接发送原始 JSON 字符串,不做 parse/reconstruct/stringify
|
|
210
162
|
// ExitPlanMode V2 空 input 的条目按需补全 plan / planFilePath,其它原样透传
|
|
211
163
|
if (res.destroyed || !res.writable) return;
|
|
@@ -215,9 +167,15 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
215
167
|
// 把 EPIPE 抛穿 async callback;res.on('close'|'error') 已会做 clients 数组清理。
|
|
216
168
|
let drained = true;
|
|
217
169
|
try {
|
|
218
|
-
res
|
|
219
|
-
|
|
220
|
-
res
|
|
170
|
+
sseWrite(res, 'event: load_chunk\ndata: [');
|
|
171
|
+
const ok = sseWrite(res, out.includes('\n') ? out.replace(/\n/g, '') : out);
|
|
172
|
+
sseWrite(res, ']\n\n');
|
|
173
|
+
// Two pressure signals: `ok` is the write target's input buffer (the
|
|
174
|
+
// ENCODER on compressed paths — its backlog would otherwise be invisible
|
|
175
|
+
// because compressed output is 20-80x smaller and rarely fills the
|
|
176
|
+
// socket), needsDrain(res) is the socket itself. awaitWireDrain below
|
|
177
|
+
// waits on whichever stream actually applies the pressure.
|
|
178
|
+
drained = ok && !needsDrain(res);
|
|
221
179
|
} catch {
|
|
222
180
|
return;
|
|
223
181
|
}
|
|
@@ -225,7 +183,7 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
225
183
|
// helper 内部会在 fulfill 时把另外两个监听器从 res 上摘掉,避免 N 次 backpressure
|
|
226
184
|
// 累积出 N 个 stale close/error listener 触发 MaxListenersExceededWarning。
|
|
227
185
|
if (!drained) {
|
|
228
|
-
await
|
|
186
|
+
await awaitWireDrain(res, deps.SSE_BACKPRESSURE_TIMEOUT_MS);
|
|
229
187
|
}
|
|
230
188
|
}, {
|
|
231
189
|
since: useIncremental ? sinceParam : undefined,
|
|
@@ -249,11 +207,62 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
249
207
|
loadStartData.hasMore = !!hasMore;
|
|
250
208
|
loadStartData.oldestTs = oldestTs || '';
|
|
251
209
|
}
|
|
252
|
-
res
|
|
210
|
+
sseWrite(res, `event: load_start\ndata: ${JSON.stringify(loadStartData)}\n\n`);
|
|
253
211
|
},
|
|
254
212
|
});
|
|
255
213
|
|
|
256
|
-
|
|
214
|
+
// Wire v3 (V3.S2/S4/S5): cold rows + native lines land BEFORE load_end so
|
|
215
|
+
// the flagged client can assemble its window in the same load cycle. The
|
|
216
|
+
// legacy chunk stream was skipped above (v3Cold) — load_start is emitted
|
|
217
|
+
// here from the rows metadata instead.
|
|
218
|
+
if (v3Cold) {
|
|
219
|
+
try {
|
|
220
|
+
const src = _v3Src;
|
|
221
|
+
// Single-flighted bundle (review P2-b): a reconnect storm coalesces to
|
|
222
|
+
// one journal fold + one native read per (dir, window). `since` scopes
|
|
223
|
+
// an incremental reconnect to the delta window (review P1-2) — the
|
|
224
|
+
// client upserts those rows instead of resetting the list.
|
|
225
|
+
const { meta, native } = await readV2ColdBundle(src, {
|
|
226
|
+
limit: useLimit ? effectiveLimit : 0,
|
|
227
|
+
since: useIncremental ? sinceParam : null,
|
|
228
|
+
});
|
|
229
|
+
// Build every payload BEFORE load_start so it can carry the exact byte
|
|
230
|
+
// total — the client renders a real received/total progress (the legacy
|
|
231
|
+
// wire's per-entry count-up has no per-frame granularity here).
|
|
232
|
+
const rowsPayload = JSON.stringify({ rows: meta.rows, totalCount: meta.totalCount, hasMore: meta.hasMore, oldestTs: meta.oldestTimestamp, incremental: !!useIncremental });
|
|
233
|
+
let v3Bytes = rowsPayload.length;
|
|
234
|
+
for (const p of native.convPayloads) v3Bytes += p.length;
|
|
235
|
+
for (const p of native.respPayloads) v3Bytes += p.length;
|
|
236
|
+
const loadStartData = { total: meta.totalCount, incremental: !!useIncremental, v3Bytes };
|
|
237
|
+
if (useLimit) {
|
|
238
|
+
loadStartData.hasMore = !!meta.hasMore;
|
|
239
|
+
loadStartData.oldestTs = meta.oldestTimestamp || '';
|
|
240
|
+
}
|
|
241
|
+
sseWrite(res, `event: load_start\ndata: ${JSON.stringify(loadStartData)}\n\n`);
|
|
242
|
+
sseWrite(res, `event: v2_requests\ndata: ${rowsPayload}\n\n`);
|
|
243
|
+
for (const payload of native.convPayloads) sseWrite(res, `event: v3_conv\ndata: ${payload}\n\n`);
|
|
244
|
+
for (const payload of native.respPayloads) sseWrite(res, `event: v3_resp\ndata: ${payload}\n\n`);
|
|
245
|
+
// kv-cache / context_window sources: rebuild the newest completed
|
|
246
|
+
// mainAgent rows. Depth 3 mirrors the legacy scan-ring fallback (review
|
|
247
|
+
// P2-e): the newest main may lack usage/cached-content or be a
|
|
248
|
+
// synthesis-gated orphan — earlier candidates then provide the values.
|
|
249
|
+
const mains = meta.rows.filter((r) => r.mainAgent && !r.inProgress).slice(-3);
|
|
250
|
+
for (const m of mains) {
|
|
251
|
+
const detail = await readV2SingleEntry(src, { seq: m.seq, sessionId: m.sessionId });
|
|
252
|
+
if (detail && detail.entry) mainAgentRawRing.push(detail.entry);
|
|
253
|
+
}
|
|
254
|
+
} catch (err) { reportSwallowed('sse.v2_requests', err); }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
sseWrite(res, `event: load_end\ndata: {}\n\n`);
|
|
258
|
+
|
|
259
|
+
// S10a: v2 sources no longer invoke onScan (the two-pass window never
|
|
260
|
+
// stringifies out-of-window entries) — the adapter returns the newest-main
|
|
261
|
+
// raws as `mainAgentRing` instead. v1 sources still fill the ring via onScan.
|
|
262
|
+
if (Array.isArray(coldLoadResult?.mainAgentRing) && coldLoadResult.mainAgentRing.length > 0) {
|
|
263
|
+
mainAgentRawRing.length = 0;
|
|
264
|
+
mainAgentRawRing.push(...coldLoadResult.mainAgentRing);
|
|
265
|
+
}
|
|
257
266
|
|
|
258
267
|
// Pass 1 入环的候选 newest-first 校验 + parse(≤K 次)。kv_cache 与 context_window
|
|
259
268
|
// 各自取"最新一条能提供该值的真实 mainAgent"—— 与旧版逐条覆盖语义等价(环深度内)。
|
|
@@ -278,10 +287,10 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
278
287
|
|
|
279
288
|
// 发送最新 MainAgent 的 KV-Cache 和 context_window
|
|
280
289
|
if (latestKvCache) {
|
|
281
|
-
res
|
|
290
|
+
sseWrite(res, `event: kv_cache_content\ndata: ${JSON.stringify(latestKvCache)}\n\n`);
|
|
282
291
|
}
|
|
283
292
|
if (latestContextWindow) {
|
|
284
|
-
res
|
|
293
|
+
sseWrite(res, `event: context_window\ndata: ${JSON.stringify(latestContextWindow)}\n\n`);
|
|
285
294
|
pushedContextWindow = true;
|
|
286
295
|
}
|
|
287
296
|
// Fallback: no MainAgent in log (e.g. fresh session after -c), read context-window.json
|
|
@@ -301,7 +310,7 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
301
310
|
const effectiveSize = adaptContextWindow(contextSize, inputTokens);
|
|
302
311
|
const usedPct = effectiveSize > 0 ? Math.round((totalTokens / effectiveSize) * 100) : 0;
|
|
303
312
|
const data = { ...cw, context_window_size: effectiveSize, used_percentage: usedPct, remaining_percentage: 100 - usedPct };
|
|
304
|
-
res
|
|
313
|
+
sseWrite(res, `event: context_window\ndata: ${JSON.stringify(data)}\n\n`);
|
|
305
314
|
}
|
|
306
315
|
} catch { }
|
|
307
316
|
}
|
|
@@ -325,69 +334,30 @@ async function events(req, res, parsedUrl, isLocal, deps) {
|
|
|
325
334
|
// API endpoint
|
|
326
335
|
async function requests(req, res) {
|
|
327
336
|
// 异步流式 JSON 数组输出,不做 reconstruct,发原始条目
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
await streamRawEntriesAsync(LOG_FILE, (raw) => {
|
|
332
|
-
if (!first) res.write(',');
|
|
333
|
-
res.write(enrichRawIfNeeded(raw));
|
|
334
|
-
first = false;
|
|
335
|
-
});
|
|
336
|
-
res.write(']');
|
|
337
|
-
res.end();
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// 分页历史条目端点:移动端"加载更多"按需拉取
|
|
341
|
-
// 支持可选 ?file= 参数指定目标文件(用于本地日志分页),默认使用活跃会话文件
|
|
342
|
-
async function entriesPage(req, res, parsedUrl) {
|
|
343
|
-
const before = parsedUrl.searchParams.get('before');
|
|
344
|
-
const limitVal = Math.min(parseInt(parsedUrl.searchParams.get('limit'), 10) || 100, 500);
|
|
345
|
-
if (!before || isNaN(new Date(before).getTime())) {
|
|
346
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
347
|
-
res.end(JSON.stringify({ error: 'missing or invalid "before" parameter' }));
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
const file = parsedUrl.searchParams.get('file');
|
|
351
|
-
let targetFile = LOG_FILE;
|
|
352
|
-
if (file) {
|
|
353
|
-
if (file.includes('..') || (!file.endsWith('.jsonl') && !file.endsWith('.jsonl.zip'))) {
|
|
354
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
355
|
-
res.end(JSON.stringify({ error: 'Invalid file name' }));
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
try { validateLogPath(LOG_DIR, file); } catch (e) {
|
|
359
|
-
const status = e.code === 'NOT_FOUND' ? 404 : e.code === 'ACCESS_DENIED' ? 403 : 400;
|
|
360
|
-
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
361
|
-
res.end(JSON.stringify({ error: e.message }));
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
targetFile = join(LOG_DIR, file);
|
|
365
|
-
}
|
|
337
|
+
// flush:false —— whole-stream response: the client parses the array only
|
|
338
|
+
// when complete, so per-macrotask flush boundaries would just cost ratio.
|
|
339
|
+
sseHead(req, res, 200, { 'Content-Type': 'application/json' }, { flush: false });
|
|
366
340
|
try {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
res
|
|
375
|
-
|
|
376
|
-
hasMore: result.hasMore,
|
|
377
|
-
oldestTimestamp: result.oldestTimestamp,
|
|
378
|
-
count: entries.length,
|
|
379
|
-
}));
|
|
341
|
+
sseWrite(res, '[');
|
|
342
|
+
let first = true;
|
|
343
|
+
await streamRawEntriesAsync(getLiveLogSource(), (raw) => {
|
|
344
|
+
if (!first) sseWrite(res, ',');
|
|
345
|
+
sseWrite(res, enrichRawIfNeeded(raw));
|
|
346
|
+
first = false;
|
|
347
|
+
});
|
|
348
|
+
sseWrite(res, ']');
|
|
349
|
+
wireEnd(res);
|
|
380
350
|
} catch (err) {
|
|
381
|
-
|
|
382
|
-
|
|
351
|
+
// Mid-stream failure (e.g. session dir converted/removed while streaming):
|
|
352
|
+
// without this catch the rejection propagates through the dispatcher and
|
|
353
|
+
// kills the process. Headers are already sent — close the stream cleanly.
|
|
354
|
+
console.error('[api-requests]', err && err.stack || err);
|
|
355
|
+
wireEnd(res);
|
|
383
356
|
}
|
|
384
357
|
}
|
|
385
358
|
|
|
386
359
|
export const eventsRoutes = [
|
|
387
360
|
{ method: 'POST', match: 'exact', path: '/api/turn-end-notify', handler: turnEndNotify },
|
|
388
|
-
{ method: 'POST', match: 'exact', path: '/api/register-log', handler: registerLog },
|
|
389
|
-
{ method: 'POST', match: 'exact', path: '/api/resume-choice', handler: resumeChoice },
|
|
390
361
|
{ method: 'GET', match: 'exact', path: '/events', handler: events },
|
|
391
362
|
{ method: 'GET', match: 'exact', path: '/api/requests', handler: requests },
|
|
392
|
-
{ method: 'GET', match: 'exact', path: '/api/entries/page', handler: entriesPage },
|
|
393
363
|
];
|
|
@@ -8,7 +8,7 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
8
8
|
import { execFile, spawn } from 'node:child_process';
|
|
9
9
|
import { bumpWorkspacesVersion } from '../lib/file-access-policy.js';
|
|
10
10
|
import { validateImportDir } from '../lib/file-api.js';
|
|
11
|
-
import {
|
|
11
|
+
import { PROFILE_PATH, _projectName, _logDir } from '../interceptor.js';
|
|
12
12
|
import { LOG_DIR, getClaudeConfigDir } from '../../findcc.js';
|
|
13
13
|
|
|
14
14
|
function upload(req, res, parsedUrl, isLocal, deps) {
|
|
@@ -844,7 +844,9 @@ function createDir(req, res, parsedUrl, isLocal, deps) {
|
|
|
844
844
|
}
|
|
845
845
|
|
|
846
846
|
function openLogDir(req, res) {
|
|
847
|
-
|
|
847
|
+
// Project dir works for both formats (legacy v1 files and the v2 sessions/
|
|
848
|
+
// tree are siblings under it); LOG_DIR is the pre-workspace fallback.
|
|
849
|
+
const dir = _logDir || LOG_DIR;
|
|
848
850
|
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'explorer' : 'xdg-open';
|
|
849
851
|
execFile(cmd, [dir], { windowsHide: true }, () => {});
|
|
850
852
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
package/server/routes/im.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// reports its own in-process adapter (deps.im.getBridgeStatus) — that's what the manager probes.
|
|
22
22
|
import { getDescriptor, loadConfig, loadState, saveConfig } from '../lib/im-config.js';
|
|
23
23
|
import { findRecentLog } from '../lib/interceptor-core.js';
|
|
24
|
+
import { listV2Sessions } from '../lib/v2/adapter.js';
|
|
24
25
|
import { readSenders } from '../lib/im-senders.js';
|
|
25
26
|
import { readImAppendSystem, writeImAppendSystem, buildImAppendSystemPreset, MAX_IM_APPEND_SYSTEM_CHARS } from '../lib/im-append-system.js';
|
|
26
27
|
import { imDir } from '../lib/im-lock.js';
|
|
@@ -256,8 +257,16 @@ function imLogs(req, res, parsedUrl, isLocal, deps) {
|
|
|
256
257
|
const project = `IM_${id}`;
|
|
257
258
|
let latest = null;
|
|
258
259
|
try {
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
// wire-v2: prefer the newest v2 session (by meta.startTs, journal mtime as
|
|
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);
|
|
263
|
+
if (sessions.length > 0) {
|
|
264
|
+
sessions.sort((a, b) => (a.startTs < b.startTs ? 1 : a.startTs > b.startTs ? -1 : 0));
|
|
265
|
+
latest = `v2:${project}/${sessions[0].sid}`; // 直接喂给 /api/local-log?file=
|
|
266
|
+
} else {
|
|
267
|
+
const abs = findRecentLog(join(LOG_DIR, project), project); // 已排除 *_temp.jsonl
|
|
268
|
+
if (abs) latest = `${project}/${basename(abs)}`; // 相对 LOG_DIR
|
|
269
|
+
}
|
|
261
270
|
} catch { /* 无目录/无日志 → latest=null */ }
|
|
262
271
|
res.writeHead(200, JSON_HEADERS);
|
|
263
272
|
res.end(JSON.stringify({ project, latest }));
|