dsh-config-manager 0.1.78 → 0.1.80

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 (302) hide show
  1. package/lib/adapters/credentials.d.ts +6 -2
  2. package/lib/adapters/credentials.js +19 -3
  3. package/lib/adapters/file-collection.js +2 -1
  4. package/lib/adapters/mcp.d.ts +48 -2
  5. package/lib/adapters/mcp.js +265 -3
  6. package/lib/adapters/messages.d.ts +3 -0
  7. package/lib/adapters/messages.js +7 -0
  8. package/lib/adapters/plugin-files.js +2 -1
  9. package/lib/adapters/plugins.js +105 -20
  10. package/lib/adapters/pnpm-workspace.d.ts +32 -0
  11. package/lib/adapters/pnpm-workspace.js +113 -2
  12. package/lib/adapters/prompts.d.ts +6 -2
  13. package/lib/adapters/prompts.js +17 -2
  14. package/lib/adapters/providers.d.ts +19 -2
  15. package/lib/adapters/providers.js +59 -3
  16. package/lib/adapters/settings.d.ts +6 -2
  17. package/lib/adapters/settings.js +13 -2
  18. package/lib/adapters/skills.d.ts +25 -1
  19. package/lib/adapters/skills.js +60 -0
  20. package/lib/adapters/ui.d.ts +6 -2
  21. package/lib/adapters/ui.js +13 -2
  22. package/lib/adapters/units.d.ts +18 -0
  23. package/lib/adapters/units.js +39 -0
  24. package/lib/adapters/workspaces.js +2 -1
  25. package/lib/client.d.ts +401 -112
  26. package/lib/client.js +2899 -1525
  27. package/lib/core/backup.js +20 -0
  28. package/lib/core/mcp-connector-store.d.ts +159 -0
  29. package/lib/core/mcp-connector-store.js +445 -0
  30. package/lib/core/messages-core.d.ts +2 -0
  31. package/lib/core/messages-core.js +4 -0
  32. package/lib/core/messages.d.ts +20 -0
  33. package/lib/core/messages.js +44 -0
  34. package/lib/core/phase3-host.js +18 -1
  35. package/lib/core/plugin-cli.d.ts +22 -2
  36. package/lib/core/plugin-cli.js +22 -3
  37. package/lib/core/recovery-orchestrator.d.ts +3 -1
  38. package/lib/core/recovery-orchestrator.js +75 -16
  39. package/lib/core/rollback-activity.d.ts +89 -0
  40. package/lib/core/rollback-activity.js +350 -0
  41. package/lib/core/rollback-ledger.d.ts +0 -9
  42. package/lib/core/rollback-ledger.js +37 -0
  43. package/lib/core/rollback-merge.d.ts +22 -2
  44. package/lib/core/rollback-merge.js +63 -13
  45. package/lib/core/rollback-point.d.ts +93 -0
  46. package/lib/core/rollback-point.js +420 -101
  47. package/lib/core/rollback-undo-journal.d.ts +123 -0
  48. package/lib/core/rollback-undo-journal.js +517 -0
  49. package/lib/core/self-update.js +2 -4
  50. package/lib/core/text-fidelity.d.ts +89 -0
  51. package/lib/core/text-fidelity.js +176 -0
  52. package/lib/core/types.d.ts +12 -3
  53. package/lib/core/update-check.js +2 -4
  54. package/lib/core/validator.d.ts +9 -0
  55. package/lib/core/validator.js +12 -0
  56. package/lib/foreign/bundle.js +42 -3
  57. package/lib/foreign/claude-code.js +358 -26
  58. package/lib/foreign/claude-sessions.d.ts +51 -8
  59. package/lib/foreign/claude-sessions.js +111 -13
  60. package/lib/foreign/read-claude-code.d.ts +33 -4
  61. package/lib/foreign/read-claude-code.js +446 -65
  62. package/lib/foreign/read-claude-plugins.d.ts +36 -0
  63. package/lib/foreign/read-claude-plugins.js +403 -0
  64. package/lib/foreign/registry.js +25 -2
  65. package/lib/foreign/truth-table.js +6 -4
  66. package/lib/foreign/types.d.ts +170 -2
  67. package/lib/index.d.ts +14 -5
  68. package/lib/index.js +60 -16
  69. package/lib/market/git-file-writer.js +7 -1
  70. package/lib/profiles/dsh-profile-launcher.d.ts +20 -0
  71. package/lib/profiles/dsh-profile-launcher.js +58 -1
  72. package/lib/profiles/dsh-profile-shared.d.ts +8 -2
  73. package/lib/profiles/dsh-profile-shared.js +8 -2
  74. package/lib/routes/foreign.js +8 -1
  75. package/lib/routes/index.d.ts +1 -1
  76. package/lib/routes/kit.d.ts +2 -2
  77. package/lib/routes/sync.js +173 -9
  78. package/lib/schema/manifest.d.ts +6 -1
  79. package/lib/schema/manifest.js +55 -0
  80. package/lib/schema/types.d.ts +47 -2
  81. package/lib/sync/ancestor.d.ts +22 -0
  82. package/lib/sync/ancestor.js +66 -2
  83. package/lib/sync/autosync-config.d.ts +73 -0
  84. package/lib/sync/autosync-config.js +89 -0
  85. package/lib/sync/autosync-scheduler.d.ts +80 -6
  86. package/lib/sync/autosync-scheduler.js +263 -40
  87. package/lib/sync/gist/gist-transport.d.ts +18 -1
  88. package/lib/sync/gist/gist-transport.js +42 -3
  89. package/lib/sync/gist/messages.d.ts +2 -0
  90. package/lib/sync/gist/messages.js +5 -0
  91. package/lib/sync/git/git-transport.d.ts +42 -3
  92. package/lib/sync/git/git-transport.js +146 -3
  93. package/lib/sync/merge.d.ts +47 -14
  94. package/lib/sync/merge.js +242 -222
  95. package/lib/sync/messages.d.ts +8 -0
  96. package/lib/sync/messages.js +19 -0
  97. package/lib/sync/risk.d.ts +19 -2
  98. package/lib/sync/risk.js +10 -3
  99. package/lib/sync/s3/messages.d.ts +6 -0
  100. package/lib/sync/s3/messages.js +13 -0
  101. package/lib/sync/s3/s3-transport.d.ts +9 -1
  102. package/lib/sync/s3/s3-transport.js +78 -6
  103. package/lib/sync/s3/sigv4.js +11 -2
  104. package/lib/sync/sync-engine.d.ts +212 -8
  105. package/lib/sync/sync-engine.js +514 -58
  106. package/lib/sync/sync-history.d.ts +11 -0
  107. package/lib/sync/sync-history.js +20 -0
  108. package/lib/sync/sync-state.d.ts +43 -0
  109. package/lib/sync/sync-state.js +44 -0
  110. package/lib/sync/sync-watcher.d.ts +76 -0
  111. package/lib/sync/sync-watcher.js +202 -0
  112. package/lib/sync/text-merge.d.ts +85 -0
  113. package/lib/sync/text-merge.js +513 -0
  114. package/lib/sync/transport.d.ts +48 -0
  115. package/lib/sync/transport.js +12 -0
  116. package/lib/sync/watch-scope.d.ts +38 -0
  117. package/lib/sync/watch-scope.js +95 -0
  118. package/lib/sync/webdav/webdav-transport.d.ts +16 -2
  119. package/lib/sync/webdav/webdav-transport.js +128 -11
  120. package/lib/ui/foreign-view.js +10 -0
  121. package/lib/ui/i18n.d.ts +9 -0
  122. package/lib/ui/i18n.js +22 -0
  123. package/lib/ui/rollback-view.d.ts +12 -0
  124. package/lib/ui/rollback-view.js +2 -0
  125. package/lib/ui/sync-settings-view.d.ts +47 -0
  126. package/lib/ui/sync-settings-view.js +24 -0
  127. package/lib/ui/types.d.ts +21 -0
  128. package/lib/utils/env-lock.d.ts +1 -0
  129. package/lib/utils/env-lock.js +7 -4
  130. package/package.json +6 -6
  131. package/src/adapters/credentials.ts +22 -4
  132. package/src/adapters/file-collection.ts +2 -1
  133. package/src/adapters/mcp.test.ts +235 -1
  134. package/src/adapters/mcp.ts +276 -5
  135. package/src/adapters/messages.ts +7 -0
  136. package/src/adapters/plugin-files.ts +2 -1
  137. package/src/adapters/plugins.test.ts +176 -2
  138. package/src/adapters/plugins.ts +107 -21
  139. package/src/adapters/pnpm-workspace.test.ts +89 -1
  140. package/src/adapters/pnpm-workspace.ts +127 -2
  141. package/src/adapters/prompts.ts +20 -3
  142. package/src/adapters/providers.ts +62 -4
  143. package/src/adapters/settings.ts +16 -3
  144. package/src/adapters/skills.test.ts +141 -0
  145. package/src/adapters/skills.ts +58 -2
  146. package/src/adapters/ui.ts +16 -3
  147. package/src/adapters/units-entries.test.ts +190 -0
  148. package/src/adapters/units.ts +50 -0
  149. package/src/adapters/workspaces.ts +2 -1
  150. package/src/client/about/PluginUpdateDialog.tsx +4 -2
  151. package/src/client/about/ReleaseNotesDialog.tsx +4 -2
  152. package/src/client/chat/RollbackReviewDialog.tsx +3 -2
  153. package/src/client/common/Skeleton.tsx +32 -0
  154. package/src/client/common/api-base.test.ts +36 -0
  155. package/src/client/common/http.test.ts +12 -1
  156. package/src/client/common/http.ts +8 -2
  157. package/src/client/common/routes.ts +46 -3
  158. package/src/client/config-manager.module.css +321 -11
  159. package/src/client/home/HomePanel.tsx +14 -6
  160. package/src/client/import/ForeignImportView.tsx +3 -1
  161. package/src/client/import/import-wizard-steps.tsx +2 -1
  162. package/src/client/library/ArtifactRow.tsx +12 -2
  163. package/src/client/library/LibraryActions.tsx +6 -3
  164. package/src/client/library/LibraryPanel.tsx +21 -3
  165. package/src/client/library/RestorePlanView.tsx +4 -2
  166. package/src/client/locales.ts +3 -0
  167. package/src/client/market/market-api.test.ts +3 -2
  168. package/src/client/recovery/RecoveryPanel.tsx +54 -7
  169. package/src/client/recovery/RollbackCard.tsx +7 -2
  170. package/src/client/recovery/RollbackDiffPane.tsx +3 -2
  171. package/src/client/recovery/recovery-locales.ts +20 -2
  172. package/src/client/recovery/recovery-view.test.ts +39 -1
  173. package/src/client/recovery/recovery-view.ts +23 -1
  174. package/src/client/run-store.test.ts +1 -1
  175. package/src/client/run-store.ts +4 -0
  176. package/src/client/sync/AutosyncCard.tsx +49 -6
  177. package/src/client/sync/MergeModal.tsx +220 -0
  178. package/src/client/sync/MergePreviewView.tsx +306 -0
  179. package/src/client/sync/SyncActivityBar.tsx +31 -0
  180. package/src/client/sync/SyncConfirmView.tsx +2 -1
  181. package/src/client/sync/SyncPanel.tsx +130 -87
  182. package/src/client/sync/SyncProbeRow.tsx +162 -0
  183. package/src/client/sync/SyncSettingsView.test.ts +27 -2
  184. package/src/client/sync/WorkDirCard.tsx +44 -6
  185. package/src/client/sync/history-model.ts +4 -1
  186. package/src/client/sync/merge-view.test.ts +248 -0
  187. package/src/client/sync/merge-view.ts +398 -0
  188. package/src/client/sync/probe-view.test.ts +90 -0
  189. package/src/client/sync/probe-view.ts +63 -0
  190. package/src/client/sync/remote-device-view.test.ts +35 -0
  191. package/src/client/sync/remote-device-view.ts +25 -0
  192. package/src/client/sync/sync-api.test.ts +5 -3
  193. package/src/client/sync/sync-api.ts +212 -2
  194. package/src/client/sync/sync-entry.test.ts +78 -0
  195. package/src/client/sync/sync-entry.ts +53 -0
  196. package/src/client/sync/sync-locales.ts +234 -2
  197. package/src/client/sync/sync-view.test.ts +18 -1
  198. package/src/client/sync/sync-view.ts +20 -0
  199. package/src/core/backup.test.ts +41 -0
  200. package/src/core/backup.ts +20 -0
  201. package/src/core/mcp-connector-store.test.ts +265 -0
  202. package/src/core/mcp-connector-store.ts +516 -0
  203. package/src/core/messages-core.ts +4 -0
  204. package/src/core/messages.ts +46 -0
  205. package/src/core/phase3-host.ts +18 -1
  206. package/src/core/phase3-production-integration.test.ts +4 -0
  207. package/src/core/plugin-cli.test.ts +26 -0
  208. package/src/core/plugin-cli.ts +30 -4
  209. package/src/core/recovery-orchestrator.test.ts +104 -5
  210. package/src/core/recovery-orchestrator.ts +74 -17
  211. package/src/core/rollback-activity.test.ts +311 -0
  212. package/src/core/rollback-activity.ts +367 -0
  213. package/src/core/rollback-cross-session.test.ts +694 -0
  214. package/src/core/rollback-interrupted.test.ts +137 -0
  215. package/src/core/rollback-ledger.ts +32 -0
  216. package/src/core/rollback-merge.test.ts +130 -0
  217. package/src/core/rollback-merge.ts +78 -14
  218. package/src/core/rollback-point.ts +468 -91
  219. package/src/core/rollback-undo-journal.test.ts +508 -0
  220. package/src/core/rollback-undo-journal.ts +571 -0
  221. package/src/core/self-update.ts +2 -5
  222. package/src/core/text-fidelity.test.ts +189 -0
  223. package/src/core/text-fidelity.ts +197 -0
  224. package/src/core/types.ts +11 -3
  225. package/src/core/update-check.ts +2 -5
  226. package/src/core/validator.ts +14 -0
  227. package/src/foreign/bundle-origin.test.ts +265 -0
  228. package/src/foreign/bundle.ts +45 -5
  229. package/src/foreign/claude-code.test.ts +268 -0
  230. package/src/foreign/claude-code.ts +406 -25
  231. package/src/foreign/claude-plugins.test.ts +234 -0
  232. package/src/foreign/claude-sessions.test.ts +141 -0
  233. package/src/foreign/claude-sessions.ts +179 -23
  234. package/src/foreign/file-budget.test.ts +4 -1
  235. package/src/foreign/read-claude-code.test.ts +320 -0
  236. package/src/foreign/read-claude-code.ts +490 -71
  237. package/src/foreign/read-claude-plugins.ts +465 -0
  238. package/src/foreign/registry.ts +25 -2
  239. package/src/foreign/session-ir.test.ts +65 -0
  240. package/src/foreign/truth-table.test.ts +81 -0
  241. package/src/foreign/truth-table.ts +6 -3
  242. package/src/foreign/types.ts +162 -2
  243. package/src/index.facade.test.ts +26 -4
  244. package/src/index.ts +76 -19
  245. package/src/market/git-file-writer.test.ts +4 -0
  246. package/src/market/git-file-writer.ts +7 -1
  247. package/src/profiles/dsh-profile-launcher.test.ts +68 -3
  248. package/src/profiles/dsh-profile-launcher.ts +56 -1
  249. package/src/profiles/dsh-profile-shared.ts +8 -2
  250. package/src/routes/foreign.ts +8 -1
  251. package/src/routes/index.ts +1 -1
  252. package/src/routes/kit.ts +2 -2
  253. package/src/routes/sync.ts +176 -9
  254. package/src/schema/manifest-schema.test.ts +87 -2
  255. package/src/schema/manifest.ts +57 -1
  256. package/src/schema/types.ts +45 -2
  257. package/src/sync/ancestor.ts +76 -2
  258. package/src/sync/autosync-config.test.ts +73 -1
  259. package/src/sync/autosync-config.ts +124 -0
  260. package/src/sync/autosync-risk-tier.test.ts +38 -2
  261. package/src/sync/autosync-scheduler.test.ts +264 -12
  262. package/src/sync/autosync-scheduler.ts +296 -44
  263. package/src/sync/gist/gist-conditional-write.test.ts +203 -0
  264. package/src/sync/gist/gist-transport.ts +53 -5
  265. package/src/sync/gist/messages.ts +5 -0
  266. package/src/sync/git/git-probe.test.ts +80 -0
  267. package/src/sync/git/git-transport.test.ts +185 -0
  268. package/src/sync/git/git-transport.ts +178 -5
  269. package/src/sync/merge.test.ts +188 -55
  270. package/src/sync/merge.ts +282 -224
  271. package/src/sync/messages.ts +19 -0
  272. package/src/sync/risk.ts +29 -5
  273. package/src/sync/s3/messages.ts +13 -0
  274. package/src/sync/s3/s3-conditional-write.test.ts +330 -0
  275. package/src/sync/s3/s3-transport.test.ts +2 -1
  276. package/src/sync/s3/s3-transport.ts +87 -9
  277. package/src/sync/s3/sigv4.test.ts +4 -1
  278. package/src/sync/s3/sigv4.ts +11 -2
  279. package/src/sync/sync-device-identity.test.ts +267 -0
  280. package/src/sync/sync-engine.test.ts +410 -19
  281. package/src/sync/sync-engine.ts +602 -57
  282. package/src/sync/sync-history.test.ts +16 -1
  283. package/src/sync/sync-history.ts +26 -0
  284. package/src/sync/sync-state.test.ts +64 -0
  285. package/src/sync/sync-state.ts +79 -0
  286. package/src/sync/sync-watcher.test.ts +197 -0
  287. package/src/sync/sync-watcher.ts +246 -0
  288. package/src/sync/text-merge.test.ts +358 -0
  289. package/src/sync/text-merge.ts +554 -0
  290. package/src/sync/transport.ts +57 -0
  291. package/src/sync/watch-scope.test.ts +122 -0
  292. package/src/sync/watch-scope.ts +120 -0
  293. package/src/sync/webdav/webdav-conditional-write.test.ts +360 -0
  294. package/src/sync/webdav/webdav-transport.ts +137 -14
  295. package/src/ui/foreign-view.test.ts +34 -0
  296. package/src/ui/foreign-view.ts +10 -0
  297. package/src/ui/i18n.ts +22 -0
  298. package/src/ui/rollback-view.ts +14 -0
  299. package/src/ui/sync-settings-view.test.ts +28 -1
  300. package/src/ui/sync-settings-view.ts +43 -0
  301. package/src/ui/types.ts +22 -0
  302. package/src/utils/env-lock.ts +8 -5
