dsh-loadout 0.11.0 → 0.11.2

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 CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  「装备栏」是一个 DeepSeek Harness 插件:给每个对话选一套工具(套装),只把用得上的工具投给模型。
4
4
 
5
+ ## 0.11.2
6
+
7
+ - README 的「预置套装」一节改写为**历史记录**的口径:明确它从 0.10.2 起就不在界面里,用户看到的是「全部 / 仅内置 / 我的套装」,而三套数据仍在 `cordis.patch.yml` 里、`/kit 写作|调研|编程` 仍可用;标题也改成「界面不展示,命令仍可用」。中英文同步。
8
+ - 无代码改动:宿主、客户端、`cordis.patch.yml` 与 0.11.1 完全一致。
9
+
10
+ ## 0.11.1
11
+
12
+ - README 与界面同步:功能表不再把**预置套装**当卖点(0.10.2 起界面已隐藏它),改成介绍**我的套装**——新建、设为默认、复制切换命令、编辑(覆盖保存 / 另存为新套装)、备注、删除;并补上 token 账本一行。
13
+ - 安装段改成三种来源并列:npm(`dsh plugin add dsh-loadout`)、GitHub(`add github:JungleYXu/dsh-loadout`)、本地目录链接安装。中英文 README 同步。
14
+ - 无行为变化,只是文档与 npm 首页对齐(npm 页面读的是发布包里的 README)。
15
+
5
16
  ## 0.11.0
6
17
 
7
18
  首个公开发布版本(npm + awesome-dsh-plugin)。
package/README.en.md CHANGED
@@ -13,30 +13,32 @@ DeepSeek Harness exposes every tool to every conversation: the more plugins you
13
13
  | Capability | Behaviour |
14
14
  | --- | --- |
15
15
  | Three bases | `All` (no masking, default) / `Built-in only` / one of your kits |
16
+ | Your kits | Tick built-in tools and plugins under "New kit" and save them as a named loadout. Each card can be set as default, copied as a switch command, edited (overwrite in place, or save as a new kit), annotated, and deleted |
16
17
  | Add-ons | Attach a plugin on top of the base for a one-off combination, without saving a kit |
17
- | Shipped presets | Writing / Research / Coding — read-only; editing saves a copy as your own kit |
18
18
  | Notes | Up to 20 characters per kit, shown as `Test 1(for video work)` in the menu |
19
+ | Token ledger | The menu status line says `saves ~1.9k tokens`; Settings shows the full account (tools kept, tokens kept, tokens saved) |
19
20
  | Refresh | `⟳` in the menu re-reads the tool catalogue and re-applies the current selection |
20
21
  | Per conversation | A switch affects only the current conversation, starting with the next message; new conversations start from the default kit in Settings → Loadout |
21
22
 
22
23
  ## Install
23
24
 
24
- Let Harness install it, which creates a **link** instead of a copy:
25
+ Three sources, same package:
25
26
 
26
- ```text
27
- plugin_manager action=install_bundle target=<absolute path to this directory>
28
- ```
27
+ ```bash
28
+ # 1) npm (published)
29
+ dsh plugin --profile desktop add dsh-loadout
29
30
 
30
- Or from the CLI:
31
+ # 2) GitHub (same package, when the registry is unreachable)
32
+ dsh plugin --profile desktop add github:JungleYXu/dsh-loadout
31
33
 
32
- ```bash
34
+ # 3) a local directory (development; installs a link, not a copy)
33
35
  dsh plugin --profile desktop add link:<absolute path to this directory>
34
36
  ```
35
37
 
36
- Or, once published to npm:
38
+ Letting Harness install it is the same as the third form:
37
39
 
38
- ```bash
39
- dsh plugin --profile desktop add dsh-loadout
40
+ ```text
41
+ plugin_manager action=install_bundle target=<absolute path to this directory>
40
42
  ```
41
43
 
42
44
  Restart the app afterwards: host plugins have no hot reload, and the UI needs at least a refresh.
@@ -61,11 +63,11 @@ node scripts/client-smoke.mjs # browser bundle: menu and settings panel render
61
63
 
