dsh-custom-mode 0.1.6-alpha.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/client.js +652 -0
- package/composition.mjs +558 -0
- package/cordis.patch.yml +17 -0
- package/index.mjs +377 -0
- package/locales.mjs +219 -0
- package/meta.mjs +94 -0
- package/package.json +56 -0
- package/paths.mjs +38 -0
package/meta.mjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The preset's display metadata: `preset.yml` next to the composition.
|
|
3
|
+
*
|
|
4
|
+
* This file is what every picker shows — the session's mode chooser and the
|
|
5
|
+
* settings nav. `agent-presets` re-reads the preset roots on each roster read, so
|
|
6
|
+
* a change here appears without a process restart; only a NEW session picks it up,
|
|
7
|
+
* exactly like a composition change.
|
|
8
|
+
*
|
|
9
|
+
* Only two scalar keys are handled, so a full YAML parser is unnecessary. Writing
|
|
10
|
+
* always emits double-quoted, escaped scalars: a user-supplied colon, leading
|
|
11
|
+
* dash, or newline would otherwise restructure the document, and a malformed
|
|
12
|
+
* `preset.yml` makes the mode vanish from the pickers.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
16
|
+
import { join } from 'node:path'
|
|
17
|
+
import { PRESET_DIR } from './paths.mjs'
|
|
18
|
+
|
|
19
|
+
/** Absolute path of the preset's metadata file. */
|
|
20
|
+
export const PRESET_META_PATH = join(PRESET_DIR, 'preset.yml')
|
|
21
|
+
|
|
22
|
+
/** Strip one layer of matching quotes and undo backslash escaping. */
|
|
23
|
+
function unquote(value) {
|
|
24
|
+
const quoted =
|
|
25
|
+
value.length >= 2 &&
|
|
26
|
+
((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
27
|
+
if (!quoted) return value
|
|
28
|
+
const inner = value.slice(1, -1)
|
|
29
|
+
let out = ''
|
|
30
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
31
|
+
if (inner[i] === '\\' && i + 1 < inner.length) {
|
|
32
|
+
out += inner[i + 1]
|
|
33
|
+
i += 1
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
out += inner[i]
|
|
37
|
+
}
|
|
38
|
+
return out
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Read the preset's display name and description.
|
|
43
|
+
*
|
|
44
|
+
* @returns {{name: string, description: string}} both empty when the file is absent.
|
|
45
|
+
*/
|
|
46
|
+
export function readPresetMeta() {
|
|
47
|
+
let text = ''
|
|
48
|
+
try {
|
|
49
|
+
text = readFileSync(PRESET_META_PATH, 'utf8')
|
|
50
|
+
} catch {
|
|
51
|
+
return { name: '', description: '' }
|
|
52
|
+
}
|
|
53
|
+
/** Read one scalar key. */
|
|
54
|
+
const read = (key) => {
|
|
55
|
+
const lines = text.split('\n')
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
if (!line.startsWith(key + ':')) continue
|
|
58
|
+
return unquote(line.slice(key.length + 1).trim())
|
|
59
|
+
}
|
|
60
|
+
return ''
|
|
61
|
+
}
|
|
62
|
+
return { name: read('name'), description: read('description') }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Quote and flatten a value into a single-line YAML scalar. */
|
|
66
|
+
function yamlScalar(value) {
|
|
67
|
+
const flat = String(value).replace(/\r?\n/g, ' ').trim()
|
|
68
|
+
return '"' + flat.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Write `preset.yml` with a display name and optional description.
|
|
73
|
+
*
|
|
74
|
+
* The name is required: an empty one would render the mode as its bare directory
|
|
75
|
+
* id everywhere.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} name - display name.
|
|
78
|
+
* @param {string|undefined} description - optional description.
|
|
79
|
+
* @returns {{ok: true, name: string} | {ok: false, error: string}} the outcome.
|
|
80
|
+
*/
|
|
81
|
+
export function writePresetMeta(name, description) {
|
|
82
|
+
const cleanName = typeof name === 'string' ? name.replace(/\r?\n/g, ' ').trim() : ''
|
|
83
|
+
if (cleanName === '') return { ok: false, error: '模式名称不能为空。' }
|
|
84
|
+
const lines = ['name: ' + yamlScalar(cleanName)]
|
|
85
|
+
if (typeof description === 'string' && description.trim() !== '') {
|
|
86
|
+
lines.push('description: ' + yamlScalar(description))
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
writeFileSync(PRESET_META_PATH, lines.join('\n') + '\n', 'utf8')
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return { ok: false, error: '写入 preset.yml 失败:' + String((error && error.message) || error) }
|
|
92
|
+
}
|
|
93
|
+
return { ok: true, name: cleanName }
|
|
94
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-custom-mode",
|
|
3
|
+
"version": "0.1.6-alpha.1",
|
|
4
|
+
"description": "自定义模式 settings page: base-mode switching, per-row plugin switches, editable system prompt, custom mode name. Follows the DSH version.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.mjs",
|
|
10
|
+
"./client": "./client.js",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/BOWLUNA/dsh-custom-mode.git",
|
|
16
|
+
"directory": "editor"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/BOWLUNA/dsh-custom-mode#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/BOWLUNA/dsh-custom-mode/issues"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"dsh",
|
|
24
|
+
"deepseek-harness",
|
|
25
|
+
"agent-preset",
|
|
26
|
+
"system-prompt",
|
|
27
|
+
"plugin"
|
|
28
|
+
],
|
|
29
|
+
"dsh": {
|
|
30
|
+
"bundle": {
|
|
31
|
+
"patch": "./cordis.patch.yml"
|
|
32
|
+
},
|
|
33
|
+
"client": {
|
|
34
|
+
"platform": "web"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"index.mjs",
|
|
39
|
+
"client.js",
|
|
40
|
+
"composition.mjs",
|
|
41
|
+
"meta.mjs",
|
|
42
|
+
"paths.mjs",
|
|
43
|
+
"locales.mjs",
|
|
44
|
+
"cordis.patch.yml"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"test": "node ../test/run.mjs"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@deepseek-ai/dsh": ">=0.1.2-alpha.1"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"tag": "alpha"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/paths.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem locations shared by the settings page's host half and the
|
|
3
|
+
* composition compiler.
|
|
4
|
+
*
|
|
5
|
+
* The prompt file and the composition file must live in the SAME preset
|
|
6
|
+
* directory — that is the whole contract between the preset and this plugin. So
|
|
7
|
+
* the composition path is derived from the prompt path rather than configured
|
|
8
|
+
* separately: one override cannot silently desynchronise them.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { join, dirname } from 'node:path'
|
|
12
|
+
import { homedir } from 'node:os'
|
|
13
|
+
|
|
14
|
+
/** DSH home, matching the deployment's own default resolution. */
|
|
15
|
+
export function dshHome() {
|
|
16
|
+
return process.env.DSH_HOME && process.env.DSH_HOME !== '' ? process.env.DSH_HOME : join(homedir(), '.dsh')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Absolute path of the prompt file the page edits.
|
|
21
|
+
*
|
|
22
|
+
* Honours `DSH_CUSTOM_PROMPT_PATH` so a deployment can point the editor at a
|
|
23
|
+
* preset directory with another id; the default matches the `custom` preset
|
|
24
|
+
* installed under DSH home.
|
|
25
|
+
*/
|
|
26
|
+
export const PROMPT_PATH =
|
|
27
|
+
process.env.DSH_CUSTOM_PROMPT_PATH && process.env.DSH_CUSTOM_PROMPT_PATH !== ''
|
|
28
|
+
? process.env.DSH_CUSTOM_PROMPT_PATH
|
|
29
|
+
: join(dshHome(), '.agent-presets', 'custom', 'prompt.md')
|
|
30
|
+
|
|
31
|
+
/** The preset directory both files live in. */
|
|
32
|
+
export const PRESET_DIR = dirname(PROMPT_PATH)
|
|
33
|
+
|
|
34
|
+
/** The preset's composition file, rewritten by the base-mode and switch settings. */
|
|
35
|
+
export const COMPOSITION_PATH = join(PRESET_DIR, 'agent.cordis.yml')
|
|
36
|
+
|
|
37
|
+
/** This feature's own private HTTP route. Browser half and host half must agree. */
|
|
38
|
+
export const ROUTE_PATH = '/custom-mode'
|