dsh-claude-move 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.es.md +9 -3
- package/README.hi.md +9 -3
- package/README.md +9 -3
- package/README.pt.md +9 -3
- package/README.zh.md +9 -3
- package/index.mjs +281 -30
- package/package.json +19 -18
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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.4.1] - 2026-09-04
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Align the `@deepseek-ai/dsh-*` devDependency pins and the `dshWorkshop.compatibility.dshVersions` list from `0.1.2-alpha.5` to the published `0.1.2-rc.1` line (all 15 packages verified on the registry), and regenerate both lockfiles. No behavior change.
|
|
10
|
+
|
|
11
|
+
## [0.4.0] - 2026-09-04
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Dual-baseline `sessionPersistence` runtime shim in `index.mjs` (feature-detected by API shape, never by version): the published legacy API (`create`/`append`/`readFrom`, `list()` returning headers) and the unreleased checkout handle seam (`create` returning a `SessionHandle`, `list()`/`stat()` returning snapshots) both work. On the handle path every append is followed by `flush()` (durability barrier) and a paired `close()` (single-writer ownership, released in `finally` on failure); headers are stamped with the backend's current format version (backend-declared `generationFormat.currentVersion`, falling back to a lazy `@deepseek-ai/dsh-session` `SESSION_FORMAT_VERSION` import — never hardcoded) plus an explicit `isSeeded: false`, and missing assistant model sources fall back to the provider string (the checkout backend refuses to read v-mismatched headers or empty model sources — found and fixed by checkout round-trip verification). The legacy path keeps the exact 0.3.x call sequence with no normalization. `lib/` is untouched.
|
|
16
|
+
- Guard against silently clearing `imports.json`: `annotateImports` now resolves `header.id` from both header and snapshot list elements, and throws loudly (skipping `cleanStale`) when an element cannot be resolved — on the handle baseline the old code read `header.id` as `undefined` and `cleanStale` would have wiped every mapping.
|
|
17
|
+
- Dual test suites: a handle-shape `sessionPersistence` mock (create→handle with `read`/`append`/`flush`/`close`, `open(id, access)`, snapshot `list()`) covering the full import/incremental/force/streamed/export flows, paired close (no handle leaks), single-writer conflict loud failures, flush ordering, and header/model normalization; plus legacy-shape regression tests for the mis-clear mapping guard.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `compat.yml` profile pins moved from `0.1.1-rc.2` to `0.1.2-rc.1`, with a workflow note that no published version carries the handle seam, so the handle path can only be verified against a local checkout.
|
|
22
|
+
|
|
23
|
+
### Notes
|
|
24
|
+
|
|
25
|
+
- No data-format changes: `index.json`/`imports.json`/`move.json` caches and imported session logs stay interoperable; no re-import needed; the Config schema is unchanged.
|
|
26
|
+
|
|
5
27
|
## [0.3.3] - 2026-09-02
|
|
6
28
|
|
|
7
29
|
### Changed
|
package/README.es.md
CHANGED
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
|
|
24
24
|
## Compatibilidad
|
|
25
25
|
|
|
26
|
-
- Dirigido a `dsh 0.1.2-
|
|
27
|
-
0.1.2-
|
|
26
|
+
- Dirigido a `dsh 0.1.2-rc.1` (perfil web); las dependencias peer requieren `>=0.1.0-rc.8 <0.2.0`. Node `^22.19 || >=24`.
|
|
27
|
+
`0.1.2-rc.1` (adaptado el 2026-09-04): el sobre de sesión conserva su campo ignorable solo para compatibilidad de lectura de logs almacenados - Session.append aún no puede estamparlo, por lo que el comportamiento de la puerta no cambia. La versión 0.4.0 también funciona contra el HEAD de master/checkout aún no publicado (costura de persistencia SessionHandle) mediante un shim de doble línea base en tiempo de ejecución; véase Compatibilidad.
|
|
28
|
+
- 0.4.0 incluye un shim de doble línea base para `sessionPersistence` en tiempo de ejecución, detectado por la forma de la API (nunca por versión): funcionan tanto la API heredada publicada (`create`/`append`/`readFrom`, `list()` devolviendo cabeceras) como la costura de handle del checkout aún no publicado (`create` devolviendo un `SessionHandle`, `list()`/`stat()` devolviendo instantáneas). En la ruta handle, cada append va seguido de `flush()` (barrera de durabilidad) y un `close()` emparejado (propiedad de escritor único); las cabeceras se sellan con la versión de formato actual del backend y un `isSeeded` explícito, y las fuentes de modelo de asistente ausentes caen al provider — verificado contra el backend real 0.1.3-alpha.1 del checkout. La limpieza del escaneo de importación se niega a ejecutarse cuando el `header.id` de un elemento listado no se puede resolver, por lo que `imports.json` nunca se vacía en silencio. Ninguna versión publicada lleva la costura de handle, así que la ruta handle solo se verifica contra un checkout local (el workflow de compatibilidad cubre la línea publicada).
|
|
28
29
|
- Última verificación con una instalación nueva desde tarball: escaneo real, importación por lotes real (reimportación idempotente), adjuntado al workspace y artefactos de persistencia confirmados; macOS/Linux cubiertos por la matriz de CI.
|
|
29
30
|
|
|
30
31
|
### Matriz de compatibilidad (solo costuras públicas)
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
| Superficie | Uso | Respaldo cuando falta |
|
|
33
34
|
|---|---|---|
|
|
34
35
|
| Servicios de host (`tools` / `sessionPersistence` / `workspaceRegistry` / `commands` / `systemPrompt` / `skills` / `webServer`) | obligatorio donde se indica | los servicios opcionales se registran reactivamente; la falta de `fs` falla en voz alta |
|
|
35
|
-
| `sessionPersistence
|
|
36
|
+
| `sessionPersistence` doble línea base: heredada `listSnapshots` / `readFrom` / `append` frente a handle `open` / `stat` / `list()` de instantáneas | detectado por característica en tiempo de ejecución (forma de API, nunca versión) | la guarda de resolución de `header.id` aborta el escaneo en voz alta en vez de vaciar `imports.json` en silencio |
|
|
37
|
+
| `fs` con capacidad `streamText` / `ctx.jobs` / `ctx.agents.resume` | detectado por característica | lectura de archivo completo con rechazo en voz alta / mapa de jobs propio / inyección de traspaso |
|
|
36
38
|
| Servicios de shell del cliente (`sessions.refresh/open`, `workspaces.refresh`) | detectado por característica al aplicar el panel | recarga completa de la página |
|
|
37
39
|
| Las capacidades de plataforma más nuevas nunca son requisitos estrictos — el plugin sigue arrancando en rc.8. | | |
|
|
38
40
|
|
|
@@ -308,6 +310,10 @@ Este proyecto es uno de los [33 complementos de DeepSeek Harness](https://github
|
|
|
308
310
|
| **[dsh-dsh-test-drive](https://github.com/PerryLink/dsh-dsh-test-drive)** | Pruebas de instalación y humo aisladas para plugins de DeepSeek Harness. | |
|
|
309
311
|
| **[dsh-dsh-translate](https://github.com/PerryLink/dsh-dsh-translate)** | Traducción de parámetros entre proveedores y reparación determinista de JSON para DeepSeek Harness. | |
|
|
310
312
|
|
|
313
|
+
### Instalar desde el mercado de DSH Desktop
|
|
314
|
+
|
|
315
|
+
Todos los plugins de PerryLink pueden explorarse en el mercado integrado de DSH Desktop: **Market → Sources → add source → pegar** `https://perrylink-dsh-catalog.perrylink.workers.dev/catalog-source.json` **→ seleccionarlo**. La instalación sigue pasando por la verificación de identidad npm del mercado y tu confirmación.
|
|
316
|
+
|
|
311
317
|
## Licencia
|
|
312
318
|
|
|
313
319
|
[Apache License 2.0](LICENSE) © 2026 dsh-claude-move contributors
|
package/README.hi.md
CHANGED
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
|
|
24
24
|
## अनुकूलता
|
|
25
25
|
|
|
26
|
-
- `dsh 0.1.2-
|
|
27
|
-
0.1.2-
|
|
26
|
+
- `dsh 0.1.2-rc.1` (web profile) को लक्षित करता है; peer निर्भरताएँ `>=0.1.0-rc.8 <0.2.0` चाहिए। Node `^22.19 || >=24`।
|
|
27
|
+
`0.1.2-rc.1` (2026-09-04 को अनुकूलित): सत्र लिफ़ाफ़ा अपना ignorable फ़ील्ड केवल संग्रहीत-लॉग पठन संगतता के लिए रखता है - Session.append अभी भी इसे स्टैम्प नहीं कर सकता, इसलिए गेट व्यवहार अपरिवर्तित है। संस्करण 0.4.0 रनटाइम दोहरी-बेसलाइन shim के ज़रिए अप्रकाशित master/checkout HEAD (SessionHandle पर्सिस्टेंस सीम) के विरुद्ध भी चलता है; अनुकूलता देखें।
|
|
28
|
+
- 0.4.0 में `sessionPersistence` के लिए दोहरी-बेसलाइन रनटाइम shim शामिल है, जिसे API आकार से पहचाना जाता है (संस्करण से कभी नहीं): प्रकाशित लीगेसी API (`create`/`append`/`readFrom`, `list()` हेडर लौटाता है) और अप्रकाशित checkout की हैंडल सीम (`create` एक `SessionHandle` लौटाता है, `list()`/`stat()` स्नैपशॉट लौटाते हैं) दोनों काम करते हैं। हैंडल पथ पर हर append के बाद `flush()` (स्थायित्व अवरोध) और युग्मित `close()` (एकल-लेखक स्वामित्व) होता है; हेडर पर बैकएंड का वर्तमान प्रारूप संस्करण और स्पष्ट `isSeeded` अंकित होता है, तथा अनुपस्थित assistant model स्रोत provider पर गिरते हैं — वास्तविक 0.1.3-alpha.1 बैकएंड के विरुद्ध checkout-सत्यापित। जब किसी सूचीबद्ध तत्व का `header.id` हल नहीं हो पाता तो आयात-स्कैन की सफ़ाई चलने से मना कर देती है, इसलिए `imports.json` कभी चुपचाप खाली नहीं होता। किसी भी प्रकाशित संस्करण में हैंडल सीम नहीं है, इसलिए हैंडल पथ केवल स्थानीय checkout के विरुद्ध सत्यापित होता है (compat वर्कफ़्लो प्रकाशित लाइन को कवर करता है)।
|
|
28
29
|
- अंतिम बार एक ताज़ा tarball इंस्टॉल के विरुद्ध सत्यापित: वास्तविक स्कैन, वास्तविक बैच आयात (आइडेम्पोटेंट पुनः-आयात), workspace जुड़ाव और पर्सिस्टेंस आर्टिफैक्ट की पुष्टि; macOS/Linux CI मैट्रिक्स द्वारा कवर हैं।
|
|
29
30
|
|
|
30
31
|
### अनुकूलता मैट्रिक्स (केवल सार्वजनिक सीम)
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
| सतह | उपयोग | अनुपस्थित होने पर फ़ॉलबैक |
|
|
33
34
|
|---|---|---|
|
|
34
35
|
| Host सेवाएँ (`tools` / `sessionPersistence` / `workspaceRegistry` / `commands` / `systemPrompt` / `skills` / `webServer`) | जहाँ सूचीबद्ध वहाँ आवश्यक | वैकल्पिक सेवाएँ प्रतिक्रियात्मक रूप से पंजीकृत होती हैं; `fs` अनुपस्थित होने पर ज़ोर से विफल |
|
|
35
|
-
| `sessionPersistence
|
|
36
|
+
| `sessionPersistence` दोहरी बेसलाइन: लीगेसी `listSnapshots` / `readFrom` / `append` बनाम हैंडल `open` / `stat` / स्नैपशॉट `list()` | रनटाइम पर फ़ीचर-डिटेक्टेड (API आकार, संस्करण कभी नहीं) | `header.id` रिज़ॉल्यूशन गार्ड स्कैन को ज़ोर से रोक देता है, `imports.json` चुपचाप खाली करने के बजाय |
|
|
37
|
+
| `streamText`-सक्षम `fs` / `ctx.jobs` / `ctx.agents.resume` | फ़ीचर-डिटेक्टेड | पूरी फ़ाइल पढ़ना और ज़ोर से अस्वीकार / स्वयं का job map / हैंडऑफ़ इंजेक्ट |
|
|
36
38
|
| Client shell सेवाएँ (`sessions.refresh/open`, `workspaces.refresh`) | पैनल apply पर फ़ीचर-डिटेक्टेड | पूर्ण-पृष्ठ रीलोड |
|
|
37
39
|
| नई प्लेटफ़ॉर्म क्षमताएँ कभी कठोर आवश्यकताएँ नहीं होतीं — प्लगइन rc.8 पर बूट होने योग्य रहता है। | | |
|
|
38
40
|
|
|
@@ -308,6 +310,10 @@ CI GitHub Actions ([test.yml](.github/workflows/test.yml)) के माध्
|
|
|
308
310
|
| **[dsh-dsh-test-drive](https://github.com/PerryLink/dsh-dsh-test-drive)** | DeepSeek Harness प्लगिनों के लिए पृथक इंस्टॉल-एंड-स्मोक टेस्ट ड्राइव। | |
|
|
309
311
|
| **[dsh-dsh-translate](https://github.com/PerryLink/dsh-dsh-translate)** | DeepSeek Harness के लिए वेंडर पैरामीटर अनुवाद और नियतात्मक JSON मरम्मत। | |
|
|
310
312
|
|
|
313
|
+
### DSH Desktop मार्केट से इंस्टॉल करें
|
|
314
|
+
|
|
315
|
+
सभी PerryLink प्लगइन DSH Desktop के बिल्ट-इन मार्केट में देखे जा सकते हैं: **Market → Sources → add source → पेस्ट करें** `https://perrylink-dsh-catalog.perrylink.workers.dev/catalog-source.json` **→ चुनें**। इंस्टॉलेशन मार्केट के npm-identity सत्यापन और आपकी पुष्टि से ही होता है।
|
|
316
|
+
|
|
311
317
|
## लाइसेंस
|
|
312
318
|
|
|
313
319
|
[Apache License 2.0](LICENSE) © 2026 dsh-claude-move contributors
|
package/README.md
CHANGED
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
|
|
25
25
|
## Compatibility
|
|
26
26
|
|
|
27
|
-
- Targets `dsh 0.1.2-
|
|
28
|
-
0.1.2-
|
|
27
|
+
- Targets `dsh 0.1.2-rc.1` (web profile); peer dependencies require `>=0.1.0-rc.8 <0.2.0`. Node `^22.19 || >=24`.
|
|
28
|
+
`0.1.2-rc.1` (adapted 2026-09-04): the session envelope keeps its ignorable field for stored-log read compatibility only - Session.append still cannot stamp it, so audit-gate behavior is unchanged. Version 0.4.0 also runs against the unreleased master/checkout HEAD (SessionHandle persistence seam) through a runtime dual-baseline shim; see Compatibility.
|
|
29
|
+
- 0.4.0 ships a dual-baseline `sessionPersistence` runtime shim, feature-detected by API shape (never by version): the published legacy API (`create`/`append`/`readFrom`, `list()` returning headers) and the unreleased checkout handle seam (`create` returning a `SessionHandle`, `list()`/`stat()` returning snapshots) both work. On the handle path every append is followed by `flush()` (durability barrier) and a paired `close()` (single-writer ownership); headers are stamped with the backend's current format version plus an explicit `isSeeded`, and missing assistant model sources fall back to the provider — checkout-verified against the real 0.1.3-alpha.1 backend. Import-scan cleanup refuses to run when a listed element's `header.id` cannot be resolved, so `imports.json` is never silently cleared. No published version carries the handle seam, so the handle path is verified against a local checkout only (the compat workflow covers the published line).
|
|
29
30
|
- Last verified against a fresh tarball install: real scan, real batch import (idempotent re-import), workspace attach and persistence artifacts confirmed; macOS/Linux covered by the CI matrix.
|
|
30
31
|
|
|
31
32
|
### Compatibility matrix (public seams only)
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
| Surface | Used | Fallback when absent |
|
|
34
35
|
|---|---|---|
|
|
35
36
|
| Host services (`tools` / `sessionPersistence` / `workspaceRegistry` / `commands` / `systemPrompt` / `skills` / `webServer`) | required where listed | optional services register reactively; missing `fs` fails loud |
|
|
36
|
-
| `sessionPersistence
|
|
37
|
+
| `sessionPersistence` dual baseline: legacy `listSnapshots` / `readFrom` / `append` vs handle `open` / `stat` / snapshot `list()` | feature-detected at runtime (API shape, never version) | the `header.id` resolution guard aborts the scan loudly instead of silently clearing `imports.json` |
|
|
38
|
+
| `streamText`-capable `fs` / `ctx.jobs` / `ctx.agents.resume` | feature-detected | whole-file read with loud rejection / own job map / handoff inject |
|
|
37
39
|
| Client shell services (`sessions.refresh/open`, `workspaces.refresh`) | feature-detected at panel apply | full-page reload |
|
|
38
40
|
| Newer platform capabilities are never hard requirements — the plugin stays bootable on rc.8. | | |
|
|
39
41
|
|
|
@@ -309,6 +311,10 @@ This project is one of the [33 DeepSeek Harness plugins](https://github.com/Perr
|
|
|
309
311
|
| **[dsh-dsh-test-drive](https://github.com/PerryLink/dsh-dsh-test-drive)** | Isolated install-and-smoke test drives for DeepSeek Harness plugins. | |
|
|
310
312
|
| **[dsh-dsh-translate](https://github.com/PerryLink/dsh-dsh-translate)** | Vendor parameter translation and deterministic JSON repair for DeepSeek Harness. | |
|
|
311
313
|
|
|
314
|
+
### Install from the DSH Desktop Market
|
|
315
|
+
|
|
316
|
+
All PerryLink plugins are browsable in the built-in DSH Desktop Market: **Market → Sources → add source → paste** `https://perrylink-dsh-catalog.perrylink.workers.dev/catalog-source.json` **→ select it**. Installation still goes through the Market's npm-identity verification and your confirmation.
|
|
317
|
+
|
|
312
318
|
## License
|
|
313
319
|
|
|
314
320
|
[Apache License 2.0](LICENSE) © 2026 dsh-claude-move contributors
|
package/README.pt.md
CHANGED
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
|
|
24
24
|
## Compatibilidade
|
|
25
25
|
|
|
26
|
-
- Direcionado a `dsh 0.1.2-
|
|
27
|
-
0.1.2-
|
|
26
|
+
- Direcionado a `dsh 0.1.2-rc.1` (perfil web); dependências peer exigem `>=0.1.0-rc.8 <0.2.0`. Node `^22.19 || >=24`.
|
|
27
|
+
`0.1.2-rc.1` (adaptado em 2026-09-04): o envelope de sessão mantém seu campo ignorable apenas para compatibilidade de leitura de logs armazenados - o Session.append ainda não consegue estampá-lo, então o comportamento da porta não muda. A versão 0.4.0 também roda contra o HEAD não publicado de master/checkout (costura de persistência SessionHandle) por meio de um shim de linha de base dupla em tempo de execução; veja Compatibilidade.
|
|
28
|
+
- A 0.4.0 inclui um shim de linha de base dupla para `sessionPersistence` em tempo de execução, detectado pela forma da API (nunca pela versão): funcionam tanto a API legada publicada (`create`/`append`/`readFrom`, `list()` retornando cabeçalhos) quanto a costura de handle do checkout ainda não publicado (`create` retornando um `SessionHandle`, `list()`/`stat()` retornando snapshots). No caminho handle, todo append é seguido de `flush()` (barreira de durabilidade) e um `close()` pareado (propriedade de escritor único); os cabeçalhos são carimbados com a versão de formato atual do backend e um `isSeeded` explícito, e fontes de modelo de assistente ausentes caem para o provider — verificado contra o backend real 0.1.3-alpha.1 do checkout. A limpeza da varredura de importação se recusa a executar quando o `header.id` de um elemento listado não pode ser resolvido, então `imports.json` nunca é esvaziado silenciosamente. Nenhuma versão publicada tem a costura de handle, então o caminho handle só é verificado contra um checkout local (o fluxo de compatibilidade cobre a linha publicada).
|
|
28
29
|
- Última verificação contra uma instalação nova de tarball: varredura real, importação em lote real (reimportação idempotente), anexo ao workspace e artefatos de persistência confirmados; macOS/Linux cobertos pela matriz de CI.
|
|
29
30
|
|
|
30
31
|
### Matriz de compatibilidade (somente costuras públicas)
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
| Superfície | Uso | Fallback quando ausente |
|
|
33
34
|
|---|---|---|
|
|
34
35
|
| Serviços de host (`tools` / `sessionPersistence` / `workspaceRegistry` / `commands` / `systemPrompt` / `skills` / `webServer`) | obrigatório onde listado | serviços opcionais registram reativamente; `fs` ausente falha em voz alta |
|
|
35
|
-
| `sessionPersistence
|
|
36
|
+
| `sessionPersistence` linha de base dupla: legada `listSnapshots` / `readFrom` / `append` versus handle `open` / `stat` / `list()` de snapshots | detectado por recurso em tempo de execução (forma da API, nunca versão) | a guarda de resolução de `header.id` aborta a varredura em voz alta em vez de esvaziar `imports.json` silenciosamente |
|
|
37
|
+
| `fs` com capacidade `streamText` / `ctx.jobs` / `ctx.agents.resume` | detectado por recurso | leitura de arquivo inteiro com rejeição em voz alta / mapa de jobs próprio / injeção de handoff |
|
|
36
38
|
| Serviços de shell do cliente (`sessions.refresh/open`, `workspaces.refresh`) | detectado por recurso ao aplicar o painel | recarga completa da página |
|
|
37
39
|
| Capacidades de plataforma mais novas nunca são requisitos rígidos — o plugin continua inicializável no rc.8. | | |
|
|
38
40
|
|
|
@@ -308,6 +310,10 @@ Este projeto é um dos [33 plugins de DeepSeek Harness](https://github.com/Perry
|
|
|
308
310
|
| **[dsh-dsh-test-drive](https://github.com/PerryLink/dsh-dsh-test-drive)** | Test drives isolados de instalação e smoke para plugins de DeepSeek Harness. | |
|
|
309
311
|
| **[dsh-dsh-translate](https://github.com/PerryLink/dsh-dsh-translate)** | Tradução de parâmetros entre fornecedores e reparo determinístico de JSON para DeepSeek Harness. | |
|
|
310
312
|
|
|
313
|
+
### Instalar a partir do mercado do DSH Desktop
|
|
314
|
+
|
|
315
|
+
Todos os plugins PerryLink podem ser explorados no mercado integrado do DSH Desktop: **Market → Sources → add source → colar** `https://perrylink-dsh-catalog.perrylink.workers.dev/catalog-source.json` **→ selecionar**. A instalação continua passando pela verificação de identidade npm do mercado e pela sua confirmação.
|
|
316
|
+
|
|
311
317
|
## Licença
|
|
312
318
|
|
|
313
319
|
[Apache License 2.0](LICENSE) © 2026 dsh-claude-move contributors
|
package/README.zh.md
CHANGED
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
|
|
24
24
|
## 兼容性
|
|
25
25
|
|
|
26
|
-
- 面向 `dsh 0.1.2-
|
|
27
|
-
0.1.2-
|
|
26
|
+
- 面向 `dsh 0.1.2-rc.1`(web profile);peer 依赖要求 `>=0.1.0-rc.8 <0.2.0`。Node `^22.19 || >=24`。
|
|
27
|
+
`0.1.2-rc.1`(2026-09-04 已适配):会话信封保留 ignorable 字段但仅用于存量日志读取兼容——Session.append 仍无法盖章,门控行为不变。0.4.0 版还通过运行时双基线 shim 兼容未发布的 master/checkout HEAD(SessionHandle 持久化接缝);见「兼容性」。
|
|
28
|
+
- 0.4.0 内置 `sessionPersistence` 双基线运行时 shim,按 API 形状特性探测(绝不按版本号猜测):已发布旧 API(`create`/`append`/`readFrom`,`list()` 返回会话头)与未发布 checkout 的 handle seam(`create` 返回 `SessionHandle`,`list()`/`stat()` 返回快照)都能工作。handle 路径上每次 append 之后必须 `flush()`(耐久屏障)并成对 `close()`(单写所有权);header 盖上后端当前格式版本并显式补 `isSeeded`,缺失的 assistant model 来源回退 provider——已对照真实 0.1.3-alpha.1 后端做 checkout 实测。导入扫描的清理在列表元素的 `header.id` 无法解析时拒绝执行,绝不静默清空 `imports.json`。任何已发布版本都没有 handle seam,因此 handle 路径只能对照本地 checkout 验证(compat workflow 覆盖已发布线)。
|
|
28
29
|
- 最近针对全新 tarball 安装验证:真实扫描、真实批量导入(幂等重导入)、工作区挂载与持久化产物均已确认;macOS/Linux 由 CI 矩阵覆盖。
|
|
29
30
|
|
|
30
31
|
### 兼容性矩阵(仅公开接缝)
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
| 接缝 | 使用 | 缺失时的回退 |
|
|
33
34
|
|---|---|---|
|
|
34
35
|
| Host 服务(`tools` / `sessionPersistence` / `workspaceRegistry` / `commands` / `systemPrompt` / `skills` / `webServer`) | 所列之处必需 | 可选服务响应式注册;缺失 `fs` 大声失败 |
|
|
35
|
-
| `sessionPersistence
|
|
36
|
+
| `sessionPersistence` 双基线:旧 API(`listSnapshots` / `readFrom` / `append`)或 handle(`open` / `stat`、快照 `list()`) | 运行时按 API 形状特性探测 | `header.id` 解析守卫:解析失败大声中止扫描,绝不静默清空 `imports.json` |
|
|
37
|
+
| 支持 `streamText` 的 `fs` / `ctx.jobs` / `ctx.agents.resume` | 特性探测 | 整文件读取并大声拒绝 / 自有 job map / 交接注入 |
|
|
36
38
|
| Client shell 服务(`sessions.refresh/open`、`workspaces.refresh`) | 面板 apply 时特性探测 | 整页刷新 |
|
|
37
39
|
| 较新的平台能力从不是硬性要求 —— 插件在 rc.8 上始终保持可启动。 | | |
|
|
38
40
|
|
|
@@ -308,6 +310,10 @@ CI 通过 GitHub Actions([test.yml](.github/workflows/test.yml))在 Linux/ma
|
|
|
308
310
|
| **[dsh-dsh-test-drive](https://github.com/PerryLink/dsh-dsh-test-drive)** | DeepSeek Harness 插件的隔离试装冒烟。 | |
|
|
309
311
|
| **[dsh-dsh-translate](https://github.com/PerryLink/dsh-dsh-translate)** | DeepSeek Harness 的厂商参数翻译与确定性 JSON 修复。 | |
|
|
310
312
|
|
|
313
|
+
### 从 DSH Desktop 市场安装
|
|
314
|
+
|
|
315
|
+
所有 PerryLink 插件均可在 DSH Desktop 内置市场中浏览:**市场 → 来源 → 添加来源 → 粘贴** `https://perrylink-dsh-catalog.perrylink.workers.dev/catalog-source.json` **→ 选中**。安装仍需通过市场的 npm 身份校验与你的确认。
|
|
316
|
+
|
|
311
317
|
## 许可证
|
|
312
318
|
|
|
313
319
|
[Apache License 2.0](LICENSE) © 2026 dsh-claude-move contributors
|
package/index.mjs
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
//
|
|
11
11
|
// 只消费公开服务:tools / systemPrompt / skills / sessionPersistence /
|
|
12
12
|
// workspaceRegistry / commands / webServer / approval(后几者经 ctx.get 可选读取)。
|
|
13
|
+
// sessionPersistence 走 0.4.0 双基线运行时 shim(handle 基线 / 旧 API,见下方
|
|
14
|
+
// "双基线运行时 shim" 段),任何已发布 dsh 与 checkout HEAD 均可工作。
|
|
13
15
|
// 源文件只读,缓存只写 resolveCacheDir();迁移落点只写 $DSH_HOME/skills、
|
|
14
16
|
// $DSH_HOME/AGENTS.md 与 imports 工作区目录。
|
|
15
17
|
|
|
@@ -63,6 +65,205 @@ export const name = 'claude-move'
|
|
|
63
65
|
|
|
64
66
|
export const inject = ['tools']
|
|
65
67
|
|
|
68
|
+
// ── sessionPersistence 双基线运行时 shim(0.4.0)──────────────────────────────
|
|
69
|
+
//
|
|
70
|
+
// 宿主 master(0.1.3-alpha.1,bec6805d6a refactor)把 sessionPersistence 改成
|
|
71
|
+
// handle 化:create 返回 SessionHandle(read/append/flush/close;close 释放
|
|
72
|
+
// 单写所有权),服务级 append/readFrom/locate/listSnapshots 移除,list()/stat()
|
|
73
|
+
// 返回快照对象({ header, revision, eventCount?, sizeBytes? })。任何已发布
|
|
74
|
+
// 版本(≤0.1.2-rc.1)都没有该 seam,仍是旧 API:create 返回 void、服务级
|
|
75
|
+
// append/readFrom、list() 返回 SessionHeader、listSnapshots 返回 { header }。
|
|
76
|
+
// 两个基线一律按运行时 API 形状探测(create 返回值 / 服务成员 / list 元素),
|
|
77
|
+
// 绝不按版本号猜测。handle 路径上每次 append 后必须 flush()(耐久屏障)并
|
|
78
|
+
// 成对 close()(单写所有权),header/事件经 normalizeHandleHeader /
|
|
79
|
+
// normalizeHandleEvents 规范化到后端当前格式(checkout 实测:版本戳与
|
|
80
|
+
// isSeeded 缺一不可,model source 必须非空);旧路径的调用序列与 0.3.x
|
|
81
|
+
// 逐字节一致,不做任何规范化。
|
|
82
|
+
|
|
83
|
+
const persistenceShapeCache = new WeakMap()
|
|
84
|
+
|
|
85
|
+
/** create 返回值是否 SessionHandle(handle 基线的最权威判定)。 */
|
|
86
|
+
function isSessionHandle(value) {
|
|
87
|
+
return value !== null && typeof value === 'object'
|
|
88
|
+
&& typeof value.read === 'function'
|
|
89
|
+
&& typeof value.append === 'function'
|
|
90
|
+
&& typeof value.flush === 'function'
|
|
91
|
+
&& typeof value.close === 'function'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 探测服务基线(API 形状,非版本号):有 open(旧 API 无)→ handle;
|
|
96
|
+
* 有 readFrom/append(handle 化后均无)→ legacy;其余未知。create 实测结果
|
|
97
|
+
* 可经 notePersistenceShape 覆写缓存。
|
|
98
|
+
* @param sp - sessionPersistence 服务。
|
|
99
|
+
* @returns 'handle' | 'legacy' | null。
|
|
100
|
+
*/
|
|
101
|
+
function detectPersistenceShape(sp) {
|
|
102
|
+
if (!sp) return null
|
|
103
|
+
const cached = persistenceShapeCache.get(sp)
|
|
104
|
+
if (cached !== undefined) return cached
|
|
105
|
+
if (typeof sp.open === 'function') return 'handle'
|
|
106
|
+
if (typeof sp.readFrom === 'function' || typeof sp.append === 'function') return 'legacy'
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 记录实测基线(create 返回值判定优先,供后续 append/read 调用复用)。 */
|
|
111
|
+
function notePersistenceShape(sp, shape) {
|
|
112
|
+
if (sp && (shape === 'handle' || shape === 'legacy')) persistenceShapeCache.set(sp, shape)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** 列表元素无法解析 header.id 的哨兵错误:annotateImports 用它响亮失败并跳过清理。 */
|
|
116
|
+
class StoredSessionIdResolutionError extends Error {}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 从 list()/listSnapshots() 元素解析存储会话 id:handle 基线的快照对象取
|
|
120
|
+
* header.id,旧基线的 SessionHeader 取 id。两者都解析不出 → null。
|
|
121
|
+
* @param element - 列表元素。
|
|
122
|
+
* @returns 会话 id 或 null。
|
|
123
|
+
*/
|
|
124
|
+
function storedSessionIdOf(element) {
|
|
125
|
+
if (element !== null && typeof element === 'object') {
|
|
126
|
+
if (element.header !== null && typeof element.header === 'object'
|
|
127
|
+
&& typeof element.header.id === 'string' && element.header.id.length > 0) {
|
|
128
|
+
return element.header.id
|
|
129
|
+
}
|
|
130
|
+
if (typeof element.id === 'string' && element.id.length > 0) return element.id
|
|
131
|
+
}
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 列表元素序列 → id 数组。任一元素无法解析 header.id 即响亮抛出——这是
|
|
137
|
+
* cleanStale 误清空防线:id 集合必须完整可解析,绝不允许把 undefined 塞进
|
|
138
|
+
* 集合让「映射指向已不存在会话」全量误判。
|
|
139
|
+
* @param elements - list()/listSnapshots() 返回的元素序列。
|
|
140
|
+
* @returns id 数组。
|
|
141
|
+
*/
|
|
142
|
+
function idsOfListedElements(elements) {
|
|
143
|
+
return elements.map((element) => {
|
|
144
|
+
const id = storedSessionIdOf(element)
|
|
145
|
+
if (id === null) {
|
|
146
|
+
throw new StoredSessionIdResolutionError(
|
|
147
|
+
'sessionPersistence 列表元素无法解析 header.id(既非 SessionHeader 也非快照对象):'
|
|
148
|
+
+ 'claude-move 拒绝继续,防止 imports.json 导入映射被误清空')
|
|
149
|
+
}
|
|
150
|
+
return id
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 读一份已存储会话的完整事件与头(双基线):handle 路径 open(id, 'read') →
|
|
156
|
+
* read(0) → close(读句柄绝不持有写所有权);旧路径 readFrom(id, 0)。
|
|
157
|
+
* 基线无法判定返回 null;会话不存在时抛出后端错误。
|
|
158
|
+
* @param sp - sessionPersistence 服务。
|
|
159
|
+
* @param id - 会话 id。
|
|
160
|
+
* @returns `{ meta, events }` 或 null。
|
|
161
|
+
*/
|
|
162
|
+
async function readStoredEvents(sp, id) {
|
|
163
|
+
const shape = detectPersistenceShape(sp)
|
|
164
|
+
if (shape === 'handle') {
|
|
165
|
+
const handle = await sp.open(id, 'read')
|
|
166
|
+
try {
|
|
167
|
+
const events = await handle.read(0)
|
|
168
|
+
return { meta: handle.header, events }
|
|
169
|
+
} finally {
|
|
170
|
+
await handle.close()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (shape === 'legacy' && typeof sp.readFrom === 'function') {
|
|
174
|
+
const suffix = await sp.readFrom(id, 0)
|
|
175
|
+
return { meta: suffix?.meta, events: suffix?.events }
|
|
176
|
+
}
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// handle 基线 header/事件规范化(0.4.0 checkout 实测):checkout 后端给所有
|
|
181
|
+
// 产物盖当前格式版本的文件名,并要求 header.version 等于后端当前
|
|
182
|
+
// SESSION_FORMAT_VERSION、isSeeded 显式给出;读取时还会按当前格式语义校验
|
|
183
|
+
// assistant/message 的 model source 必须是非空字符串。旧基线不需要这些,
|
|
184
|
+
// 规范化只发生在 handle 路径。
|
|
185
|
+
|
|
186
|
+
const handleFormatVersionCache = new WeakMap()
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 惰性读取 handle 基线的当前会话格式版本(绝无硬编码、绝不按版本号猜测):
|
|
190
|
+
* 首选后端实例自报的 generationFormat.currentVersion(checkout JSONL 后端
|
|
191
|
+
* 公开字段),回退宿主 `@deepseek-ai/dsh-session` 的 SESSION_FORMAT_VERSION
|
|
192
|
+
* 导出。两者都不可得返回 null(header 保持原样,后端读取时响亮拒绝,绝不
|
|
193
|
+
* 静默写坏)。
|
|
194
|
+
* @param sp - sessionPersistence 服务。
|
|
195
|
+
* @returns 当前格式版本号或 null。
|
|
196
|
+
*/
|
|
197
|
+
async function currentHandleFormatVersion(sp) {
|
|
198
|
+
const cached = handleFormatVersionCache.get(sp)
|
|
199
|
+
if (cached !== undefined) return cached
|
|
200
|
+
let version = null
|
|
201
|
+
const backend = sp?.generationFormat
|
|
202
|
+
if (backend !== null && typeof backend === 'object'
|
|
203
|
+
&& Number.isSafeInteger(backend.currentVersion) && backend.currentVersion >= 0) {
|
|
204
|
+
version = backend.currentVersion
|
|
205
|
+
} else {
|
|
206
|
+
try {
|
|
207
|
+
const sessionMod = await import('@deepseek-ai/dsh-session')
|
|
208
|
+
if (Number.isSafeInteger(sessionMod.SESSION_FORMAT_VERSION) && sessionMod.SESSION_FORMAT_VERSION >= 0) {
|
|
209
|
+
version = sessionMod.SESSION_FORMAT_VERSION
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// 宿主 session 包不可解析:保持原 header(读取时响亮失败,绝不静默)。
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
handleFormatVersionCache.set(sp, version)
|
|
216
|
+
return version
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* handle 基线 header 规范化:version 盖为后端当前格式版本;isSeeded 缺省时
|
|
221
|
+
* 显式置 false(后端序列化会丢弃 undefined,缺 key 的产物不可读)。
|
|
222
|
+
* @param sp - sessionPersistence 服务。
|
|
223
|
+
* @param meta - 原 SessionHeader。
|
|
224
|
+
* @returns 规范化后的新 header 对象(原对象不变)。
|
|
225
|
+
*/
|
|
226
|
+
async function normalizeHandleHeader(sp, meta) {
|
|
227
|
+
const header = { ...meta }
|
|
228
|
+
const version = await currentHandleFormatVersion(sp)
|
|
229
|
+
if (typeof version === 'number' && version >= 0) header.version = version
|
|
230
|
+
if (header.isSeeded === undefined) header.isSeeded = false
|
|
231
|
+
return header
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* handle 基线的事件批规范化:当前格式语义要求 assistant/message 的 model
|
|
236
|
+
* source 是非空字符串;缺 model 的源记录(真实 Claude transcript 恒有
|
|
237
|
+
* message.model,仅防御合成/历史记录)回退 provider 字符串,避免落盘出
|
|
238
|
+
* 无法读取的会话。未改动时原样返回调用方数组。
|
|
239
|
+
* @param events - 事件批。
|
|
240
|
+
* @returns 规范化后的事件批。
|
|
241
|
+
*/
|
|
242
|
+
function normalizeHandleEvents(events) {
|
|
243
|
+
let changed = false
|
|
244
|
+
const out = events.map((event) => {
|
|
245
|
+
if (event?.type !== 'assistant/message') return event
|
|
246
|
+
const source = event.data?.message?.source
|
|
247
|
+
if (source !== null && typeof source === 'object'
|
|
248
|
+
&& source.kind === 'model'
|
|
249
|
+
&& (typeof source.model !== 'string' || source.model.length === 0)) {
|
|
250
|
+
changed = true
|
|
251
|
+
return {
|
|
252
|
+
...event,
|
|
253
|
+
data: {
|
|
254
|
+
...event.data,
|
|
255
|
+
message: {
|
|
256
|
+
...event.data.message,
|
|
257
|
+
source: { ...source, model: typeof source.provider === 'string' && source.provider.length > 0 ? source.provider : 'claude-code' },
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return event
|
|
263
|
+
})
|
|
264
|
+
return changed ? out : events
|
|
265
|
+
}
|
|
266
|
+
|
|
66
267
|
/** 批量导入「读取 + 转换」阶段的默认并发上限(落盘阶段保持串行,保证幂等确定性)。 */
|
|
67
268
|
export const DEFAULT_IMPORT_CONCURRENCY = 4
|
|
68
269
|
|
|
@@ -350,7 +551,10 @@ export async function annotateSettings(index) {
|
|
|
350
551
|
|
|
351
552
|
/**
|
|
352
553
|
* 用 sessionPersistence 列表 + imports 映射标注每个会话的导入状态(F4 幂等基础)。
|
|
353
|
-
*
|
|
554
|
+
* 双基线(0.4.0):listSnapshots 优先(快照 .header.id),回退 list()——
|
|
555
|
+
* handle 基线 list() 返回快照对象(header.id),旧基线返回 SessionHeader(id),
|
|
556
|
+
* 统一经 storedSessionIdOf 解析;任一元素解析不出 header.id 时响亮抛出并
|
|
557
|
+
* 跳过 cleanStale,绝不把 undefined 计入集合导致 imports.json 被误清空。
|
|
354
558
|
* `cleanStale`(全量扫描时)惰性清理「映射指向已不存在会话」的残留记录并
|
|
355
559
|
* 报告清理条数(B4:用户在 UI 删除导入会话后映射不再残留)。
|
|
356
560
|
* @param ctx - Cordis 上下文。
|
|
@@ -366,14 +570,16 @@ export async function annotateImports(ctx, cacheDir, index, cleanStale = false)
|
|
|
366
570
|
if (sp) {
|
|
367
571
|
try {
|
|
368
572
|
if (typeof sp.listSnapshots === 'function') {
|
|
369
|
-
for (const
|
|
370
|
-
listSucceeded = true
|
|
573
|
+
for (const id of idsOfListedElements(await sp.listSnapshots())) imported.add(id)
|
|
371
574
|
} else if (typeof sp.list === 'function') {
|
|
372
|
-
for (const
|
|
373
|
-
listSucceeded = true
|
|
575
|
+
for (const id of idsOfListedElements(await sp.list())) imported.add(id)
|
|
374
576
|
}
|
|
375
|
-
|
|
577
|
+
listSucceeded = true
|
|
578
|
+
} catch (err) {
|
|
579
|
+
// 元素无法解析 header.id:响亮抛出(cleanStale 随之中止,绝不静默清空映射)。
|
|
580
|
+
if (err instanceof StoredSessionIdResolutionError) throw err
|
|
376
581
|
// 持久化不可读:全部按未导入处理,也不做清理。
|
|
582
|
+
console.error('[claude-move] sessionPersistence 列表读取失败:', String((err && err.message) || err))
|
|
377
583
|
listSucceeded = false
|
|
378
584
|
}
|
|
379
585
|
}
|
|
@@ -521,13 +727,14 @@ export function resolveImportTarget(raw, claudeHome) {
|
|
|
521
727
|
return path.resolve(expanded)
|
|
522
728
|
}
|
|
523
729
|
|
|
524
|
-
/** 已持久化会话 id 集合(批量导入一次快照,避免逐文件 O(n)
|
|
730
|
+
/** 已持久化会话 id 集合(批量导入一次快照,避免逐文件 O(n) 列表;双基线快照/头解析)。 */
|
|
525
731
|
async function listPersistedIds(ctx) {
|
|
526
732
|
const sp = ctx.get('sessionPersistence')
|
|
527
733
|
if (!sp || typeof sp.list !== 'function') return new Set()
|
|
528
734
|
try {
|
|
529
|
-
return new Set((await sp.list())
|
|
530
|
-
} catch {
|
|
735
|
+
return new Set(idsOfListedElements(await sp.list()))
|
|
736
|
+
} catch (err) {
|
|
737
|
+
if (err instanceof StoredSessionIdResolutionError) throw err
|
|
531
738
|
return new Set()
|
|
532
739
|
}
|
|
533
740
|
}
|
|
@@ -752,12 +959,12 @@ function unwrapImport(entry) {
|
|
|
752
959
|
return null
|
|
753
960
|
}
|
|
754
961
|
|
|
755
|
-
/**
|
|
962
|
+
/** 读取已存储日志的事件数(双基线 readFrom / open+read);不可用返回 null。 */
|
|
756
963
|
async function storedEventCount(ctx, dshId) {
|
|
757
964
|
const sp = ctx.get('sessionPersistence')
|
|
758
|
-
if (!sp ||
|
|
965
|
+
if (!sp || detectPersistenceShape(sp) === null) return null
|
|
759
966
|
try {
|
|
760
|
-
const read = await sp
|
|
967
|
+
const read = await readStoredEvents(sp, dshId)
|
|
761
968
|
return Array.isArray(read?.events) ? read.events.length : null
|
|
762
969
|
} catch {
|
|
763
970
|
return null
|
|
@@ -769,7 +976,8 @@ async function storedEventCount(ctx, dshId) {
|
|
|
769
976
|
* 复制式语义,绝不删除/改写既有内容:
|
|
770
977
|
* - 首次导入:目标 id 由「显式 sessionId > 源 sessionId > 文件名 slug」确定,
|
|
771
978
|
* 若目标 id 已被占用则后缀避让(import-<src>-<n>),绝不静默丢弃历史;
|
|
772
|
-
* 落盘 = create + append(append-only
|
|
979
|
+
* 落盘 = create + append(append-only;handle 基线 append 后 flush + close,
|
|
980
|
+
* 见双基线 shim),随后按 cwd 挂接工作区。
|
|
773
981
|
* - 重复导入且源文件已增长(turns 变多):把新增轮次以连续 seq 续写到同一
|
|
774
982
|
* DSH 会话(增量同步),旧事件一个字节不动。
|
|
775
983
|
* - force:为同一源文件创建一份**新的**完整副本(新 id),旧副本原样保留。
|
|
@@ -793,9 +1001,9 @@ export async function persistConverted(ctx, converted, args, persisted, sourcePa
|
|
|
793
1001
|
/** 探测同名持久化会话是否为空日志(上次 create 成功、append 失败残留);无法确定返回 false。 */
|
|
794
1002
|
async function isEmptyStoredSession(ctx, dshId) {
|
|
795
1003
|
const sp = ctx.get('sessionPersistence')
|
|
796
|
-
if (!sp ||
|
|
1004
|
+
if (!sp || detectPersistenceShape(sp) === null) return false
|
|
797
1005
|
try {
|
|
798
|
-
const read = await sp
|
|
1006
|
+
const read = await readStoredEvents(sp, dshId)
|
|
799
1007
|
return Array.isArray(read?.events) && read.events.length === 0
|
|
800
1008
|
} catch {
|
|
801
1009
|
// 读不到/读失败:按「非空」处理,走保守路径。
|
|
@@ -872,11 +1080,7 @@ async function persistConvertedInner(ctx, converted, args, persisted, sourcePath
|
|
|
872
1080
|
}
|
|
873
1081
|
const tail = tailSessionEvents(converted, { fromTurn: known.turns + 1, fromSeq })
|
|
874
1082
|
if (tail.events.length > 0) {
|
|
875
|
-
|
|
876
|
-
if (!sp || typeof sp.append !== 'function') {
|
|
877
|
-
throw new Error('会话持久化服务(sessionPersistence)不可用:claude-move 增量续写需要该服务')
|
|
878
|
-
}
|
|
879
|
-
await sp.append(knownId, tail.events)
|
|
1083
|
+
await spAppend(ctx, knownId, tail.events)
|
|
880
1084
|
}
|
|
881
1085
|
await rememberImport(ctx, sourcePath, {
|
|
882
1086
|
dshId: knownId, turns: turns.length, events: fromSeq + tail.events.length,
|
|
@@ -949,22 +1153,66 @@ async function persistConvertedInner(ctx, converted, args, persisted, sourcePath
|
|
|
949
1153
|
}
|
|
950
1154
|
}
|
|
951
1155
|
|
|
952
|
-
/**
|
|
1156
|
+
/**
|
|
1157
|
+
* append 一份事件批次(双基线);服务缺失响亮抛出。handle 路径
|
|
1158
|
+
* open(id, 'write') 独占单写所有权,append 后必须 flush()(耐久屏障)并
|
|
1159
|
+
* 成对 close()(释放所有权);事件批经 normalizeHandleEvents 规范化。
|
|
1160
|
+
* 已被他人持有时 open 响亮拒绝(单写冲突)。
|
|
1161
|
+
*/
|
|
953
1162
|
async function spAppend(ctx, id, events) {
|
|
954
1163
|
const sp = ctx.get('sessionPersistence')
|
|
955
|
-
if (!sp
|
|
1164
|
+
if (!sp) {
|
|
1165
|
+
throw new Error('会话持久化服务(sessionPersistence)不可用:claude-move 导入需要该服务')
|
|
1166
|
+
}
|
|
1167
|
+
if (detectPersistenceShape(sp) === 'handle') {
|
|
1168
|
+
if (typeof sp.open !== 'function') {
|
|
1169
|
+
throw new Error('会话持久化服务(sessionPersistence)不可用:claude-move 增量续写需要该服务')
|
|
1170
|
+
}
|
|
1171
|
+
const handle = await sp.open(id, 'write')
|
|
1172
|
+
try {
|
|
1173
|
+
await handle.append(normalizeHandleEvents(events))
|
|
1174
|
+
await handle.flush()
|
|
1175
|
+
} finally {
|
|
1176
|
+
await handle.close()
|
|
1177
|
+
}
|
|
1178
|
+
return
|
|
1179
|
+
}
|
|
1180
|
+
if (typeof sp.append !== 'function') {
|
|
956
1181
|
throw new Error('会话持久化服务(sessionPersistence)不可用:claude-move 导入需要该服务')
|
|
957
1182
|
}
|
|
958
1183
|
await sp.append(id, events)
|
|
959
1184
|
}
|
|
960
1185
|
|
|
961
|
-
/**
|
|
1186
|
+
/**
|
|
1187
|
+
* create + append 一份完整会话日志(双基线);服务缺失/落盘失败响亮抛出。
|
|
1188
|
+
* handle 路径:按服务形状先探测(有 open 即 handle),create 前经
|
|
1189
|
+
* normalizeHandleHeader 盖当前格式版本与 isSeeded(checkout 后端要求),
|
|
1190
|
+
* create 返回值实测判定(isSessionHandle),append 后 flush() 并在 finally
|
|
1191
|
+
* 成对 close()——任何失败路径都释放单写所有权;空事件批次(流式导入的首个
|
|
1192
|
+
* create)只 flush 物化空会话。旧路径 create+append 调用序列与 0.3.x 逐字节
|
|
1193
|
+
* 一致(header/事件不做任何规范化)。
|
|
1194
|
+
*/
|
|
962
1195
|
async function spPersist(ctx, meta, events) {
|
|
963
1196
|
const sp = ctx.get('sessionPersistence')
|
|
964
|
-
if (!sp || typeof sp.create !== 'function'
|
|
1197
|
+
if (!sp || typeof sp.create !== 'function') {
|
|
1198
|
+
throw new Error('会话持久化服务(sessionPersistence)不可用:claude-move 导入需要该服务')
|
|
1199
|
+
}
|
|
1200
|
+
const header = detectPersistenceShape(sp) === 'handle' ? await normalizeHandleHeader(sp, meta) : meta
|
|
1201
|
+
const created = await sp.create(header)
|
|
1202
|
+
if (isSessionHandle(created)) {
|
|
1203
|
+
notePersistenceShape(sp, 'handle')
|
|
1204
|
+
try {
|
|
1205
|
+
if (events.length > 0) await created.append(normalizeHandleEvents(events))
|
|
1206
|
+
await created.flush()
|
|
1207
|
+
} finally {
|
|
1208
|
+
await created.close()
|
|
1209
|
+
}
|
|
1210
|
+
return
|
|
1211
|
+
}
|
|
1212
|
+
notePersistenceShape(sp, 'legacy')
|
|
1213
|
+
if (typeof sp.append !== 'function') {
|
|
965
1214
|
throw new Error('会话持久化服务(sessionPersistence)不可用:claude-move 导入需要该服务')
|
|
966
1215
|
}
|
|
967
|
-
await sp.create(meta)
|
|
968
1216
|
await sp.append(meta.id, events)
|
|
969
1217
|
}
|
|
970
1218
|
|
|
@@ -1580,9 +1828,9 @@ export function resolveExportTarget(raw, sessionId, config) {
|
|
|
1580
1828
|
}
|
|
1581
1829
|
|
|
1582
1830
|
/**
|
|
1583
|
-
* 执行一次回迁导出:读 DSH 会话日志(sessionPersistence
|
|
1584
|
-
* Claude 可 resume JSONL →
|
|
1585
|
-
*
|
|
1831
|
+
* 执行一次回迁导出:读 DSH 会话日志(sessionPersistence 双基线 readFrom /
|
|
1832
|
+
* open+read)→ 反向折叠为 Claude 可 resume JSONL → 写入目标文件。只读源会话、
|
|
1833
|
+
* 只写导出落点文件,绝不改写 DSH 会话日志。会话不可读/无可导出内容时大声失败。
|
|
1586
1834
|
* @param ctx - Cordis 上下文。
|
|
1587
1835
|
* @param config - 插件配置。
|
|
1588
1836
|
* @param args - 工具参数 `{ sessionId, path? }`。
|
|
@@ -1591,7 +1839,7 @@ export function resolveExportTarget(raw, sessionId, config) {
|
|
|
1591
1839
|
*/
|
|
1592
1840
|
export async function runExport(ctx, config, args, signal) {
|
|
1593
1841
|
const sp = ctx.get('sessionPersistence')
|
|
1594
|
-
if (!sp || typeof sp.readFrom !== 'function') {
|
|
1842
|
+
if (!sp || (typeof sp.readFrom !== 'function' && typeof sp.open !== 'function')) {
|
|
1595
1843
|
throw new Error('会话持久化服务(sessionPersistence)不可用:claude-move 回迁导出需要该服务')
|
|
1596
1844
|
}
|
|
1597
1845
|
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId.trim() : ''
|
|
@@ -1599,7 +1847,10 @@ export async function runExport(ctx, config, args, signal) {
|
|
|
1599
1847
|
signal?.throwIfAborted()
|
|
1600
1848
|
let read
|
|
1601
1849
|
try {
|
|
1602
|
-
read = await sp
|
|
1850
|
+
read = await readStoredEvents(sp, sessionId)
|
|
1851
|
+
if (read === null) {
|
|
1852
|
+
throw new Error('sessionPersistence 基线无法判定(既无 readFrom 也无 open 形状)')
|
|
1853
|
+
}
|
|
1603
1854
|
} catch (err) {
|
|
1604
1855
|
throw new Error(`读取会话 ${sessionId} 失败(不存在或不可读):${String((err && err.message) || err)}`)
|
|
1605
1856
|
}
|
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.4.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
],
|
|
67
67
|
"compatibility": {
|
|
68
68
|
"dshVersions": [
|
|
69
|
-
"0.1.2-
|
|
69
|
+
"0.1.2-rc.1"
|
|
70
70
|
]
|
|
71
71
|
},
|
|
72
72
|
"capability": {
|
|
@@ -110,22 +110,23 @@
|
|
|
110
110
|
"license": "Apache-2.0",
|
|
111
111
|
"devDependencies": {
|
|
112
112
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
113
|
-
"@deepseek-ai/cordis-plugin-include": "^1.0.
|
|
114
|
-
"@deepseek-ai/cordis-plugin-loader": "^1.0.
|
|
115
|
-
"@deepseek-ai/dsh-agent": "0.1.2-
|
|
116
|
-
"@deepseek-ai/dsh-attachment": "0.1.2-
|
|
117
|
-
"@deepseek-ai/dsh-brand": "0.1.2-
|
|
118
|
-
"@deepseek-ai/dsh-code-runtime": "0.1.2-
|
|
119
|
-
"@deepseek-ai/dsh-commands": "0.1.2-
|
|
120
|
-
"@deepseek-ai/dsh-invariants": "0.1.2-
|
|
121
|
-
"@deepseek-ai/dsh-llm": "0.1.2-
|
|
122
|
-
"@deepseek-ai/dsh-scope": "0.1.2-
|
|
123
|
-
"@deepseek-ai/dsh-session": "0.1.2-
|
|
124
|
-
"@deepseek-ai/dsh-
|
|
125
|
-
"@deepseek-ai/dsh-
|
|
126
|
-
"@deepseek-ai/dsh-
|
|
127
|
-
"@deepseek-ai/dsh-
|
|
128
|
-
"@deepseek-ai/dsh-
|
|
113
|
+
"@deepseek-ai/cordis-plugin-include": "^1.0.7",
|
|
114
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.3",
|
|
115
|
+
"@deepseek-ai/dsh-agent": "0.1.2-rc.1",
|
|
116
|
+
"@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
|
|
117
|
+
"@deepseek-ai/dsh-brand": "0.1.2-rc.1",
|
|
118
|
+
"@deepseek-ai/dsh-code-runtime": "0.1.2-rc.1",
|
|
119
|
+
"@deepseek-ai/dsh-commands": "0.1.2-rc.1",
|
|
120
|
+
"@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
|
|
121
|
+
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
122
|
+
"@deepseek-ai/dsh-scope": "0.1.2-rc.1",
|
|
123
|
+
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
124
|
+
"@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
|
|
125
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
|
|
126
|
+
"@deepseek-ai/dsh-timeout": "0.1.2-rc.1",
|
|
127
|
+
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|
|
128
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.2-rc.1",
|
|
129
|
+
"@deepseek-ai/dsh-user-approval": "0.1.2-rc.1",
|
|
129
130
|
"oxlint": "^0.18.1"
|
|
130
131
|
},
|
|
131
132
|
"funding": {
|