62
64
  Settings has no conversation of its own, so it only decides *where new conversations start* — use the header button to change the current one. In the default list, `All` / `Built-in only` and kits are the same kind of card: each can copy its switch command or be set as default. **My kits** starts collapsed and shows only the current default; open it to see the rest. A saved kit can be **edited** on its card: the form opens under that card, and you either overwrite it (conversations already using it switch immediately) or save as a new kit.
63
65
 
64
- ### What the shipped presets contain
66
+ ### Shipped presets (not shown in the UI, still selectable by command)
65
67
 
66
- > Since 0.10.2 the UI does **not** show the presets (neither the header menu nor Settings). The three entries still live in `cordis.patch.yml` and `/kit writing` keeps working; they are kept for our own testing until they are clear enough to ship to users. The table documents them for review.
68
+ > This section is a **historical record**, not a user-facing feature. Since 0.10.2 neither the header menu nor Settings shows the presets; what users see is `All` / `Built-in only` / `My kits`. The three entries still live in `cordis.patch.yml` and `/kit writing`, `/kit research`, `/kit coding` keep working — they are kept for our own testing until they are clear enough to put back in the UI. The table below is for review and troubleshooting.
67
69
 
68
- All three are read-only; an edit becomes a copy of your own. They list **built-in tools only** — plugin tools come in through add-ons or by saving your own kit.
70
+ The presets are read-only; an edit can only become a copy of your own. They list **built-in tools only** — plugin tools come in through add-ons or by saving your own kit.
69
71
 
70
72
  | Preset | Built-in tools |
71
73
  | --- | --- |
package/README.md CHANGED
@@ -13,26 +13,34 @@ DeepSeek Harness 的工具是全局可见的:插件装得越多,每个对话
13
13
  | 能力 | 说明 |
14
14
  | --- | --- |
15
15
  | 三档底座 | `全部`(不裁剪,默认)/ `仅内置`(只留 DSH 自带工具)/ `我的套装` |
16
+ | 我的套装 | 「新建套装」里勾选内置工具与插件,存成一套带名字的装备。卡片上可以**设为默认**、**复制切换命令**、**编辑**(改完覆盖保存,或另存成新的一套)、**备注**、**删除** |
16
17
  | 加挂 | 在底座之上临时加挂插件,不必为一次性组合新建套装 |
17
- | 预置套装 | 写作 / 调研 / 编程,只读;改动会另存为你的套装 |
18
18
  | 备注 | 每个套装可加 20 字备注,菜单里显示成 `测试1(用于视频制作)` |
19
+ | token 账本 | 菜单状态行 `省 ~1.9k tokens`,设置页给出「保留 N 个工具 · 约 X tokens · 省多少」的完整账目 |
19
20
  | 刷新 | 菜单右上 `⟳` 重拉工具目录并重套用当前选择 |
20
21
  | 每对话独立 | 切换只影响当前对话,下一条消息即按新清单组装;新对话用「设置 → 装备栏」的默认套装 |
21
22
 
22
23
  ## 安装
23
24
 
24
- 推荐让 Harness 自己装(会创建**链接**,而不是拷贝):
25
+ 三种来源,装的是一个东西:
25
26
 
26
- ```text
27
- plugin_manager action=install_bundle target=<本目录绝对路径>
28
- ```
27
+ ```bash
28
+ # 1) npm(已发布,最短)
29
+ dsh plugin --profile desktop add dsh-loadout
29
30
 
30
- 或走 CLI:
31
+ # 2) GitHub(npm 不可达时用同一个包)
32
+ dsh plugin --profile desktop add github:JungleYXu/dsh-loadout
31
33
 
32
- ```bash
34
+ # 3) 本地目录(开发用,链接安装,不拷贝)
33
35
  dsh plugin --profile desktop add link:<本目录绝对路径>
