dsh-loadout 0.11.1 → 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 +5 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/icon.svg +1 -1
- package/package.json +1 -1
- package/scripts/agent-lookup-probe.mjs +24 -0
- package/scripts/agent-probe.mjs +25 -0
- package/scripts/attribution-probe.mjs +91 -0
- package/scripts/cmd-probe.mjs +30 -0
- package/scripts/dock-probe.mjs +53 -0
- package/scripts/slots-probe.mjs +59 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
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
|
+
|
|
5
10
|
## 0.11.1
|
|
6
11
|
|
|
7
12
|
- README 与界面同步:功能表不再把**预置套装**当卖点(0.10.2 起界面已隐藏它),改成介绍**我的套装**——新建、设为默认、复制切换命令、编辑(覆盖保存 / 另存为新套装)、备注、删除;并补上 token 账本一行。
|
package/README.en.md
CHANGED
|
@@ -63,11 +63,11 @@ node scripts/client-smoke.mjs # browser bundle: menu and settings panel render
|
|
|
63
63
|
|
|
64
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.
|
|
65
65
|
|
|
66
|
-
###
|
|
66
|
+
### Shipped presets (not shown in the UI, still selectable by command)
|
|
67
67
|
|
|
68
|
-
> Since 0.10.2 the
|
|
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.
|
|
69
69
|
|
|
70
|
-
|
|
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.
|
|
71
71
|
|
|
72
72
|
| Preset | Built-in tools |
|
|
73
73
|
| --- | --- |
|
package/README.md
CHANGED
|
@@ -63,11 +63,11 @@ node scripts/client-smoke.mjs # 浏览器 bundle:菜单与设置页渲染
|
|
|
63
63
|
|
|
64
64
|
设置页没有自己的对话,所以它只管「新对话从哪套开始」,不切换当前对话——当前对话请在标题栏里切。默认列表里「全部 / 仅内置」和套装是同一种卡片:都能「复制切换命令」或「设为默认」;「我的套装」默认折叠,只显示当前默认的那一个,点开才列出全部。已经保存的套装可以在卡片上「编辑」:改动就在那张卡片下面展开,「覆盖保存」(正在用它的对话立刻按新工具集走)或「另存为新套装」。
|
|
65
65
|
|
|
66
|
-
###
|
|
66
|
+
### 预置套装(界面不展示,命令仍可用)
|
|
67
67
|
|
|
68
|
-
> 0.10.2
|
|
68
|
+
> 这一节是**历史记录**,不是面向用户的功能。从 0.10.2 起,标题栏菜单和设置页都不再出现预置套装,用户看到的就是「全部 / 仅内置 / 我的套装」。三套数据仍留在 `cordis.patch.yml` 里,`/kit 写作`、`/kit 调研`、`/kit 编程` 照常可用,保留给我们自己测试;等它们足够直观再放回界面。下面的内容用于核对和排障。
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
预置套装是只读的,改动只能另存为你的套装。它们只列**内置工具**,插件工具要靠「加挂」或在另存时勾选。
|
|
71
71
|
|
|
72
72
|
| 套装 | 内置工具 |
|
|
73
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="
|
|
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.
|
|
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}`)
|