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
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Wire Format v2 — on-disk layout contract (docs/refactor/WIRE_FORMAT_V2.md §2/§3).
|
|
2
|
+
//
|
|
3
|
+
// Single owner of every v2 path: nothing outside this module joins path segments
|
|
4
|
+
// into a session directory. All directory creation is SYNCHRONOUS on purpose —
|
|
5
|
+
// an async mkdir would lose the race against AsyncWriteQueue's microtask drain
|
|
6
|
+
// and the swallowed ENOENT would silently drop v2 lines (plan risk F2).
|
|
7
|
+
|
|
8
|
+
import { mkdirSync, writeFileSync, renameSync, existsSync, openSync, writeSync, fsyncSync, closeSync, readdirSync, statSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
// The wire-format version this build writes AND the only one it reads.
|
|
12
|
+
// Stamped into meta.json and the journal sentinel at session creation; the
|
|
13
|
+
// read side refuses sessions carrying any OTHER version (spec §14 reader
|
|
14
|
+
// version gate) — a future wireFormat:3 dir must never be silently misread
|
|
15
|
+
// as v2. A missing version (torn creation) is treated as the current one.
|
|
16
|
+
export const WIRE_FORMAT_VERSION = 2;
|
|
17
|
+
|
|
18
|
+
export function isSupportedWireFormat(v) {
|
|
19
|
+
return v == null || v === WIRE_FORMAT_VERSION;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Path-component whitelist (spec §2): anything outside [a-zA-Z0-9._-] is replaced
|
|
23
|
+
// with '_'; empty / dot-only results are rejected to '_'. Guards convKey /
|
|
24
|
+
// sessionId derived from wire content against path injection (plan risk F14).
|
|
25
|
+
export function sanitizePathComponent(name) {
|
|
26
|
+
const cleaned = String(name == null ? '' : name).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
27
|
+
if (cleaned === '' || /^\.+$/.test(cleaned)) return '_';
|
|
28
|
+
return cleaned;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Session creation time (ISO) → a 14-digit LOCAL-time stamp `yyyymmddhhmmss`
|
|
32
|
+
* for the session dir-name prefix (task C). Deliberately NO internal separator
|
|
33
|
+
* (unlike log-management's `compactLocalTs`, which emits `YYYYMMDD_HHMMSS`) so
|
|
34
|
+
* the dir name `<14digits>_<sessionId>` splits unambiguously on the first `_`.
|
|
35
|
+
* Returns '' on an unparseable ts. */
|
|
36
|
+
export function compactLocalTs14(iso) {
|
|
37
|
+
const d = new Date(iso);
|
|
38
|
+
if (isNaN(d.getTime())) return '';
|
|
39
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
40
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The `<project>/<SESSIONS_DIR_NAME>/<dirName>/` nesting is load-bearing: the
|
|
44
|
+
// read adapter re-derives the project root as `dirname(dirname(sessionDir))`
|
|
45
|
+
// (adapter.js/replay.js), so anything that materializes a session dir off the
|
|
46
|
+
// hot path (e.g. the zip-upload extractor) MUST reuse this constant to stay in
|
|
47
|
+
// sync — a divergent literal would silently synthesize zero entries.
|
|
48
|
+
export const SESSIONS_DIR_NAME = 'sessions';
|
|
49
|
+
|
|
50
|
+
/** All absolute paths of one session directory, derived once and passed around.
|
|
51
|
+
* The 3rd arg is the DIR-NAME (path segment) — for live sessions that is now
|
|
52
|
+
* `<ts>_<uuid>` (task C), for the converter a staged name, for legacy the bare
|
|
53
|
+
* UUID. It is NOT the identity (the UUID lives in meta.sessionId).
|
|
54
|
+
* `sessionsDirName` lets the offline converter target a sibling staging root
|
|
55
|
+
* (`sessions-migrating`) with the exact same layout; live writers keep the
|
|
56
|
+
* default and never pass it. */
|
|
57
|
+
export function sessionPaths(logDir, project, dirName, sessionsDirName = SESSIONS_DIR_NAME) {
|
|
58
|
+
const dir = join(logDir, sanitizePathComponent(project), sanitizePathComponent(sessionsDirName), sanitizePathComponent(dirName));
|
|
59
|
+
return {
|
|
60
|
+
dir,
|
|
61
|
+
metaPath: join(dir, 'meta.json'),
|
|
62
|
+
journalPath: join(dir, 'journal.jsonl'),
|
|
63
|
+
responsesPath: join(dir, 'responses.jsonl'),
|
|
64
|
+
// Optional display cache: deduped user prompts, one {seq, texts} line per
|
|
65
|
+
// emitting request. Append-only, never read by replay/verify (spec §15).
|
|
66
|
+
promptsPath: join(dir, 'prompts.jsonl'),
|
|
67
|
+
conversationsDir: join(dir, 'conversations'),
|
|
68
|
+
blobsDir: join(dir, 'blobs'),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Recursive byte size of a directory tree (the session-dir display size). */
|
|
73
|
+
export function dirSizeSync(dir) {
|
|
74
|
+
let total = 0;
|
|
75
|
+
let entries;
|
|
76
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return total; }
|
|
77
|
+
for (const e of entries) {
|
|
78
|
+
const p = join(dir, e.name);
|
|
79
|
+
try {
|
|
80
|
+
if (e.isDirectory()) total += dirSizeSync(p);
|
|
81
|
+
else if (e.isFile()) total += statSync(p).size;
|
|
82
|
+
} catch { /* raced deletion — skip */ }
|
|
83
|
+
}
|
|
84
|
+
return total;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function convEpochPath(paths, convKey, epoch) {
|
|
88
|
+
return join(paths.conversationsDir, sanitizePathComponent(convKey), `e${epoch}.jsonl`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function convDir(paths, convKey) {
|
|
92
|
+
return join(paths.conversationsDir, sanitizePathComponent(convKey));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function blobPath(paths, ref) {
|
|
96
|
+
// ref is produced by blob-store ('sha256-<hex16>') but sanitize anyway — the
|
|
97
|
+
// reader side may feed refs parsed back from journal lines.
|
|
98
|
+
return join(paths.blobsDir, `${sanitizePathComponent(ref)}.json`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Atomic small-file write: tmp → fsync → rename. Used for meta.json and blobs —
|
|
103
|
+
* the two file kinds whose partial content must never be observable (spec §3/§7).
|
|
104
|
+
* fsync is deliberate: blobs are the durability barrier of the write-order
|
|
105
|
+
* protocol (blob → conversation → journal), see spec §1.3.
|
|
106
|
+
*/
|
|
107
|
+
export function writeFileAtomicSync(finalPath, data) {
|
|
108
|
+
const tmp = `${finalPath}.tmp-${process.pid}`;
|
|
109
|
+
const fd = openSync(tmp, 'w');
|
|
110
|
+
try {
|
|
111
|
+
writeSync(fd, data);
|
|
112
|
+
fsyncSync(fd);
|
|
113
|
+
} finally {
|
|
114
|
+
closeSync(fd);
|
|
115
|
+
}
|
|
116
|
+
renameSync(tmp, finalPath);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Create the session directory skeleton + meta.json + the journal self-describing
|
|
121
|
+
* first line, idempotently. Must be called (synchronously) before any enqueue
|
|
122
|
+
* targeting this session. Returns the paths object.
|
|
123
|
+
*
|
|
124
|
+
* meta: { wireFormat:2, sessionId, project, pid, startTs, userIdRaw,
|
|
125
|
+
* userIdEncoding, leader?, im? } — spec §3. Existing meta is never
|
|
126
|
+
* rewritten here (later additive updates go through updateMetaSync).
|
|
127
|
+
*/
|
|
128
|
+
export function ensureSessionDirSync(logDir, project, sessionId, meta = {}, sessionsDirName = SESSIONS_DIR_NAME, dirName = sessionId) {
|
|
129
|
+
// Path uses dirName (`<ts>_<uuid>` for live, task C); IDENTITY (meta.sessionId
|
|
130
|
+
// + journal sentinel) stays the bare `sessionId` (UUID). Legacy callers omit
|
|
131
|
+
// dirName → it defaults to sessionId → old behavior (dir == UUID).
|
|
132
|
+
const paths = sessionPaths(logDir, project, dirName, sessionsDirName);
|
|
133
|
+
mkdirSync(paths.conversationsDir, { recursive: true });
|
|
134
|
+
mkdirSync(paths.blobsDir, { recursive: true });
|
|
135
|
+
if (!existsSync(paths.metaPath)) {
|
|
136
|
+
// `...meta` first: identity fields below must win even if a caller ever
|
|
137
|
+
// passes overlapping keys (defensive — current callers pass none of them).
|
|
138
|
+
const record = {
|
|
139
|
+
...meta,
|
|
140
|
+
wireFormat: WIRE_FORMAT_VERSION,
|
|
141
|
+
sessionId,
|
|
142
|
+
project,
|
|
143
|
+
startTs: meta.startTs || new Date().toISOString(),
|
|
144
|
+
};
|
|
145
|
+
writeFileAtomicSync(paths.metaPath, JSON.stringify(record, null, 2) + '\n');
|
|
146
|
+
}
|
|
147
|
+
if (!existsSync(paths.journalPath)) {
|
|
148
|
+
// Journal sentinel first frame — must exist atomically at creation (same
|
|
149
|
+
// hazard as the v1 rotation sentinel: a watcher may read the file the
|
|
150
|
+
// instant it appears). 'wx' create-exclusive keeps a concurrent creator
|
|
151
|
+
// from double-writing the sentinel.
|
|
152
|
+
const sentinel = JSON.stringify({ ph: 'meta', wireFormat: WIRE_FORMAT_VERSION, sessionId }) + '\n';
|
|
153
|
+
try {
|
|
154
|
+
writeFileSync(paths.journalPath, sentinel, { flag: 'wx' });
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (err && err.code !== 'EEXIST') throw err;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return paths;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Ensure a conversation subdirectory exists (before first enqueue to it). */
|
|
163
|
+
export function ensureConvDirSync(paths, convKey) {
|
|
164
|
+
const dir = convDir(paths, convKey);
|
|
165
|
+
mkdirSync(dir, { recursive: true });
|
|
166
|
+
return dir;
|
|
167
|
+
}
|