34
36
  ```
35
37
 
38
+ 让 Harness 自己装也可以(等价于第 3 种):
39
+
40
+ ```text
41
+ plugin_manager action=install_bundle target=<本目录绝对路径>
42
+ ```
43
+
36
44
  装完重启应用:宿主插件没有热重载,界面至少刷新一次。
37
45
 
38
46
  ### 为什么不能复制文件
@@ -55,11 +63,11 @@ node scripts/client-smoke.mjs # 浏览器 bundle:菜单与设置页渲染
55
63
 
56
64
  设置页没有自己的对话,所以它只管「新对话从哪套开始」,不切换当前对话——当前对话请在标题栏里切。默认列表里「全部 / 仅内置」和套装是同一种卡片:都能「复制切换命令」或「设为默认」;「我的套装」默认折叠,只显示当前默认的那一个,点开才列出全部。已经保存的套装可以在卡片上「编辑」:改动就在那张卡片下面展开,「覆盖保存」(正在用它的对话立刻按新工具集走)或「另存为新套装」。
57
65
 
58
- ### 预置套装里有什么
66
+ ### 预置套装(界面不展示,命令仍可用)
59
67
 
60
- > 0.10.2 起,**界面暂时不展示预置套装**(标题栏菜单和设置页都不出现)。三套数据仍在 `cordis.patch.yml` 里、`/kit 写作` 这类命令也照常可用,保留给自己测试;等它们对用户足够直观再放出来。下面表格记录它们的内容,方便核对。
68
+ > 这一节是**历史记录**,不是面向用户的功能。从 0.10.2 起,标题栏菜单和设置页都不再出现预置套装,用户看到的就是「全部 / 仅内置 / 我的套装」。三套数据仍留在 `cordis.patch.yml` 里,`/kit 写作`、`/kit 调研`、`/kit 编程` 照常可用,保留给我们自己测试;等它们足够直观再放回界面。下面的内容用于核对和排障。
61
69
 
62
- 三套都是只读的,改动会另存为你的套装。它们只列**内置工具**,插件工具要靠「加挂」或另存时勾选。
70
+ 预置套装是只读的,改动只能另存为你的套装。它们只列**内置工具**,插件工具要靠「加挂」或在另存时勾选。
63
71
 
64
72
  | 套装 | 内置工具 |
65
73
  | --- | --- |
package/icon.svg CHANGED
@@ -1,4 +1,4 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Tool Kits">
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Loadout">
2
2
  <rect x="7" y="9" width="50" height="13" rx="4" fill="none" stroke="currentColor" stroke-width="3.5"/>
3
3
  <rect x="7" y="26" width="50" height="13" rx="4" fill="none" stroke="currentColor" stroke-width="3.5" opacity="0.7"/>
4
4
  <rect x="7" y="43" width="32" height="12" rx="4" fill="currentColor" opacity="0.92"/>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-loadout",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Loadout for DeepSeek Harness: group tools into kits, keep only the current kit's tools visible in each conversation, and manage everything from a settings panel.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Is there a host service that maps a session id to its live agent?
3
+ */
4
+ import { readFileSync, readdirSync } from 'node:fs'
5
+
6
+ const BASE = 'C:/Users/JungleYXu/AppData/Local/Programs/DeepSeek Harness/resources/app.asar/dsh/node_modules/@deepseek-ai'
7
+ const packages = readdirSync(BASE)
8
+ const pattern = /agentFor|bySession|sessionAgent|agentOf|resolveAgent/g
9
+
10
+ for (const name of packages) {
11
+ if (!name.startsWith('dsh-')) continue
12
+ let text
13
+ try {
14
+ text = readFileSync(`${BASE}/${name}/lib/index.js`, 'utf8')
15
+ } catch {
16
+ continue
17
+ }
18
+ const hits = [...new Set([...text.matchAll(pattern)].map((m) => m[0]))]
19
+ if (hits.length === 0) continue
20
+ const index = text.search(pattern)
21
+ console.log(`\n### ${name} :: ${hits.join(', ')}`)
22
+ console.log(text.slice(Math.max(0, index - 200), index + 260).replace(/\s+/g, ' '))
23
+ }
24
+
@@ -0,0 +1,25 @@
1
+ /**
2
+ * How does host code get a session id from an agent object?
3
+ */
4
+ import { readFileSync } from 'node:fs'
5
+
6
+ const BASE = 'C:/Users/JungleYXu/AppData/Local/Programs/DeepSeek Harness/resources/app.asar/dsh/node_modules/@deepseek-ai'
7
+ const files = [
8
+ 'dsh-tools/lib/index.js',
9
+ 'dsh-agent/lib/index.js',
10
+ 'dsh-session/lib/index.js',
11
+ 'dsh-subagent/lib/index.js',
12
+ ]
13
+
14
+ for (const rel of files) {
15
+ let text
16
+ try {
17
+ text = readFileSync(`${BASE}/${rel}`, 'utf8')
18
+ } catch (error) {
19
+ console.log(`skip ${rel}: ${error.code}`)
20
+ continue
21
+ }
22
+ const hits = [...text.matchAll(/agent\.session[A-Za-z]*/g)].map((m) => m[0])
23
+ const unique = [...new Set(hits)]
24
+ console.log(`${rel} -> ${unique.join(', ') || '(none)'}`)
25
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Offline probe: does the package-name heuristic actually recover the plugins
3
+ * that own the host-registered tools? Prints the derived index and the mapping
4
+ * for a list of real tool names, so the heuristic can be checked before the app
5
+ * is restarted.
6
+ *
7
+ * ELECTRON_RUN_AS_NODE=1 "DeepSeek Harness.exe" scripts/attribution-probe.mjs
8
+ */
9
+ import { existsSync, readFileSync, readdirSync } from 'node:fs'
10
+ import { homedir } from 'node:os'
11
+ import { join } from 'node:path'
12
+
13
+ const BUILTIN_PREFIXES = new Set([
14
+ 'todo', 'web', 'kit', 'agent', 'work', 'read', 'write', 'edit', 'glob', 'grep',
15
+ 'ask', 'present', 'skill', 'workflow', 'subagent', 'send', 'list', 'interrupt',
16
+ 'job', 'schedule', 'create', 'get', 'update', 'exit', 'compress', 'decompress',
17
+ 'search', 'load', 'working',
18
+ ])
19
+
20
+ const NAME_NOISE = new Set([
21
+ 'core', 'tools', 'tool', 'client', 'server', 'shared', 'common', 'plugin', 'plugins',
22
+ 'dsh', 'ui', 'web', 'harness', 'extension', 'preset', 'settings', 'sidebar', 'runtime',
23
+ ])
24
+
25
+ const SELF = 'dsh-loadout'
26
+
27
+ const REAL_GLOBAL_TOOLS = [
28
+ 'kit_list', 'kit_apply', 'kit_create', 'kit_set_tools', 'kit_set_default', 'kit_delete',
29
+ 'modlens_read_image',
30
+ 'weknora_list_knowledge_bases', 'weknora_search', 'weknora_read_document', 'weknora_ask',
31
+ 'hindsight_sync_status', 'hindsight_diagnose', 'hindsight_search_knowledge_pages',
32
+ 'hindsight_list_knowledge_pages', 'hindsight_read_knowledge_page', 'hindsight_reflect',
33
+ 'hindsight_capture_initiative', 'hindsight_ingest_document',
34
+ 'working_directory',
35
+ 'vision_describe', 'vision_bootstrap', 'vision_materialize', 'vision_ground', 'vision_detect',
36
+ 'vision_crop', 'vision_present', 'vision_pixel_diff', 'vision_colors', 'vision_ocr',
37
+ 'vision_long_screenshot_ocr', 'vision_trace', 'vision_extract_foreground', 'vision_html_screenshot',
38
+ 'list_mcp_resources', 'list_mcp_resource_templates', 'read_mcp_resource',
39
+ 'compress', 'decompress', 'search_context',
40
+ 'acp_status', 'acp_cache', 'acp_read_original_image',
41
+ 'load_workspace_dependencies',
42
+ ]
43
+
44
+ function profilePackages() {
45
+ const root = join(homedir(), '.dsh', 'profiles')
46
+ const names = new Set()
47
+ for (const entry of readdirSync(root)) {
48
+ const file = join(root, entry, 'package.json')
49
+ if (!existsSync(file)) continue
50
+ const parsed = JSON.parse(readFileSync(file, 'utf8'))
51
+ for (const name of Object.keys(parsed.dependencies ?? {})) names.add(name)
52
+ for (const bundle of parsed?.dsh?.profile?.bundles ?? []) if (typeof bundle === 'string') names.add(bundle)
53
+ }
54
+ return [...names]
55
+ }
56
+
57
+ function buildIndex(packages) {
58
+ const index = new Map()
59
+ for (const packageName of packages) {
60
+ const base = packageName.replace(/^@[^/]+\//, '')
61
+ if (base === SELF) continue
62
+ const stripped = base.replace(/^dsh-/, '').replace(/^plugin-/, '')
63
+ const tokens = [stripped, ...stripped.split('-')]
64
+ .map((token) => token.toLowerCase())
65
+ .filter((token) => token.length >= 3 && !NAME_NOISE.has(token) && !BUILTIN_PREFIXES.has(token))
66
+ for (const token of tokens) if (!index.has(token)) index.set(token, { id: token, title: stripped, packageName: base })
67
+ }
68
+ return index
69
+ }
70
+
71
+ const packages = profilePackages()
72
+ const index = buildIndex(packages)
73
+ console.log(`profile packages: ${packages.length}`)
74
+ console.log(`tokens: ${[...index.keys()].sort().join(', ')}`)
75
+ console.log('')
76
+ const grouped = new Map()
77
+ const orphan = []
78
+ for (const toolName of REAL_GLOBAL_TOOLS) {
79
+ const cut = toolName.indexOf('_')
80
+ const prefix = cut > 0 ? toolName.slice(0, cut).toLowerCase() : ''
81
+ const owner = prefix === '' || BUILTIN_PREFIXES.has(prefix) ? undefined : index.get(prefix)
82
+ if (owner === undefined) { orphan.push(toolName); continue }
83
+ if (!grouped.has(owner.title)) grouped.set(owner.title, [])
84
+ grouped.get(owner.title).push(toolName)
85
+ }
86
+ for (const [title, tools] of [...grouped.entries()].sort()) {
87
+ console.log(`${title} (${tools.length})`)
88
+ console.log(` ${tools.join(', ')}`)
89
+ }
90
+ console.log('')
91
+ console.log(`built-in / unattributed (${orphan.length}): ${orphan.join(', ')}`)
@@ -0,0 +1,30 @@
1
+ /**
2
+ * What can a client plugin do with the command system, and how is a session id
3
+ * exposed to a session-scoped slot?
4
+ */
5
+ import { readFileSync } from 'node:fs'
6
+
7
+ const BASE = 'C:/Users/JungleYXu/AppData/Local/Programs/DeepSeek Harness/resources/app.asar/dsh/node_modules/@deepseek-ai'
8
+
9
+ function show(rel, pattern, width = 260, limit = 6) {
10
+ let text
11
+ try {
12
+ text = readFileSync(`${BASE}/${rel}`, 'utf8')
13
+ } catch (error) {
14
+ console.log(`skip ${rel}: ${error.code}`)
15
+ return
16
+ }
17
+ console.log(`\n##### ${rel}`)
18
+ let index = text.search(pattern)
19
+ let shown = 0
20
+ while (index !== -1 && shown < limit) {
21
+ console.log('...' + text.slice(Math.max(0, index - 80), index + width).replace(/\s+/g, ' '))
22
+ index = text.slice(index + 1).search(pattern)
23
+ if (index !== -1) index += 1
24
+ shown += 1
25
+ }
26
+ }
27
+
28
+ show('dsh-client-ui-commands/lib/client.js', /submitCommand|runCommand|executeCommand/)
29
+ show('dsh-client-commands/lib/index.js', /^\s*(run|execute|dispatch|submit)\(|exports\.(run|execute|dispatch|submit)/, 200, 8)
30
+ show('dsh-client-ui-conversation/lib/client.js', /inject: \(sessionId\) =>/, 700, 2)
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Look at how the shipped bundles use the composer-area slots, so the kit bar
3
+ * can plug into a real surface with real props.
4
+ */
5
+ import { readFileSync, readdirSync } from 'node:fs'
6
+
7
+ const BASE = 'C:/Users/JungleYXu/AppData/Local/Programs/DeepSeek Harness/resources/app.asar/dsh/node_modules/@deepseek-ai'
8
+
9
+ function files(pkg) {
10
+ const out = []
11
+ const walk = (dir, depth) => {
12
+ if (depth > 3) return
13
+ let names = []
14
+ try {
15
+ names = readdirSync(dir)
16
+ } catch {
17
+ return
18
+ }
19
+ for (const name of names) {
20
+ if (name === 'node_modules') continue
21
+ const full = `${dir}/${name}`
22
+ if (name.endsWith('.js') || name.endsWith('.d.ts')) out.push(full)
23
+ else if (!name.includes('.')) walk(full, depth + 1)
24
+ }
25
+ }
26
+ walk(`${BASE}/${pkg}`, 0)
27
+ return out
28
+ }
29
+
30
+ const targets = ['composer.dock', 'conversation.composer', 'session.header.utilities', 'shell.overlay']
31
+ const pkgs = readdirSync(BASE).filter((n) => n.startsWith('dsh-client-ui'))
32
+
33
+ for (const pkg of pkgs) {
34
+ for (const file of files(pkg)) {
35
+ let text
36
+ try {
37
+ text = readFileSync(file, 'utf8')
38
+ } catch {
39
+ continue
40
+ }
41
+ for (const target of targets) {
42
+ let index = text.indexOf(target)
43
+ let shown = 0
44
+ while (index !== -1 && shown < 2) {
45
+ const start = Math.max(0, index - 320)
46
+ console.log(`\n=== ${pkg}/${file.split('/').slice(-2).join('/')} :: ${target}`)
47
+ console.log(text.slice(start, index + 320).replace(/\s+/g, ' '))
48
+ index = text.indexOf(target, index + 1)
49
+ shown += 1
50
+ }
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Find real slot names in the shipped client bundles.
3
+ * Electron's fs is asar-aware, so archive-internal paths start with '/'.
4
+ */
5
+ import { readFileSync, readdirSync, statSync } from 'node:fs'
6
+
7
+ const ROOT = '/dsh/node_modules/@deepseek-ai'
8
+ const found = new Map()
9
+
10
+ function safeReaddir(dir) {
11
+ try {
12
+ return readdirSync(dir, { withFileTypes: false })
13
+ } catch {
14
+ try {
15
+ return readdirSync(dir)
16
+ } catch {
17
+ return []
18
+ }
19
+ }
20
+ }
21
+
22
+ const packages = safeReaddir(ROOT).filter((name) => name.startsWith('dsh-client-ui') || name.startsWith('dsh-client-'))
23
+ console.log(`packages: ${packages.length}`)
24
+
25
+ const pattern = /["']((?:shell|conversation|settings|sidebar|composer|workspace|agent|home|chat|message|session)\.[a-zA-Z.]+)["']/g
26
+
27
+ function scan(dir, depth) {
28
+ if (depth > 3) return
29
+ for (const entry of safeReaddir(dir)) {
30
+ const full = `${dir}/${entry}`
31
+ let isDir = false
32
+ try {
33
+ isDir = statSync(full).isDirectory()
34
+ } catch {
35
+ continue
36
+ }
37
+ if (isDir) {
38
+ if (entry === 'node_modules') continue
39
+ scan(full, depth + 1)
40
+ continue
41
+ }
42
+ if (!entry.endsWith('.js')) continue
43
+ let text
44
+ try {
45
+ text = readFileSync(full, 'utf8')
46
+ } catch {
47
+ continue
48
+ }
49
+ for (const match of text.matchAll(pattern)) {
50
+ const slot = match[1]
51
+ if (slot.split('.').length < 2) continue
52
+ found.set(slot, (found.get(slot) ?? 0) + 1)
53
+ }
54
+ }
55
+ }
56
+
57
+ for (const name of packages) scan(`${ROOT}/${name}`, 0)
58
+
59
+ for (const [slot, count] of [...found.entries()].sort()) console.log(`${slot}\t${count}`)