dsh-multi-chat 1.0.2 → 1.0.4
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/README.md +287 -268
- package/README.zh.md +269 -232
- package/bin/dsh-multi-chat.mjs +33 -0
- package/lib/client.js +66 -28
- package/lib/client.js.map +1 -1
- package/lib/index.js +211 -22
- package/lib/invariant.js +23 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/client/WallToggle.js +2 -2
- package/lib/types/client/WallToggle.js.map +1 -1
- package/lib/types/client/WallView.js +8 -8
- package/lib/types/client/WallView.js.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/store.d.ts +1 -1
- package/lib/types/client/store.d.ts.map +1 -1
- package/lib/types/client/store.js +1 -1
- package/lib/types/client/store.js.map +1 -1
- package/lib/types/index.d.ts +80 -80
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +117 -12
- package/lib/types/index.js.map +1 -1
- package/package.json +139 -108
- package/scripts/check-platform-contract.mjs +171 -0
- package/src/client/WallToggle.tsx +52 -52
- package/src/client/WallView.tsx +406 -406
- package/src/client/index.ts +108 -103
- package/src/client/store.ts +40 -40
- package/src/index.ts +715 -618
- package/scripts/sync-vitest-paths.mjs +0 -63
package/package.json
CHANGED
|
@@ -1,108 +1,139 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-multi-chat",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "DSH 多窗口墙:在官方 DeepSeek Harness Web 界面里以多窗口网格并排运行、监控多个对话实例,一屏盯住所有 Agent 的实时进度;内置带口令认证的局域网网关,手机/平板也能看。",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "./lib/index.js",
|
|
8
|
-
"types": "./lib/types/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./lib/types/index.d.ts",
|
|
12
|
-
"default": "./lib/index.js"
|
|
13
|
-
},
|
|
14
|
-
"./client": {
|
|
15
|
-
"types": "./lib/types/client/index.d.ts",
|
|
16
|
-
"default": "./lib/client.js"
|
|
17
|
-
},
|
|
18
|
-
"./invariant": {
|
|
19
|
-
"types": "./lib/types/invariant.d.ts",
|
|
20
|
-
"default": "./lib/invariant.js"
|
|
21
|
-
},
|
|
22
|
-
"./package.json": "./package.json"
|
|
23
|
-
},
|
|
24
|
-
"dsh": {
|
|
25
|
-
"bundle": {
|
|
26
|
-
"patch": "./cordis.patch.yml"
|
|
27
|
-
},
|
|
28
|
-
"client": {
|
|
29
|
-
"platform": "web",
|
|
30
|
-
"inject": [
|
|
31
|
-
"@deepseek-ai/dsh-client-
|
|
32
|
-
"@deepseek-ai/dsh-client-locale",
|
|
33
|
-
"@deepseek-ai/dsh-client-ui-layout",
|
|
34
|
-
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
"deepseek
|
|
61
|
-
"
|
|
62
|
-
"multi-
|
|
63
|
-
"multi-
|
|
64
|
-
"multi-
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"README.md",
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"@
|
|
91
|
-
"@
|
|
92
|
-
"@
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"
|
|
107
|
-
|
|
108
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-multi-chat",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "DSH 多窗口墙:在官方 DeepSeek Harness Web 界面里以多窗口网格并排运行、监控多个对话实例,一屏盯住所有 Agent 的实时进度;内置带口令认证的局域网网关,手机/平板也能看。",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./lib/index.js",
|
|
8
|
+
"types": "./lib/types/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/types/index.d.ts",
|
|
12
|
+
"default": "./lib/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./client": {
|
|
15
|
+
"types": "./lib/types/client/index.d.ts",
|
|
16
|
+
"default": "./lib/client.js"
|
|
17
|
+
},
|
|
18
|
+
"./invariant": {
|
|
19
|
+
"types": "./lib/types/invariant.d.ts",
|
|
20
|
+
"default": "./lib/invariant.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"dsh": {
|
|
25
|
+
"bundle": {
|
|
26
|
+
"patch": "./cordis.patch.yml"
|
|
27
|
+
},
|
|
28
|
+
"client": {
|
|
29
|
+
"platform": "web",
|
|
30
|
+
"inject": [
|
|
31
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
32
|
+
"@deepseek-ai/dsh-client-locale",
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"marketplace": {
|
|
38
|
+
"profiles": [
|
|
39
|
+
"web"
|
|
40
|
+
],
|
|
41
|
+
"requiresRestart": true,
|
|
42
|
+
"manualSteps": false,
|
|
43
|
+
"requiresBuildApproval": false
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"bin": {
|
|
47
|
+
"dsh-multi-chat": "bin/dsh-multi-chat.mjs"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/daetz-coder/dsh-multi-chat.git"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/daetz-coder/dsh-multi-chat#readme",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/daetz-coder/dsh-multi-chat/issues"
|
|
56
|
+
},
|
|
57
|
+
"keywords": [
|
|
58
|
+
"dsh",
|
|
59
|
+
"deepseek-harness",
|
|
60
|
+
"deepseek",
|
|
61
|
+
"multi-chat",
|
|
62
|
+
"multi-conversation",
|
|
63
|
+
"multi-window",
|
|
64
|
+
"multi-agent",
|
|
65
|
+
"client-plugin",
|
|
66
|
+
"gateway",
|
|
67
|
+
"phone-access"
|
|
68
|
+
],
|
|
69
|
+
"files": [
|
|
70
|
+
"bin/",
|
|
71
|
+
"scripts/",
|
|
72
|
+
"patches/",
|
|
73
|
+
"assets/",
|
|
74
|
+
"lib/",
|
|
75
|
+
"src/",
|
|
76
|
+
"cordis.patch.yml",
|
|
77
|
+
"README.md",
|
|
78
|
+
"README.zh.md",
|
|
79
|
+
"LICENSE"
|
|
80
|
+
],
|
|
81
|
+
"scripts": {
|
|
82
|
+
"build": "tsc -b tsconfig.json && tsdown && npm run check:platform",
|
|
83
|
+
"clean": "rm -rf lib",
|
|
84
|
+
"test": "npm run build && vitest run",
|
|
85
|
+
"check:platform": "node scripts/check-platform-contract.mjs",
|
|
86
|
+
"check:platform:against": "node scripts/check-platform-contract.mjs --against",
|
|
87
|
+
"sync:platform": "node scripts/check-platform-contract.mjs --sync"
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@deepseek-ai/cordis": "4.0.4",
|
|
91
|
+
"@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
|
|
92
|
+
"@deepseek-ai/dsh-client-store": "0.1.7-rc.1",
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.1",
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.1.7-rc.1",
|
|
95
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
|
|
96
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
|
|
97
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.1.7-rc.1",
|
|
98
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
|
|
99
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
|
|
100
|
+
"@deepseek-ai/dsh-invariants": "0.1.7-rc.1",
|
|
101
|
+
"@deepseek-ai/dsh-util-workspace-path": "0.1.7-rc.1",
|
|
102
|
+
"@deepseek-ai/schemastery": "3.18.4",
|
|
103
|
+
"@shikijs/langs": "^4.4.3",
|
|
104
|
+
"@testing-library/dom": "^10.4.1",
|
|
105
|
+
"@testing-library/react": "^16.3.2",
|
|
106
|
+
"@types/node": "^26.2.0",
|
|
107
|
+
"@types/react": "^18.3.31",
|
|
108
|
+
"@types/react-dom": "^18.3.7",
|
|
109
|
+
"anser": "^2.3.5",
|
|
110
|
+
"clsx": "^2.1.1",
|
|
111
|
+
"diff": "^9.0.0",
|
|
112
|
+
"immer": "^10.2.0",
|
|
113
|
+
"jsdom": "^29.1.1",
|
|
114
|
+
"katex": "^0.18.9",
|
|
115
|
+
"lightningcss": "^1.33.0",
|
|
116
|
+
"mdast-util-from-markdown": "^2.0.3",
|
|
117
|
+
"mdast-util-gfm": "^3.1.0",
|
|
118
|
+
"mdast-util-math": "^3.0.0",
|
|
119
|
+
"micromark-core-commonmark": "^2.0.4",
|
|
120
|
+
"micromark-extension-gfm": "^3.0.0",
|
|
121
|
+
"micromark-extension-math": "^3.1.0",
|
|
122
|
+
"micromark-factory-space": "^2.1.0",
|
|
123
|
+
"micromark-util-character": "^2.1.1",
|
|
124
|
+
"micromark-util-classify-character": "^2.0.1",
|
|
125
|
+
"micromark-util-sanitize-uri": "^2.0.1",
|
|
126
|
+
"micromark-util-symbol": "^2.0.1",
|
|
127
|
+
"react": "^18.3.1",
|
|
128
|
+
"react-dom": "^18.3.1",
|
|
129
|
+
"shiki": "^4.4.3",
|
|
130
|
+
"simple-icons": "^16.33.0",
|
|
131
|
+
"tsdown": "^0.11.13",
|
|
132
|
+
"typescript": "^5.9.3",
|
|
133
|
+
"vitest": "^2.1.9",
|
|
134
|
+
"zustand": "^4.4.7"
|
|
135
|
+
},
|
|
136
|
+
"engines": {
|
|
137
|
+
"node": ">=20"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Platform-contract guard for the plugin's browser half.
|
|
4
|
+
*
|
|
5
|
+
* A client plugin bundle is NOT loaded by Node or Vite — it is a factory handed
|
|
6
|
+
* to `window.__ModuleLoader__`, and every `require(spec)` inside it is resolved
|
|
7
|
+
* against the shell's module table. That table has exactly two sources:
|
|
8
|
+
*
|
|
9
|
+
* 1. the platform SEED TABLE — a fixed set of specifiers the web shell
|
|
10
|
+
* hardcodes into its own Vite bundle, and
|
|
11
|
+
* 2. boot-graph package rows — other plugins' bundles.
|
|
12
|
+
*
|
|
13
|
+
* A plugin can only rely on (1): it cannot conjure another package's row. So a
|
|
14
|
+
* require() of anything outside the seed table throws at materialization time
|
|
15
|
+
* with "missed the module table", and the boot is fail-closed — the whole web
|
|
16
|
+
* UI stops on its "Failed to load plugins" card.
|
|
17
|
+
*
|
|
18
|
+
* That is exactly how v1.0.3 broke: it required
|
|
19
|
+
* `@deepseek-ai/dsh-client-runtime/client`, a package upstream has since
|
|
20
|
+
* deleted. Nothing in the build noticed, because the type came from a local
|
|
21
|
+
* unversioned checkout of the harness monorepo.
|
|
22
|
+
*
|
|
23
|
+
* This script closes that hole: it reads the seed table from
|
|
24
|
+
* platform-seeds.json (the same file tsdown.config.ts builds its externals
|
|
25
|
+
* list from) and fails if the built bundle requires anything else.
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* node scripts/check-platform-contract.mjs # check lib/client.js
|
|
29
|
+
* node scripts/check-platform-contract.mjs --against <dsh> # + diff vs an installed DSH
|
|
30
|
+
* node scripts/check-platform-contract.mjs --sync <dsh> # rewrite platform-seeds.json from an installed DSH
|
|
31
|
+
*/
|
|
32
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'node:fs'
|
|
33
|
+
import { join, dirname } from 'node:path'
|
|
34
|
+
import { fileURLToPath } from 'node:url'
|
|
35
|
+
|
|
36
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
37
|
+
const SEEDS_FILE = join(ROOT, 'platform-seeds.json')
|
|
38
|
+
const BUNDLE = join(ROOT, 'lib', 'client.js')
|
|
39
|
+
|
|
40
|
+
/** Every module specifier a built bundle asks the loader for, in source order. */
|
|
41
|
+
function requiredSpecifiers(code) {
|
|
42
|
+
const found = []
|
|
43
|
+
for (const m of code.matchAll(/\brequire(?:\.async)?\(\s*["']([^"']+)["']\s*\)/g)) {
|
|
44
|
+
const spec = m[1]
|
|
45
|
+
// Relative chunk requests are package-local code the loader serves itself.
|
|
46
|
+
if (!spec.startsWith('./') && !spec.startsWith('../')) found.push(spec)
|
|
47
|
+
}
|
|
48
|
+
return [...new Set(found)]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Locate the web shell bundle inside an installed DSH package directory. */
|
|
52
|
+
function findShellBundle(dshRoot) {
|
|
53
|
+
const hits = []
|
|
54
|
+
const walk = (dir, depth) => {
|
|
55
|
+
if (depth > 6 || hits.length > 0) return
|
|
56
|
+
let entries
|
|
57
|
+
try { entries = readdirSync(dir) } catch { return }
|
|
58
|
+
for (const name of entries) {
|
|
59
|
+
const full = join(dir, name)
|
|
60
|
+
let st
|
|
61
|
+
try { st = statSync(full) } catch { continue }
|
|
62
|
+
if (st.isDirectory()) walk(full, depth + 1)
|
|
63
|
+
else if (/^index-[\w-]+\.js$/.test(name) && full.includes('dsh-web-frontend')) hits.push(full)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
walk(dshRoot, 0)
|
|
67
|
+
if (hits.length === 0) throw new Error(`no dsh-web-frontend shell bundle found under ${dshRoot}`)
|
|
68
|
+
return hits[0]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read the seed table out of the minified shell.
|
|
73
|
+
* The shell writes it as `staticModules: <fn>()`, where `<fn>` returns the table
|
|
74
|
+
* object literal. Both shapes seen in the wild: `function iE(){return{...}}` on
|
|
75
|
+
* 0.1.7-rc.1 and `function by(){return{...}}` on 0.1.5-rc.2.
|
|
76
|
+
*/
|
|
77
|
+
function extractSeeds(shellCode) {
|
|
78
|
+
const call = /staticModules\s*:\s*([A-Za-z_$][\w$]*)\s*\(\s*\)/.exec(shellCode)
|
|
79
|
+
if (call === null) throw new Error('shell has no `staticModules: <fn>()` — the boot protocol changed; update this extractor')
|
|
80
|
+
const fn = call[1]
|
|
81
|
+
const body = new RegExp(`function\\s+${fn}\\s*\\(\\)\\s*\\{\\s*return\\s*\\{([^}]*)\\}`).exec(shellCode)
|
|
82
|
+
if (body === null) throw new Error(`could not read the body of seed-table function "${fn}"`)
|
|
83
|
+
const seeds = []
|
|
84
|
+
for (const m of body[1].matchAll(/(?:"((?:[^"\\]|\\.)+)"|([A-Za-z_$][\w$]*))\s*:/g)) {
|
|
85
|
+
seeds.push(m[1] !== undefined ? JSON.parse(`"${m[1]}"`) : m[2])
|
|
86
|
+
}
|
|
87
|
+
return seeds
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Bare (non-`node:`) specifiers the node half imports; those resolve by normal Node rules. */
|
|
91
|
+
function nodeHalfSpecifiers() {
|
|
92
|
+
const file = join(ROOT, 'lib', 'index.js')
|
|
93
|
+
if (!existsSync(file)) return []
|
|
94
|
+
const specs = []
|
|
95
|
+
// Anchored to the start of a line so a `from "..."` inside a string literal
|
|
96
|
+
// (error messages, URLs) is not mistaken for an import statement.
|
|
97
|
+
for (const m of readFileSync(file, 'utf8').matchAll(/^import\s+(?:[^"'\n]*?from\s+)?["']([^"']+)["']/gm)) {
|
|
98
|
+
if (!m[1].startsWith('.') && !m[1].startsWith('node:')) specs.push(m[1])
|
|
99
|
+
}
|
|
100
|
+
return [...new Set(specs)]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const die = (msg) => { console.error(`\n✗ platform-contract: ${msg}\n`); process.exit(1) }
|
|
104
|
+
|
|
105
|
+
const argv = process.argv.slice(2)
|
|
106
|
+
const flag = (name) => {
|
|
107
|
+
const i = argv.indexOf(name)
|
|
108
|
+
if (i === -1) return undefined
|
|
109
|
+
const value = argv[i + 1]
|
|
110
|
+
if (value === undefined || value.startsWith('--')) die(`${name} needs a path argument`)
|
|
111
|
+
return value
|
|
112
|
+
}
|
|
113
|
+
const syncTo = flag('--sync')
|
|
114
|
+
const against = flag('--against')
|
|
115
|
+
|
|
116
|
+
// --sync: adopt the seed table of an installed DSH as this repo's contract.
|
|
117
|
+
if (syncTo !== undefined) {
|
|
118
|
+
if (!existsSync(syncTo)) die(`--sync path does not exist: ${syncTo}`)
|
|
119
|
+
const seeds = extractSeeds(readFileSync(findShellBundle(syncTo), 'utf8'))
|
|
120
|
+
const prev = JSON.parse(readFileSync(SEEDS_FILE, 'utf8'))
|
|
121
|
+
const version = (() => {
|
|
122
|
+
try { return JSON.parse(readFileSync(join(syncTo, 'package.json'), 'utf8')).version } catch { return undefined }
|
|
123
|
+
})()
|
|
124
|
+
const verified = [...new Set([...(prev.verifiedAgainst ?? []), ...(version === undefined ? [] : [version])])]
|
|
125
|
+
writeFileSync(SEEDS_FILE, `${JSON.stringify({ ...prev, verifiedAgainst: verified, seeds }, null, 2)}\n`)
|
|
126
|
+
console.log(`✓ platform-seeds.json updated from ${syncTo}${version === undefined ? '' : ` (dsh ${version})`}`)
|
|
127
|
+
console.log(` seeds: ${seeds.join(', ')}`)
|
|
128
|
+
process.exit(0)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const { seeds, verifiedAgainst } = JSON.parse(readFileSync(SEEDS_FILE, 'utf8'))
|
|
132
|
+
const seedSet = new Set(seeds)
|
|
133
|
+
|
|
134
|
+
// --against: report drift between the pinned contract and a real installation.
|
|
135
|
+
if (against !== undefined) {
|
|
136
|
+
if (!existsSync(against)) die(`--against path does not exist: ${against}`)
|
|
137
|
+
const live = extractSeeds(readFileSync(findShellBundle(against), 'utf8'))
|
|
138
|
+
const liveSet = new Set(live)
|
|
139
|
+
const missing = seeds.filter((s) => !liveSet.has(s))
|
|
140
|
+
const added = live.filter((s) => !seedSet.has(s))
|
|
141
|
+
if (missing.length > 0 || added.length > 0) {
|
|
142
|
+
console.error(`\n✗ platform-contract drift against ${against}`)
|
|
143
|
+
if (missing.length > 0) console.error(` required but absent upstream: ${missing.join(', ')}`)
|
|
144
|
+
if (added.length > 0) console.error(` new upstream seeds (run --sync to adopt): ${added.join(', ')}`)
|
|
145
|
+
console.error()
|
|
146
|
+
process.exit(1)
|
|
147
|
+
}
|
|
148
|
+
console.log(`✓ seed table matches ${against}`)
|
|
149
|
+
for (const spec of nodeHalfSpecifiers()) {
|
|
150
|
+
const ok = existsSync(join(against, 'node_modules', ...spec.split('/')))
|
|
151
|
+
if (!ok) die(`node half imports "${spec}" but it is not installed under ${against}`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!existsSync(BUNDLE)) die(`built bundle missing: ${BUNDLE} (run the build first)`)
|
|
156
|
+
const required = requiredSpecifiers(readFileSync(BUNDLE, 'utf8'))
|
|
157
|
+
const offenders = required.filter((spec) => !seedSet.has(spec))
|
|
158
|
+
|
|
159
|
+
console.log(`platform-contract: lib/client.js requires ${required.length} external module(s)`)
|
|
160
|
+
console.log(` pinned seed table (verified against dsh ${verifiedAgainst.join(', ')}): ${seeds.length} entries`)
|
|
161
|
+
for (const spec of required) console.log(` ${seedSet.has(spec) ? '✓' : '✗'} ${spec}`)
|
|
162
|
+
|
|
163
|
+
if (offenders.length > 0) {
|
|
164
|
+
die(
|
|
165
|
+
`lib/client.js requires module(s) the platform does not seed: ${offenders.join(', ')}\n` +
|
|
166
|
+
` A plugin bundle cannot reach these — the boot will fail with "missed the module table"\n` +
|
|
167
|
+
` and take the whole web UI down. Either import a seed-table package instead, or,\n` +
|
|
168
|
+
` if upstream genuinely added one, adopt it: npm run sync:platform -- <path-to-installed-dsh>`,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
console.log('✓ every required module is a platform seed word')
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WallToggle: the sidebar-foot shortcut. Clicking it opens the wall view —
|
|
3
|
-
* the `conversation.view` ring switches to the 'multi-wall' entry, which the
|
|
4
|
-
* header renders as the "多窗口" tab. The click is a plain user-equivalent
|
|
5
|
-
* activation: it finds the header's view-ring tab for this plugin's label and
|
|
6
|
-
* clicks it, so the official view-ring state machine (the chat store's active
|
|
7
|
-
* view field) performs the switch. No store is declared: the ring decides
|
|
8
|
-
* what renders, so this row owns no state.
|
|
9
|
-
*
|
|
10
|
-
* Session-scoped by design: the view ring (and its header tabs) only renders
|
|
11
|
-
* with an active session, so the shortcut is inert on the empty-hero screen —
|
|
12
|
-
* the user first opens or creates a session (the official flow), after which
|
|
13
|
-
* the tab is present and the click lands.
|
|
14
|
-
*/
|
|
15
|
-
import {
|
|
16
|
-
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
17
|
-
import css from './WallToggle.module.css'
|
|
18
|
-
|
|
19
|
-
/** Composed props: sidebar column state + locale. */
|
|
20
|
-
export type WallToggleProps =
|
|
21
|
-
& PropsRuntime<'sidebar.footer.action'>
|
|
22
|
-
& PropsLocale<'multiWall'>
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Render the wall shortcut row (icon; label only in the wide column).
|
|
26
|
-
* @param props - composed slot props.
|
|
27
|
-
* @returns the shortcut row.
|
|
28
|
-
*/
|
|
29
|
-
export function WallToggle({ wide, t }: WallToggleProps) {
|
|
30
|
-
return (
|
|
31
|
-
<button
|
|
32
|
-
type="button"
|
|
33
|
-
className={css.row}
|
|
34
|
-
aria-label={t('toggle.aria')}
|
|
35
|
-
title={t('toggle')}
|
|
36
|
-
onClick={() => {
|
|
37
|
-
// The official view ring renders the active entry through
|
|
38
|
-
// `only: <active id>`; the only sanctioned way to move it is the
|
|
39
|
-
// header tab's click handler (actions.setView). Trigger that same
|
|
40
|
-
// element instead of reaching into the chat store from the root
|
|
41
|
-
// scope. No tab (no active session) is a no-op.
|
|
42
|
-
const label = t('view.multiWall')
|
|
43
|
-
const tab = Array.from(document.querySelectorAll<HTMLButtonElement>('[role="tab"]'))
|
|
44
|
-
.find(el => el.textContent?.trim() === label)
|
|
45
|
-
tab?.click()
|
|
46
|
-
}}
|
|
47
|
-
>
|
|
48
|
-
<
|
|
49
|
-
{wide && <span className={css.label}>{t('toggle')}</span>}
|
|
50
|
-
</button>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* WallToggle: the sidebar-foot shortcut. Clicking it opens the wall view —
|
|
3
|
+
* the `conversation.view` ring switches to the 'multi-wall' entry, which the
|
|
4
|
+
* header renders as the "多窗口" tab. The click is a plain user-equivalent
|
|
5
|
+
* activation: it finds the header's view-ring tab for this plugin's label and
|
|
6
|
+
* clicks it, so the official view-ring state machine (the chat store's active
|
|
7
|
+
* view field) performs the switch. No store is declared: the ring decides
|
|
8
|
+
* what renders, so this row owns no state.
|
|
9
|
+
*
|
|
10
|
+
* Session-scoped by design: the view ring (and its header tabs) only renders
|
|
11
|
+
* with an active session, so the shortcut is inert on the empty-hero screen —
|
|
12
|
+
* the user first opens or creates a session (the official flow), after which
|
|
13
|
+
* the tab is present and the click lands.
|
|
14
|
+
*/
|
|
15
|
+
import { IconFullscreenOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
16
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
17
|
+
import css from './WallToggle.module.css'
|
|
18
|
+
|
|
19
|
+
/** Composed props: sidebar column state + locale. */
|
|
20
|
+
export type WallToggleProps =
|
|
21
|
+
& PropsRuntime<'sidebar.footer.action'>
|
|
22
|
+
& PropsLocale<'multiWall'>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Render the wall shortcut row (icon; label only in the wide column).
|
|
26
|
+
* @param props - composed slot props.
|
|
27
|
+
* @returns the shortcut row.
|
|
28
|
+
*/
|
|
29
|
+
export function WallToggle({ wide, t }: WallToggleProps) {
|
|
30
|
+
return (
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
className={css.row}
|
|
34
|
+
aria-label={t('toggle.aria')}
|
|
35
|
+
title={t('toggle')}
|
|
36
|
+
onClick={() => {
|
|
37
|
+
// The official view ring renders the active entry through
|
|
38
|
+
// `only: <active id>`; the only sanctioned way to move it is the
|
|
39
|
+
// header tab's click handler (actions.setView). Trigger that same
|
|
40
|
+
// element instead of reaching into the chat store from the root
|
|
41
|
+
// scope. No tab (no active session) is a no-op.
|
|
42
|
+
const label = t('view.multiWall')
|
|
43
|
+
const tab = Array.from(document.querySelectorAll<HTMLButtonElement>('[role="tab"]'))
|
|
44
|
+
.find(el => el.textContent?.trim() === label)
|
|
45
|
+
tab?.click()
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<IconFullscreenOutlineRegular size={wide ? 16 : 18} />
|
|
49
|
+
{wide && <span className={css.label}>{t('toggle')}</span>}
|
|
50
|
+
</button>
|
|
51
|
+
)
|
|
52
|
+
}
|