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.
Files changed (222) hide show
  1. package/README.md +13 -1
  2. package/cli.js +116 -37
  3. package/dist/assets/App-gUSmU6Ny.js +2 -0
  4. package/dist/assets/{MdxEditorPanel-ualoRFOL.js → MdxEditorPanel-DAR39A3S.js} +1 -1
  5. package/dist/assets/Mobile-qfRsRth0.js +1 -0
  6. package/dist/assets/{_baseUniq-CG81mgN_.js → _baseUniq-D9_E5ZDp.js} +1 -1
  7. package/dist/assets/{arc-C161J4f2.js → arc-D4iFrlhb.js} +1 -1
  8. package/dist/assets/{architectureDiagram-Q4EWVU46-BmbDMdn9.js → architectureDiagram-Q4EWVU46-f1neo7hg.js} +1 -1
  9. package/dist/assets/{blockDiagram-DXYQGD6D-DXpMvdxd.js → blockDiagram-DXYQGD6D-DWi9e9tG.js} +1 -1
  10. package/dist/assets/{c4Diagram-AHTNJAMY-8Mypg0IU.js → c4Diagram-AHTNJAMY-DAdj49A2.js} +1 -1
  11. package/dist/assets/{channel-B2khdmvc.js → channel-B3VthT0m.js} +1 -1
  12. package/dist/assets/{chunk-4BX2VUAB-I61r1g6z.js → chunk-4BX2VUAB-BrPcAHV3.js} +1 -1
  13. package/dist/assets/{chunk-4TB4RGXK-VFPlRE5E.js → chunk-4TB4RGXK-Bvopc1V9.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-DvCamg_6.js → chunk-55IACEB6-Juw-7Z3i.js} +1 -1
  15. package/dist/assets/{chunk-EDXVE4YY-Dj4Q8RwR.js → chunk-EDXVE4YY-DVYP9tpB.js} +1 -1
  16. package/dist/assets/{chunk-FMBD7UC4-_bgQuuJh.js → chunk-FMBD7UC4-VQ7VOXl3.js} +1 -1
  17. package/dist/assets/{chunk-OYMX7WX6-3to38LyN.js → chunk-OYMX7WX6-F8Rj4ZUZ.js} +1 -1
  18. package/dist/assets/{chunk-QZHKN3VN-Zl26wKKz.js → chunk-QZHKN3VN-Dn7K3yi9.js} +1 -1
  19. package/dist/assets/{chunk-YZCP3GAM-RLYPrlh0.js → chunk-YZCP3GAM-UjO-KTsn.js} +1 -1
  20. package/dist/assets/classDiagram-6PBFFD2Q-DmSozxoO.js +1 -0
  21. package/dist/assets/classDiagram-v2-HSJHXN6E-DmSozxoO.js +1 -0
  22. package/dist/assets/clone-BQtvy9Ae.js +1 -0
  23. package/dist/assets/{cose-bilkent-S5V4N54A-CsYOjJ0V.js → cose-bilkent-S5V4N54A-BDhNj8rq.js} +1 -1
  24. package/dist/assets/{dagre-KV5264BT-C3YXXaFU.js → dagre-KV5264BT-BzdcDuyt.js} +1 -1
  25. package/dist/assets/{diagram-5BDNPKRD-DUCOg1ld.js → diagram-5BDNPKRD-DP_SX65N.js} +1 -1
  26. package/dist/assets/{diagram-G4DWMVQ6-BtlLl_zX.js → diagram-G4DWMVQ6-B-pBFw8Q.js} +1 -1
  27. package/dist/assets/{diagram-MMDJMWI5-1Qp9xfOk.js → diagram-MMDJMWI5-6XSfN1eN.js} +1 -1
  28. package/dist/assets/{diagram-TYMM5635-Cj7VMp3L.js → diagram-TYMM5635-DJvGxvpE.js} +1 -1
  29. package/dist/assets/{erDiagram-SMLLAGMA-CVZVn541.js → erDiagram-SMLLAGMA-CpqdpGYj.js} +1 -1
  30. package/dist/assets/{flowDiagram-DWJPFMVM-CiU9Fkkv.js → flowDiagram-DWJPFMVM-C9m7wjwI.js} +1 -1
  31. package/dist/assets/{ganttDiagram-T4ZO3ILL-aRaG97ID.js → ganttDiagram-T4ZO3ILL-CU0XOfY3.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-UUTBAWPF-Cr2LJlvX.js → gitGraphDiagram-UUTBAWPF-ipyGULnP.js} +1 -1
  33. package/dist/assets/{graph-B59lDyMn.js → graph-BR2KmES9.js} +1 -1
  34. package/dist/assets/{index-C1ptnCpq.js → index--wgRW_OB.js} +1 -1
  35. package/dist/assets/index-B51Ci0GL.js +2 -0
  36. package/dist/assets/{index-CRAWA8PC.js → index-C-be29ey.js} +1 -1
  37. package/dist/assets/{index-CSM1Kw8l.js → index-C16fiVhv.js} +1 -1
  38. package/dist/assets/{index-Ddk-kg22.js → index-D5YruOKD.js} +1 -1
  39. package/dist/assets/{index-BnhNIjbj.js → index-D5tSeMU7.js} +1 -1
  40. package/dist/assets/{index-vFEQq-NB.js → index-DuZQ6FqR.js} +1 -1
  41. package/dist/assets/{index-CO2hZ1s4.js → index-OWzSX2T8.js} +1 -1
  42. package/dist/assets/{infoDiagram-42DDH7IO-CnO22bw8.js → infoDiagram-42DDH7IO-BQWQjfFX.js} +1 -1
  43. package/dist/assets/{ishikawaDiagram-UXIWVN3A-ByNzpScH.js → ishikawaDiagram-UXIWVN3A-Df-KH_Hz.js} +1 -1
  44. package/dist/assets/{journeyDiagram-VCZTEJTY-yaVaCWPq.js → journeyDiagram-VCZTEJTY-CMbB_4G9.js} +1 -1
  45. package/dist/assets/{jszip.min-BdVFP3OW.js → jszip.min-B6k2t9Ch.js} +1 -1
  46. package/dist/assets/{kanban-definition-6JOO6SKY-RIPR_RQ3.js → kanban-definition-6JOO6SKY-CImdlKN3.js} +1 -1
  47. package/dist/assets/{layout-0rzel0v_.js → layout-DQl4aw09.js} +1 -1
  48. package/dist/assets/{linear-sktN0tB6.js → linear-CLD3aPlK.js} +1 -1
  49. package/dist/assets/mermaid.core-CN35dvKJ.js +7 -0
  50. package/dist/assets/{min-rspGOkz_.js → min-_D86henJ.js} +1 -1
  51. package/dist/assets/{mindmap-definition-QFDTVHPH-BsVzZzTz.js → mindmap-definition-QFDTVHPH-2JVcu1nk.js} +1 -1
  52. package/dist/assets/{pieDiagram-DEJITSTG-CsoWsw4f.js → pieDiagram-DEJITSTG-Dik-m5u7.js} +1 -1
  53. package/dist/assets/{quadrantDiagram-34T5L4WZ-CTMKuCAI.js → quadrantDiagram-34T5L4WZ-Bm5l40Jq.js} +1 -1
  54. package/dist/assets/{requirementDiagram-MS252O5E-C67256te.js → requirementDiagram-MS252O5E-Ck9w5rTj.js} +1 -1
  55. package/dist/assets/{sankeyDiagram-XADWPNL6-X8Y7KnW0.js → sankeyDiagram-XADWPNL6-BbGVCFEf.js} +1 -1
  56. package/dist/assets/{seqResourceLoaders-B5Zy_CC_.css → seqResourceLoaders-DFrHmgnK.css} +1 -1
  57. package/dist/assets/seqResourceLoaders-DZJxMMaR.js +2 -0
  58. package/dist/assets/{sequenceDiagram-FGHM5R23-Cils0TAp.js → sequenceDiagram-FGHM5R23-vjvEdksk.js} +1 -1
  59. package/dist/assets/{stateDiagram-FHFEXIEX-DFnohvpG.js → stateDiagram-FHFEXIEX-DTqMoiB2.js} +1 -1
  60. package/dist/assets/{stateDiagram-v2-QKLJ7IA2-BTX_4o81.js → stateDiagram-v2-QKLJ7IA2-CNbAwnhu.js} +1 -1
  61. package/dist/assets/{timeline-definition-GMOUNBTQ-DrIYFrzZ.js → timeline-definition-GMOUNBTQ-Cw8O-52d.js} +1 -1
  62. package/dist/assets/{vendor-antd-D5HmSDSg.js → vendor-antd-DI7JL-mE.js} +1 -1
  63. package/dist/assets/{vendor-codemirror-C5yuj7ze.js → vendor-codemirror-B9c49dtM.js} +1 -1
  64. package/dist/assets/{vendor-mdxeditor-BcEooTvb.js → vendor-mdxeditor-B6cpBtIE.js} +2 -2
  65. package/dist/assets/{vendor-qrcode-d9LCn0-9.js → vendor-qrcode-Dn90-s-u.js} +1 -1
  66. package/dist/assets/{vendor-virtuoso-K9m4MN6a.js → vendor-virtuoso-CpdSKD_6.js} +1 -1
  67. package/dist/assets/{vennDiagram-DHZGUBPP-lRqBYTr9.js → vennDiagram-DHZGUBPP-DCuqymb7.js} +1 -1
  68. package/dist/assets/{wardley-RL74JXVD-e8YfxpOe.js → wardley-RL74JXVD-BJ20wJUU.js} +1 -1
  69. package/dist/assets/{wardleyDiagram-NUSXRM2D-Bxrg774h.js → wardleyDiagram-NUSXRM2D-Vz6US3qt.js} +1 -1
  70. package/dist/assets/{xychartDiagram-5P7HB3ND-DWhZqhq8.js → xychartDiagram-5P7HB3ND-CzkLKrmi.js} +1 -1
  71. package/dist/index.html +4 -4
  72. package/findcc.js +4 -3
  73. package/package.json +3 -1
  74. package/server/i18n.js +21 -141
  75. package/server/interceptor.js +211 -403
  76. package/server/lib/async-write-queue.js +12 -1
  77. package/server/lib/delta-reconstructor.js +3 -3
  78. package/server/lib/error-report.js +45 -0
  79. package/server/lib/im-log-watcher.js +68 -8
  80. package/server/lib/interceptor-core.js +19 -174
  81. package/server/lib/log-management.js +214 -247
  82. package/server/lib/log-stream.js +59 -108
  83. package/server/lib/log-watcher.js +62 -274
  84. package/server/lib/log-zip.js +148 -0
  85. package/server/lib/session-boundary.js +266 -0
  86. package/server/lib/session-pin-store.js +10 -19
  87. package/server/lib/stats-worker.js +138 -267
  88. package/server/lib/user-prompt-extract.js +250 -0
  89. package/server/lib/v2/adapter.js +1124 -0
  90. package/server/lib/v2/blob-store.js +44 -0
  91. package/server/lib/v2/conversation-store.js +217 -0
  92. package/server/lib/v2/convert-manager.js +79 -0
  93. package/server/lib/v2/convert-worker.js +42 -0
  94. package/server/lib/v2/convert.js +409 -0
  95. package/server/lib/v2/identity.js +204 -0
  96. package/server/lib/v2/journal.js +62 -0
  97. package/server/lib/v2/layout.js +167 -0
  98. package/server/lib/v2/live-feed.js +558 -0
  99. package/server/lib/v2/meta-rows.js +360 -0
  100. package/server/lib/v2/migrate-prompt.js +54 -0
  101. package/server/lib/v2/replay.js +191 -0
  102. package/server/lib/v2/session-select.js +222 -0
  103. package/server/lib/v2/singleflight.js +104 -0
  104. package/server/lib/v2/v2-writer.js +528 -0
  105. package/server/lib/v2/verify.js +233 -0
  106. package/server/lib/wire-compress.js +167 -0
  107. package/server/routes/events.js +120 -150
  108. package/server/routes/files-fs.js +4 -2
  109. package/server/routes/im.js +11 -2
  110. package/server/routes/logs.js +209 -164
  111. package/server/routes/preferences.js +0 -3
  112. package/server/routes/project-meta.js +2 -3
  113. package/server/routes/session-pin.js +7 -8
  114. package/server/routes/v2.js +60 -0
  115. package/server/routes/workspaces.js +50 -14
  116. package/server/server.js +70 -66
  117. package/server/workspace-registry.js +24 -4
  118. package/src/utils/apiUrl.js +26 -0
  119. package/src/utils/askFallback.js +21 -0
  120. package/src/utils/askOptionDesc.js +55 -0
  121. package/src/utils/askPortalMatcher.js +54 -0
  122. package/src/utils/autoApproveOptions.js +27 -0
  123. package/src/utils/avatarAnimationPostPass.js +49 -0
  124. package/src/utils/builtinPresets.js +18 -0
  125. package/src/utils/clearCheckpoint.js +7 -0
  126. package/src/utils/commandValidator.js +45 -0
  127. package/src/utils/contentFilter.js +620 -0
  128. package/src/utils/contextRaw.js +18 -0
  129. package/src/utils/contextSidebarNavigation.js +16 -0
  130. package/src/utils/contextTurns.js +117 -0
  131. package/src/utils/displayScaleHelper.js +47 -0
  132. package/src/utils/effectiveModel.js +13 -0
  133. package/src/utils/entry-slim.js +563 -0
  134. package/src/utils/entryCache.js +144 -0
  135. package/src/utils/errorReport.js +6 -0
  136. package/src/utils/fileExpandedPathsStorage.js +70 -0
  137. package/src/utils/fileIcons.jsx +36 -0
  138. package/src/utils/fileOpen.js +37 -0
  139. package/src/utils/formatters.js +48 -0
  140. package/src/utils/gitApi.js +50 -0
  141. package/src/utils/gitTreeBuilder.js +19 -0
  142. package/src/utils/helpers.js +846 -0
  143. package/src/utils/identityHeal.js +99 -0
  144. package/src/utils/imConnState.js +59 -0
  145. package/src/utils/imOrigin.js +24 -0
  146. package/src/utils/imTr.js +7 -0
  147. package/src/utils/imageCompress.js +83 -0
  148. package/src/utils/imageDownscale.js +28 -0
  149. package/src/utils/imageResize.js +112 -0
  150. package/src/utils/ingestPipeline.js +47 -0
  151. package/src/utils/lazyWithReload.js +53 -0
  152. package/src/utils/markdown.js +30 -0
  153. package/src/utils/markdownIncremental.js +39 -0
  154. package/src/utils/markdownProfiler.js +113 -0
  155. package/src/utils/mdExtensionDetect.js +64 -0
  156. package/src/utils/memoryLinkParser.js +44 -0
  157. package/src/utils/modalMask.js +15 -0
  158. package/src/utils/monotime.js +6 -0
  159. package/src/utils/pinnedMenu.js +55 -0
  160. package/src/utils/presetShortcuts.js +14 -0
  161. package/src/utils/projectAlias.js +135 -0
  162. package/src/utils/promptClassifier.js +112 -0
  163. package/src/utils/promptDetect.js +196 -0
  164. package/src/utils/promptNav.js +60 -0
  165. package/src/utils/ptyChunkBuilder.js +251 -0
  166. package/src/utils/quickMenuHoverIntent.js +35 -0
  167. package/src/utils/rateLimitParser.js +119 -0
  168. package/src/utils/readResultPool.js +106 -0
  169. package/src/utils/refreshCachedItemProp.js +30 -0
  170. package/src/utils/requestType.js +208 -0
  171. package/src/utils/resizeCalc.js +21 -0
  172. package/src/utils/resolveLocalized.js +17 -0
  173. package/src/utils/searchApi.js +47 -0
  174. package/src/utils/searchReplace.js +51 -0
  175. package/src/utils/seqResourceLoaders.js +79 -0
  176. package/src/utils/sessionManager.js +471 -0
  177. package/src/utils/sessionMerge.js +180 -0
  178. package/src/utils/skillModalController.js +111 -0
  179. package/src/utils/skillsParser.js +116 -0
  180. package/src/utils/slashCommandLabels.js +80 -0
  181. package/src/utils/splitDragCalc.js +73 -0
  182. package/src/utils/stickyBottomController.js +643 -0
  183. package/src/utils/svgSanitize.js +48 -0
  184. package/src/utils/systemTags.js +47 -0
  185. package/src/utils/tClaude.js +20 -0
  186. package/src/utils/teamModalBuilder.js +351 -0
  187. package/src/utils/teamSessionParser.js +190 -0
  188. package/src/utils/teammateAvatars.js +194 -0
  189. package/src/utils/teammateDetector.js +121 -0
  190. package/src/utils/termDiag.js +114 -0
  191. package/src/utils/terminalClipboard.js +117 -0
  192. package/src/utils/terminalWriteQueue.js +398 -0
  193. package/src/utils/thinkingMerge.js +49 -0
  194. package/src/utils/toolCatalog.js +29 -0
  195. package/src/utils/toolResultBuilder.js +393 -0
  196. package/src/utils/toolResultClassifier.js +16 -0
  197. package/src/utils/toolResultCore.js +167 -0
  198. package/src/utils/toolsDiff.js +44 -0
  199. package/src/utils/toolsXmlFormatter.js +3 -0
  200. package/src/utils/ultraplanController.js +109 -0
  201. package/src/utils/ultraplanExperts.js +86 -0
  202. package/src/utils/ultraplanTemplates.js +154 -0
  203. package/src/utils/userImageRefs.js +49 -0
  204. package/src/utils/v3Assembler.js +112 -0
  205. package/src/utils/v3Rows.js +48 -0
  206. package/src/utils/voicePackPlayer.js +233 -0
  207. package/src/utils/webSearchGrouping.js +111 -0
  208. package/src/utils/workflowFormat.js +52 -0
  209. package/src/utils/workflowRuns.js +118 -0
  210. package/src/utils/workflowStore.js +94 -0
  211. package/dist/assets/App-VTo4NVdo.js +0 -2
  212. package/dist/assets/Mobile-CcmYvMm_.js +0 -1
  213. package/dist/assets/classDiagram-6PBFFD2Q-BXnyUgsj.js +0 -1
  214. package/dist/assets/classDiagram-v2-HSJHXN6E-BXnyUgsj.js +0 -1
  215. package/dist/assets/clone-md-Mc1-T.js +0 -1
  216. package/dist/assets/index-DCo650PG.js +0 -2
  217. package/dist/assets/mermaid.core-C2Parj1q.js +0 -7
  218. package/dist/assets/seqResourceLoaders-BVozq68i.js +0 -2
  219. package/server/lib/instance-registry.js +0 -57
  220. package/server/lib/jsonl-archive.js +0 -180
  221. package/server/lib/teammate-detect.js +0 -32
  222. package/server/lib/zip-safety.js +0 -68
