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.
- package/lib/adapters/credentials.d.ts +6 -2
- package/lib/adapters/credentials.js +19 -3
- package/lib/adapters/file-collection.js +2 -1
- package/lib/adapters/mcp.d.ts +48 -2
- package/lib/adapters/mcp.js +265 -3
- package/lib/adapters/messages.d.ts +3 -0
- package/lib/adapters/messages.js +7 -0
- package/lib/adapters/plugin-files.js +2 -1
- package/lib/adapters/plugins.js +105 -20
- package/lib/adapters/pnpm-workspace.d.ts +32 -0
- package/lib/adapters/pnpm-workspace.js +113 -2
- package/lib/adapters/prompts.d.ts +6 -2
- package/lib/adapters/prompts.js +17 -2
- package/lib/adapters/providers.d.ts +19 -2
- package/lib/adapters/providers.js +59 -3
- package/lib/adapters/settings.d.ts +6 -2
- package/lib/adapters/settings.js +13 -2
- package/lib/adapters/skills.d.ts +25 -1
- package/lib/adapters/skills.js +60 -0
- package/lib/adapters/ui.d.ts +6 -2
- package/lib/adapters/ui.js +13 -2
- package/lib/adapters/units.d.ts +18 -0
- package/lib/adapters/units.js +39 -0
- package/lib/adapters/workspaces.js +2 -1
- package/lib/client.d.ts +401 -112
- package/lib/client.js +2899 -1525
- package/lib/core/backup.js +20 -0
- package/lib/core/mcp-connector-store.d.ts +159 -0
- package/lib/core/mcp-connector-store.js +445 -0
- package/lib/core/messages-core.d.ts +2 -0
- package/lib/core/messages-core.js +4 -0
- package/lib/core/messages.d.ts +20 -0
- package/lib/core/messages.js +44 -0
- package/lib/core/phase3-host.js +18 -1
- package/lib/core/plugin-cli.d.ts +22 -2
- package/lib/core/plugin-cli.js +22 -3
- package/lib/core/recovery-orchestrator.d.ts +3 -1
- package/lib/core/recovery-orchestrator.js +75 -16
- package/lib/core/rollback-activity.d.ts +89 -0
- package/lib/core/rollback-activity.js +350 -0
- package/lib/core/rollback-ledger.d.ts +0 -9
- package/lib/core/rollback-ledger.js +37 -0
- package/lib/core/rollback-merge.d.ts +22 -2
- package/lib/core/rollback-merge.js +63 -13
- package/lib/core/rollback-point.d.ts +93 -0
- package/lib/core/rollback-point.js +420 -101
- package/lib/core/rollback-undo-journal.d.ts +123 -0
- package/lib/core/rollback-undo-journal.js +517 -0
- package/lib/core/self-update.js +2 -4
- package/lib/core/text-fidelity.d.ts +89 -0
- package/lib/core/text-fidelity.js +176 -0
- package/lib/core/types.d.ts +12 -3
- package/lib/core/update-check.js +2 -4
- package/lib/core/validator.d.ts +9 -0
- package/lib/core/validator.js +12 -0
- package/lib/foreign/bundle.js +42 -3
- package/lib/foreign/claude-code.js +358 -26
- package/lib/foreign/claude-sessions.d.ts +51 -8
- package/lib/foreign/claude-sessions.js +111 -13
- package/lib/foreign/read-claude-code.d.ts +33 -4
- package/lib/foreign/read-claude-code.js +446 -65
- package/lib/foreign/read-claude-plugins.d.ts +36 -0
- package/lib/foreign/read-claude-plugins.js +403 -0
- package/lib/foreign/registry.js +25 -2
- package/lib/foreign/truth-table.js +6 -4
- package/lib/foreign/types.d.ts +170 -2
- package/lib/index.d.ts +14 -5
- package/lib/index.js +60 -16
- package/lib/market/git-file-writer.js +7 -1
- package/lib/profiles/dsh-profile-launcher.d.ts +20 -0
- package/lib/profiles/dsh-profile-launcher.js +58 -1
- package/lib/profiles/dsh-profile-shared.d.ts +8 -2
- package/lib/profiles/dsh-profile-shared.js +8 -2
- package/lib/routes/foreign.js +8 -1
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +2 -2
- package/lib/routes/sync.js +173 -9
- package/lib/schema/manifest.d.ts +6 -1
- package/lib/schema/manifest.js +55 -0
- package/lib/schema/types.d.ts +47 -2
- package/lib/sync/ancestor.d.ts +22 -0
- package/lib/sync/ancestor.js +66 -2
- package/lib/sync/autosync-config.d.ts +73 -0
- package/lib/sync/autosync-config.js +89 -0
- package/lib/sync/autosync-scheduler.d.ts +80 -6
- package/lib/sync/autosync-scheduler.js +263 -40
- package/lib/sync/gist/gist-transport.d.ts +18 -1
- package/lib/sync/gist/gist-transport.js +42 -3
- package/lib/sync/gist/messages.d.ts +2 -0
- package/lib/sync/gist/messages.js +5 -0
- package/lib/sync/git/git-transport.d.ts +42 -3
- package/lib/sync/git/git-transport.js +146 -3
- package/lib/sync/merge.d.ts +47 -14
- package/lib/sync/merge.js +242 -222
- package/lib/sync/messages.d.ts +8 -0
- package/lib/sync/messages.js +19 -0
- package/lib/sync/risk.d.ts +19 -2
- package/lib/sync/risk.js +10 -3
- package/lib/sync/s3/messages.d.ts +6 -0
- package/lib/sync/s3/messages.js +13 -0
- package/lib/sync/s3/s3-transport.d.ts +9 -1
- package/lib/sync/s3/s3-transport.js +78 -6
- package/lib/sync/s3/sigv4.js +11 -2
- package/lib/sync/sync-engine.d.ts +212 -8
- package/lib/sync/sync-engine.js +514 -58
- package/lib/sync/sync-history.d.ts +11 -0
- package/lib/sync/sync-history.js +20 -0
- package/lib/sync/sync-state.d.ts +43 -0
- package/lib/sync/sync-state.js +44 -0
- package/lib/sync/sync-watcher.d.ts +76 -0
- package/lib/sync/sync-watcher.js +202 -0
- package/lib/sync/text-merge.d.ts +85 -0
- package/lib/sync/text-merge.js +513 -0
- package/lib/sync/transport.d.ts +48 -0
- package/lib/sync/transport.js +12 -0
- package/lib/sync/watch-scope.d.ts +38 -0
- package/lib/sync/watch-scope.js +95 -0
- package/lib/sync/webdav/webdav-transport.d.ts +16 -2
- package/lib/sync/webdav/webdav-transport.js +128 -11
- package/lib/ui/foreign-view.js +10 -0
- package/lib/ui/i18n.d.ts +9 -0
- package/lib/ui/i18n.js +22 -0
- package/lib/ui/rollback-view.d.ts +12 -0
- package/lib/ui/rollback-view.js +2 -0
- package/lib/ui/sync-settings-view.d.ts +47 -0
- package/lib/ui/sync-settings-view.js +24 -0
- package/lib/ui/types.d.ts +21 -0
- package/lib/utils/env-lock.d.ts +1 -0
- package/lib/utils/env-lock.js +7 -4
- package/package.json +6 -6
- package/src/adapters/credentials.ts +22 -4
- package/src/adapters/file-collection.ts +2 -1
- package/src/adapters/mcp.test.ts +235 -1
- package/src/adapters/mcp.ts +276 -5
- package/src/adapters/messages.ts +7 -0
- package/src/adapters/plugin-files.ts +2 -1
- package/src/adapters/plugins.test.ts +176 -2
- package/src/adapters/plugins.ts +107 -21
- package/src/adapters/pnpm-workspace.test.ts +89 -1
- package/src/adapters/pnpm-workspace.ts +127 -2
- package/src/adapters/prompts.ts +20 -3
- package/src/adapters/providers.ts +62 -4
- package/src/adapters/settings.ts +16 -3
- package/src/adapters/skills.test.ts +141 -0
- package/src/adapters/skills.ts +58 -2
- package/src/adapters/ui.ts +16 -3
- package/src/adapters/units-entries.test.ts +190 -0
- package/src/adapters/units.ts +50 -0
- package/src/adapters/workspaces.ts +2 -1
- package/src/client/about/PluginUpdateDialog.tsx +4 -2
- package/src/client/about/ReleaseNotesDialog.tsx +4 -2
- package/src/client/chat/RollbackReviewDialog.tsx +3 -2
- package/src/client/common/Skeleton.tsx +32 -0
- package/src/client/common/api-base.test.ts +36 -0
- package/src/client/common/http.test.ts +12 -1
- package/src/client/common/http.ts +8 -2
- package/src/client/common/routes.ts +46 -3
- package/src/client/config-manager.module.css +321 -11
- package/src/client/home/HomePanel.tsx +14 -6
- package/src/client/import/ForeignImportView.tsx +3 -1
- package/src/client/import/import-wizard-steps.tsx +2 -1
- package/src/client/library/ArtifactRow.tsx +12 -2
- package/src/client/library/LibraryActions.tsx +6 -3
- package/src/client/library/LibraryPanel.tsx +21 -3
- package/src/client/library/RestorePlanView.tsx +4 -2
- package/src/client/locales.ts +3 -0
- package/src/client/market/market-api.test.ts +3 -2
- package/src/client/recovery/RecoveryPanel.tsx +54 -7
- package/src/client/recovery/RollbackCard.tsx +7 -2
- package/src/client/recovery/RollbackDiffPane.tsx +3 -2
- package/src/client/recovery/recovery-locales.ts +20 -2
- package/src/client/recovery/recovery-view.test.ts +39 -1
- package/src/client/recovery/recovery-view.ts +23 -1
- package/src/client/run-store.test.ts +1 -1
- package/src/client/run-store.ts +4 -0
- package/src/client/sync/AutosyncCard.tsx +49 -6
- package/src/client/sync/MergeModal.tsx +220 -0
- package/src/client/sync/MergePreviewView.tsx +306 -0
- package/src/client/sync/SyncActivityBar.tsx +31 -0
- package/src/client/sync/SyncConfirmView.tsx +2 -1
- package/src/client/sync/SyncPanel.tsx +130 -87
- package/src/client/sync/SyncProbeRow.tsx +162 -0
- package/src/client/sync/SyncSettingsView.test.ts +27 -2
- package/src/client/sync/WorkDirCard.tsx +44 -6
- package/src/client/sync/history-model.ts +4 -1
- package/src/client/sync/merge-view.test.ts +248 -0
- package/src/client/sync/merge-view.ts +398 -0
- package/src/client/sync/probe-view.test.ts +90 -0
- package/src/client/sync/probe-view.ts +63 -0
- package/src/client/sync/remote-device-view.test.ts +35 -0
- package/src/client/sync/remote-device-view.ts +25 -0
- package/src/client/sync/sync-api.test.ts +5 -3
- package/src/client/sync/sync-api.ts +212 -2
- package/src/client/sync/sync-entry.test.ts +78 -0
- package/src/client/sync/sync-entry.ts +53 -0
- package/src/client/sync/sync-locales.ts +234 -2
- package/src/client/sync/sync-view.test.ts +18 -1
- package/src/client/sync/sync-view.ts +20 -0
- package/src/core/backup.test.ts +41 -0
- package/src/core/backup.ts +20 -0
- package/src/core/mcp-connector-store.test.ts +265 -0
- package/src/core/mcp-connector-store.ts +516 -0
- package/src/core/messages-core.ts +4 -0
- package/src/core/messages.ts +46 -0
- package/src/core/phase3-host.ts +18 -1
- package/src/core/phase3-production-integration.test.ts +4 -0
- package/src/core/plugin-cli.test.ts +26 -0
- package/src/core/plugin-cli.ts +30 -4
- package/src/core/recovery-orchestrator.test.ts +104 -5
- package/src/core/recovery-orchestrator.ts +74 -17
- package/src/core/rollback-activity.test.ts +311 -0
- package/src/core/rollback-activity.ts +367 -0
- package/src/core/rollback-cross-session.test.ts +694 -0
- package/src/core/rollback-interrupted.test.ts +137 -0
- package/src/core/rollback-ledger.ts +32 -0
- package/src/core/rollback-merge.test.ts +130 -0
- package/src/core/rollback-merge.ts +78 -14
- package/src/core/rollback-point.ts +468 -91
- package/src/core/rollback-undo-journal.test.ts +508 -0
- package/src/core/rollback-undo-journal.ts +571 -0
- package/src/core/self-update.ts +2 -5
- package/src/core/text-fidelity.test.ts +189 -0
- package/src/core/text-fidelity.ts +197 -0
- package/src/core/types.ts +11 -3
- package/src/core/update-check.ts +2 -5
- package/src/core/validator.ts +14 -0
- package/src/foreign/bundle-origin.test.ts +265 -0
- package/src/foreign/bundle.ts +45 -5
- package/src/foreign/claude-code.test.ts +268 -0
- package/src/foreign/claude-code.ts +406 -25
- package/src/foreign/claude-plugins.test.ts +234 -0
- package/src/foreign/claude-sessions.test.ts +141 -0
- package/src/foreign/claude-sessions.ts +179 -23
- package/src/foreign/file-budget.test.ts +4 -1
- package/src/foreign/read-claude-code.test.ts +320 -0
- package/src/foreign/read-claude-code.ts +490 -71
- package/src/foreign/read-claude-plugins.ts +465 -0
- package/src/foreign/registry.ts +25 -2
- package/src/foreign/session-ir.test.ts +65 -0
- package/src/foreign/truth-table.test.ts +81 -0
- package/src/foreign/truth-table.ts +6 -3
- package/src/foreign/types.ts +162 -2
- package/src/index.facade.test.ts +26 -4
- package/src/index.ts +76 -19
- package/src/market/git-file-writer.test.ts +4 -0
- package/src/market/git-file-writer.ts +7 -1
- package/src/profiles/dsh-profile-launcher.test.ts +68 -3
- package/src/profiles/dsh-profile-launcher.ts +56 -1
- package/src/profiles/dsh-profile-shared.ts +8 -2
- package/src/routes/foreign.ts +8 -1
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +2 -2
- package/src/routes/sync.ts +176 -9
- package/src/schema/manifest-schema.test.ts +87 -2
- package/src/schema/manifest.ts +57 -1
- package/src/schema/types.ts +45 -2
- package/src/sync/ancestor.ts +76 -2
- package/src/sync/autosync-config.test.ts +73 -1
- package/src/sync/autosync-config.ts +124 -0
- package/src/sync/autosync-risk-tier.test.ts +38 -2
- package/src/sync/autosync-scheduler.test.ts +264 -12
- package/src/sync/autosync-scheduler.ts +296 -44
- package/src/sync/gist/gist-conditional-write.test.ts +203 -0
- package/src/sync/gist/gist-transport.ts +53 -5
- package/src/sync/gist/messages.ts +5 -0
- package/src/sync/git/git-probe.test.ts +80 -0
- package/src/sync/git/git-transport.test.ts +185 -0
- package/src/sync/git/git-transport.ts +178 -5
- package/src/sync/merge.test.ts +188 -55
- package/src/sync/merge.ts +282 -224
- package/src/sync/messages.ts +19 -0
- package/src/sync/risk.ts +29 -5
- package/src/sync/s3/messages.ts +13 -0
- package/src/sync/s3/s3-conditional-write.test.ts +330 -0
- package/src/sync/s3/s3-transport.test.ts +2 -1
- package/src/sync/s3/s3-transport.ts +87 -9
- package/src/sync/s3/sigv4.test.ts +4 -1
- package/src/sync/s3/sigv4.ts +11 -2
- package/src/sync/sync-device-identity.test.ts +267 -0
- package/src/sync/sync-engine.test.ts +410 -19
- package/src/sync/sync-engine.ts +602 -57
- package/src/sync/sync-history.test.ts +16 -1
- package/src/sync/sync-history.ts +26 -0
- package/src/sync/sync-state.test.ts +64 -0
- package/src/sync/sync-state.ts +79 -0
- package/src/sync/sync-watcher.test.ts +197 -0
- package/src/sync/sync-watcher.ts +246 -0
- package/src/sync/text-merge.test.ts +358 -0
- package/src/sync/text-merge.ts +554 -0
- package/src/sync/transport.ts +57 -0
- package/src/sync/watch-scope.test.ts +122 -0
- package/src/sync/watch-scope.ts +120 -0
- package/src/sync/webdav/webdav-conditional-write.test.ts +360 -0
- package/src/sync/webdav/webdav-transport.ts +137 -14
- package/src/ui/foreign-view.test.ts +34 -0
- package/src/ui/foreign-view.ts +10 -0
- package/src/ui/i18n.ts +22 -0
- package/src/ui/rollback-view.ts +14 -0
- package/src/ui/sync-settings-view.test.ts +28 -1
- package/src/ui/sync-settings-view.ts +43 -0
- package/src/ui/types.ts +22 -0
- package/src/utils/env-lock.ts +8 -5
|
@@ -26,6 +26,19 @@
|
|
|
26
26
|
* 本文件**刻意不出现在 file-budget 的 import 白名单里**:它 import 了 `node:fs` 的**类型**用于
|
|
27
27
|
* 构造只读句柄的回调(`ClaudeReadHandle`,追加能力通道),这是**零运行期 I/O** 的类型依赖。
|
|
28
28
|
* 守卫按运行时性质放行(见 file-budget.test.ts 的白名单说明)。
|
|
29
|
+
*
|
|
30
|
+
* 会话保真修复(2026-10-18,task-4;四条都在**本文件内**收口,共享合成器 session-ir.ts 一字未改):
|
|
31
|
+
* ① **幂等时间**:墙钟回落只有一个确定性来源(`resolveWallClock`:显式 `options.now` → 记录自身
|
|
32
|
+
* 最早时间戳 → `input.createdAtMs`),只有全都缺失才回落 `Date.now()` 并在
|
|
33
|
+
* `info.counts.timestampsFallback` **如实记账**。旧实现 parse 与 synthesize 各调一次
|
|
34
|
+
* `Date.now()` ⇒ 同一来源两次转码字节必然不同 ⇒ DSH 侧「同内容 → Skip」永不成立。
|
|
35
|
+
* ② **isSidechain**:真实 Claude 子代理记录(`type=user|assistant` + `isSidechain:true`)**一律
|
|
36
|
+
* 不进 IR**,整类计报 `isSidechain`(旧实现零命中 → 子代理正文静默混进父对话)。
|
|
37
|
+
* ③ **sessionId 不一致**:`input.recordSessionId` 存在且 ≠ `input.id` → 非致命发现
|
|
38
|
+
* `session-id-mismatch`(会话 id 仍以文件名 stem 为准,见 `TranscodeResult.findings`)。
|
|
39
|
+
* ④ **推理块**(Claude Code 的 `thinking` / `redacted_thinking`):共享 IR 没有 reasoning
|
|
40
|
+
* 承载位(docs/spec/known-gaps.md F-01)→ 计报 `thinking`,**绝不伪装成正文**;非 Claude 原生的
|
|
41
|
+
* `{type:'reasoning',text:…}` 刻意保持既有口径(理由与切换方式见 `REASONING_BLOCK_TYPES`)。
|
|
29
42
|
*/
|
|
30
43
|
import type { Stats } from 'node:fs';
|
|
31
44
|
|
|
@@ -54,8 +67,11 @@ import type {
|
|
|
54
67
|
IrMessage,
|
|
55
68
|
IrParseStats,
|
|
56
69
|
IrSession,
|
|
70
|
+
IrTimeMs,
|
|
57
71
|
SynthesisStats,
|
|
58
72
|
} from './session-ir.ts';
|
|
73
|
+
// types.ts 只被**类型**引用(lead 冻结的输入形态 + 契约码),不做任何值导入
|
|
74
|
+
import type { ClaudeSessionInput, ForeignSkip } from './types.ts';
|
|
59
75
|
|
|
60
76
|
export type { DshSessionHeader, DshSessionRow } from './session-ir.ts';
|
|
61
77
|
|
|
@@ -78,12 +94,14 @@ const CLAUDE_PROVIDER = 'claude-code';
|
|
|
78
94
|
const CLAUDE_REASONING_EFFORT = 'medium';
|
|
79
95
|
const CLAUDE_MAX_TOKENS = 8192;
|
|
80
96
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
97
|
+
/**
|
|
98
|
+
* 待转码的 Claude 会话文件。
|
|
99
|
+
*
|
|
100
|
+
* **等价于 `types.ts` 的 `ClaudeSessionInput`**(2026-10-18 起不再重复声明):读盘层额外给的
|
|
101
|
+
* `recordSessionId`(≠ 文件名 stem 时报 session-id-mismatch)与 `createdAtMs`(确定性 createdAt
|
|
102
|
+
* 回落值,见 `resolveWallClock`)因此天然可达本层,而 `{ id, text }` 的老调用方一字不用改。
|
|
103
|
+
*/
|
|
104
|
+
export type ClaudeSessionFile = ClaudeSessionInput;
|
|
87
105
|
|
|
88
106
|
/**
|
|
89
107
|
* **只读**的文件读取面(additive,2026-10):**内部**用它支持「大文件只读头尾」的追加通道。
|
|
@@ -109,8 +127,20 @@ export interface ClaudeSessionInfo {
|
|
|
109
127
|
assistantMessages: number;
|
|
110
128
|
toolCalls: number;
|
|
111
129
|
toolResults: number;
|
|
112
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* 未迁移的记录类型 / 内容块 → 条数(attachment / queue-operation / system / isSidechain /
|
|
132
|
+
* thinking / unparsable …)。**每个键在下游都会被翻成 `unsupported-session-record` 的 detail**
|
|
133
|
+
* (kernel.collectSessionSections),所以这里只放「这条内容本版不迁移」这一件事。
|
|
134
|
+
*/
|
|
113
135
|
ignored: Record<string, number>;
|
|
136
|
+
/**
|
|
137
|
+
* 结算类记账(**刻意不进 `ignored`**:那里的键会被翻成 `unsupported-session-record`,语义不符)。
|
|
138
|
+
*
|
|
139
|
+
* 目前只有 `timestampsFallback`(0/1):记录里与 `input.createdAtMs` **都没有**可用时间戳 ⇒
|
|
140
|
+
* 本次转码只能回落墙钟 `Date.now()`,**同一来源两次转码的字节可能不同**(DSH 侧「同内容 → Skip」
|
|
141
|
+
* 因此不成立、重复导入会判 Conflict)。这是必须如实报出来的不确定性,不是错误。
|
|
142
|
+
*/
|
|
143
|
+
counts: Record<string, number>;
|
|
114
144
|
}
|
|
115
145
|
|
|
116
146
|
export interface TranscodedSession {
|
|
@@ -125,7 +155,12 @@ export interface TranscodedSession {
|
|
|
125
155
|
export interface TranscodeOptions {
|
|
126
156
|
/** 目标机 DSH 的 SESSION_FORMAT_VERSION(必填) */
|
|
127
157
|
formatVersion: number;
|
|
128
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* 墙钟回落的**最高优先级**注入点(测试 / 调用方显式给定)。
|
|
160
|
+
*
|
|
161
|
+
* 缺省时按 `resolveWallClock` 的回落链取:记录自身最早时间戳 → `input.createdAtMs` →
|
|
162
|
+
* `Date.now()`(最后一级会在返回值里记 `timestampsFallback`)。非法值(非安全整数)视同没给。
|
|
163
|
+
*/
|
|
129
164
|
now?: number;
|
|
130
165
|
}
|
|
131
166
|
|
|
@@ -142,13 +177,29 @@ export interface TranscodeSkip {
|
|
|
142
177
|
export interface TranscodeResult {
|
|
143
178
|
session?: TranscodedSession;
|
|
144
179
|
skip?: TranscodeSkip;
|
|
180
|
+
/**
|
|
181
|
+
* **非致命**发现(会话照样产出):目前只有 `session-id-mismatch`(记录里的 sessionId 与文件名
|
|
182
|
+
* stem 不一致)。会话 id 仍以文件名 stem 为准,只有「不一致」这件事必须可见。
|
|
183
|
+
*
|
|
184
|
+
* 为什么放在结果上而不是 `info.ignored`:`ignored` 的键在下游一律被翻成
|
|
185
|
+
* `unsupported-session-record`(码固定),而本条要用契约里冻结的 `session-id-mismatch`。
|
|
186
|
+
* **调用方负责原样带出**(`skipped.push(...result.findings)`)—— 现有
|
|
187
|
+
* `kernel.collectSessionSections` 尚未读本字段(不在本次改动范围)。空数组 = 没有发现。
|
|
188
|
+
*/
|
|
189
|
+
findings: ForeignSkip[];
|
|
145
190
|
}
|
|
146
191
|
|
|
147
192
|
/* ---------------- ① 解析:Claude 记录 → IR ---------------- */
|
|
148
193
|
|
|
149
194
|
/** Claude 记录里可识别的块(原始形态) */
|
|
150
195
|
interface ParsedBlock {
|
|
151
|
-
|
|
196
|
+
/**
|
|
197
|
+
* `thinking` = 推理块(Claude Code 的 `thinking` / `redacted_thinking`):共享 IR 的 `IrBlock`
|
|
198
|
+
* **没有 reasoning 承载位**(docs/spec/known-gaps.md F-01),所以它既不进 IR 也不伪装成正文,
|
|
199
|
+
* 只**逐条计数**(ignored['thinking'])。判定口径与「`reasoning` 为何不在此列」见
|
|
200
|
+
* `REASONING_BLOCK_TYPES` 的注释。
|
|
201
|
+
*/
|
|
202
|
+
readonly kind: 'text' | 'call' | 'result' | 'thinking' | 'other';
|
|
152
203
|
readonly text: string;
|
|
153
204
|
readonly id: string;
|
|
154
205
|
readonly name: string;
|
|
@@ -161,12 +212,39 @@ function usageField(usage: unknown, key: string): unknown {
|
|
|
161
212
|
return typeof usage === 'object' && usage !== null ? (usage as Record<string, unknown>)[key] : undefined;
|
|
162
213
|
}
|
|
163
214
|
|
|
215
|
+
/**
|
|
216
|
+
* 推理块的类型名 = Claude Code **自己**写的两种:`thinking` 与 `redacted_thinking`。
|
|
217
|
+
*
|
|
218
|
+
* 为什么必须按类型名判定:`thinking` 块的正文在 **`thinking` 键**里(没有 `text` 字段),旧实现
|
|
219
|
+
* 沿「对象带 text 才算文本」的兜底把它变成一个**空文本块** —— 内容静默丢失,且那个空块还会多插入
|
|
220
|
+
* 一个换行。本仓其余 20+ 来源一律「推理不伪装成正文、只计数」(read-crush / read-opencode /
|
|
221
|
+
* read-qwen / read-pi / read-openclaw …),这里对齐同一口径;共享 IR 无 reasoning 承载位这件事
|
|
222
|
+
* 记在 docs/spec/known-gaps.md 的 F-01。
|
|
223
|
+
*
|
|
224
|
+
* **为什么不把 `{type:'reasoning', text:'…'}` 一并纳入**(如实记明,别当成漏掉):
|
|
225
|
+
* ① Claude Code 不写这种块(它是别的导出器的归一命名,各来源自己的解析器已各自计报);
|
|
226
|
+
* ② 共享 IR 的文本兜底分支(**对象带 `text` 就当文本块**)是**被逐字节回归钉住的既有行为**
|
|
227
|
+
* (见 `blocksOf` 的注释),而这个块恰好命中它 —— 一并丢掉会让
|
|
228
|
+
* `claude-sessions-bytes.test.ts` 的 `timestamps-numeric-and-unparsable` 一案从 728 字节
|
|
229
|
+
* 变成 722 字节(正文少一段 'r')。那是**用户可见**的正文变化,按本轮的硬约束(既有语料
|
|
230
|
+
* 逐字一致)不由「修推理块静默丢弃」顺手引入。
|
|
231
|
+
* ③ 要一并计报,改这一个数组即可,并同步更新该案基线(现行行为已被
|
|
232
|
+
* `claude-sessions.test.ts` 的「reasoning 形状」用例显式钉住)。
|
|
233
|
+
*/
|
|
234
|
+
const REASONING_BLOCK_TYPES: readonly string[] = ['thinking', 'redacted_thinking'];
|
|
235
|
+
|
|
236
|
+
/** 推理块的占位(字段一律空:它不进 IR,只需被识别出来计数) */
|
|
237
|
+
function reasoningBlock(): ParsedBlock {
|
|
238
|
+
return { kind: 'thinking', text: '', id: '', name: '', input: undefined, isError: false };
|
|
239
|
+
}
|
|
240
|
+
|
|
164
241
|
function blockOf(raw: unknown): ParsedBlock {
|
|
165
242
|
const rec = typeof raw === 'object' && raw !== null && !Array.isArray(raw) ? (raw as Record<string, unknown>) : undefined;
|
|
166
243
|
if (rec === undefined) {
|
|
167
244
|
return { kind: 'other', text: '', id: '', name: '', input: undefined, isError: false };
|
|
168
245
|
}
|
|
169
246
|
const rawType = irStr(rec['type']) ?? '';
|
|
247
|
+
if (REASONING_BLOCK_TYPES.includes(rawType)) return reasoningBlock();
|
|
170
248
|
const id = irStr(rec['id']) ?? '';
|
|
171
249
|
const name = irStr(rec['name']);
|
|
172
250
|
const isText = rawType === 'text' || typeof rec['text'] === 'string';
|
|
@@ -287,6 +365,43 @@ interface ParsedClaudeSession {
|
|
|
287
365
|
readonly cwd: string | undefined;
|
|
288
366
|
readonly unparsable: number;
|
|
289
367
|
readonly title: string;
|
|
368
|
+
/** `isSidechain === true` 的**记录条数**(子代理记录:不进 IR,由转码入口整类计报) */
|
|
369
|
+
readonly sidechain: number;
|
|
370
|
+
/** 内容块里的推理块条数(IR 无承载位:不进 IR,由转码入口整类计报) */
|
|
371
|
+
readonly thinking: number;
|
|
372
|
+
/** 本次解析**唯一**算出的墙钟回落值;转码入口必须复用它,绝不再调一次 Date.now() */
|
|
373
|
+
readonly now: number;
|
|
374
|
+
/** `now` 是否来自墙钟回落(记录与 `input.createdAtMs` 都没有可用时间戳) */
|
|
375
|
+
readonly wallClockFallback: boolean;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* 墙钟回落的**唯一**来源(缺陷 1 的修复)。
|
|
380
|
+
*
|
|
381
|
+
* 为什么必须只有一处:会话产物的字节里带 createdAt / 各行各列的时间;只要有一次 `Date.now()`
|
|
382
|
+
* 参与,同一来源两次转码的字节就必然不同 ⇒ DSH 侧「同内容 → Skip」永不成立、重复导入恒判
|
|
383
|
+
* Conflict。旧实现 parse 与 synthesize **各调一次** `Date.now()`(返回值还可能是两个不同的毫秒)。
|
|
384
|
+
*
|
|
385
|
+
* 回落链(前三级都是确定性的:同一输入 → 同一产物):
|
|
386
|
+
* ① `options.now`(调用方/测试显式给定;**保持最高优先级**,既有字节回归靠它稳定);
|
|
387
|
+
* ② 记录自身的最早可解析时间戳(会话文件里最权威的确定性来源);
|
|
388
|
+
* ③ `input.createdAtMs`(读盘层给的回落值:最早可解析记录时间,没有就给文件 mtime);
|
|
389
|
+
* ④ 都没有 → `Date.now()`,并**如实记账**(`timestampsFallback`)。
|
|
390
|
+
*
|
|
391
|
+
* 非法值(非安全整数 / 非有限数)一律当「没给」:DSH 对时间字段要求安全整数,宁可继续回落也
|
|
392
|
+
* 不写出一条会被拒读的行。
|
|
393
|
+
*/
|
|
394
|
+
function resolveWallClock(
|
|
395
|
+
file: ClaudeSessionFile,
|
|
396
|
+
options: TranscodeOptions,
|
|
397
|
+
earliest: IrTimeMs | undefined,
|
|
398
|
+
): { readonly now: number; readonly fallback: boolean } {
|
|
399
|
+
const explicit = irSafeTime(options.now);
|
|
400
|
+
if (explicit !== undefined) return { now: explicit, fallback: false };
|
|
401
|
+
if (earliest !== undefined) return { now: earliest, fallback: false };
|
|
402
|
+
const given = irSafeTime(file.createdAtMs);
|
|
403
|
+
if (given !== undefined) return { now: given, fallback: false };
|
|
404
|
+
return { now: Date.now(), fallback: true };
|
|
290
405
|
}
|
|
291
406
|
|
|
292
407
|
/**
|
|
@@ -301,35 +416,47 @@ function parseClaudeSessionText(file: ClaudeSessionFile, options: TranscodeOptio
|
|
|
301
416
|
|
|
302
417
|
let cwd: string | undefined;
|
|
303
418
|
let createdAt: number | undefined;
|
|
419
|
+
let sidechain = 0;
|
|
304
420
|
for (const r of records) {
|
|
421
|
+
// 子代理记录(真实 Claude 的子代理对话就是 type=user/assistant + isSidechain:true):
|
|
422
|
+
// 它们**不是父对话的回合**,混进来会静默改写父对话正文(设计文档 2026-10-04-foreign-import-v1.md:89
|
|
423
|
+
// 「不迁移 isSidechain 的子代理会话」,而旧实现连计数都没有)。
|
|
424
|
+
// 它们仍参与 cwd / 最早时间:子代理与父对话同属一份日志文件,cwd 与时间线是**文件级**事实。
|
|
425
|
+
if (r['isSidechain'] === true) sidechain++;
|
|
305
426
|
if (cwd === undefined) cwd = irStr(r['cwd']);
|
|
306
427
|
createdAt = irEarlier(createdAt, irSafeTime(r['timestamp']));
|
|
307
428
|
}
|
|
429
|
+
// 墙钟回落**只在这里解析一次**,转码入口复用 `now`(同一输入一次调用内不可能出现两个 Date.now())
|
|
430
|
+
const wall = resolveWallClock(file, options, createdAt);
|
|
308
431
|
|
|
309
432
|
const messages: IrMessage[] = [];
|
|
310
433
|
let messageIndex = 0;
|
|
434
|
+
let thinking = 0;
|
|
311
435
|
let firstUserText = '';
|
|
312
436
|
for (const rec of records) {
|
|
313
437
|
const type = typeOf(rec);
|
|
314
438
|
if (type !== 'user' && type !== 'assistant') continue;
|
|
439
|
+
if (rec['isSidechain'] === true) continue;
|
|
315
440
|
const blocks = blocksOf(rec['message']);
|
|
316
|
-
|
|
441
|
+
// 推理块:先逐条计数,再**整块丢掉**(既不进 IR,也不留空文本块 —— 空块还会污染正文的换行串联)
|
|
442
|
+
thinking += blocks.filter((b) => b.kind === 'thinking').length;
|
|
443
|
+
const carried = blocks.filter((b) => b.kind !== 'thinking');
|
|
444
|
+
const message = irMessageOf(rec, carried, messageIndex);
|
|
317
445
|
if (message === undefined) continue;
|
|
318
446
|
messages.push(message);
|
|
319
447
|
// 消息 id 的兜底序 = 「第几条已知消息」(重构前的序号语义,逐字节回归钉住)
|
|
320
448
|
messageIndex++;
|
|
321
449
|
if (type === 'user' && firstUserText === '') {
|
|
322
|
-
const text = irTextOfBlocks(
|
|
450
|
+
const text = irTextOfBlocks(carried.map(irBlockOf));
|
|
323
451
|
if (text.trim() !== '') firstUserText = text;
|
|
324
452
|
}
|
|
325
453
|
}
|
|
326
454
|
|
|
327
|
-
const now = options.now ?? Date.now();
|
|
328
455
|
return {
|
|
329
456
|
session: {
|
|
330
457
|
id: file.id,
|
|
331
458
|
cwd: cwd ?? '',
|
|
332
|
-
createdAt: createdAt ?? now,
|
|
459
|
+
createdAt: createdAt ?? wall.now,
|
|
333
460
|
records: records.length,
|
|
334
461
|
messages,
|
|
335
462
|
},
|
|
@@ -337,6 +464,10 @@ function parseClaudeSessionText(file: ClaudeSessionFile, options: TranscodeOptio
|
|
|
337
464
|
cwd,
|
|
338
465
|
unparsable: stats.unparsable,
|
|
339
466
|
title: titleOf(records, firstUserText),
|
|
467
|
+
sidechain,
|
|
468
|
+
thinking,
|
|
469
|
+
now: wall.now,
|
|
470
|
+
wallClockFallback: wall.fallback,
|
|
340
471
|
};
|
|
341
472
|
}
|
|
342
473
|
|
|
@@ -359,21 +490,33 @@ export function dshSessionLogName(version: number): string {
|
|
|
359
490
|
}
|
|
360
491
|
|
|
361
492
|
export function transcodeClaudeSession(file: ClaudeSessionFile, options: TranscodeOptions): TranscodeResult {
|
|
493
|
+
/**
|
|
494
|
+
* 非致命发现(会话照样产出):记录里的 sessionId 与文件名 stem 不一致时**如实报码**,
|
|
495
|
+
* 但会话 id 仍以文件名 stem 为准(DSH 按「目录名 / 文件名」定位会话)。读盘层没给
|
|
496
|
+
* `recordSessionId`(老调用方)就什么都不报 —— 不猜。
|
|
497
|
+
*/
|
|
498
|
+
const findings: ForeignSkip[] = [];
|
|
499
|
+
const recordSessionId = irStr(file.recordSessionId);
|
|
500
|
+
if (recordSessionId !== undefined && recordSessionId !== file.id) {
|
|
501
|
+
findings.push({ code: 'session-id-mismatch', origin: file.id });
|
|
502
|
+
}
|
|
503
|
+
|
|
362
504
|
if (!SUPPORTED_DSH_SESSION_FORMAT_VERSIONS.includes(options.formatVersion)) {
|
|
363
|
-
return { skip: { code: 'session-format-unsupported', detail: String(options.formatVersion) } };
|
|
505
|
+
return { skip: { code: 'session-format-unsupported', detail: String(options.formatVersion) }, findings };
|
|
364
506
|
}
|
|
365
|
-
if (!isSafeIrId(file.id)) return { skip: { code: 'session-unsafe-id', detail: file.id } };
|
|
507
|
+
if (!isSafeIrId(file.id)) return { skip: { code: 'session-unsafe-id', detail: file.id }, findings };
|
|
366
508
|
|
|
367
509
|
const parsed = parseClaudeSessionText(file, options);
|
|
368
|
-
if (parsed.records.length === 0) return { skip: { code: 'session-empty' } };
|
|
369
|
-
if (parsed.cwd === undefined) return { skip: { code: 'session-missing-cwd', detail: file.id } };
|
|
510
|
+
if (parsed.records.length === 0) return { skip: { code: 'session-empty' }, findings };
|
|
511
|
+
if (parsed.cwd === undefined) return { skip: { code: 'session-missing-cwd', detail: file.id }, findings };
|
|
370
512
|
// 有内容但一行都解析不出来:如实区分「空文件」与「解析不了」(诊断更准,不新增码 —— 两者
|
|
371
513
|
// 在下游都是「这条会话没有产出」,区别只在忽略计数里可见)
|
|
372
514
|
if (parsed.session.messages.length === 0 && parsed.unparsable > 0) {
|
|
373
|
-
return { skip: { code: 'session-unparsable', detail: file.id } };
|
|
515
|
+
return { skip: { code: 'session-unparsable', detail: file.id }, findings };
|
|
374
516
|
}
|
|
375
517
|
|
|
376
|
-
|
|
518
|
+
// 时间兜底**只有一处**(parseClaudeSessionText 里解析过一次):这里绝不再调 Date.now()
|
|
519
|
+
const now = parsed.now;
|
|
377
520
|
const stats: SynthesisStats = {
|
|
378
521
|
ignored: {},
|
|
379
522
|
userMessages: 0,
|
|
@@ -382,10 +525,16 @@ export function transcodeClaudeSession(file: ClaudeSessionFile, options: Transco
|
|
|
382
525
|
toolResults: 0,
|
|
383
526
|
};
|
|
384
527
|
if (parsed.unparsable > 0) irBump(stats.ignored, 'unparsable', parsed.unparsable);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// IR
|
|
528
|
+
// 推理块:共享 IR 没有承载位 → 计报,绝不伪装成正文(与其余 20+ 来源同口径)
|
|
529
|
+
if (parsed.thinking > 0) irBump(stats.ignored, 'thinking', parsed.thinking);
|
|
530
|
+
// 子代理记录:不进 IR → 整类计报(detail 固定为 isSidechain)
|
|
531
|
+
if (parsed.sidechain > 0) irBump(stats.ignored, 'isSidechain', parsed.sidechain);
|
|
532
|
+
|
|
533
|
+
// 未迁移的记录类型**逐类计数**(attachment / queue-operation / system …)——
|
|
534
|
+
// IR 只承载 user/assistant 消息,所以这里按原始记录把其余类型补进忽略计数。
|
|
535
|
+
// isSidechain 记录已整类记账(上一步),不在这里重复计数。
|
|
388
536
|
for (const rec of parsed.records) {
|
|
537
|
+
if (rec['isSidechain'] === true) continue;
|
|
389
538
|
const type = typeOf(rec);
|
|
390
539
|
if (type === 'user' || type === 'assistant') continue;
|
|
391
540
|
irBump(stats.ignored, type);
|
|
@@ -400,7 +549,7 @@ export function transcodeClaudeSession(file: ClaudeSessionFile, options: Transco
|
|
|
400
549
|
maxTokens: CLAUDE_MAX_TOKENS,
|
|
401
550
|
}, stats, now);
|
|
402
551
|
|
|
403
|
-
if (rows.length === 0) return { skip: { code: 'session-empty' } };
|
|
552
|
+
if (rows.length === 0) return { skip: { code: 'session-empty' }, findings };
|
|
404
553
|
|
|
405
554
|
const header: DshSessionHeader = {
|
|
406
555
|
type: 'session',
|
|
@@ -412,6 +561,11 @@ export function transcodeClaudeSession(file: ClaudeSessionFile, options: Transco
|
|
|
412
561
|
delegationDepth: 0,
|
|
413
562
|
};
|
|
414
563
|
const data = encodeDshSessionLog(header, rows);
|
|
564
|
+
// 结算类记账(与 ignored 分开放:ignored 的键会被下游翻成 unsupported-session-record 码)。
|
|
565
|
+
// 0 也写出来:调用方一眼能看出「这次转码是否确定性」,不需要靠键的有无去猜。
|
|
566
|
+
const counts: Record<string, number> = {
|
|
567
|
+
timestampsFallback: parsed.wallClockFallback ? 1 : 0,
|
|
568
|
+
};
|
|
415
569
|
return {
|
|
416
570
|
session: {
|
|
417
571
|
id: file.id,
|
|
@@ -425,8 +579,10 @@ export function transcodeClaudeSession(file: ClaudeSessionFile, options: Transco
|
|
|
425
579
|
toolCalls: stats.toolCalls,
|
|
426
580
|
toolResults: stats.toolResults,
|
|
427
581
|
ignored: stats.ignored,
|
|
582
|
+
counts,
|
|
428
583
|
},
|
|
429
584
|
},
|
|
585
|
+
findings,
|
|
430
586
|
};
|
|
431
587
|
}
|
|
432
588
|
|
|
@@ -247,7 +247,10 @@ test('C 来源形状:共享模块清单是封闭集合(新模块必须显式
|
|
|
247
247
|
/* t6 新增:本域追加消息目录(零 source 形状;中文只在这里,装配期抛错的文案经 engineMsg 取)。 */
|
|
248
248
|
'messages.ts',
|
|
249
249
|
/* 档 B 新增的 5 个共享地基(零 source 形状,但也不是来源模块): */
|
|
250
|
-
'platform-paths.ts',
|
|
250
|
+
'platform-paths.ts',
|
|
251
|
+
/* 2026-10-18 新增:Claude Code 插件/市场读盘层(名字不含来源 id,故不是来源形状模块;
|
|
252
|
+
它是 claude-code 专属读盘子模块,由 read-claude-code.ts 动态 import)。清单按字母序比较。 */
|
|
253
|
+
'read-claude-plugins.ts', 'registry.ts', 'session-ir.ts', 'session-read.ts', 'session-source.ts',
|
|
251
254
|
'source-modules.ts', 'sqlite.ts', 'truth-table.ts', 'types.ts',
|
|
252
255
|
];
|
|
253
256
|
assert.deepEqual(
|