dsh-claude-move 0.2.6 → 0.3.0
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/CHANGELOG.md +16 -0
- package/README.es.md +10 -1
- package/README.hi.md +10 -1
- package/README.md +12 -3
- package/README.pt.md +10 -1
- package/README.zh.md +11 -2
- package/index.mjs +169 -0
- package/lib/export.mjs +261 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [SemVer](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-08-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- DSH 会话回迁 Claude jsonl 双向导出:新增 `claude_export` 工具与 `/claude-export` 命令,把 DSH 会话导出回 Claude 的 jsonl 格式。
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Use platform-neutral absolute paths in the export path tests (CI).
|
|
14
|
+
|
|
15
|
+
## [0.2.7] - 2026-08-23
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Tighten the `@deepseek-ai/schemastery` peer range from `>=3.0.0` to `^3.18.0` to match the range every other DSH plugin declares (and the harness's pinned `3.18.x` line); `>=3.0.0` over-claimed API compatibility across 3.x minors.
|
|
20
|
+
|
|
5
21
|
## [0.2.6] - 2026-08-22
|
|
6
22
|
|
|
7
23
|
### Changed
|
package/README.es.md
CHANGED
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
4. **Solo-copia e incremental** — nada se mueve, reescribe ni elimina en ninguno de los dos lados; reejecutar solo añade los turnos nuevos (`force: true` guarda una copia completa adicional con un id nuevo).
|
|
43
43
|
5. **Contexto personal, siempre fresco** — las memorias se inyectan como una sección de prompt en vivo, las habilidades de Claude se registran como habilidades DSH reales (globales + a nivel de proyecto), y el `CLAUDE.md` global + de proyecto se inyecta temprano.
|
|
44
44
|
6. **Asistente de migración de cuatro fuentes** — `/move` más `move_detect` / `move_preview` / `move_run` migran Claude Code, Codex, OpenCode y Hermes, con aprobación e idempotencia (`move.json`).
|
|
45
|
-
7. **Panel web y comandos** — `/claude-import-all`, `/resume-claude`, `/claude-move-reset` y un panel de migración flotante.
|
|
45
|
+
7. **Panel web y comandos** — `/claude-import-all`, `/resume-claude`, `/claude-move-reset`, `/claude-export` y un panel de migración flotante.
|
|
46
|
+
8. **Exportación bidireccional** — `claude_export` (o `/claude-export <sessionId>`) vuelve a escribir una sesión DSH como transcript JSONL reanudable de Claude Code (turnos `user`/`assistant`/`tool`, emparejamiento `thinking` + `tool_use`/`tool_result`, mapeo `cwd` de mejor esfuerzo), para que el historial pueda salir de DSH de nuevo.
|
|
46
47
|
|
|
47
48
|
## Asistente de migración de cuatro fuentes
|
|
48
49
|
|
|
@@ -122,6 +123,9 @@ import_claude { path: "all" } # todo
|
|
|
122
123
|
# Reejecuta en cualquier momento: los archivos sin cambios se omiten, las transcripciones crecidas solo añaden los turnos nuevos.
|
|
123
124
|
# Los archivos mayores que maxTranscriptBytes se importan por streaming en trozos (sin techo de memoria).
|
|
124
125
|
import_claude { path: "...", force: true } # copia completa nueva (se conserva la copia anterior)
|
|
126
|
+
|
|
127
|
+
claude_export { sessionId: "<dsh-session-id>" } # escribe una sesión DSH de vuelta a JSONL de Claude
|
|
128
|
+
claude_export { sessionId: "...", path: "~/.claude/projects/<slug>/<id>.jsonl" } # destino explícito
|
|
125
129
|
```
|
|
126
130
|
|
|
127
131
|
Comandos (activados por el usuario, sin turno del modelo):
|
|
@@ -132,6 +136,7 @@ Comandos (activados por el usuario, sin turno del modelo):
|
|
|
132
136
|
/resume-claude <sessionId> # por id de sesión fuente o id import-<src>
|
|
133
137
|
/resume-claude <keyword> # coincide con títulos; varias coincidencias se listan, nunca se adivina
|
|
134
138
|
/claude-move-reset # reinicia la caché del plugin (marcadores + mapa de importación); las sesiones importadas se conservan
|
|
139
|
+
/claude-export <sessionId> [path] # exporta una sesión DSH a un transcript JSONL reanudable de Claude
|
|
135
140
|
```
|
|
136
141
|
|
|
137
142
|
Panel web: un panel de migración flotante con el árbol de proyectos/sesiones, insignias de estado (no importado / importado / importado-con-turnos-nuevos / fuente ausente / directorio ausente / git sucio), filtro por palabra clave, renderizado paginado, "Importar y continuar" + "Abrir sesión" + "Refrescar lista de sesiones" por sesión, importación por lotes con barra de progreso en vivo y cancelar, y un botón de reinicio de caché. Los textos siguen el idioma del navegador (zh/en). Servido a través de las rutas JSON `/api/claude-move/*` propias del plugin en la costura pública `ctx.webServer`.
|
|
@@ -177,6 +182,8 @@ Todo opcional, anulable en cordis.yml.
|
|
|
177
182
|
| `skillsDir` | `$DSH_HOME/skills` | Destino de habilidades del asistente |
|
|
178
183
|
| `agentsMdPath` | `$DSH_HOME/AGENTS.md` | Destino de memoria/instrucciones del asistente |
|
|
179
184
|
| `moveWorkspaceMode` | `per-source` | Agrupación de workspace para importaciones del asistente: `per-source` · `single` |
|
|
185
|
+
| `enableExport` | `true` | Registra la herramienta `claude_export` y el comando `/claude-export` |
|
|
186
|
+
| `exportDir` | `$DSH_HOME/claude-export` | Carpeta de exportación por defecto (un `path` explícito siempre gana) |
|
|
180
187
|
|
|
181
188
|
## Herramientas y superficies
|
|
182
189
|
|
|
@@ -184,10 +191,12 @@ Todo opcional, anulable en cordis.yml.
|
|
|
184
191
|
|---|---|---|
|
|
185
192
|
| `claude_scan` | herramienta | Índice estructurado de proyectos/sesiones/memorias/habilidades/ajustes |
|
|
186
193
|
| `import_claude` | herramienta | Importa una sesión, un directorio o `all` (incremental; `force` para una copia nueva) |
|
|
194
|
+
| `claude_export` | herramienta | Exporta una sesión DSH a un transcript JSONL reanudable de Claude Code |
|
|
187
195
|
| `move_detect` / `move_preview` / `move_run` | herramientas | Asistente de cuatro fuentes: escanear, plan por ítem con diffs, ejecutar tras la aprobación |
|
|
188
196
|
| `/claude-import-all` | comando | Escanea → importa todo → informa |
|
|
189
197
|
| `/resume-claude` | comando | Continúa una sesión de Claude (latest, id o palabra clave) |
|
|
190
198
|
| `/claude-move-reset` | comando | Reinicia la caché del plugin (las sesiones importadas se conservan) |
|
|
199
|
+
| `/claude-export` | comando | Exporta una sesión DSH a un transcript JSONL reanudable de Claude |
|
|
191
200
|
| `/move` | comando | Asistente de cuatro fuentes de un solo paso |
|
|
192
201
|
| Panel web de migración | cliente | Panel flotante con progreso, cancelación, paginación, abrir sesión |
|
|
193
202
|
|
package/README.hi.md
CHANGED
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
4. **केवल-कॉपी और वृद्धिशील** — किसी भी तरफ कुछ भी स्थानांतरित, दोबारा लिखा या हटाया नहीं जाता; फिर चलाने पर केवल नए टर्न जोड़े जाते हैं (`force: true` एक नए id के तहत अतिरिक्त पूरी कॉपी सहेजता है)।
|
|
43
43
|
5. **व्यक्तिगत संदर्भ, हमेशा ताज़ा** — यादें लाइव प्रॉम्प्ट अनुभाग के रूप में इंजेक्ट होती हैं, Claude कौशल वास्तविक DSH कौशल के रूप में पंजीकृत होते हैं (वैश्विक + प्रोजेक्ट-स्तर), वैश्विक + प्रोजेक्ट `CLAUDE.md` जल्दी इंजेक्ट होता है।
|
|
44
44
|
6. **चार-स्रोत माइग्रेशन विज़ार्ड** — `/move` और `move_detect` / `move_preview` / `move_run` Claude Code, Codex, OpenCode और Hermes को माइग्रेट करते हैं, अनुमोदन-गेटेड और आइडेम्पोटेंट (`move.json`)।
|
|
45
|
-
7. **वेब पैनल और कमांड** — `/claude-import-all`, `/resume-claude`, `/claude-move-reset`, और एक फ़्लोटिंग माइग्रेशन पैनल।
|
|
45
|
+
7. **वेब पैनल और कमांड** — `/claude-import-all`, `/resume-claude`, `/claude-move-reset`, `/claude-export`, और एक फ़्लोटिंग माइग्रेशन पैनल।
|
|
46
|
+
8. **द्विदिश निर्यात** — `claude_export` (या `/claude-export <sessionId>`) एक DSH सत्र को वापस resumable Claude Code JSONL transcript के रूप में लिखता है (`user`/`assistant`/`tool` टर्न, `thinking` + `tool_use`/`tool_result` जोड़ी, सर्वोत्तम-प्रयास `cwd` मैपिंग), ताकि इतिहास फिर से DSH से बाहर जा सके।
|
|
46
47
|
|
|
47
48
|
## चार-स्रोत माइग्रेशन विज़ार्ड
|
|
48
49
|
|
|
@@ -122,6 +123,9 @@ import_claude { path: "all" } # सब कुछ
|
|
|
122
123
|
# कभी भी फिर चलाएँ: अपरिवर्तित फ़ाइलें छोड़ दी जाती हैं, बढ़ी ट्रांसक्रिप्ट केवल नए टर्न जोड़ती हैं।
|
|
123
124
|
# maxTranscriptBytes से बड़ी फ़ाइलें खंडों में स्ट्रीम-आयात होती हैं (कोई मेमोरी सीमा नहीं)।
|
|
124
125
|
import_claude { path: "...", force: true } # ताज़ा पूरी कॉपी (पिछली कॉपी रखी जाती है)
|
|
126
|
+
|
|
127
|
+
claude_export { sessionId: "<dsh-session-id>" } # DSH सत्र को वापस Claude JSONL में लिखें
|
|
128
|
+
claude_export { sessionId: "...", path: "~/.claude/projects/<slug>/<id>.jsonl" } # स्पष्ट लक्ष्य
|
|
125
129
|
```
|
|
126
130
|
|
|
127
131
|
कमांड (उपयोगकर्ता-ट्रिगर, कोई मॉडल टर्न नहीं):
|
|
@@ -132,6 +136,7 @@ import_claude { path: "...", force: true } # ताज़ा पूर
|
|
|
132
136
|
/resume-claude <sessionId> # स्रोत सत्र id या import-<src> id से
|
|
133
137
|
/resume-claude <keyword> # शीर्षक मिलाएँ; कई मिलान सूचीबद्ध होते हैं, कभी अनुमान नहीं
|
|
134
138
|
/claude-move-reset # प्लगइन कैश रीसेट करें (बुकमार्क + आयात मैप); आयातित सत्र बने रहते हैं
|
|
139
|
+
/claude-export <sessionId> [path] # DSH सत्र को resumable Claude JSONL transcript में निर्यात करें
|
|
135
140
|
```
|
|
136
141
|
|
|
137
142
|
वेब पैनल: प्रोजेक्ट/सत्र ट्री, स्थिति बैज (आयातित नहीं / आयातित / नए-टर्न-सहित-आयातित / स्रोत अनुपस्थित / निर्देशिका अनुपस्थित / git गंदा), कीवर्ड फ़िल्टर, पृष्ठांकित रेंडरिंग, प्रति-सत्र "आयात करें और जारी रखें" + "सत्र खोलें" + "सत्र सूची रीफ़्रेश करें", लाइव प्रगति पट्टी और रद्द के साथ बैच आयात, और एक कैश-रीसेट बटन वाला फ़्लोटिंग माइग्रेशन पैनल। पाठ ब्राउज़र भाषा (zh/en) का अनुसरण करते हैं। सार्वजनिक `ctx.webServer` सीम पर प्लगइन के अपने `/api/claude-move/*` JSON मार्गों से परोसा जाता है।
|
|
@@ -177,6 +182,8 @@ import_claude { path: "...", force: true } # ताज़ा पूर
|
|
|
177
182
|
| `skillsDir` | `$DSH_HOME/skills` | विज़ार्ड कौशल लक्ष्य |
|
|
178
183
|
| `agentsMdPath` | `$DSH_HOME/AGENTS.md` | विज़ार्ड याद/निर्देश लक्ष्य |
|
|
179
184
|
| `moveWorkspaceMode` | `per-source` | विज़ार्ड आयात के लिए workspace समूहन: `per-source` · `single` |
|
|
185
|
+
| `enableExport` | `true` | `claude_export` टूल और `/claude-export` कमांड पंजीकृत करें |
|
|
186
|
+
| `exportDir` | `$DSH_HOME/claude-export` | डिफ़ॉल्ट निर्यात फ़ोल्डर (स्पष्ट `path` हमेशा प्राथमिकता) |
|
|
180
187
|
|
|
181
188
|
## उपकरण और सतहें
|
|
182
189
|
|
|
@@ -184,10 +191,12 @@ import_claude { path: "...", force: true } # ताज़ा पूर
|
|
|
184
191
|
|---|---|---|
|
|
185
192
|
| `claude_scan` | टूल | प्रोजेक्ट/सत्र/याद/कौशल/सेटिंग का संरचित इंडेक्स |
|
|
186
193
|
| `import_claude` | टूल | एक सत्र, निर्देशिका या `all` आयात करें (वृद्धिशील; `force` से नई कॉपी) |
|
|
194
|
+
| `claude_export` | टूल | DSH सत्र को resumable Claude Code JSONL transcript में निर्यात करें |
|
|
187
195
|
| `move_detect` / `move_preview` / `move_run` | टूल | चार-स्रोत विज़ार्ड: स्कैन, diff सहित प्रति-आइटम योजना, अनुमोदन के बाद निष्पादन |
|
|
188
196
|
| `/claude-import-all` | कमांड | स्कैन → सब आयात → रिपोर्ट |
|
|
189
197
|
| `/resume-claude` | कमांड | Claude सत्र जारी करें (latest, id या कीवर्ड) |
|
|
190
198
|
| `/claude-move-reset` | कमांड | प्लगइन कैश रीसेट करें (आयातित सत्र बने रहते हैं) |
|
|
199
|
+
| `/claude-export` | कमांड | DSH सत्र को resumable Claude JSONL transcript में निर्यात करें |
|
|
191
200
|
| `/move` | कमांड | एक-चरण चार-स्रोत विज़ार्ड |
|
|
192
201
|
| वेब माइग्रेशन पैनल | क्लाइंट | प्रगति, रद्द, पेजिंग, सत्र खोलें वाला फ़्लोटिंग पैनल |
|
|
193
202
|
|
package/README.md
CHANGED
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
4. **Copy-only & incremental** — nothing on either side is moved, rewritten, or deleted; re-running appends only the new turns (`force: true` saves an extra full copy under a new id).
|
|
44
44
|
5. **Personal context, always fresh** — memories injected as a live prompt section, Claude skills registered as real DSH skills (global + project-level), global + project `CLAUDE.md` injected early.
|
|
45
45
|
6. **Four-source migration wizard** — `/move` plus `move_detect` / `move_preview` / `move_run` migrate Claude Code, Codex, OpenCode and Hermes, approval-gated and idempotent (`move.json`).
|
|
46
|
-
7. **Web panel & commands** — `/claude-import-all`, `/resume-claude`, `/claude-move-reset`, and a floating migration panel.
|
|
46
|
+
7. **Web panel & commands** — `/claude-import-all`, `/resume-claude`, `/claude-move-reset`, `/claude-export`, and a floating migration panel.
|
|
47
|
+
8. **Bidirectional export** — `claude_export` (or `/claude-export <sessionId>`) writes a DSH session back out as a resumable Claude Code JSONL transcript (`user`/`assistant`/`tool` turns, `thinking` + `tool_use`/`tool_result` pairing, best-effort `cwd` mapping), so history can leave DSH again.
|
|
47
48
|
|
|
48
49
|
## Four-source migration wizard
|
|
49
50
|
|
|
@@ -123,6 +124,9 @@ import_claude { path: "all" } # everything
|
|
|
123
124
|
# Re-run any time: unchanged files are skipped, grown transcripts append only the new turns.
|
|
124
125
|
# Files over maxTranscriptBytes are stream-imported in chunks (no memory ceiling).
|
|
125
126
|
import_claude { path: "...", force: true } # fresh full copy (previous copy kept)
|
|
127
|
+
|
|
128
|
+
claude_export { sessionId: "<dsh-session-id>" } # write a DSH session back to Claude JSONL
|
|
129
|
+
claude_export { sessionId: "...", path: "~/.claude/projects/<slug>/<id>.jsonl" } # explicit target
|
|
126
130
|
```
|
|
127
131
|
|
|
128
132
|
Commands (user-triggered, no model turn):
|
|
@@ -133,6 +137,7 @@ Commands (user-triggered, no model turn):
|
|
|
133
137
|
/resume-claude <sessionId> # by source session id or import-<src> id
|
|
134
138
|
/resume-claude <keyword> # match titles; multiple matches are listed, never guessed
|
|
135
139
|
/claude-move-reset # reset the plugin cache (bookmarks + import map); imported sessions are kept
|
|
140
|
+
/claude-export <sessionId> [path] # export a DSH session to a resumable Claude JSONL transcript
|
|
136
141
|
```
|
|
137
142
|
|
|
138
143
|
Web panel: a floating migration panel with the project/session tree, status badges (not imported / imported / imported-with-new-turns / source missing / directory missing / git dirty), keyword filter, paged rendering, per-session "Import & continue" + "Open session" + "Refresh session list", batch import with a live progress bar and cancel, and a cache-reset button. Texts follow the browser language (zh/en). Served through the plugin's own `/api/claude-move/*` JSON routes on the public `ctx.webServer` seam.
|
|
@@ -178,6 +183,8 @@ All optional, overridable in cordis.yml.
|
|
|
178
183
|
| `skillsDir` | `$DSH_HOME/skills` | Wizard skill target |
|
|
179
184
|
| `agentsMdPath` | `$DSH_HOME/AGENTS.md` | Wizard memory/instruction target |
|
|
180
185
|
| `moveWorkspaceMode` | `per-source` | `per-source` · `single` workspace grouping for wizard imports |
|
|
186
|
+
| `enableExport` | `true` | Register the `claude_export` tool and `/claude-export` command |
|
|
187
|
+
| `exportDir` | `$DSH_HOME/claude-export` | Default export folder (explicit `path` always wins) |
|
|
181
188
|
|
|
182
189
|
## Tools & surfaces
|
|
183
190
|
|
|
@@ -185,10 +192,12 @@ All optional, overridable in cordis.yml.
|
|
|
185
192
|
|---|---|---|
|
|
186
193
|
| `claude_scan` | tool | Structured index of projects/sessions/memories/skills/settings |
|
|
187
194
|
| `import_claude` | tool | Import one session, a directory, or `all` (incremental, `force` for a fresh copy) |
|
|
195
|
+
| `claude_export` | tool | Export a DSH session to a resumable Claude Code JSONL transcript |
|
|
188
196
|
| `move_detect` / `move_preview` / `move_run` | tools | Four-source wizard: scan, per-item plan with diffs, execute behind approval |
|
|
189
197
|
| `/claude-import-all` | command | Scan → import everything → report |
|
|
190
198
|
| `/resume-claude` | command | Continue a Claude session (latest, id, or keyword) |
|
|
191
199
|
| `/claude-move-reset` | command | Reset the plugin cache (imported sessions kept) |
|
|
200
|
+
| `/claude-export` | command | Export a DSH session to a resumable Claude JSONL transcript |
|
|
192
201
|
| `/move` | command | One-shot four-source wizard |
|
|
193
202
|
| Web migration panel | client | Floating panel with progress, cancel, paging, open session |
|
|
194
203
|
|
|
@@ -196,7 +205,7 @@ All optional, overridable in cordis.yml.
|
|
|
196
205
|
|
|
197
206
|
- **Permissions**: the workshop manifest declares `filesystem:read` and `filesystem:write`.
|
|
198
207
|
- **Reads** `~/.claude` (transcripts, memories, skills, `CLAUDE.md`, `settings.json`) — strictly read-only — and the project directories it imports into.
|
|
199
|
-
- **Writes** DSH session logs via the public `sessionPersistence` service (create + append only, never delete/rewrite/archive), workspace-registry records, its cache under `$DSH_HOME/claude-move/`,
|
|
208
|
+
- **Writes** DSH session logs via the public `sessionPersistence` service (create + append only, never delete/rewrite/archive), workspace-registry records, its cache under `$DSH_HOME/claude-move/`, the `claudecode` workspace folder, and exported `.jsonl` files under `$DSH_HOME/claude-export/` (or an explicit target path).
|
|
200
209
|
- **Never** modifies Claude source files, touches other applications' data, or accesses the network. **No credentials** are read or transmitted.
|
|
201
210
|
|
|
202
211
|
## Security boundaries
|
|
@@ -220,7 +229,7 @@ All optional, overridable in cordis.yml.
|
|
|
220
229
|
|
|
221
230
|
## Model Experience
|
|
222
231
|
|
|
223
|
-
- The model-facing surface is the
|
|
232
|
+
- The model-facing surface is the tools' descriptions/schemas and their outputs: `claude_scan` returns the structured index, `import_claude` returns per-file summaries with positions of warnings, and `claude_export` returns the export summary (target path + turn/message/tool counts). Tool results are themselves logged `tool/result` events, so everything is reconstructable.
|
|
224
233
|
- No hidden model-facing text; memory/`CLAUDE.md` sections are registered on `ctx.systemPrompt` (prompt assembly, rebuildable from the session log).
|
|
225
234
|
|
|
226
235
|
## Troubleshooting
|
package/README.pt.md
CHANGED
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
4. **Somente-cópia e incremental** — nada é movido, reescrito ou excluído em nenhum dos lados; reexecutar apenas anexa os turnos novos (`force: true` salva uma cópia completa extra sob um novo id).
|
|
43
43
|
5. **Contexto pessoal, sempre atualizado** — memórias injetadas como uma seção de prompt em tempo real, habilidades do Claude registradas como habilidades DSH reais (globais + de projeto), e o `CLAUDE.md` global + de projeto injetado cedo.
|
|
44
44
|
6. **Assistente de migração de quatro fontes** — `/move` mais `move_detect` / `move_preview` / `move_run` migram Claude Code, Codex, OpenCode e Hermes, com aprovação e idempotência (`move.json`).
|
|
45
|
-
7. **Painel web e comandos** — `/claude-import-all`, `/resume-claude`, `/claude-move-reset` e um painel de migração flutuante.
|
|
45
|
+
7. **Painel web e comandos** — `/claude-import-all`, `/resume-claude`, `/claude-move-reset`, `/claude-export` e um painel de migração flutuante.
|
|
46
|
+
8. **Exportação bidirecional** — `claude_export` (ou `/claude-export <sessionId>`) grava uma sessão DSH de volta como transcript JSONL retomável do Claude Code (turnos `user`/`assistant`/`tool`, pareamento `thinking` + `tool_use`/`tool_result`, mapeamento `cwd` de melhor esforço), para que o histórico possa sair do DSH novamente.
|
|
46
47
|
|
|
47
48
|
## Assistente de migração de quatro fontes
|
|
48
49
|
|
|
@@ -122,6 +123,9 @@ import_claude { path: "all" } # tudo
|
|
|
122
123
|
# Reexecute a qualquer momento: arquivos inalterados são pulados, transcrições crescidas anexam apenas os turnos novos.
|
|
123
124
|
# Arquivos acima de maxTranscriptBytes são importados por streaming em blocos (sem teto de memória).
|
|
124
125
|
import_claude { path: "...", force: true } # cópia completa nova (cópia anterior mantida)
|
|
126
|
+
|
|
127
|
+
claude_export { sessionId: "<dsh-session-id>" } # grava uma sessão DSH de volta para JSONL do Claude
|
|
128
|
+
claude_export { sessionId: "...", path: "~/.claude/projects/<slug>/<id>.jsonl" } # destino explícito
|
|
125
129
|
```
|
|
126
130
|
|
|
127
131
|
Comandos (acionados pelo usuário, sem turno do modelo):
|
|
@@ -132,6 +136,7 @@ Comandos (acionados pelo usuário, sem turno do modelo):
|
|
|
132
136
|
/resume-claude <sessionId> # por id de sessão fonte ou id import-<src>
|
|
133
137
|
/resume-claude <keyword> # corresponde a títulos; múltiplas correspondências são listadas, nunca adivinhadas
|
|
134
138
|
/claude-move-reset # reinicia o cache do plugin (marcadores + mapa de importação); sessões importadas são mantidas
|
|
139
|
+
/claude-export <sessionId> [path] # exporta uma sessão DSH para um transcript JSONL retomável do Claude
|
|
135
140
|
```
|
|
136
141
|
|
|
137
142
|
Painel web: um painel de migração flutuante com a árvore de projetos/sessões, selos de status (não importado / importado / importado-com-turnos-novos / fonte ausente / diretório ausente / git sujo), filtro por palavra-chave, renderização paginada, "Importar e continuar" + "Abrir sessão" + "Atualizar lista de sessões" por sessão, importação em lote com barra de progresso em tempo real e cancelar, e um botão de reinício de cache. Os textos seguem o idioma do navegador (zh/en). Servido pelas rotas JSON `/api/claude-move/*` próprias do plugin na costura pública `ctx.webServer`.
|
|
@@ -177,6 +182,8 @@ Tudo opcional, anulável no cordis.yml.
|
|
|
177
182
|
| `skillsDir` | `$DSH_HOME/skills` | Destino de skills do assistente |
|
|
178
183
|
| `agentsMdPath` | `$DSH_HOME/AGENTS.md` | Destino de memória/instruções do assistente |
|
|
179
184
|
| `moveWorkspaceMode` | `per-source` | Agrupamento de workspace para importações do assistente: `per-source` · `single` |
|
|
185
|
+
| `enableExport` | `true` | Registra a ferramenta `claude_export` e o comando `/claude-export` |
|
|
186
|
+
| `exportDir` | `$DSH_HOME/claude-export` | Pasta de exportação padrão (um `path` explícito sempre vence) |
|
|
180
187
|
|
|
181
188
|
## Ferramentas e superfícies
|
|
182
189
|
|
|
@@ -184,10 +191,12 @@ Tudo opcional, anulável no cordis.yml.
|
|
|
184
191
|
|---|---|---|
|
|
185
192
|
| `claude_scan` | ferramenta | Índice estruturado de projetos/sessões/memórias/habilidades/ajustes |
|
|
186
193
|
| `import_claude` | ferramenta | Importa uma sessão, um diretório ou `all` (incremental; `force` para cópia nova) |
|
|
194
|
+
| `claude_export` | ferramenta | Exporta uma sessão DSH para um transcript JSONL retomável do Claude Code |
|
|
187
195
|
| `move_detect` / `move_preview` / `move_run` | ferramentas | Assistente de quatro fontes: varrer, plano por item com diffs, executar após aprovação |
|
|
188
196
|
| `/claude-import-all` | comando | Varre → importa tudo → relata |
|
|
189
197
|
| `/resume-claude` | comando | Continua uma sessão do Claude (latest, id ou palavra-chave) |
|
|
190
198
|
| `/claude-move-reset` | comando | Reinicia o cache do plugin (sessões importadas mantidas) |
|
|
199
|
+
| `/claude-export` | comando | Exporta uma sessão DSH para um transcript JSONL retomável do Claude |
|
|
191
200
|
| `/move` | comando | Assistente de quatro fontes de um só passo |
|
|
192
201
|
| Painel web de migração | cliente | Painel flutuante com progresso, cancelamento, paginação, abrir sessão |
|
|
193
202
|
|
package/README.zh.md
CHANGED
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
4. **只复制且增量** —— 两侧都不会被移动、改写或删除;重新运行只追加新的轮次(`force: true` 以新 id 额外保存一份完整副本)。
|
|
43
43
|
5. **个人上下文,始终新鲜** —— 记忆作为实时提示词段落注入,Claude 技能注册为真正的 DSH 技能(全局 + 项目级),全局 + 项目 `CLAUDE.md` 提前注入。
|
|
44
44
|
6. **四来源迁移向导** —— `/move` 加上 `move_detect` / `move_preview` / `move_run` 迁移 Claude Code、Codex、OpenCode 和 Hermes,审批门控且幂等(`move.json`)。
|
|
45
|
-
7. **Web 面板与命令** —— `/claude-import-all`、`/resume-claude`、`/claude-move-reset`,以及一个浮动迁移面板。
|
|
45
|
+
7. **Web 面板与命令** —— `/claude-import-all`、`/resume-claude`、`/claude-move-reset`、`/claude-export`,以及一个浮动迁移面板。
|
|
46
|
+
8. **双向导出** —— `claude_export`(或 `/claude-export <sessionId>`)把 DSH 会话回写为 Claude Code 可 resume 的 JSONL transcript(user/assistant/tool 轮次、thinking + tool_use/tool_result 配对、cwd 尽力映射),让历史可以再次离开 DSH。
|
|
46
47
|
|
|
47
48
|
## 四来源迁移向导
|
|
48
49
|
|
|
@@ -122,6 +123,9 @@ import_claude { path: "all" } # 全部
|
|
|
122
123
|
# 随时重新运行:未变更的文件被跳过,增长的 transcript 只追加新的轮次。
|
|
123
124
|
# 超过 maxTranscriptBytes 的文件以分块流式导入(无内存上限)。
|
|
124
125
|
import_claude { path: "...", force: true } # 全新的完整副本(保留之前的副本)
|
|
126
|
+
|
|
127
|
+
claude_export { sessionId: "<dsh-session-id>" } # 把 DSH 会话回写为 Claude JSONL
|
|
128
|
+
claude_export { sessionId: "...", path: "~/.claude/projects/<slug>/<id>.jsonl" } # 指定目标
|
|
125
129
|
```
|
|
126
130
|
|
|
127
131
|
命令(用户触发,不占模型轮次):
|
|
@@ -132,6 +136,7 @@ import_claude { path: "...", force: true } # 全新的完整副本(
|
|
|
132
136
|
/resume-claude <sessionId> # 按源会话 id 或 import-<src> id
|
|
133
137
|
/resume-claude <keyword> # 匹配标题;多个匹配会列出,绝不猜测
|
|
134
138
|
/claude-move-reset # 重置插件缓存(书签 + 导入映射);导入的会话保留
|
|
139
|
+
/claude-export <sessionId> [path] # 导出 DSH 会话为可 resume 的 Claude JSONL transcript
|
|
135
140
|
```
|
|
136
141
|
|
|
137
142
|
Web 面板:一个浮动迁移面板,包含项目/会话树、状态徽章(未导入 / 已导入 / 已导入并有新轮次 / 源缺失 / 目录缺失 / git 脏)、关键字过滤、分页渲染、每个会话的「导入并继续」+「打开会话」+「刷新会话列表」、带实时进度条和取消的批量导入,以及一个缓存重置按钮。文本跟随浏览器语言(zh/en)。通过插件自身的 `/api/claude-move/*` JSON 路由在公开 `ctx.webServer` 接缝上提供。
|
|
@@ -177,6 +182,8 @@ Web 面板:一个浮动迁移面板,包含项目/会话树、状态徽章(
|
|
|
177
182
|
| `skillsDir` | `$DSH_HOME/skills` | 向导技能目标 |
|
|
178
183
|
| `agentsMdPath` | `$DSH_HOME/AGENTS.md` | 向导记忆/指令目标 |
|
|
179
184
|
| `moveWorkspaceMode` | `per-source` | 向导导入的工作区分组:`per-source` · `single` |
|
|
185
|
+
| `enableExport` | `true` | 注册 `claude_export` 工具与 `/claude-export` 命令 |
|
|
186
|
+
| `exportDir` | `$DSH_HOME/claude-export` | 默认导出目录(显式 `path` 始终优先) |
|
|
180
187
|
|
|
181
188
|
## 工具与界面
|
|
182
189
|
|
|
@@ -184,10 +191,12 @@ Web 面板:一个浮动迁移面板,包含项目/会话树、状态徽章(
|
|
|
184
191
|
|---|---|---|
|
|
185
192
|
| `claude_scan` | 工具 | 项目/会话/记忆/技能/设置的结构化索引 |
|
|
186
193
|
| `import_claude` | 工具 | 导入单个会话、一个目录或 `all`(增量;`force` 生成全新副本) |
|
|
194
|
+
| `claude_export` | 工具 | 把 DSH 会话导出为可 resume 的 Claude Code JSONL transcript |
|
|
187
195
|
| `move_detect` / `move_preview` / `move_run` | 工具 | 四来源向导:扫描、带 diff 的逐项计划、在审批之后执行 |
|
|
188
196
|
| `/claude-import-all` | 命令 | 扫描 → 导入全部 → 报告 |
|
|
189
197
|
| `/resume-claude` | 命令 | 继续一个 Claude 会话(latest、id 或关键字) |
|
|
190
198
|
| `/claude-move-reset` | 命令 | 重置插件缓存(导入的会话保留) |
|
|
199
|
+
| `/claude-export` | 命令 | 导出 DSH 会话为可 resume 的 Claude JSONL transcript |
|
|
191
200
|
| `/move` | 命令 | 一次性四来源向导 |
|
|
192
201
|
| Web 迁移面板 | 客户端 | 带进度、取消、分页、打开会话的浮动面板 |
|
|
193
202
|
|
|
@@ -219,7 +228,7 @@ Web 面板:一个浮动迁移面板,包含项目/会话树、状态徽章(
|
|
|
219
228
|
|
|
220
229
|
## 模型体验
|
|
221
230
|
|
|
222
|
-
-
|
|
231
|
+
- 面向模型的面是这些工具的描述/schema 及其输出:`claude_scan` 返回结构化索引,`import_claude` 返回逐文件摘要以及警告的位置,`claude_export` 返回导出摘要(目标路径 + 轮次/消息/工具计数)。工具结果本身被记录为 `tool/result` 事件,因此一切都可以重建。
|
|
223
232
|
- 没有隐藏的面向模型的文本;记忆/`CLAUDE.md` 段落注册在 `ctx.systemPrompt` 上(提示词组装,可从会话日志重建)。
|
|
224
233
|
|
|
225
234
|
## 故障排查
|
package/index.mjs
CHANGED
|
@@ -42,6 +42,7 @@ import { makeFileCache, readMemoriesSync, renderMemories, renderClaudeMd, fileEx
|
|
|
42
42
|
import { makeClaudeSkillsProvider } from './lib/skills-provider.mjs'
|
|
43
43
|
import { translateSettings } from './lib/settings.mjs'
|
|
44
44
|
import { buildHandoff, DEFAULT_HANDOFF_MAX_CHARS } from './lib/handoff.mjs'
|
|
45
|
+
import { eventsToClaudeJsonl } from './lib/export.mjs'
|
|
45
46
|
import { importsStore } from './lib/imports-store.mjs'
|
|
46
47
|
import { manifestStore } from './lib/manifest.mjs'
|
|
47
48
|
import { runPreview, runExecute, runWizard, reportLines } from './lib/wizard.mjs'
|
|
@@ -97,6 +98,8 @@ export const DEFAULT_IMPORT_CONCURRENCY = 4
|
|
|
97
98
|
* @property {'per-source'|'single'} [moveWorkspaceMode] 向导会话归组:'per-source'(默认)每源一个工作区(`$DSH_HOME/imports/<source>`);'single' 全部挂到一个 imports 工作区(`$DSH_HOME/imports`)。
|
|
98
99
|
* @property {string} [skillsDir] 向导技能落点;缺省 `$DSH_HOME/skills`(官方用户技能根,DSH 自动发现)。
|
|
99
100
|
* @property {string} [agentsMdPath] 向导记忆/指令落点;缺省 `$DSH_HOME/AGENTS.md`(DSH 全局指令文件)。
|
|
101
|
+
* @property {boolean} [enableExport] 注册 DSH 会话回迁导出(claude_export 工具与 /claude-export 命令,默认 true)。
|
|
102
|
+
* @property {string} [exportDir] 回迁导出落点目录;缺省 `$DSH_HOME/claude-export`(DSH_HOME 缺失时 `~/.dsh/claude-export`)。
|
|
100
103
|
*/
|
|
101
104
|
|
|
102
105
|
export const Config = Schema.object({
|
|
@@ -131,6 +134,8 @@ export const Config = Schema.object({
|
|
|
131
134
|
moveWorkspaceMode: Schema.union([Schema.const('per-source'), Schema.const('single')]).default('per-source'),
|
|
132
135
|
skillsDir: Schema.string(),
|
|
133
136
|
agentsMdPath: Schema.string(),
|
|
137
|
+
enableExport: Schema.boolean().default(true),
|
|
138
|
+
exportDir: Schema.string(),
|
|
134
139
|
})
|
|
135
140
|
|
|
136
141
|
const sessionImportSchema = {
|
|
@@ -1520,6 +1525,146 @@ function makeImportTool(ctx, config) {
|
|
|
1520
1525
|
})
|
|
1521
1526
|
}
|
|
1522
1527
|
|
|
1528
|
+
// ── 会话回迁导出(F18):DSH 会话 → Claude 可 resume JSONL ────────────────────
|
|
1529
|
+
|
|
1530
|
+
/** claude_export 输出 schema。 */
|
|
1531
|
+
const exportResultSchema = {
|
|
1532
|
+
type: 'object',
|
|
1533
|
+
additionalProperties: false,
|
|
1534
|
+
properties: {
|
|
1535
|
+
sessionId: { type: 'string', required: true },
|
|
1536
|
+
path: { type: 'string', required: true },
|
|
1537
|
+
title: { type: 'string' },
|
|
1538
|
+
cwd: { type: 'string' },
|
|
1539
|
+
lines: { type: 'integer', required: true },
|
|
1540
|
+
turns: { type: 'integer', required: true },
|
|
1541
|
+
user: { type: 'integer', required: true },
|
|
1542
|
+
assistant: { type: 'integer', required: true },
|
|
1543
|
+
toolCalls: { type: 'integer', required: true },
|
|
1544
|
+
toolResults: { type: 'integer', required: true },
|
|
1545
|
+
},
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* 回迁导出落点目录:config.exportDir 显式给出则按绝对路径解析,否则
|
|
1550
|
+
* `$DSH_HOME/claude-export`(DSH_HOME 缺失时 `~/.dsh/claude-export`)。
|
|
1551
|
+
* @param config - 插件配置。
|
|
1552
|
+
* @param env - 环境对象,缺省 process.env。
|
|
1553
|
+
* @returns 目录绝对路径。
|
|
1554
|
+
*/
|
|
1555
|
+
export function resolveExportDir(config, env = process.env) {
|
|
1556
|
+
if (typeof config?.exportDir === 'string' && config.exportDir.trim().length > 0) {
|
|
1557
|
+
return path.resolve(config.exportDir)
|
|
1558
|
+
}
|
|
1559
|
+
const base = env.DSH_HOME || path.join(homedir(), '.dsh')
|
|
1560
|
+
return path.join(base, 'claude-export')
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/**
|
|
1564
|
+
* 解析导出目标文件路径:显式 path(支持 `~`)直接解析;缺省落回
|
|
1565
|
+
* `<exportDir>/<sanitized-sessionId>.jsonl`。
|
|
1566
|
+
* @param raw - 工具参数里的 path。
|
|
1567
|
+
* @param sessionId - DSH 会话 id。
|
|
1568
|
+
* @param config - 插件配置。
|
|
1569
|
+
* @returns 绝对文件路径。
|
|
1570
|
+
*/
|
|
1571
|
+
export function resolveExportTarget(raw, sessionId, config) {
|
|
1572
|
+
if (typeof raw === 'string' && raw.trim().length > 0) {
|
|
1573
|
+
const expanded = raw.startsWith('~')
|
|
1574
|
+
? path.join(homedir(), raw.slice(1).replace(/^[\\/]+/, ''))
|
|
1575
|
+
: raw
|
|
1576
|
+
return path.resolve(expanded)
|
|
1577
|
+
}
|
|
1578
|
+
const safe = String(sessionId ?? '').replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 128)
|
|
1579
|
+
return path.join(resolveExportDir(config), `${safe || 'session'}.jsonl`)
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* 执行一次回迁导出:读 DSH 会话日志(sessionPersistence.readFrom)→ 反向折叠为
|
|
1584
|
+
* Claude 可 resume JSONL → 写入目标文件。只读源会话、只写导出落点文件,绝不
|
|
1585
|
+
* 改写 DSH 会话日志。会话不可读/无可导出内容时大声失败。
|
|
1586
|
+
* @param ctx - Cordis 上下文。
|
|
1587
|
+
* @param config - 插件配置。
|
|
1588
|
+
* @param args - 工具参数 `{ sessionId, path? }`。
|
|
1589
|
+
* @param signal - 可选 AbortSignal。
|
|
1590
|
+
* @returns `{ sessionId, path, title, cwd, lines, turns, user, assistant, toolCalls, toolResults }`。
|
|
1591
|
+
*/
|
|
1592
|
+
export async function runExport(ctx, config, args, signal) {
|
|
1593
|
+
const sp = ctx.get('sessionPersistence')
|
|
1594
|
+
if (!sp || typeof sp.readFrom !== 'function') {
|
|
1595
|
+
throw new Error('会话持久化服务(sessionPersistence)不可用:claude-move 回迁导出需要该服务')
|
|
1596
|
+
}
|
|
1597
|
+
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId.trim() : ''
|
|
1598
|
+
if (sessionId.length === 0) throw new Error('sessionId 必填:要回迁为 Claude jsonl 的 DSH 会话 id')
|
|
1599
|
+
signal?.throwIfAborted()
|
|
1600
|
+
let read
|
|
1601
|
+
try {
|
|
1602
|
+
read = await sp.readFrom(sessionId, 0)
|
|
1603
|
+
} catch (err) {
|
|
1604
|
+
throw new Error(`读取会话 ${sessionId} 失败(不存在或不可读):${String((err && err.message) || err)}`)
|
|
1605
|
+
}
|
|
1606
|
+
const events = Array.isArray(read?.events) ? read.events : null
|
|
1607
|
+
if (!events || events.length === 0) throw new Error(`会话 ${sessionId} 无日志事件,无法导出`)
|
|
1608
|
+
const cwd = typeof read?.meta?.cwd === 'string' ? read.meta.cwd : null
|
|
1609
|
+
const result = eventsToClaudeJsonl({ events, sessionId, cwd })
|
|
1610
|
+
if (result.lines.length === 0) {
|
|
1611
|
+
throw new Error(`会话 ${sessionId} 无可导出的对话内容(user/assistant/tool 轮次为空)`)
|
|
1612
|
+
}
|
|
1613
|
+
const target = resolveExportTarget(args?.path, sessionId, config)
|
|
1614
|
+
await mkdir(path.dirname(target), { recursive: true })
|
|
1615
|
+
await writeFile(target, result.lines.map((l) => l + '\n').join(''), 'utf8')
|
|
1616
|
+
return {
|
|
1617
|
+
sessionId,
|
|
1618
|
+
path: target,
|
|
1619
|
+
title: result.title ?? null,
|
|
1620
|
+
cwd,
|
|
1621
|
+
lines: result.lines.length,
|
|
1622
|
+
turns: result.counts.turns,
|
|
1623
|
+
user: result.counts.user,
|
|
1624
|
+
assistant: result.counts.assistant,
|
|
1625
|
+
toolCalls: result.counts.toolCalls,
|
|
1626
|
+
toolResults: result.counts.toolResults,
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/** claude_export 结果的模型可读摘要(中文)。 */
|
|
1631
|
+
export function renderExport(args, value) {
|
|
1632
|
+
const lines = [
|
|
1633
|
+
`已把 DSH 会话 ${value.sessionId} 回迁为 Claude 可 resume JSONL:`,
|
|
1634
|
+
`- 输出文件:${value.path}`,
|
|
1635
|
+
`- 轮次 ${value.turns}、用户消息 ${value.user}、助手消息 ${value.assistant}、` +
|
|
1636
|
+
`工具调用 ${value.toolCalls}、工具结果 ${value.toolResults}(共 ${value.lines} 行)`,
|
|
1637
|
+
value.title ? `- 标题:${value.title}` : null,
|
|
1638
|
+
value.cwd ? `- 工作目录:${value.cwd}` : '- 工作目录:未知(Claude 打开时回退当前目录)',
|
|
1639
|
+
'Claude Code 续接:claude --resume <输出文件>(或放入项目 .claude/projects 后 resume)。',
|
|
1640
|
+
].filter(Boolean)
|
|
1641
|
+
return [{ type: 'text', text: lines.join('\n') }]
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function makeExportTool(ctx, config) {
|
|
1645
|
+
return defineTool({
|
|
1646
|
+
name: 'claude_export',
|
|
1647
|
+
description:
|
|
1648
|
+
'Export a DSH session back into a resumable Claude Code JSONL transcript (user/assistant/tool turns, thinking and tool_use/tool_result pairing, file reference best-effort mapping). Read-only on the source session; writes one .jsonl target. Round-trips the same shape claude_scan/import_claude consume, so the export can be re-imported or resumed by Claude Code. ' +
|
|
1649
|
+
'(把 DSH 会话回迁为 Claude Code 可 resume 的 JSONL transcript:覆盖 user/assistant/tool 轮次、thinking 与 tool_use/tool_result 配对、文件引用尽力映射。只读源会话,只写一个目标 .jsonl;与 import_claude 同构,可被再次导入或由 Claude Code resume。)',
|
|
1650
|
+
parameters: {
|
|
1651
|
+
sessionId: {
|
|
1652
|
+
type: 'string',
|
|
1653
|
+
required: true,
|
|
1654
|
+
description: '要导出的 DSH 会话 id(会话列表/import_claude 结果中的 dshSessionId)。',
|
|
1655
|
+
},
|
|
1656
|
+
path: {
|
|
1657
|
+
type: 'string',
|
|
1658
|
+
description: `可选:目标 .jsonl 文件路径(支持 ~)。缺省写入 ${resolveExportDir(config)}/<sessionId>.jsonl。`,
|
|
1659
|
+
},
|
|
1660
|
+
},
|
|
1661
|
+
output: { schema: exportResultSchema, render: renderExport },
|
|
1662
|
+
async execute(args, exec) {
|
|
1663
|
+
return runExport(ctx, config, args, exec?.signal)
|
|
1664
|
+
},
|
|
1665
|
+
})
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1523
1668
|
// ── 个人信息搬移(F11-F13):同步注入 + 技能 provider ────────────────────────
|
|
1524
1669
|
|
|
1525
1670
|
/**
|
|
@@ -2001,6 +2146,29 @@ function registerCommandDefinitions(ctx, config, commands) {
|
|
|
2001
2146
|
}
|
|
2002
2147
|
},
|
|
2003
2148
|
})
|
|
2149
|
+
|
|
2150
|
+
// F18:DSH 会话回迁 Claude 可 resume JSONL(双向迁移的导出方向)。
|
|
2151
|
+
if (config.enableExport !== false) {
|
|
2152
|
+
commands.register({
|
|
2153
|
+
name: 'claude-export',
|
|
2154
|
+
description: 'Export a DSH session into a resumable Claude Code JSONL transcript(把 DSH 会话回迁为 Claude Code 可 resume 的 JSONL transcript)',
|
|
2155
|
+
input: { hint: '<DSH 会话 id> [目标 .jsonl 路径]' },
|
|
2156
|
+
handler: async (invocation) => {
|
|
2157
|
+
try {
|
|
2158
|
+
const parts = invocation.rawInput.trim().split(/\s+/)
|
|
2159
|
+
const sessionId = parts[0]
|
|
2160
|
+
if (!sessionId) {
|
|
2161
|
+
return { kind: 'error', text: '请提供要导出的 DSH 会话 id:/claude-export <sessionId> [path]' }
|
|
2162
|
+
}
|
|
2163
|
+
const value = await runExport(ctx, config, { sessionId, path: parts[1] }, invocation.signal)
|
|
2164
|
+
const text = renderExport({}, value).map((b) => b.text).join('\n')
|
|
2165
|
+
return { kind: 'success', text }
|
|
2166
|
+
} catch (err) {
|
|
2167
|
+
return { kind: 'error', text: 'claude-export 失败:' + String((err && err.message) || err) }
|
|
2168
|
+
}
|
|
2169
|
+
},
|
|
2170
|
+
})
|
|
2171
|
+
}
|
|
2004
2172
|
}
|
|
2005
2173
|
|
|
2006
2174
|
// ── 面板 JSON 路由(F16):ctx.webServer 公开 seam ─────────────────────────────
|
|
@@ -2887,6 +3055,7 @@ export function apply(ctx, config = {}) {
|
|
|
2887
3055
|
const state = makeClaudeState(config)
|
|
2888
3056
|
ctx.tools.register(makeScanTool(ctx, config, state))
|
|
2889
3057
|
ctx.tools.register(makeImportTool(ctx, config))
|
|
3058
|
+
if (config.enableExport !== false) ctx.tools.register(makeExportTool(ctx, config))
|
|
2890
3059
|
if (config.enableMove !== false) {
|
|
2891
3060
|
const move = makeMoveTools(ctx, config, state)
|
|
2892
3061
|
ctx.tools.register(move.detect)
|
package/lib/export.mjs
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// lib/export.mjs — DSH 会话事件 → Claude Code 可 resume JSONL(纯函数,零 DSH 依赖)。
|
|
3
|
+
//
|
|
4
|
+
// 与 convert.mjs 相对:convert.mjs 把外部 transcript(Claude/Codex/OpenCode/Hermes)
|
|
5
|
+
// 折成 DSH 会话事件;本模块把 DSH 会话事件反向折叠为 Claude Code 可直接 --resume
|
|
6
|
+
// 的 JSONL 行。覆盖 user/assistant/tool 轮次:
|
|
7
|
+
// user/message → type:user(直连提问字符串,或内联 tool_result 的 user 消息)
|
|
8
|
+
// assistant/message → type:assistant(text→text、reasoning→thinking、tool-call→tool_use)
|
|
9
|
+
// tool/call → 并入所属 assistant 消息的 tool_use 块(按 callId 去重,仅当
|
|
10
|
+
// assistant content 未内联时补入)
|
|
11
|
+
// tool/result → type:user(连续多条合并为一条 tool_result 数组,规范形状)
|
|
12
|
+
// session/title → type:custom-title(时间线任意位置均可被 Claude 读取)
|
|
13
|
+
// 文件引用(cwd)由调用方从会话 header 传入;映射不到时省略 cwd(Claude 打开时
|
|
14
|
+
// 回退当前目录)。tool_use 参数经 JSON 解析还原为对象;解析失败保留原文(尽力映射)。
|
|
15
|
+
|
|
16
|
+
import { randomUUID } from 'node:crypto'
|
|
17
|
+
|
|
18
|
+
/** Claude Code 可 resume 的 JSONL 版本字符串。 */
|
|
19
|
+
export const CLAUDE_JSONL_VERSION = '2.0.0'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 把 DSH tool-call 的 arguments(JSON 字符串)还原为 Claude tool_use 的 input 对象。
|
|
23
|
+
* 非字符串原样返回;JSON 字符串解析失败时保留原文(不静默丢参数)。
|
|
24
|
+
* @param argumentsText - DSH tool-call.arguments 字段。
|
|
25
|
+
* @returns input 对象或原值。
|
|
26
|
+
*/
|
|
27
|
+
function parseToolInput(argumentsText) {
|
|
28
|
+
if (typeof argumentsText !== 'string') return argumentsText ?? {}
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(argumentsText)
|
|
31
|
+
return parsed !== null && typeof parsed === 'object' ? parsed : argumentsText
|
|
32
|
+
} catch {
|
|
33
|
+
// 非 JSON 参数(自由格式字符串):保留原文,Claude 侧仍可读。
|
|
34
|
+
return argumentsText
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* DSH assistant content block → Claude assistant content block。
|
|
40
|
+
* @param block - DSH content block(text/reasoning/tool-call)。
|
|
41
|
+
* @returns Claude block(text/thinking/tool_use)或 null。
|
|
42
|
+
*/
|
|
43
|
+
function mapAssistantBlock(block) {
|
|
44
|
+
if (!block) return null
|
|
45
|
+
if (block.type === 'text' && typeof block.text === 'string') return { type: 'text', text: block.text }
|
|
46
|
+
if (block.type === 'reasoning' && typeof block.text === 'string') return { type: 'thinking', thinking: block.text }
|
|
47
|
+
if (block.type === 'tool-call') {
|
|
48
|
+
return { type: 'tool_use', id: block.id, name: block.name, input: parseToolInput(block.arguments) }
|
|
49
|
+
}
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 提取纯文本(content 为字符串或 [{type:'text',text}] 数组)。 */
|
|
54
|
+
function extractText(content) {
|
|
55
|
+
if (typeof content === 'string') return content
|
|
56
|
+
if (!Array.isArray(content)) return ''
|
|
57
|
+
return content
|
|
58
|
+
.filter((b) => b && b.type === 'text' && typeof b.text === 'string')
|
|
59
|
+
.map((b) => b.text)
|
|
60
|
+
.join('\n')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** DSH tool-result 内容 → Claude tool_result content 数组(仅 text 块)。 */
|
|
64
|
+
function mapToolResultContent(content) {
|
|
65
|
+
if (Array.isArray(content)) {
|
|
66
|
+
const blocks = content.map(mapAssistantBlock).filter(Boolean)
|
|
67
|
+
return blocks.length > 0 ? blocks : [{ type: 'text', text: '' }]
|
|
68
|
+
}
|
|
69
|
+
if (typeof content === 'string') return [{ type: 'text', text: content }]
|
|
70
|
+
return [{ type: 'text', text: String(content ?? '') }]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 从 user/message 的 content 里提取 tool_result 块(部分会话把工具结果作为
|
|
75
|
+
* user/message 的 content 内联,而非独立 tool/result 事件)。
|
|
76
|
+
* @param content - user/message 的 content。
|
|
77
|
+
* @returns Claude tool_result 块数组。
|
|
78
|
+
*/
|
|
79
|
+
function extractInlineToolResults(content) {
|
|
80
|
+
if (!Array.isArray(content)) return []
|
|
81
|
+
const blocks = []
|
|
82
|
+
for (const b of content) {
|
|
83
|
+
if (b && b.type === 'tool-result' && typeof b.toolCallId === 'string') {
|
|
84
|
+
blocks.push({
|
|
85
|
+
type: 'tool_result',
|
|
86
|
+
tool_use_id: b.toolCallId,
|
|
87
|
+
content: mapToolResultContent(b.content),
|
|
88
|
+
is_error: b.isError === true,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return blocks
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** DSH 事件时间(毫秒 epoch)→ ISO 字符串;缺失回退当前时间。 */
|
|
96
|
+
function isoTime(ev) {
|
|
97
|
+
return typeof ev?.time === 'number' ? new Date(ev.time).toISOString() : new Date().toISOString()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 构造一条 Claude JSONL 记录(与 Claude Code 自身 transcript 的信封一致)。
|
|
102
|
+
* @param fields - `{ type, message?, sessionId, cwd?, timestamp }`;`custom-title`
|
|
103
|
+
* 记录用顶层 `customTitle` 字段而非 message。
|
|
104
|
+
* @returns 记录对象。
|
|
105
|
+
*/
|
|
106
|
+
function makeRecord({ type, message, customTitle, sessionId, cwd, timestamp }) {
|
|
107
|
+
const rec = {
|
|
108
|
+
parentUuid: null,
|
|
109
|
+
isSidechain: false,
|
|
110
|
+
userType: 'user',
|
|
111
|
+
...(typeof cwd === 'string' && cwd.length > 0 ? { cwd } : {}),
|
|
112
|
+
sessionId,
|
|
113
|
+
version: CLAUDE_JSONL_VERSION,
|
|
114
|
+
gitBranch: null,
|
|
115
|
+
type,
|
|
116
|
+
...(message !== undefined ? { message } : {}),
|
|
117
|
+
...(customTitle !== undefined ? { customTitle } : {}),
|
|
118
|
+
uuid: randomUUID(),
|
|
119
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
120
|
+
}
|
|
121
|
+
return rec
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 把 DSH 会话事件反向折叠为 Claude Code 可 resume 的 JSONL 记录。
|
|
126
|
+
* 纯函数:不读写文件,输入事件数组,输出记录对象数组与 JSONL 行。
|
|
127
|
+
* @param options - `{ events, sessionId, cwd, model }`;model 为 assistant
|
|
128
|
+
* 消息缺省模型名(事件自带 source.model 时优先)。
|
|
129
|
+
* @returns `{ records, lines, title, counts }`:
|
|
130
|
+
* counts 为 `{ turns, user, assistant, toolCalls, toolResults }`。
|
|
131
|
+
*/
|
|
132
|
+
export function eventsToClaudeJsonl({ events, sessionId, cwd, model } = {}) {
|
|
133
|
+
const list = Array.isArray(events) ? events : []
|
|
134
|
+
const records = []
|
|
135
|
+
let title = null
|
|
136
|
+
|
|
137
|
+
for (const ev of list) {
|
|
138
|
+
if (ev?.type === 'session/title' && typeof ev.data?.title === 'string' && ev.data.title.trim().length > 0) {
|
|
139
|
+
title = ev.data.title.trim()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 标题先行:Claude 在时间线任意位置读到 custom-title 均视为本会话标题。
|
|
144
|
+
if (title) {
|
|
145
|
+
records.push(makeRecord({ type: 'custom-title', customTitle: title, sessionId, cwd }))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let i = 0
|
|
149
|
+
while (i < list.length) {
|
|
150
|
+
const ev = list[i]
|
|
151
|
+
const type = ev?.type
|
|
152
|
+
const ts = isoTime(ev)
|
|
153
|
+
|
|
154
|
+
if (type === 'user/message') {
|
|
155
|
+
const inline = extractInlineToolResults(ev.data?.content)
|
|
156
|
+
if (inline.length > 0) {
|
|
157
|
+
records.push(makeRecord({
|
|
158
|
+
type: 'user', message: { role: 'user', content: inline }, sessionId, cwd, timestamp: ts,
|
|
159
|
+
}))
|
|
160
|
+
} else {
|
|
161
|
+
const text = extractText(ev.data?.content)
|
|
162
|
+
if (text.length > 0) {
|
|
163
|
+
records.push(makeRecord({
|
|
164
|
+
type: 'user', message: { role: 'user', content: text }, sessionId, cwd, timestamp: ts,
|
|
165
|
+
}))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
i++
|
|
169
|
+
continue
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (type === 'assistant/message') {
|
|
173
|
+
const content = Array.isArray(ev.data?.message?.content) ? ev.data.message.content : []
|
|
174
|
+
const blocks = []
|
|
175
|
+
for (const b of content) {
|
|
176
|
+
const mapped = mapAssistantBlock(b)
|
|
177
|
+
if (mapped) blocks.push(mapped)
|
|
178
|
+
}
|
|
179
|
+
// 紧随其后的 tool/call 事件:仅当 assistant content 未内联同 id tool_use 时
|
|
180
|
+
// 补入(claude-move 导入的会话 content 已内联,这里保证其余会话形状也可导出)。
|
|
181
|
+
let j = i + 1
|
|
182
|
+
while (j < list.length && list[j]?.type === 'tool/call') {
|
|
183
|
+
const tc = list[j]
|
|
184
|
+
const callId = tc.data?.callId
|
|
185
|
+
if (typeof callId === 'string' && !blocks.some((b) => b.type === 'tool_use' && b.id === callId)) {
|
|
186
|
+
blocks.push({
|
|
187
|
+
type: 'tool_use',
|
|
188
|
+
id: callId,
|
|
189
|
+
name: tc.data?.name ?? 'unknown',
|
|
190
|
+
input: parseToolInput(tc.data?.arguments),
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
j++
|
|
194
|
+
}
|
|
195
|
+
if (blocks.length > 0) {
|
|
196
|
+
const modelName = ev.data?.message?.source?.model ?? model ?? null
|
|
197
|
+
records.push(makeRecord({
|
|
198
|
+
type: 'assistant',
|
|
199
|
+
message: {
|
|
200
|
+
id: randomUUID(),
|
|
201
|
+
role: 'assistant',
|
|
202
|
+
...(modelName ? { model: modelName } : {}),
|
|
203
|
+
content: blocks,
|
|
204
|
+
},
|
|
205
|
+
sessionId, cwd, timestamp: ts,
|
|
206
|
+
}))
|
|
207
|
+
}
|
|
208
|
+
i = j
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (type === 'tool/result') {
|
|
213
|
+
// 连续 tool/result 合并为一条 user 消息(Claude 规范:一步的工具结果并入一条)。
|
|
214
|
+
const blocks = []
|
|
215
|
+
while (i < list.length && list[i]?.type === 'tool/result') {
|
|
216
|
+
const tr = list[i]
|
|
217
|
+
const block = tr.data?.message?.content?.[0]
|
|
218
|
+
if (block && typeof block.toolCallId === 'string') {
|
|
219
|
+
blocks.push({
|
|
220
|
+
type: 'tool_result',
|
|
221
|
+
tool_use_id: block.toolCallId,
|
|
222
|
+
content: mapToolResultContent(block.content),
|
|
223
|
+
is_error: block.isError === true,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
i++
|
|
227
|
+
}
|
|
228
|
+
if (blocks.length > 0) {
|
|
229
|
+
records.push(makeRecord({
|
|
230
|
+
type: 'user', message: { role: 'user', content: blocks }, sessionId, cwd, timestamp: ts,
|
|
231
|
+
}))
|
|
232
|
+
}
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
i++
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const counts = {
|
|
240
|
+
turns: list.filter((e) => e?.type === 'turn/start').length,
|
|
241
|
+
user: records.filter((r) => r.type === 'user').length,
|
|
242
|
+
assistant: records.filter((r) => r.type === 'assistant').length,
|
|
243
|
+
toolCalls: records.reduce(
|
|
244
|
+
(n, r) => n + (r.type === 'assistant' ? (r.message.content ?? []).filter((b) => b.type === 'tool_use').length : 0),
|
|
245
|
+
0,
|
|
246
|
+
),
|
|
247
|
+
toolResults: records.reduce(
|
|
248
|
+
(n, r) => n + (r.type === 'user' && Array.isArray(r.message.content)
|
|
249
|
+
? r.message.content.filter((b) => b.type === 'tool_result').length
|
|
250
|
+
: 0),
|
|
251
|
+
0,
|
|
252
|
+
),
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
records,
|
|
257
|
+
lines: records.map((r) => JSON.stringify(r)),
|
|
258
|
+
title,
|
|
259
|
+
counts,
|
|
260
|
+
}
|
|
261
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-claude-move",
|
|
3
3
|
"description": "Four-source migration wizard: copy Claude Code, Codex, OpenCode and Hermes sessions, memories, skills, instructions and slash commands into DeepSeek Harness as resumable sessions — copy-only, approval-gated, idempotent",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"peerDependencies": {
|
|
86
86
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
87
87
|
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.8 <0.2.0",
|
|
88
|
-
"@deepseek-ai/schemastery": "
|
|
88
|
+
"@deepseek-ai/schemastery": "^3.18.0"
|
|
89
89
|
},
|
|
90
90
|
"scripts": {
|
|
91
91
|
"test": "node --test \"test/*.test.mjs\"",
|