@@ -0,0 +1,250 @@
1
+ // Shared user-prompt extraction — the server-side canonical for pulling clean,
2
+ // display-grade user prompts out of wire `messages` arrays and out of v2
3
+ // conversation event lines. Consumers: stats-worker previews, the V2Writer's
4
+ // per-session `prompts.jsonl` cache, the log-list read side, and the one-off
5
+ // backfill tooling. Keep them on THIS module — forked copies drift.
6
+ //
7
+ // NOTE: this is a deliberate server-side SUBSET of the frontend classifier.
8
+ // The full classification (synthetic prompts, secondary recycling, …) lives in
9
+ // src/utils/contentFilter.js:isSystemText — frontend (dist) and server are two
10
+ // separate bundles that cannot share a module, so only the rules that would
11
+ // LEAK into previews are mirrored here: system tags + cross-session teammate
12
+ // notifications.
13
+ import { openSync, readSync, closeSync } from 'node:fs';
14
+
15
+ // Cross-session / teammate protocol notification `type` whitelist (must match
16
+ // src/utils/contentFilter.js INTER_SESSION_NOTIFICATION_TYPES; add new types in
17
+ // BOTH places). One array derives the Set (brace scan) + the RegExp
18
+ // (isSystemText) so this file cannot drift internally.
19
+ // test/stats-worker-notification-filter.test.js guards frontend↔server sync.
20
+ export const INTER_SESSION_TYPES = [
21
+ 'idle_notification', 'shutdown_request', 'shutdown_response', 'shutdown_approved',
22
+ 'teammate_terminated', 'plan_approval_request', 'plan_approval_response',
23
+ ];
24
+ const INTER_SESSION_TYPES_SET = new Set(INTER_SESSION_TYPES);
25
+ const INTER_SESSION_TYPES_RE = new RegExp(`"type"\\s*:\\s*"(?:${INTER_SESSION_TYPES.join('|')})"`);
26
+
27
+ // CLI-synthesized prompts (HTTP role=user but not typed by a human): Recap /
28
+ // Title / Compact / Topic / Summary. Must match src/utils/contentFilter.js
29
+ // SYNTHETIC_PROMPTS (KEEP IN SYNC — same rule set, `^`-anchored on trimmed
30
+ // text so quoted user text is never swallowed). Without this filter they leak
31
+ // into the "all user prompts" preview (observed on real data: "The user
32
+ // stepped away and is coming back. Recap in under 40 words…").
33
+ const SYNTHETIC_PROMPT_RES = [
34
+ /^The user stepped away and is coming back\. Recap in under/i,
35
+ /^(Based on the above conversation, generate a|Please write a)\s+(short|concise)\s+title/i,
36
+ /^(Your task is to create a detailed summary of the conversation|This session is being continued from a previous conversation)/i,
37
+ /^Analyze if this message indicates a new/i,
38
+ /^Summarize this coding session/i,
39
+ ];
40
+
41
+ /** True when a text block is system-injected chrome, not a user prompt. */
42
+ export function isSystemText(text) {
43
+ if (!text) return true;
44
+ const trimmed = text.trim();
45
+ if (!trimmed) return true;
46
+ // Blocks that carry plan content must never be filtered (even when they
47
+ // open with a system tag).
48
+ if (/Implement the following plan:/i.test(trimmed)) return false;
49
+ if (/^<[a-zA-Z_][\w-]*[\s>]/i.test(trimmed)) return true; // incl. <teammate-message> wrappers
50
+ if (/^\[SUGGESTION MODE:/i.test(trimmed)) return true;
51
+ if (/^Your response was cut off because it exceeded the output token limit/i.test(trimmed)) return true;
52
+ if (/^Base directory for this skill:/i.test(trimmed)) return true;
53
+ for (const re of SYNTHETIC_PROMPT_RES) { if (re.test(trimmed)) return true; }
54
+ // Unwrapped cross-session teammate notifications: prefix line / newer caveat
55
+ // / bare protocol JSON — keep them out of previews.
56
+ if (/^Another Claude session sent a message:/i.test(trimmed)) return true;
57
+ if (/^This came from another Claude session\b/i.test(trimmed)) return true;
58
+ if (trimmed.startsWith('{') && INTER_SESSION_TYPES_RE.test(trimmed)) return true;
59
+ return false;
60
+ }
61
+
62
+ // Single-pass strip of top-level protocol-notification JSON objects (brace
63
+ // pairing handles nesting; braces inside string literals / escapes are
64
+ // skipped). Same semantics as contentFilter's scanTopLevelJsonObjects +
65
+ // extractProtocolNotifications — a flat `\{[^{}]*\}` regex cannot cross nested
66
+ // braces, so protocol bodies with nested fields (plan_approval_*) would leak.
67
+ function stripProtocolJson(s) {
68
+ if (typeof s !== 'string' || s.indexOf('{') === -1) return s;
69
+ let out = '', cursor = 0, depth = 0, start = -1, inStr = false, esc = false;
70
+ for (let i = 0; i < s.length; i++) {
71
+ const ch = s[i];
72
+ if (inStr) {
73
+ if (esc) esc = false;
74
+ else if (ch === '\\') esc = true;
75
+ else if (ch === '"') inStr = false;
76
+ continue;
77
+ }
78
+ if (ch === '"') inStr = true;
79
+ else if (ch === '{') { if (depth === 0) start = i; depth++; }
80
+ else if (ch === '}' && depth > 0) {
81
+ depth--;
82
+ if (depth === 0 && start >= 0) {
83
+ let j; try { j = JSON.parse(s.slice(start, i + 1)); } catch { j = null; }
84
+ if (j && typeof j.type === 'string' && INTER_SESSION_TYPES_SET.has(j.type)) {
85
+ out += s.slice(cursor, start);
86
+ cursor = i + 1;
87
+ }
88
+ start = -1;
89
+ }
90
+ }
91
+ }
92
+ out += s.slice(cursor);
93
+ return out;
94
+ }
95
+
96
+ /**
97
+ * Strip system-injected tags, keeping the user text outside them (only for
98
+ * string-typed content where system chrome and user text can be mixed).
99
+ */
100
+ export function stripSystemTags(text) {
101
+ let out = text
102
+ .replace(/<(system-reminder|local-command-caveat|project-reminder|important-instruction-reminders|file-modified-reminder|todo-reminder|user-prompt-submit-hook|local-command-stdout|command-name|task-notification|environment_details|context|teammate-message)\b[^>]*>[\s\S]*?<\/\1>/gi, '')
103
+ // Unwrapped cross-session notification chrome: prefix line + both caveat
104
+ // generations + bare protocol JSON (incl. nested) — strip, keep any user
105
+ // text mixed into the body.
106
+ .replace(/^Another Claude session sent a message:\s*/i, '')
107
+ .replace(/(^|\n)This came from another Claude session[\s\S]*?(?=\n\n|$)/i, '')
108
+ .replace(/(^|\n)IMPORTANT: This is NOT from your user[\s\S]*?(?=\n\n|$)/i, '');
109
+ out = stripProtocolJson(out);
110
+ return out.trim();
111
+ }
112
+
113
+ /**
114
+ * Extract the list of user prompt texts from a wire `messages` array.
115
+ * (Kept in sync with src/utils/contentFilter.js:classifyUserContent.)
116
+ */
117
+ export function extractUserTexts(messages) {
118
+ const texts = [];
119
+ for (const msg of messages) {
120
+ if (msg.role !== 'user') continue;
121
+ if (typeof msg.content === 'string') {
122
+ // string content can mix system tags with user text — strip tags first
123
+ const text = stripSystemTags(msg.content).trim();
124
+ if (text && !isSystemText(text)) {
125
+ if (/^Implement the following plan:/i.test(text)) continue;
126
+ texts.push(text);
127
+ }
128
+ } else if (Array.isArray(msg.content)) {
129
+ const hasCommand = msg.content.some(b => b.type === 'text' && /<command-message>/i.test(b.text || ''));
130
+ const userParts = [];
131
+ for (const b of msg.content) {
132
+ if (b.type !== 'text') continue;
133
+ const text = (b.text || '').trim();
134
+ if (!text || isSystemText(text)) continue;
135
+ if (hasCommand && /<command-message>/i.test(text)) continue;
136
+ if (/^Implement the following plan:/i.test(text)) continue;
137
+ userParts.push(text);
138
+ }
139
+ if (userParts.length > 0) {
140
+ texts.push(userParts.join(' '));
141
+ }
142
+ }
143
+ }
144
+ return texts;
145
+ }
146
+
147
+ /** True when a request is a SUGGESTION MODE probe (next-input prediction). */
148
+ export function isSuggestionMode(messages) {
149
+ if (!Array.isArray(messages) || messages.length === 0) return false;
150
+ const last = messages[messages.length - 1];
151
+ if (last?.role !== 'user') return false;
152
+ const content = last.content;
153
+ if (Array.isArray(content)) {
154
+ return content.some(b => b.type === 'text' && /^\[SUGGESTION MODE:/i.test((b.text || '').trim()));
155
+ }
156
+ if (typeof content === 'string') return /^\[SUGGESTION MODE:/im.test(content.trim());
157
+ return false;
158
+ }
159
+
160
+ /** Preview normalization: one line, trimmed, capped at 100 chars (the stats
161
+ * precedent — a display truncation, not a prompt drop). */
162
+ export function flattenPromptText(text) {
163
+ return text.replace(/[\r\n]+/g, ' ').trim().slice(0, 100);
164
+ }
165
+
166
+ /** Numeric epoch-file sort: e2 before e10 (a lexical sort mis-orders them). */
167
+ export function sortEpochFiles(names) {
168
+ return names
169
+ .filter(f => /^e\d+\.jsonl$/.test(f))
170
+ .sort((a, b) => Number(a.match(/\d+/)[0]) - Number(b.match(/\d+/)[0]));
171
+ }
172
+
173
+ /**
174
+ * Fold conversation EVENT LINES into a deduped, display-ready prompt list.
175
+ * The single implementation shared by the stats preview, the read-side
176
+ * derivation and the backfill tooling:
177
+ * - snapshot/append lines carry `msgs:[…]`;
178
+ * - ctl replace-tail lines carry the swapped tail as `.msg` — a suggestion
179
+ * probe replaced by the REAL next prompt arrives exactly this way, so
180
+ * dropping ctl lines would lose those prompts;
181
+ * - skips whole SUGGESTION MODE events;
182
+ * - normalizes via flattenPromptText and dedupes via `seen`.
183
+ * @param {Iterable<object>} events parsed conversation event lines
184
+ * @param {{seen?: Set<string>, out?: string[]}} [acc] accumulator (reusable across epochs)
185
+ * @returns {string[]} the accumulated prompt list (=== acc.out when given)
186
+ */
187
+ export function collectPromptsFromEvents(events, acc = {}) {
188
+ const seen = acc.seen || (acc.seen = new Set());
189
+ const out = acc.out || (acc.out = []);
190
+ for (const ev of events) {
191
+ if (!ev) continue;
192
+ let msgs = null;
193
+ if (Array.isArray(ev.msgs) && ev.msgs.length > 0) msgs = ev.msgs;
194
+ else if (ev.t === 'ctl' && ev.op === 'replace-tail' && ev.msg) msgs = [ev.msg];
195
+ if (!msgs) continue;
196
+ if (isSuggestionMode(msgs)) continue;
197
+ for (const text of extractUserTexts(msgs)) {
198
+ const flat = flattenPromptText(text);
199
+ if (flat && !seen.has(flat)) {
200
+ seen.add(flat);
201
+ out.push(flat);
202
+ }
203
+ }
204
+ }
205
+ return out;
206
+ }
207
+
208
+ /**
209
+ * Bounded head-read of a session's `prompts.jsonl`: parse at most `maxBytes`
210
+ * from the file head, one `{seq, texts:[…]}` object per line, dropping the
211
+ * (possibly truncated) tail beyond the budget. `readJsonlTolerant` reads whole
212
+ * files and must NOT be used here — prompts.jsonl of a resume-heavy long
213
+ * session is the one v2 file a list render touches per session, so the read
214
+ * must stay O(budget).
215
+ * @returns {string[]} deduped, display-ready prompts (empty when absent/unreadable)
216
+ */
217
+ export function readPromptsHead(path, maxBytes = 256 * 1024) {
218
+ let fd;
219
+ let head = '';
220
+ try {
221
+ fd = openSync(path, 'r');
222
+ const buf = Buffer.alloc(maxBytes);
223
+ const n = readSync(fd, buf, 0, maxBytes, 0);
224
+ head = buf.toString('utf-8', 0, n);
225
+ } catch {
226
+ return [];
227
+ } finally {
228
+ if (fd !== undefined) { try { closeSync(fd); } catch { /* already closed */ } }
229
+ }
230
+ const seen = new Set();
231
+ const out = [];
232
+ const lines = head.split('\n');
233
+ // A read that filled the whole budget may end mid-line — drop that tail.
234
+ if (head.length >= maxBytes) lines.pop();
235
+ for (const line of lines) {
236
+ if (!line.trim()) continue;
237
+ let obj;
238
+ try { obj = JSON.parse(line); } catch { continue; } // torn/corrupt line — skip
239
+ if (!obj || !Array.isArray(obj.texts)) continue;
240
+ for (const t of obj.texts) {
241
+ if (typeof t !== 'string') continue;
242
+ const flat = flattenPromptText(t);
243
+ if (flat && !seen.has(flat)) {
244
+ seen.add(flat);
245
+ out.push(flat);
246
+ }
247
+ }
248
+ }
249
+ return out;
250
+ }