dsh-deepseek-import 0.1.1 → 0.2.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/dsh-import.cmd CHANGED
@@ -1,65 +1,65 @@
1
- @echo off
2
- setlocal
3
- cd /d "%~dp0"
4
-
5
- set "LOG=%~dp0import.log"
6
- chcp 65001 >nul 2>&1
7
-
8
- rem ---------- find Node.js: always absolute paths, never relative ----------
9
- set "NODE="
10
-
11
- rem 0) manual override: put the full node.exe path in node-path.txt (highest priority)
12
- if exist "%~dp0node-path.txt" set /p NODE=<"%~dp0node-path.txt"
13
-
14
- rem 1) resolve node.exe from PATH (a bare "node" would fail an exist check)
15
- if not defined NODE for %%I in (node.exe) do set "NODE=%%~$PATH:I"
16
-
17
- rem 2) common install locations
18
- if not defined NODE if exist "%ProgramFiles%\nodejs\node.exe" set "NODE=%ProgramFiles%\nodejs\node.exe"
19
- if not defined NODE if exist "%ProgramFiles(x86)%\nodejs\node.exe" set "NODE=%ProgramFiles(x86)%\nodejs\node.exe"
20
- if not defined NODE if exist "%LOCALAPPDATA%\Programs\nodejs\node.exe" set "NODE=%LOCALAPPDATA%\Programs\nodejs\node.exe"
21
- if not defined NODE if exist "%APPDATA%\npm\node.exe" set "NODE=%APPDATA%\npm\node.exe"
22
-
23
- rem 3) portable: put node.exe next to this file
24
- if not defined NODE if exist "%~dp0node.exe" set "NODE=%~dp0node.exe"
25
-
26
- if not defined NODE (
27
- echo.
28
- echo Node.js not found.
29
- echo.
30
- echo Install Node 22 or newer: https://nodejs.org/
31
- echo Or drop node.exe next to this file.
32
- echo Or create node-path.txt with the full path to node.exe.
33
- echo.
34
- pause
35
- exit /b 1
36
- )
37
-
38
- rem version check: built-in zstd requires Node 22+
39
- "%NODE%" -e "process.exit(Number(process.versions.node.split('.')[0]) >= 22 ? 0 : 1)" >nul 2>&1
40
- if errorlevel 1 (
41
- echo.
42
- echo Node.js is too old:
43
- "%NODE%" -v
44
- echo Node 22 or newer is required - session compression uses built-in zstd.
45
- echo.
46
- pause
47
- exit /b 1
48
- )
49
-
50
- echo ============================================
51
- echo DeepSeek chat import into DeepSeek Harness
52
- echo Node: %NODE%
53
- echo ============================================
54
- echo.
55
-
56
- "%NODE%" "%~dp0dsh-import.mjs" %*
57
- set "CODE=%ERRORLEVEL%"
58
-
59
- echo.
60
- if not "%CODE%"=="0" (
61
- echo [FAILED] exit code %CODE%, see import.log
62
- ) else (
63
- echo [DONE] see import.log
64
- )
65
- pause
1
+ @echo off
2
+ setlocal
3
+ cd /d "%~dp0"
4
+
5
+ set "LOG=%~dp0import.log"
6
+ chcp 65001 >nul 2>&1
7
+
8
+ rem ---------- find Node.js: always absolute paths, never relative ----------
9
+ set "NODE="
10
+
11
+ rem 0) manual override: put the full node.exe path in node-path.txt (highest priority)
12
+ if exist "%~dp0node-path.txt" set /p NODE=<"%~dp0node-path.txt"
13
+
14
+ rem 1) resolve node.exe from PATH (a bare "node" would fail an exist check)
15
+ if not defined NODE for %%I in (node.exe) do set "NODE=%%~$PATH:I"
16
+
17
+ rem 2) common install locations
18
+ if not defined NODE if exist "%ProgramFiles%\nodejs\node.exe" set "NODE=%ProgramFiles%\nodejs\node.exe"
19
+ if not defined NODE if exist "%ProgramFiles(x86)%\nodejs\node.exe" set "NODE=%ProgramFiles(x86)%\nodejs\node.exe"
20
+ if not defined NODE if exist "%LOCALAPPDATA%\Programs\nodejs\node.exe" set "NODE=%LOCALAPPDATA%\Programs\nodejs\node.exe"
21
+ if not defined NODE if exist "%APPDATA%\npm\node.exe" set "NODE=%APPDATA%\npm\node.exe"
22
+
23
+ rem 3) portable: put node.exe next to this file
24
+ if not defined NODE if exist "%~dp0node.exe" set "NODE=%~dp0node.exe"
25
+
26
+ if not defined NODE (
27
+ echo.
28
+ echo Node.js not found.
29
+ echo.
30
+ echo Install Node 22 or newer: https://nodejs.org/
31
+ echo Or drop node.exe next to this file.
32
+ echo Or create node-path.txt with the full path to node.exe.
33
+ echo.
34
+ pause
35
+ exit /b 1
36
+ )
37
+
38
+ rem version check: built-in zstd requires Node 22+
39
+ "%NODE%" -e "process.exit(Number(process.versions.node.split('.')[0]) >= 22 ? 0 : 1)" >nul 2>&1
40
+ if errorlevel 1 (
41
+ echo.
42
+ echo Node.js is too old:
43
+ "%NODE%" -v
44
+ echo Node 22 or newer is required - session compression uses built-in zstd.
45
+ echo.
46
+ pause
47
+ exit /b 1
48
+ )
49
+
50
+ echo ============================================
51
+ echo DeepSeek chat import into DeepSeek Harness
52
+ echo Node: %NODE%
53
+ echo ============================================
54
+ echo.
55
+
56
+ "%NODE%" "%~dp0dsh-import.mjs" %*
57
+ set "CODE=%ERRORLEVEL%"
58
+
59
+ echo.
60
+ if not "%CODE%"=="0" (
61
+ echo [FAILED] exit code %CODE%, see import.log
62
+ ) else (
63
+ echo [DONE] see import.log
64
+ )
65
+ pause
package/dsh-import.mjs CHANGED
@@ -424,7 +424,7 @@ function sidecarDir(workspacePath, sessionId) {
424
424
  const ignore = path.join(root, '.gitignore');
425
425
  if (!fs.existsSync(ignore)) {
426
426
  try {
427
- fs.writeFileSync(ignore, '# DSH 导入存档(思考 / 引用 / 全文 / 附件)\n*\n');
427
+ fs.writeFileSync(ignore, '# DSH 导入存档(思考 / 全文 / 多版本;数据源带引用或附件时另存)\n*\n');
428
428
  } catch {
429
429
  /* 没有写权限就算了 */
430
430
  }
@@ -1162,7 +1162,7 @@ async function main() {
1162
1162
  * 供 TUI / 其它程序调用:不解析命令行,直接指定文件与工作区。
1163
1163
  * 返回 { workspace, created }。
1164
1164
  */
1165
- export async function runImport({ files, workspacePath = null, dryRun = false, register = true, thinking = false, only = null, keepAll = false, keepInterrupted = false, fromIndex = null, fromMatch = null }) {
1165
+ export async function runImport({ files, workspacePath = null, dryRun = false, register = true, thinking = false, only = null, keepAll = false, keepInterrupted = false, fromIndex = null, fromMatch = null, tail = null }) {
1166
1166
  opts.files = [...files];
1167
1167
  opts.workspace = workspacePath;
1168
1168
  opts.dryRun = dryRun;
@@ -1173,6 +1173,7 @@ export async function runImport({ files, workspacePath = null, dryRun = false, r
1173
1173
  opts.keepInterrupted = keepInterrupted;
1174
1174
  opts.fromIndex = fromIndex;
1175
1175
  opts.fromMatch = fromMatch;
1176
+ opts.tail = Number.isInteger(tail) && tail > 0 ? tail : undefined;
1176
1177
  opts.list = false;
1177
1178
  opts.json = false;
1178
1179
  return main();
@@ -437,11 +437,31 @@
437
437
  try {
438
438
  const cached = await readCachedHistory();
439
439
  if (cached.ok && cached.messages?.length) {
440
- pushCapture(cached.messages);
440
+ // 缓存里没有附件(DeepSeek 不把 files 写进 IndexedDB)。
441
+ // 当前屏幕上已渲染的消息能拿到 <img>,按「角色+正文完全相同」贴回去,
442
+ // 只做并集、不新增消息,避免重复。
443
+ let withFiles = cached.messages;
444
+ try {
445
+ const domMessages = domExtract().messages || [];
446
+ const byKey = new Map(withFiles.map((m) => [`${m.role}|${String(m.content || '').trim()}`, m]));
447
+ let attached = 0;
448
+ for (const domMessage of domMessages) {
449
+ if (!(domMessage.files || []).length) continue;
450
+ const target = byKey.get(`${domMessage.role}|${String(domMessage.content || '').trim()}`);
451
+ if (!target) continue;
452
+ target.files = [...(target.files || []), ...domMessage.files];
453
+ attached += domMessage.files.length;
454
+ }
455
+ if (attached) withFiles = [...withFiles];
456
+ } catch {
457
+ /* DOM 读不到就维持原样 */
458
+ }
459
+
460
+ pushCapture(withFiles);
441
461
  return {
442
462
  ok: true,
443
463
  source: 'cache',
444
- messages: cached.messages,
464
+ messages: withFiles,
445
465
  rounds: 0,
446
466
  captures: 1,
447
467
  delta: 0,
@@ -16,8 +16,25 @@
16
16
  return (message?.content || '').length + (message?.thinking || '').length;
17
17
  }
18
18
 
19
+ /** 同一消息的两份副本:正文取更全的,附件取并集 */
20
+ function unionFiles(a, b) {
21
+ const merged = [];
22
+ const seen = new Set();
23
+ for (const file of [...(a || []), ...(b || [])]) {
24
+ const key = file?.dataUrl ? String(file.dataUrl).slice(0, 64) : file?.url || file?.name;
25
+ if (!key || seen.has(key)) continue;
26
+ seen.add(key);
27
+ merged.push(file);
28
+ }
29
+ return merged;
30
+ }
31
+
19
32
  function richer(a, b) {
20
- return score(b) > score(a) ? b : a;
33
+ if (!a) return b;
34
+ if (!b) return a;
35
+ const winner = score(b) > score(a) ? b : a;
36
+ const files = unionFiles(a.files, b.files);
37
+ return files.length ? { ...winner, files } : winner;
21
38
  }
22
39
 
23
40
  function keyOf(message) {
package/icon.svg ADDED
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="DeepSeek Chat Import">
2
+ <rect x="4" y="10" width="40" height="30" rx="8" fill="#4d6bfe" opacity="0.15"/>
3
+ <path d="M12 20h24M12 28h16" stroke="#4d6bfe" stroke-width="3.5" stroke-linecap="round"/>
4
+ <path d="M24 52l14-12h10a6 6 0 0 0 6-6V22" fill="none" stroke="#4d6bfe" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
5
+ <path d="M48 22l6-6M48 22l6 6" stroke="#4d6bfe" stroke-width="4" stroke-linecap="round"/>
6
+ </svg>
package/index.js ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * dsh-deepseek-import 的 Host 插件入口。
3
+ *
4
+ * 提供一个模型可调用的工具:把 Edge 扩展导出的 DeepSeek 对话 JSON
5
+ * 重建为当前工作区里的 DSH 会话。
6
+ */
7
+
8
+ import { defineTool } from '@deepseek-ai/dsh-tools';
9
+
10
+ /** Cordis 插件名 */
11
+ export const name = 'dsh-deepseek-import';
12
+
13
+ /** 需要工具注册表 */
14
+ export const inject = ['tools'];
15
+
16
+ /**
17
+ * 注册 import_deepseek_chat。
18
+ * @param ctx - 工具注册表(以及可选的 session/agent 服务)
19
+ * @param config - 插件配置(当前未使用)
20
+ */
21
+ export function apply(ctx, config = {}) {
22
+ void config;
23
+
24
+ ctx.tools.register(
25
+ defineTool({
26
+ name: 'import_deepseek_chat',
27
+ description:
28
+ 'Import a DeepSeek web chat export (the JSON produced by the dsh-deepseek-import Edge extension) into DeepSeek Harness as new session(s) in the current workspace. Returns the created session ids and the sidecar paths (thinking/transcript/references). New sessions become visible in the sidebar on the next session-list refresh; if they do not appear, restart DSH.',
29
+ parameters: {
30
+ file: {
31
+ type: 'string',
32
+ description: 'Absolute path to a dsh-deepseek-*.json export. Renamed exports are recognised by their content.',
33
+ },
34
+ workspace: {
35
+ type: 'string',
36
+ description: 'Target workspace directory. Omit to use the calling session working directory.',
37
+ },
38
+ tail: {
39
+ type: 'number',
40
+ description: 'Import only the last N turns of each conversation (recommended for very long chats).',
41
+ },
42
+ fromMatch: {
43
+ type: 'string',
44
+ description: 'Start the branch at the first user message containing this text.',
45
+ },
46
+ keepInterrupted: {
47
+ type: 'boolean',
48
+ description: 'Keep partial interrupted assistant output instead of a placeholder.',
49
+ },
50
+ dryRun: {
51
+ type: 'boolean',
52
+ description: 'Parse and report only; write nothing.',
53
+ },
54
+ },
55
+ output: {
56
+ schema: {
57
+ type: 'object',
58
+ additionalProperties: false,
59
+ properties: {
60
+ ok: { type: 'boolean', required: true },
61
+ created: { type: 'array', items: { type: 'string' }, required: true },
62
+ workspace: { type: 'string', required: true },
63
+ message: { type: 'string', required: true },
64
+ },
65
+ },
66
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
67
+ },
68
+ async execute(args, exec) {
69
+ const workspace =
70
+ args.workspace ||
71
+ exec?.agent?.cwd ||
72
+ exec?.agent?.workingDirectory ||
73
+ exec?.cwd ||
74
+ process.cwd();
75
+
76
+ const { runImport } = await import(new URL('./dsh-import.mjs', import.meta.url).href);
77
+ const result = await runImport({
78
+ files: [args.file],
79
+ workspacePath: workspace,
80
+ dryRun: args.dryRun === true,
81
+ tail: args.tail,
82
+ fromMatch: args.fromMatch,
83
+ keepInterrupted: args.keepInterrupted === true,
84
+ });
85
+
86
+ const created = (result?.created ?? []).map((item) =>
87
+ typeof item === 'string' ? item : item.sessionId
88
+ );
89
+ return {
90
+ ok: created.length > 0,
91
+ created,
92
+ workspace,
93
+ message: created.length
94
+ ? `已导入 ${created.length} 个会话。侧栏下次刷新可见;若没有出现,重启 DSH。`
95
+ : '没有创建任何会话,请检查导出文件是否包含消息。',
96
+ };
97
+ },
98
+ presentCall: (args) => ({
99
+ card: 'generic',
100
+ title: 'import_deepseek_chat',
101
+ rawInput: args,
102
+ }),
103
+ })
104
+ );
105
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "DeepSeek Chat Import",
4
+ "description": "Rebuild DeepSeek web conversations exported by the Edge extension as DeepSeek Harness sessions."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "DeepSeek 对话导入",
4
+ "description": "把 Edge 扩展导出的 DeepSeek 网页对话重建为 DeepSeek Harness 会话。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,14 +1,37 @@
1
1
  {
2
2
  "name": "dsh-deepseek-import",
3
- "version": "0.1.1",
4
- "description": "DeepSeek web chat → DeepSeek Harness sessions (Edge extension + importer CLI)",
3
+ "version": "0.2.0",
4
+ "description": "DeepSeek web chat → DeepSeek Harness sessions (Edge extension + importer CLI + DSH plugin)",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Hungry-M",
8
8
  "bin": {
9
9
  "dsh-import": "./dsh-import.mjs"
10
10
  },
11
+ "exports": {
12
+ ".": "./index.js",
13
+ "./locale/*.json": "./locale/*.json",
14
+ "./icon": "./icon.svg",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "dsh": {
18
+ "bundle": {
19
+ "patch": "./cordis.patch.yml"
20
+ }
21
+ },
22
+ "peerDependencies": {
23
+ "@deepseek-ai/dsh-tools": "*"
24
+ },
25
+ "peerDependenciesMeta": {
26
+ "@deepseek-ai/dsh-tools": {
27
+ "optional": true
28
+ }
29
+ },
11
30
  "files": [
31
+ "index.js",
32
+ "cordis.patch.yml",
33
+ "locale/*.json",
34
+ "icon.svg",
12
35
  "dsh-import.mjs",
13
36
  "dsh-import-tui.mjs",
14
37
  "dsh-import.cmd",