@@ -21,12 +21,38 @@ const CLAUDE_MAX_TOKENS = 8192;
21
21
  function usageField(usage, key) {
22
22
  return typeof usage === 'object' && usage !== null ? usage[key] : undefined;
23
23
  }
24
+ /**
25
+ * 推理块的类型名 = Claude Code **自己**写的两种:`thinking` 与 `redacted_thinking`。
26
+ *
27
+ * 为什么必须按类型名判定:`thinking` 块的正文在 **`thinking` 键**里(没有 `text` 字段),旧实现
28
+ * 沿「对象带 text 才算文本」的兜底把它变成一个**空文本块** —— 内容静默丢失,且那个空块还会多插入
29
+ * 一个换行。本仓其余 20+ 来源一律「推理不伪装成正文、只计数」(read-crush / read-opencode /
30
+ * read-qwen / read-pi / read-openclaw …),这里对齐同一口径;共享 IR 无 reasoning 承载位这件事
31
+ * 记在 docs/spec/known-gaps.md 的 F-01。
32
+ *
33
+ * **为什么不把 `{type:'reasoning', text:'…'}` 一并纳入**(如实记明,别当成漏掉):
34
+ * ① Claude Code 不写这种块(它是别的导出器的归一命名,各来源自己的解析器已各自计报);
35
+ * ② 共享 IR 的文本兜底分支(**对象带 `text` 就当文本块**)是**被逐字节回归钉住的既有行为**
36
+ * (见 `blocksOf` 的注释),而这个块恰好命中它 —— 一并丢掉会让
37
+ * `claude-sessions-bytes.test.ts` 的 `timestamps-numeric-and-unparsable` 一案从 728 字节
38
+ * 变成 722 字节(正文少一段 'r')。那是**用户可见**的正文变化,按本轮的硬约束(既有语料
39
+ * 逐字一致)不由「修推理块静默丢弃」顺手引入。
40
+ * ③ 要一并计报,改这一个数组即可,并同步更新该案基线(现行行为已被
41
+ * `claude-sessions.test.ts` 的「reasoning 形状」用例显式钉住)。
42
+ */
43
+ const REASONING_BLOCK_TYPES = ['thinking', 'redacted_thinking'];
44
+ /** 推理块的占位(字段一律空:它不进 IR,只需被识别出来计数) */
45
+ function reasoningBlock() {
46
+ return { kind: 'thinking', text: '', id: '', name: '', input: undefined, isError: false };
47
+ }
24
48
  function blockOf(raw) {
25
49
  const rec = typeof raw === 'object' && raw !== null && !Array.isArray(raw) ? raw : undefined;
26
50
  if (rec === undefined) {
27
51
  return { kind: 'other', text: '', id: '', name: '', input: undefined, isError: false };
28
52
  }
29
53
  const rawType = irStr(rec['type']) ?? '';
54
+ if (REASONING_BLOCK_TYPES.includes(rawType))
55
+ return reasoningBlock();
30
56
  const id = irStr(rec['id']) ?? '';
31
57
  const name = irStr(rec['name']);
32
58
  const isText = rawType === 'text' || typeof rec['text'] === 'string';
@@ -126,6 +152,33 @@ export function parseClaudeSession(file, options, stats) {
126
152
  function typeOf(rec) {
127
153
  return irStr(rec['type']) ?? 'unknown';
128
154
  }
155
+ /**
156
+ * 墙钟回落的**唯一**来源(缺陷 1 的修复)。
157
+ *
158
+ * 为什么必须只有一处:会话产物的字节里带 createdAt / 各行各列的时间;只要有一次 `Date.now()`
159
+ * 参与,同一来源两次转码的字节就必然不同 ⇒ DSH 侧「同内容 → Skip」永不成立、重复导入恒判
160
+ * Conflict。旧实现 parse 与 synthesize **各调一次** `Date.now()`(返回值还可能是两个不同的毫秒)。
161
+ *
162
+ * 回落链(前三级都是确定性的:同一输入 → 同一产物):
163
+ * ① `options.now`(调用方/测试显式给定;**保持最高优先级**,既有字节回归靠它稳定);
164
+ * ② 记录自身的最早可解析时间戳(会话文件里最权威的确定性来源);
165
+ * ③ `input.createdAtMs`(读盘层给的回落值:最早可解析记录时间,没有就给文件 mtime);
166
+ * ④ 都没有 → `Date.now()`,并**如实记账**(`timestampsFallback`)。
167
+ *
168
+ * 非法值(非安全整数 / 非有限数)一律当「没给」:DSH 对时间字段要求安全整数,宁可继续回落也
169
+ * 不写出一条会被拒读的行。
170
+ */
171
+ function resolveWallClock(file, options, earliest) {
172
+ const explicit = irSafeTime(options.now);
173
+ if (explicit !== undefined)
174
+ return { now: explicit, fallback: false };
175
+ if (earliest !== undefined)
176
+ return { now: earliest, fallback: false };
177
+ const given = irSafeTime(file.createdAtMs);
178
+ if (given !== undefined)
179
+ return { now: given, fallback: false };
180
+ return { now: Date.now(), fallback: true };
181
+ }
129
182
  /**
130
183
  * 解析入口:**只走一遍 JSON.parse**,把「记录 / cwd / 最早时间 / IR 消息 / 标题」一次算齐。
131
184
  *
@@ -137,37 +190,51 @@ function parseClaudeSessionText(file, options) {
137
190
  const records = parseJsonlObjects(file.text, stats);
138
191
  let cwd;
139
192
  let createdAt;
193
+ let sidechain = 0;
140
194
  for (const r of records) {
195
+ // 子代理记录(真实 Claude 的子代理对话就是 type=user/assistant + isSidechain:true):
196
+ // 它们**不是父对话的回合**,混进来会静默改写父对话正文(设计文档 2026-10-04-foreign-import-v1.md:89
197
+ // 「不迁移 isSidechain 的子代理会话」,而旧实现连计数都没有)。
198
+ // 它们仍参与 cwd / 最早时间:子代理与父对话同属一份日志文件,cwd 与时间线是**文件级**事实。
199
+ if (r['isSidechain'] === true)
200
+ sidechain++;
141
201
  if (cwd === undefined)
142
202
  cwd = irStr(r['cwd']);
143
203
  createdAt = irEarlier(createdAt, irSafeTime(r['timestamp']));
144
204
  }
205
+ // 墙钟回落**只在这里解析一次**,转码入口复用 `now`(同一输入一次调用内不可能出现两个 Date.now())
206
+ const wall = resolveWallClock(file, options, createdAt);
145
207
  const messages = [];
146
208
  let messageIndex = 0;
209
+ let thinking = 0;
147
210
  let firstUserText = '';
148
211
  for (const rec of records) {
149
212
  const type = typeOf(rec);
150
213
  if (type !== 'user' && type !== 'assistant')
151
214
  continue;
215
+ if (rec['isSidechain'] === true)
216
+ continue;
152
217
  const blocks = blocksOf(rec['message']);
153
- const message = irMessageOf(rec, blocks, messageIndex);
218
+ // 推理块:先逐条计数,再**整块丢掉**(既不进 IR,也不留空文本块 —— 空块还会污染正文的换行串联)
219
+ thinking += blocks.filter((b) => b.kind === 'thinking').length;
220
+ const carried = blocks.filter((b) => b.kind !== 'thinking');
221
+ const message = irMessageOf(rec, carried, messageIndex);
154
222
  if (message === undefined)
155
223
  continue;
156
224
  messages.push(message);
157
225
  // 消息 id 的兜底序 = 「第几条已知消息」(重构前的序号语义,逐字节回归钉住)
158
226
  messageIndex++;
159
227
  if (type === 'user' && firstUserText === '') {
160
- const text = irTextOfBlocks(blocks.map(irBlockOf));
228
+ const text = irTextOfBlocks(carried.map(irBlockOf));
161
229
  if (text.trim() !== '')
162
230
  firstUserText = text;
163
231
  }
164
232
  }
165
- const now = options.now ?? Date.now();
166
233
  return {
167
234
  session: {
168
235
  id: file.id,
169
236
  cwd: cwd ?? '',
170
- createdAt: createdAt ?? now,
237
+ createdAt: createdAt ?? wall.now,
171
238
  records: records.length,
172
239
  messages,
173
240
  },
@@ -175,6 +242,10 @@ function parseClaudeSessionText(file, options) {
175
242
  cwd,
176
243
  unparsable: stats.unparsable,
177
244
  title: titleOf(records, firstUserText),
245
+ sidechain,
246
+ thinking,
247
+ now: wall.now,
248
+ wallClockFallback: wall.fallback,
178
249
  };
179
250
  }
180
251
  /* ---------------- ② ③ 合成 + 编码 ---------------- */
@@ -194,22 +265,33 @@ export function dshSessionLogName(version) {
194
265
  return version === 0 ? 'session.jsonl.zstd' : 'session.v' + version + '.jsonl.zstd';
195
266
  }
196
267
  export function transcodeClaudeSession(file, options) {
268
+ /**
269
+ * 非致命发现(会话照样产出):记录里的 sessionId 与文件名 stem 不一致时**如实报码**,
270
+ * 但会话 id 仍以文件名 stem 为准(DSH 按「目录名 / 文件名」定位会话)。读盘层没给
271
+ * `recordSessionId`(老调用方)就什么都不报 —— 不猜。
272
+ */
273
+ const findings = [];
274
+ const recordSessionId = irStr(file.recordSessionId);
275
+ if (recordSessionId !== undefined && recordSessionId !== file.id) {
276
+ findings.push({ code: 'session-id-mismatch', origin: file.id });
277
+ }
197
278
  if (!SUPPORTED_DSH_SESSION_FORMAT_VERSIONS.includes(options.formatVersion)) {
198
- return { skip: { code: 'session-format-unsupported', detail: String(options.formatVersion) } };
279
+ return { skip: { code: 'session-format-unsupported', detail: String(options.formatVersion) }, findings };
199
280
  }
200
281
  if (!isSafeIrId(file.id))
201
- return { skip: { code: 'session-unsafe-id', detail: file.id } };
282
+ return { skip: { code: 'session-unsafe-id', detail: file.id }, findings };
202
283
  const parsed = parseClaudeSessionText(file, options);
203
284
  if (parsed.records.length === 0)
204
- return { skip: { code: 'session-empty' } };
285
+ return { skip: { code: 'session-empty' }, findings };
205
286
  if (parsed.cwd === undefined)
206
- return { skip: { code: 'session-missing-cwd', detail: file.id } };
287
+ return { skip: { code: 'session-missing-cwd', detail: file.id }, findings };
207
288
  // 有内容但一行都解析不出来:如实区分「空文件」与「解析不了」(诊断更准,不新增码 —— 两者
208
289
  // 在下游都是「这条会话没有产出」,区别只在忽略计数里可见)
209
290
  if (parsed.session.messages.length === 0 && parsed.unparsable > 0) {
210
- return { skip: { code: 'session-unparsable', detail: file.id } };
291
+ return { skip: { code: 'session-unparsable', detail: file.id }, findings };
211
292
  }
212
- const now = options.now ?? Date.now();
293
+ // 时间兜底**只有一处**(parseClaudeSessionText 里解析过一次):这里绝不再调 Date.now()
294
+ const now = parsed.now;
213
295
  const stats = {
214
296
  ignored: {},
215
297
  userMessages: 0,
@@ -219,9 +301,18 @@ export function transcodeClaudeSession(file, options) {
219
301
  };
220
302
  if (parsed.unparsable > 0)
221
303
  irBump(stats.ignored, 'unparsable', parsed.unparsable);
222
- // 未迁移的记录类型**逐类计数**(sidechain / attachment / queue-operation / system …)——
223
- // IR 只承载 user/assistant 消息,所以这里按原始记录把其余类型补进忽略计数
304
+ // 推理块:共享 IR 没有承载位 → 计报,绝不伪装成正文(与其余 20+ 来源同口径)
305
+ if (parsed.thinking > 0)
306
+ irBump(stats.ignored, 'thinking', parsed.thinking);
307
+ // 子代理记录:不进 IR → 整类计报(detail 固定为 isSidechain)
308
+ if (parsed.sidechain > 0)
309
+ irBump(stats.ignored, 'isSidechain', parsed.sidechain);
310
+ // 未迁移的记录类型**逐类计数**(attachment / queue-operation / system …)——
311
+ // IR 只承载 user/assistant 消息,所以这里按原始记录把其余类型补进忽略计数。
312
+ // isSidechain 记录已整类记账(上一步),不在这里重复计数。
224
313
  for (const rec of parsed.records) {
314
+ if (rec['isSidechain'] === true)
315
+ continue;
225
316
  const type = typeOf(rec);
226
317
  if (type === 'user' || type === 'assistant')
227
318
  continue;
@@ -236,7 +327,7 @@ export function transcodeClaudeSession(file, options) {
236
327
  maxTokens: CLAUDE_MAX_TOKENS,
237
328
  }, stats, now);
238
329
  if (rows.length === 0)
239
- return { skip: { code: 'session-empty' } };
330
+ return { skip: { code: 'session-empty' }, findings };
240
331
  const header = {
241
332
  type: 'session',
242
333
  version: options.formatVersion,
@@ -247,6 +338,11 @@ export function transcodeClaudeSession(file, options) {
247
338
  delegationDepth: 0,
248
339
  };
249
340
  const data = encodeDshSessionLog(header, rows);
341
+ // 结算类记账(与 ignored 分开放:ignored 的键会被下游翻成 unsupported-session-record 码)。
342
+ // 0 也写出来:调用方一眼能看出「这次转码是否确定性」,不需要靠键的有无去猜。
343
+ const counts = {
344
+ timestampsFallback: parsed.wallClockFallback ? 1 : 0,
345
+ };
250
346
  return {
251
347
  session: {
252
348
  id: file.id,
@@ -260,8 +356,10 @@ export function transcodeClaudeSession(file, options) {
260
356
  toolCalls: stats.toolCalls,
261
357
  toolResults: stats.toolResults,
262
358
  ignored: stats.ignored,
359
+ counts,
263
360
  },
264
361
  },
362
+ findings,
265
363
  };
266
364
  }
267
365
  /** 帧拼接:首帧 = header 单行 JSON,之后每批事件一帧(与 DSH 的物理编码一致,帧带内容校验和) */
@@ -1,24 +1,53 @@
1
- import type { ClaudeCodeInput, ForeignLimitOverrides, ForeignSkip } from './types.ts';
1
+ import type { ClaudeCodeInput, ClaudeSkillInput, ForeignLimitOverrides, ForeignSkip } from './types.ts';
2
2
  export interface ClaudeCodeReadOptions {
3
3
  /** 用户 home(含 .claude 目录 / .claude.json) */
4
4
  homeDir: string;
5
+ /** 进程环境快照(只读):只有 CLAUDE_CONFIG_DIR 参与判定,绝不读凭据类变量 */
6
+ env?: Readonly<Record<string, string | undefined>>;
7
+ /** 调用方**显式**给出的项目根(优先于 ~/.claude.json 的 projects 键;缺省 = 不猜任何项目) */
8
+ projectPaths?: readonly string[];
5
9
  /** 单文件读取上限(默认 8 MiB);超过即不读,并如实计入 unreadable */
6
10
  maxFileBytes?: number;
7
11
  /** 单个 skill 目录的文件数上限(默认 200) */
8
12
  maxSkillFiles?: number;
9
- /** 会话文件数上限(默认 500) */
13
+ /** 会话文件数上限(默认 500);触顶报 source-unreadable/max-sessions-reached */
10
14
  maxSessionFiles?: number;
11
15
  /** 单个会话文件的上限(默认 32 MiB);超限即不读并计入 unreadable,绝不截断 */
12
16
  maxSessionFileBytes?: number;
17
+ /** 项目扫描上限(默认 50);触顶报 source-unreadable/max-projects-reached */
18
+ maxProjects?: number;
19
+ /** 插件数上限(默认 50,透传给 read-claude-plugins) */
20
+ maxPlugins?: number;
21
+ /** 单插件文件数上限(默认 200,透传给 read-claude-plugins) */
22
+ maxPluginFiles?: number;
13
23
  /** 可选上限覆盖(t36,装配层透传;缺省 = 上面各默认值逐字不变) */
14
24
  limits?: ForeignLimitOverrides;
15
25
  }
16
26
  export interface ClaudeCodeReadResult {
17
- /** 是否找到 Claude Code 的痕迹(~/.claude 目录或 ~/.claude.json) */
27
+ /** 是否找到 Claude Code 的痕迹(配置目录或 ~/.claude.json) */
18
28
  found: boolean;
19
29
  input: ClaudeCodeInput;
20
30
  skipped: ForeignSkip[];
21
- /** 读不到 / 超限 / JSON 解析失败的**相对路径**(不含任何内容) */
31
+ /** 读不到 / 超限 / JSON 解析失败的**相对路径**(不含任何内容、不含机器绝对路径) */
22
32
  unreadable: string[];
23
33
  }
34
+ /** 技能目录读取的可选参数(用户级 / 项目级 / 插件级共用同一份口径) */
35
+ export interface SkillDirReadOptions {
36
+ /** 单个技能目录的文件数上限 */
37
+ maxFiles: number;
38
+ /** 单文件字节上限 */
39
+ maxBytes: number;
40
+ /** 位置标签前缀(**包内相对标签**,绝不含机器路径) */
41
+ origin: string;
42
+ /** 发现写这里(符号链接跳过 / 条数触顶 / 字节超限) */
43
+ skips: ForeignSkip[];
44
+ }
45
+ /**
46
+ * 读一个 **skills 目录**(只压平一层:`<skills>/<技能名>/**`)。
47
+ *
48
+ * 三条不静默(缺陷 2 修复):技能目录本身是符号链接 → source-unreadable + symlink-skipped;
49
+ * 条数触顶 → max-skill-files-reached;单文件超字节 → too-large。
50
+ * 结果按技能名排序(readdir 顺序在三个平台不同)。
51
+ */
52
+ export declare function readSkillUnits(dir: string, opts: SkillDirReadOptions): Promise<ClaudeSkillInput[]>;
24
53
  export declare function readClaudeCode(opts: ClaudeCodeReadOptions): Promise<ClaudeCodeReadResult>;